182097ef1c
The isDirectChild() function failed to match files when the API used absolute paths (/Users/x/project/app/api) but the database stored relative paths (app/api/router.py). This caused all folder CLAUDE.md files to incorrectly show "No recent activity". Changes: - Create shared path-utils module with proper path normalization - Implement suffix matching strategy for mixed path formats - Update SessionSearch.ts to use shared utilities - Update regenerate-claude-md.ts to use shared utilities (was using outdated broken logic) - Prevent spurious directory creation from malformed paths - Add comprehensive test coverage for path matching edge cases This is the proper fix for #794, replacing PR #809 which only masked the bug by skipping file creation when "no activity" was shown. Co-authored-by: bigphoot <bigphoot@gmail.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
125 lines
4.7 KiB
TypeScript
125 lines
4.7 KiB
TypeScript
import { describe, expect, test } from 'bun:test';
|
|
import { isDirectChild, normalizePath } from '../../../src/shared/path-utils.js';
|
|
|
|
/**
|
|
* Tests for path matching logic, specifically the isDirectChild() algorithm
|
|
* Covers fix for issue #794: Path format mismatch causes folder CLAUDE.md files to show "No recent activity"
|
|
*
|
|
* These tests validate the shared path-utils module which is used by:
|
|
* - SessionSearch.ts (runtime folder CLAUDE.md generation)
|
|
* - regenerate-claude-md.ts (CLI regeneration tool)
|
|
*/
|
|
|
|
describe('isDirectChild path matching', () => {
|
|
describe('same path format', () => {
|
|
test('returns true for direct child with relative paths', () => {
|
|
expect(isDirectChild('app/api/router.py', 'app/api')).toBe(true);
|
|
});
|
|
|
|
test('returns true for direct child with absolute paths', () => {
|
|
expect(isDirectChild('/Users/dev/project/app/api/router.py', '/Users/dev/project/app/api')).toBe(true);
|
|
});
|
|
|
|
test('returns false for files in subdirectory with relative paths', () => {
|
|
expect(isDirectChild('app/api/v1/router.py', 'app/api')).toBe(false);
|
|
});
|
|
|
|
test('returns false for files in subdirectory with absolute paths', () => {
|
|
expect(isDirectChild('/Users/dev/project/app/api/v1/router.py', '/Users/dev/project/app/api')).toBe(false);
|
|
});
|
|
|
|
test('returns false for unrelated paths', () => {
|
|
expect(isDirectChild('lib/utils/helper.py', 'app/api')).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('mixed path formats (absolute folder, relative file) - fixes #794', () => {
|
|
test('returns true when absolute folder ends with relative file directory', () => {
|
|
// This is the exact bug case from #794
|
|
expect(isDirectChild('app/api/router.py', '/Users/dev/project/app/api')).toBe(true);
|
|
});
|
|
|
|
test('returns true for deeply nested folder match', () => {
|
|
expect(isDirectChild('src/components/Button.tsx', '/home/user/project/src/components')).toBe(true);
|
|
});
|
|
|
|
test('returns false for files in subdirectory of matched folder', () => {
|
|
expect(isDirectChild('app/api/v1/router.py', '/Users/dev/project/app/api')).toBe(false);
|
|
});
|
|
|
|
test('returns false when file path does not match folder suffix', () => {
|
|
expect(isDirectChild('lib/api/router.py', '/Users/dev/project/app/api')).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('path normalization', () => {
|
|
test('handles Windows backslash paths', () => {
|
|
expect(isDirectChild('app\\api\\router.py', 'app\\api')).toBe(true);
|
|
});
|
|
|
|
test('handles mixed slashes', () => {
|
|
expect(isDirectChild('app/api\\router.py', 'app\\api')).toBe(true);
|
|
});
|
|
|
|
test('handles trailing slashes on folder path', () => {
|
|
expect(isDirectChild('app/api/router.py', 'app/api/')).toBe(true);
|
|
});
|
|
|
|
test('handles double slashes (path normalization bug)', () => {
|
|
expect(isDirectChild('app//api/router.py', 'app/api')).toBe(true);
|
|
});
|
|
|
|
test('collapses multiple consecutive slashes', () => {
|
|
expect(isDirectChild('app///api///router.py', 'app//api//')).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('edge cases', () => {
|
|
test('returns false for single segment file path', () => {
|
|
expect(isDirectChild('router.py', '/Users/dev/project/app/api')).toBe(false);
|
|
});
|
|
|
|
test('returns false for empty paths', () => {
|
|
expect(isDirectChild('', 'app/api')).toBe(false);
|
|
expect(isDirectChild('app/api/router.py', '')).toBe(false);
|
|
});
|
|
|
|
test('handles root-level folders', () => {
|
|
expect(isDirectChild('src/file.ts', '/project/src')).toBe(true);
|
|
});
|
|
|
|
test('prevents false positive from partial segment match', () => {
|
|
// "api" folder should not match "api-v2" folder
|
|
expect(isDirectChild('app/api-v2/router.py', '/Users/dev/project/app/api')).toBe(false);
|
|
});
|
|
|
|
test('handles similar folder names correctly', () => {
|
|
// "components" should not match "components-old"
|
|
expect(isDirectChild('src/components-old/Button.tsx', '/project/src/components')).toBe(false);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('normalizePath', () => {
|
|
test('converts backslashes to forward slashes', () => {
|
|
expect(normalizePath('app\\api\\router.py')).toBe('app/api/router.py');
|
|
});
|
|
|
|
test('collapses consecutive slashes', () => {
|
|
expect(normalizePath('app//api///router.py')).toBe('app/api/router.py');
|
|
});
|
|
|
|
test('removes trailing slashes', () => {
|
|
expect(normalizePath('app/api/')).toBe('app/api');
|
|
expect(normalizePath('app/api///')).toBe('app/api');
|
|
});
|
|
|
|
test('handles Windows UNC paths', () => {
|
|
expect(normalizePath('\\\\server\\share\\file.txt')).toBe('/server/share/file.txt');
|
|
});
|
|
|
|
test('preserves leading slash for absolute paths', () => {
|
|
expect(normalizePath('/Users/dev/project')).toBe('/Users/dev/project');
|
|
});
|
|
});
|