Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: Add ignore filter tests and improve ci #3

Merged
merged 4 commits into from
Jul 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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