Skip to content

Commit

Permalink
Merge pull request #3 from yamadashy/feature/improve-ignore
Browse files Browse the repository at this point in the history
chore: Add ignore filter tests and improve ci
  • Loading branch information
yamadashy authored Jul 21, 2024
2 parents aee2df2 + 7792cc6 commit 1e5a699
Show file tree
Hide file tree
Showing 5 changed files with 165 additions and 40 deletions.
29 changes: 15 additions & 14 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 test
- run: npm run test-coverage -- --reporter=verbose
env:
CI_OS: ${{ runner.os }}
3 changes: 2 additions & 1 deletion src/core/packager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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 });
Expand Down
4 changes: 3 additions & 1 deletion src/utils/gitignoreUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
168 changes: 144 additions & 24 deletions tests/utils/gitignoreUtils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,58 +2,178 @@ 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');

const isWindows = os.platform() === 'win32';

describe('gitignoreUtils', () => {
beforeEach(() => {
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');

test('getGitignorePatterns should return empty array if .gitignore is not found', async () => {
vi.mocked(fs.readFile).mockRejectedValue(new Error('File not found'));
expect(patterns).toEqual([]);
});

const patterns = await getGitignorePatterns('/mock/root');
test('should handle CRLF line endings', async () => {
const mockContent = 'node_modules\r\n*.log\r\n.DS_Store';
vi.mocked(fs.readFile).mockResolvedValue(mockContent);

expect(patterns).toEqual([]);
const patterns = await getGitignorePatterns('/mock/root');

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);

// 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);

expect(filter('test/unit/component.spec.js')).toBe(false);
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);

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', () => {
const patterns = ['*.MD', 'TEST'];
const filter = createIgnoreFilter(patterns);

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);
});
});
});
1 change: 1 addition & 0 deletions vite.config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export default defineConfig({
environment: 'node',
include: ['tests/**/*.test.ts'],
coverage: {
include: ['src/**/*'],
reporter: ['text', 'json', 'html'],
},
}
Expand Down

0 comments on commit 1e5a699

Please sign in to comment.