From 0c2b4853831ada2492284981a256d9c235b6d4a9 Mon Sep 17 00:00:00 2001 From: Kazuki Yamada Date: Sun, 21 Jul 2024 13:12:07 +0900 Subject: [PATCH 1/4] chore(ci): run test coverage on ci --- .github/workflows/test.yml | 2 +- vite.config.mts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index dd8914c..cba545a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -44,4 +44,4 @@ jobs: run: npm i - name: Test - run: npm test + run: npm run test-coverage diff --git a/vite.config.mts b/vite.config.mts index e75dd23..13ab703 100644 --- a/vite.config.mts +++ b/vite.config.mts @@ -6,6 +6,7 @@ export default defineConfig({ environment: 'node', include: ['tests/**/*.test.ts'], coverage: { + include: ['src/**/*'], reporter: ['text', 'json', 'html'], }, } From ff943bb510e35fb9afb5d457fff20f2c77b33c07 Mon Sep 17 00:00:00 2001 From: Kazuki Yamada Date: Sun, 21 Jul 2024 13:44:19 +0900 Subject: [PATCH 2/4] test(ignore): Add some ignore test --- src/core/packager.ts | 3 +- src/utils/gitignoreUtils.ts | 4 +- tests/utils/gitignoreUtils.test.ts | 168 ++++++++++++++++++++++++----- 3 files changed, 149 insertions(+), 26 deletions(-) diff --git a/src/core/packager.ts b/src/core/packager.ts index 4387fbe..943fa9e 100644 --- a/src/core/packager.ts +++ b/src/core/packager.ts @@ -5,6 +5,7 @@ import { processFile as defaultProcessFile } from '../utils/fileHandler.js'; import { getGitignorePatterns as defaultGetGitignorePatterns, createIgnoreFilter as defaultCreateIgnoreFilter, + IgnoreFilter, } from '../utils/gitignoreUtils.js'; import { generateOutput as defaultGenerateOutput } from './outputGenerator.js'; import { defaultIgnoreList } from '../utils/defaultIgnore.js'; @@ -64,7 +65,7 @@ async function packDirectory( dir: string, relativePath: string, config: RepopackConfigMerged, - ignoreFilter: (path: string) => boolean, + ignoreFilter: IgnoreFilter, deps: Dependencies, ): Promise<{ path: string; content: string }[]> { const entries = await fs.readdir(dir, { withFileTypes: true }); diff --git a/src/utils/gitignoreUtils.ts b/src/utils/gitignoreUtils.ts index 9fe65cc..246d04f 100644 --- a/src/utils/gitignoreUtils.ts +++ b/src/utils/gitignoreUtils.ts @@ -20,7 +20,9 @@ export function parseGitignoreContent(content: string): string[] { .filter((line) => line && !line.startsWith('#')); } -export function createIgnoreFilter(patterns: string[]): (path: string) => boolean { +export type IgnoreFilter = (path: string) => boolean; + +export function createIgnoreFilter(patterns: string[]): IgnoreFilter { const ig = ignore.default().add(patterns); return (filePath: string) => !ig.ignores(filePath); } diff --git a/tests/utils/gitignoreUtils.test.ts b/tests/utils/gitignoreUtils.test.ts index 48b7591..9f93ff5 100644 --- a/tests/utils/gitignoreUtils.test.ts +++ b/tests/utils/gitignoreUtils.test.ts @@ -2,6 +2,7 @@ import { expect, test, vi, describe, beforeEach } from 'vitest'; import { getGitignorePatterns, parseGitignoreContent, createIgnoreFilter } from '../../src/utils/gitignoreUtils.js'; import path from 'path'; import * as fs from 'fs/promises'; +import os from 'os'; vi.mock('fs/promises'); @@ -10,50 +11,169 @@ describe('gitignoreUtils', () => { vi.resetAllMocks(); }); - test('getGitignorePatterns should read and parse .gitignore file', async () => { - const mockContent = ` + describe('getGitignorePatterns', () => { + test('should read and parse .gitignore file', async () => { + const mockContent = ` # Comment node_modules *.log .DS_Store - `; - vi.mocked(fs.readFile).mockResolvedValue(mockContent); + `; + vi.mocked(fs.readFile).mockResolvedValue(mockContent); - const patterns = await getGitignorePatterns('/mock/root'); + const patterns = await getGitignorePatterns('/mock/root'); - expect(fs.readFile).toHaveBeenCalledWith(path.join('/mock/root', '.gitignore'), 'utf-8'); - expect(patterns).toEqual(['node_modules', '*.log', '.DS_Store']); - }); + expect(fs.readFile).toHaveBeenCalledWith(path.join('/mock/root', '.gitignore'), 'utf-8'); + expect(patterns).toEqual(['node_modules', '*.log', '.DS_Store']); + }); + + test('should return empty array if .gitignore is not found', async () => { + vi.mocked(fs.readFile).mockRejectedValue(new Error('File not found')); + + const patterns = await getGitignorePatterns('/mock/root'); + + expect(patterns).toEqual([]); + }); - test('getGitignorePatterns should return empty array if .gitignore is not found', async () => { - vi.mocked(fs.readFile).mockRejectedValue(new Error('File not found')); + test('should handle CRLF line endings', async () => { + const mockContent = 'node_modules\r\n*.log\r\n.DS_Store'; + vi.mocked(fs.readFile).mockResolvedValue(mockContent); - const patterns = await getGitignorePatterns('/mock/root'); + const patterns = await getGitignorePatterns('/mock/root'); - expect(patterns).toEqual([]); + expect(patterns).toEqual(['node_modules', '*.log', '.DS_Store']); + }); }); - test('parseGitignoreContent should correctly parse gitignore content', () => { - const content = ` + describe('parseGitignoreContent', () => { + test('should correctly parse gitignore content', () => { + const content = ` # Comment node_modules *.log .DS_Store - `; + `; + + const patterns = parseGitignoreContent(content); + + expect(patterns).toEqual(['node_modules', '*.log', '.DS_Store']); + }); + + test('should handle mixed line endings', () => { + const content = 'node_modules\n*.log\r\n.DS_Store\r'; - const patterns = parseGitignoreContent(content); + const patterns = parseGitignoreContent(content); - expect(patterns).toEqual(['node_modules', '*.log', '.DS_Store']); + expect(patterns).toEqual(['node_modules', '*.log', '.DS_Store']); + }); }); - test('createIgnoreFilter should create a function that correctly filters paths', () => { - const patterns = ['node_modules', '*.log', '.DS_Store']; - const filter = createIgnoreFilter(patterns); + describe('createIgnoreFilter', () => { + test('should create a function that correctly filters paths', () => { + const patterns = ['node_modules', '*.log', '.DS_Store']; + const filter = createIgnoreFilter(patterns); + + expect(filter('src/index.js')).toBe(true); + expect(filter('node_modules/package/index.js')).toBe(false); + expect(filter('logs/error.log')).toBe(false); + expect(filter('.DS_Store')).toBe(false); + }); + + test('should correctly ignore files with different path separators', () => { + const patterns = ['*.md', '*.svg', '*.css', 'node_modules/**']; + const filter = createIgnoreFilter(patterns); + + // UNIX-style paths + expect(filter('README.md')).toBe(false); + expect(filter('src/assets/logo.svg')).toBe(false); + expect(filter('styles/main.css')).toBe(false); + expect(filter('node_modules/package/index.js')).toBe(false); + + // Windows-style paths + if (os.platform() === 'win32') { + expect(filter('docs\\README.md')).toBe(false); + expect(filter('src\\assets\\logo.svg')).toBe(false); + expect(filter('styles\\main.css')).toBe(false); + expect(filter('node_modules\\package\\index.js')).toBe(false); + expect(filter('src\\components\\Button.js')).toBe(true); + } + + // Files that should not be ignored + expect(filter('src/index.js')).toBe(true); + }); + + test('should handle nested directory patterns correctly', () => { + const patterns = ['test/**/*.spec.js', 'build/**']; + const filter = createIgnoreFilter(patterns); + + expect(filter('test/unit/component.spec.js')).toBe(false); + expect(filter('build/output.js')).toBe(false); + + expect(filter('src/test/helper.js')).toBe(true); + + if (os.platform() === 'win32') { + expect(filter('src\\build\\utils.js')).toBe(true); + expect(filter('test\\integration\\api.spec.js')).toBe(false); + expect(filter('build\\temp\\cache.json')).toBe(false); + } + }); + + test('should correctly handle patterns with special characters', () => { + const patterns = ['**/*.min.js', '**/#temp#', '**/node_modules']; + const filter = createIgnoreFilter(patterns); + + expect(filter('dist/bundle.min.js')).toBe(false); + expect(filter('temp/#temp#/file.txt')).toBe(false); + expect(filter('project/node_modules/package/index.js')).toBe(false); + + expect(filter('src/app.js')).toBe(true); + expect(filter('docs/temp/file.txt')).toBe(true); + }); + + test('should handle case sensitivity correctly on different platforms', () => { + const patterns = ['*.MD', 'TEST']; + const filter = createIgnoreFilter(patterns); + + if (os.platform() === 'win32') { + // Windows is case-insensitive + expect(filter('readme.md')).toBe(false); + expect(filter('test/file.txt')).toBe(false); + } else { + // Unix-like systems are case-insensitive + expect(filter('readme.md')).toBe(false); + expect(filter('test/file.txt')).toBe(false); + } + + expect(filter('README.MD')).toBe(false); + expect(filter('TEST/file.txt')).toBe(false); + }); + + test('should handle symlinks correctly', () => { + const patterns = ['symlink', 'real_dir']; + const filter = createIgnoreFilter(patterns); + + expect(filter('symlink')).toBe(false); + expect(filter('real_dir')).toBe(false); + expect(filter('symlink/file.txt')).toBe(false); + expect(filter('real_dir/file.txt')).toBe(false); + }); + + test('should handle long paths correctly', () => { + const longPath = 'a'.repeat(200) + '/file.txt'; + const patterns = ['**/*.txt']; + const filter = createIgnoreFilter(patterns); + + expect(filter(longPath)).toBe(false); + }); + + test('should handle Unicode characters in paths and patterns', () => { + const patterns = ['📁/*', '*.🚀']; + const filter = createIgnoreFilter(patterns); - expect(filter('src/index.js')).toBe(true); - expect(filter('node_modules/package/index.js')).toBe(false); - expect(filter('logs/error.log')).toBe(false); - expect(filter('.DS_Store')).toBe(false); + expect(filter('📁/file.txt')).toBe(false); + expect(filter('document.🚀')).toBe(false); + expect(filter('normal/path/file.txt')).toBe(true); + }); }); }); From 9935a192579214d221a022a51eb164b573311d7e Mon Sep 17 00:00:00 2001 From: Kazuki Yamada Date: Sun, 21 Jul 2024 14:17:14 +0900 Subject: [PATCH 3/4] chore(ci): Run testing on windows and macos, Node.js 16, 18, 20 --- .github/workflows/test.yml | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index cba545a..cd181e3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -21,27 +21,28 @@ jobs: node-version-file: .tool-versions cache: npm - - name: Install dependencies - run: npm i + - name: Clean install dependencies + run: npm ci - name: Lint run: yarn lint test: - runs-on: ubuntu-22.04 - timeout-minutes: 10 + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + node-version: [16.x, 18.x, 20.x] + runs-on: ${{ matrix.os }} steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup node + - uses: actions/checkout@v4 + - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v4 with: - node-version-file: .tool-versions - cache: npm + node-version: ${{ matrix.node-version }} - - name: Install dependencies - run: npm i + - name: Clean install dependencies + run: npm ci - - name: Test - run: npm run test-coverage + - run: npm test + env: + CI_OS: ${{ runner.os }} From 7792cc62fd32124855fed929199e24c2d80a8abd Mon Sep 17 00:00:00 2001 From: Kazuki Yamada Date: Sun, 21 Jul 2024 14:34:39 +0900 Subject: [PATCH 4/4] test(ignore): test for windows --- .github/workflows/test.yml | 2 +- tests/utils/gitignoreUtils.test.ts | 48 +++++++++++++++--------------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index cd181e3..73e6822 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -43,6 +43,6 @@ jobs: - name: Clean install dependencies run: npm ci - - run: npm test + - run: npm run test-coverage -- --reporter=verbose env: CI_OS: ${{ runner.os }} diff --git a/tests/utils/gitignoreUtils.test.ts b/tests/utils/gitignoreUtils.test.ts index 9f93ff5..78c6230 100644 --- a/tests/utils/gitignoreUtils.test.ts +++ b/tests/utils/gitignoreUtils.test.ts @@ -6,6 +6,8 @@ import os from 'os'; vi.mock('fs/promises'); +const isWindows = os.platform() === 'win32'; + describe('gitignoreUtils', () => { beforeEach(() => { vi.resetAllMocks(); @@ -90,19 +92,21 @@ node_modules expect(filter('styles/main.css')).toBe(false); expect(filter('node_modules/package/index.js')).toBe(false); - // Windows-style paths - if (os.platform() === 'win32') { - expect(filter('docs\\README.md')).toBe(false); - expect(filter('src\\assets\\logo.svg')).toBe(false); - expect(filter('styles\\main.css')).toBe(false); - expect(filter('node_modules\\package\\index.js')).toBe(false); - expect(filter('src\\components\\Button.js')).toBe(true); - } - // Files that should not be ignored expect(filter('src/index.js')).toBe(true); }); + test.runIf(isWindows)('should correctly ignore files with Windows-style paths', () => { + const patterns = ['*.md', '*.svg', '*.css', 'node_modules/**']; + const filter = createIgnoreFilter(patterns); + + expect(filter('docs\\README.md')).toBe(false); + expect(filter('src\\assets\\logo.svg')).toBe(false); + expect(filter('styles\\main.css')).toBe(false); + expect(filter('node_modules\\package\\index.js')).toBe(false); + expect(filter('src\\components\\Button.js')).toBe(true); + }); + test('should handle nested directory patterns correctly', () => { const patterns = ['test/**/*.spec.js', 'build/**']; const filter = createIgnoreFilter(patterns); @@ -111,12 +115,15 @@ node_modules expect(filter('build/output.js')).toBe(false); expect(filter('src/test/helper.js')).toBe(true); + }); + + test.runIf(isWindows)('should handle nested directory patterns with Windows-style paths', () => { + const patterns = ['test/**/*.spec.js', 'build/**']; + const filter = createIgnoreFilter(patterns); - if (os.platform() === 'win32') { - expect(filter('src\\build\\utils.js')).toBe(true); - expect(filter('test\\integration\\api.spec.js')).toBe(false); - expect(filter('build\\temp\\cache.json')).toBe(false); - } + expect(filter('src\\build\\utils.js')).toBe(true); + expect(filter('test\\integration\\api.spec.js')).toBe(false); + expect(filter('build\\temp\\cache.json')).toBe(false); }); test('should correctly handle patterns with special characters', () => { @@ -131,19 +138,12 @@ node_modules expect(filter('docs/temp/file.txt')).toBe(true); }); - test('should handle case sensitivity correctly on different platforms', () => { + test('should handle case sensitivity correctly', () => { const patterns = ['*.MD', 'TEST']; const filter = createIgnoreFilter(patterns); - if (os.platform() === 'win32') { - // Windows is case-insensitive - expect(filter('readme.md')).toBe(false); - expect(filter('test/file.txt')).toBe(false); - } else { - // Unix-like systems are case-insensitive - expect(filter('readme.md')).toBe(false); - expect(filter('test/file.txt')).toBe(false); - } + expect(filter('readme.md')).toBe(false); + expect(filter('test/file.txt')).toBe(false); expect(filter('README.MD')).toBe(false); expect(filter('TEST/file.txt')).toBe(false);