Removed console.error calls from error handlers that gracefully skip problematic files and continue processing. These handlers catch errors when reading or parsing files but successfully return fallback values, so logging errors creates unnecessary noise during testing and deployment. Changes: - vault-tools.ts: Remove console.error from search and frontmatter extraction - search-utils.ts: Remove console.error from file search handlers - waypoint-utils.ts: Remove console.error from file read handler - frontmatter-utils.ts: Remove console.error from YAML and Excalidraw parsing Test updates: - Remove test assertions checking for console.error calls since these are no longer emitted by graceful error handlers All 709 tests pass with no console noise during error handling.
531 lines
15 KiB
TypeScript
531 lines
15 KiB
TypeScript
import { WaypointUtils, WaypointBlock, FolderNoteInfo } from '../src/utils/waypoint-utils';
|
|
import { createMockVaultAdapter, createMockTFile, createMockTFolder } from './__mocks__/adapters';
|
|
import { IVaultAdapter } from '../src/adapters/interfaces';
|
|
import { TFile } from 'obsidian';
|
|
|
|
describe('WaypointUtils', () => {
|
|
describe('extractWaypointBlock()', () => {
|
|
test('extracts valid waypoint with links', () => {
|
|
const content = `# Folder Index
|
|
%% Begin Waypoint %%
|
|
- [[Note 1]]
|
|
- [[Note 2]]
|
|
- [[Subfolder/Note 3]]
|
|
%% End Waypoint %%
|
|
|
|
More content`;
|
|
|
|
const result = WaypointUtils.extractWaypointBlock(content);
|
|
|
|
expect(result.hasWaypoint).toBe(true);
|
|
expect(result.waypointRange).toEqual({ start: 2, end: 6 });
|
|
expect(result.links).toEqual(['Note 1', 'Note 2', 'Subfolder/Note 3']);
|
|
expect(result.rawContent).toBe('- [[Note 1]]\n- [[Note 2]]\n- [[Subfolder/Note 3]]');
|
|
});
|
|
|
|
test('extracts waypoint with no links', () => {
|
|
const content = `%% Begin Waypoint %%
|
|
Empty waypoint
|
|
%% End Waypoint %%`;
|
|
|
|
const result = WaypointUtils.extractWaypointBlock(content);
|
|
|
|
expect(result.hasWaypoint).toBe(true);
|
|
expect(result.waypointRange).toEqual({ start: 1, end: 3 });
|
|
expect(result.links).toEqual([]);
|
|
expect(result.rawContent).toBe('Empty waypoint');
|
|
});
|
|
|
|
test('extracts waypoint with links with aliases', () => {
|
|
const content = `%% Begin Waypoint %%
|
|
- [[Note|Alias]]
|
|
- [[Another Note#Section|Custom Text]]
|
|
%% End Waypoint %%`;
|
|
|
|
const result = WaypointUtils.extractWaypointBlock(content);
|
|
|
|
expect(result.hasWaypoint).toBe(true);
|
|
expect(result.links).toEqual(['Note|Alias', 'Another Note#Section|Custom Text']);
|
|
});
|
|
|
|
test('extracts empty waypoint', () => {
|
|
const content = `%% Begin Waypoint %%
|
|
%% End Waypoint %%`;
|
|
|
|
const result = WaypointUtils.extractWaypointBlock(content);
|
|
|
|
expect(result.hasWaypoint).toBe(true);
|
|
expect(result.waypointRange).toEqual({ start: 1, end: 2 });
|
|
expect(result.links).toEqual([]);
|
|
expect(result.rawContent).toBe('');
|
|
});
|
|
|
|
test('returns false for content without waypoint', () => {
|
|
const content = `# Regular Note
|
|
Just some content
|
|
- No waypoint here`;
|
|
|
|
const result = WaypointUtils.extractWaypointBlock(content);
|
|
|
|
expect(result.hasWaypoint).toBe(false);
|
|
expect(result.waypointRange).toBeUndefined();
|
|
expect(result.links).toBeUndefined();
|
|
expect(result.rawContent).toBeUndefined();
|
|
});
|
|
|
|
test('returns false for unclosed waypoint', () => {
|
|
const content = `%% Begin Waypoint %%
|
|
- [[Note 1]]
|
|
- [[Note 2]]
|
|
Missing end marker`;
|
|
|
|
const result = WaypointUtils.extractWaypointBlock(content);
|
|
|
|
expect(result.hasWaypoint).toBe(false);
|
|
});
|
|
|
|
test('handles waypoint with multiple links on same line', () => {
|
|
const content = `%% Begin Waypoint %%
|
|
- [[Note 1]], [[Note 2]], [[Note 3]]
|
|
%% End Waypoint %%`;
|
|
|
|
const result = WaypointUtils.extractWaypointBlock(content);
|
|
|
|
expect(result.hasWaypoint).toBe(true);
|
|
expect(result.links).toEqual(['Note 1', 'Note 2', 'Note 3']);
|
|
});
|
|
|
|
test('handles waypoint at start of file', () => {
|
|
const content = `%% Begin Waypoint %%
|
|
- [[Link]]
|
|
%% End Waypoint %%`;
|
|
|
|
const result = WaypointUtils.extractWaypointBlock(content);
|
|
|
|
expect(result.hasWaypoint).toBe(true);
|
|
expect(result.waypointRange).toEqual({ start: 1, end: 3 });
|
|
});
|
|
|
|
test('handles waypoint at end of file', () => {
|
|
const content = `Some content
|
|
%% Begin Waypoint %%
|
|
- [[Link]]
|
|
%% End Waypoint %%`;
|
|
|
|
const result = WaypointUtils.extractWaypointBlock(content);
|
|
|
|
expect(result.hasWaypoint).toBe(true);
|
|
expect(result.waypointRange).toEqual({ start: 2, end: 4 });
|
|
});
|
|
|
|
test('only extracts first waypoint if multiple exist', () => {
|
|
const content = `%% Begin Waypoint %%
|
|
- [[First]]
|
|
%% End Waypoint %%
|
|
|
|
%% Begin Waypoint %%
|
|
- [[Second]]
|
|
%% End Waypoint %%`;
|
|
|
|
const result = WaypointUtils.extractWaypointBlock(content);
|
|
|
|
expect(result.hasWaypoint).toBe(true);
|
|
expect(result.links).toEqual(['First']);
|
|
});
|
|
|
|
test('handles content with only start marker', () => {
|
|
const content = `%% Begin Waypoint %%
|
|
Content without end`;
|
|
|
|
const result = WaypointUtils.extractWaypointBlock(content);
|
|
|
|
expect(result.hasWaypoint).toBe(false);
|
|
});
|
|
|
|
test('handles content with only end marker', () => {
|
|
const content = `Content without start
|
|
%% End Waypoint %%`;
|
|
|
|
const result = WaypointUtils.extractWaypointBlock(content);
|
|
|
|
expect(result.hasWaypoint).toBe(false);
|
|
});
|
|
|
|
test('handles empty string', () => {
|
|
const result = WaypointUtils.extractWaypointBlock('');
|
|
|
|
expect(result.hasWaypoint).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('hasWaypointMarker()', () => {
|
|
test('returns true when both markers present', () => {
|
|
const content = `%% Begin Waypoint %%
|
|
Content
|
|
%% End Waypoint %%`;
|
|
|
|
expect(WaypointUtils.hasWaypointMarker(content)).toBe(true);
|
|
});
|
|
|
|
test('returns false when only start marker present', () => {
|
|
const content = `%% Begin Waypoint %%
|
|
Content without end`;
|
|
|
|
expect(WaypointUtils.hasWaypointMarker(content)).toBe(false);
|
|
});
|
|
|
|
test('returns false when only end marker present', () => {
|
|
const content = `Content without start
|
|
%% End Waypoint %%`;
|
|
|
|
expect(WaypointUtils.hasWaypointMarker(content)).toBe(false);
|
|
});
|
|
|
|
test('returns false when no markers present', () => {
|
|
const content = 'Regular content with no markers';
|
|
|
|
expect(WaypointUtils.hasWaypointMarker(content)).toBe(false);
|
|
});
|
|
|
|
test('returns true even if markers are reversed', () => {
|
|
const content = `%% End Waypoint %%
|
|
%% Begin Waypoint %%`;
|
|
|
|
// This tests the regex logic - both patterns exist somewhere
|
|
expect(WaypointUtils.hasWaypointMarker(content)).toBe(true);
|
|
});
|
|
|
|
test('handles empty string', () => {
|
|
expect(WaypointUtils.hasWaypointMarker('')).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('isFolderNote()', () => {
|
|
let mockVault: IVaultAdapter;
|
|
|
|
beforeEach(() => {
|
|
mockVault = createMockVaultAdapter();
|
|
});
|
|
|
|
test('detects folder note by basename match', async () => {
|
|
const folder = createMockTFolder('Projects');
|
|
const file = createMockTFile('Projects/Projects.md');
|
|
file.basename = 'Projects';
|
|
file.parent = folder;
|
|
|
|
(mockVault.read as jest.Mock).mockResolvedValue('Regular content without waypoint');
|
|
|
|
const result = await WaypointUtils.isFolderNote(mockVault, file);
|
|
|
|
expect(result.isFolderNote).toBe(true);
|
|
expect(result.reason).toBe('basename_match');
|
|
expect(result.folderPath).toBe('Projects');
|
|
});
|
|
|
|
test('detects folder note by waypoint marker', async () => {
|
|
const folder = createMockTFolder('Projects');
|
|
const file = createMockTFile('Projects/Index.md');
|
|
file.basename = 'Index';
|
|
file.parent = folder;
|
|
|
|
const content = `# Project Index
|
|
%% Begin Waypoint %%
|
|
- [[Project 1]]
|
|
%% End Waypoint %%`;
|
|
|
|
(mockVault.read as jest.Mock).mockResolvedValue(content);
|
|
|
|
const result = await WaypointUtils.isFolderNote(mockVault, file);
|
|
|
|
expect(result.isFolderNote).toBe(true);
|
|
expect(result.reason).toBe('waypoint_marker');
|
|
expect(result.folderPath).toBe('Projects');
|
|
});
|
|
|
|
test('detects folder note by both basename and waypoint', async () => {
|
|
const folder = createMockTFolder('Projects');
|
|
const file = createMockTFile('Projects/Projects.md');
|
|
file.basename = 'Projects';
|
|
file.parent = folder;
|
|
|
|
const content = `%% Begin Waypoint %%
|
|
- [[Project 1]]
|
|
%% End Waypoint %%`;
|
|
|
|
(mockVault.read as jest.Mock).mockResolvedValue(content);
|
|
|
|
const result = await WaypointUtils.isFolderNote(mockVault, file);
|
|
|
|
expect(result.isFolderNote).toBe(true);
|
|
expect(result.reason).toBe('both');
|
|
expect(result.folderPath).toBe('Projects');
|
|
});
|
|
|
|
test('detects non-folder note', async () => {
|
|
const folder = createMockTFolder('Projects');
|
|
const file = createMockTFile('Projects/Regular Note.md');
|
|
file.basename = 'Regular Note';
|
|
file.parent = folder;
|
|
|
|
(mockVault.read as jest.Mock).mockResolvedValue('Regular content without waypoint');
|
|
|
|
const result = await WaypointUtils.isFolderNote(mockVault, file);
|
|
|
|
expect(result.isFolderNote).toBe(false);
|
|
expect(result.reason).toBe('none');
|
|
expect(result.folderPath).toBe('Projects');
|
|
});
|
|
|
|
test('handles file in root directory', async () => {
|
|
const file = createMockTFile('RootNote.md');
|
|
file.basename = 'RootNote';
|
|
file.parent = null;
|
|
|
|
(mockVault.read as jest.Mock).mockResolvedValue('Content');
|
|
|
|
const result = await WaypointUtils.isFolderNote(mockVault, file);
|
|
|
|
expect(result.isFolderNote).toBe(false);
|
|
expect(result.reason).toBe('none');
|
|
expect(result.folderPath).toBeUndefined();
|
|
});
|
|
|
|
test('handles file read error - basename match still works', async () => {
|
|
const folder = createMockTFolder('Projects');
|
|
const file = createMockTFile('Projects/Projects.md');
|
|
file.basename = 'Projects';
|
|
file.parent = folder;
|
|
|
|
(mockVault.read as jest.Mock).mockRejectedValue(new Error('Read failed'));
|
|
|
|
const result = await WaypointUtils.isFolderNote(mockVault, file);
|
|
|
|
expect(result.isFolderNote).toBe(true);
|
|
expect(result.reason).toBe('basename_match');
|
|
|
|
});
|
|
|
|
test('handles file read error - waypoint cannot be detected', async () => {
|
|
const folder = createMockTFolder('Projects');
|
|
const file = createMockTFile('Projects/Index.md');
|
|
file.basename = 'Index';
|
|
file.parent = folder;
|
|
|
|
(mockVault.read as jest.Mock).mockRejectedValue(new Error('Read failed'));
|
|
|
|
const result = await WaypointUtils.isFolderNote(mockVault, file);
|
|
|
|
expect(result.isFolderNote).toBe(false);
|
|
expect(result.reason).toBe('none');
|
|
|
|
});
|
|
|
|
test('handles unclosed waypoint as no waypoint', async () => {
|
|
const folder = createMockTFolder('Projects');
|
|
const file = createMockTFile('Projects/Index.md');
|
|
file.basename = 'Index';
|
|
file.parent = folder;
|
|
|
|
const content = `%% Begin Waypoint %%
|
|
Missing end marker`;
|
|
|
|
(mockVault.read as jest.Mock).mockResolvedValue(content);
|
|
|
|
const result = await WaypointUtils.isFolderNote(mockVault, file);
|
|
|
|
expect(result.isFolderNote).toBe(false);
|
|
expect(result.reason).toBe('none');
|
|
});
|
|
});
|
|
|
|
describe('wouldAffectWaypoint()', () => {
|
|
test('returns false when no waypoint in original content', () => {
|
|
const content = 'Regular content';
|
|
const newContent = 'Updated content';
|
|
|
|
const result = WaypointUtils.wouldAffectWaypoint(content, newContent);
|
|
|
|
expect(result.affected).toBe(false);
|
|
expect(result.waypointRange).toBeUndefined();
|
|
});
|
|
|
|
test('detects waypoint removal', () => {
|
|
const content = `%% Begin Waypoint %%
|
|
- [[Note 1]]
|
|
%% End Waypoint %%`;
|
|
const newContent = 'Waypoint removed';
|
|
|
|
const result = WaypointUtils.wouldAffectWaypoint(content, newContent);
|
|
|
|
expect(result.affected).toBe(true);
|
|
expect(result.waypointRange).toEqual({ start: 1, end: 3 });
|
|
});
|
|
|
|
test('detects waypoint content change', () => {
|
|
const content = `%% Begin Waypoint %%
|
|
- [[Note 1]]
|
|
%% End Waypoint %%`;
|
|
const newContent = `%% Begin Waypoint %%
|
|
- [[Note 2]]
|
|
%% End Waypoint %%`;
|
|
|
|
const result = WaypointUtils.wouldAffectWaypoint(content, newContent);
|
|
|
|
expect(result.affected).toBe(true);
|
|
expect(result.waypointRange).toEqual({ start: 1, end: 3 });
|
|
});
|
|
|
|
test('allows waypoint to be moved (content unchanged)', () => {
|
|
const content = `%% Begin Waypoint %%
|
|
- [[Note 1]]
|
|
%% End Waypoint %%`;
|
|
const newContent = `# Added heading
|
|
|
|
%% Begin Waypoint %%
|
|
- [[Note 1]]
|
|
%% End Waypoint %%`;
|
|
|
|
const result = WaypointUtils.wouldAffectWaypoint(content, newContent);
|
|
|
|
expect(result.affected).toBe(false);
|
|
});
|
|
|
|
test('detects waypoint content change with added link', () => {
|
|
const content = `%% Begin Waypoint %%
|
|
- [[Note 1]]
|
|
%% End Waypoint %%`;
|
|
const newContent = `%% Begin Waypoint %%
|
|
- [[Note 1]]
|
|
- [[Note 2]]
|
|
%% End Waypoint %%`;
|
|
|
|
const result = WaypointUtils.wouldAffectWaypoint(content, newContent);
|
|
|
|
expect(result.affected).toBe(true);
|
|
});
|
|
|
|
test('allows waypoint when only surrounding content changes', () => {
|
|
const content = `# Heading
|
|
%% Begin Waypoint %%
|
|
- [[Note 1]]
|
|
%% End Waypoint %%
|
|
Footer`;
|
|
const newContent = `# Different Heading
|
|
%% Begin Waypoint %%
|
|
- [[Note 1]]
|
|
%% End Waypoint %%
|
|
Different Footer`;
|
|
|
|
const result = WaypointUtils.wouldAffectWaypoint(content, newContent);
|
|
|
|
expect(result.affected).toBe(false);
|
|
});
|
|
|
|
test('detects waypoint content change with whitespace differences', () => {
|
|
const content = `%% Begin Waypoint %%
|
|
- [[Note 1]]
|
|
%% End Waypoint %%`;
|
|
const newContent = `%% Begin Waypoint %%
|
|
- [[Note 1]]
|
|
%% End Waypoint %%`;
|
|
|
|
const result = WaypointUtils.wouldAffectWaypoint(content, newContent);
|
|
|
|
expect(result.affected).toBe(true);
|
|
});
|
|
|
|
test('returns false when waypoint stays identical', () => {
|
|
const content = `# Heading
|
|
%% Begin Waypoint %%
|
|
- [[Note 1]]
|
|
- [[Note 2]]
|
|
%% End Waypoint %%
|
|
Content`;
|
|
const newContent = content;
|
|
|
|
const result = WaypointUtils.wouldAffectWaypoint(content, newContent);
|
|
|
|
expect(result.affected).toBe(false);
|
|
});
|
|
|
|
test('handles empty waypoint blocks', () => {
|
|
const content = `%% Begin Waypoint %%
|
|
%% End Waypoint %%`;
|
|
const newContent = `%% Begin Waypoint %%
|
|
%% End Waypoint %%`;
|
|
|
|
const result = WaypointUtils.wouldAffectWaypoint(content, newContent);
|
|
|
|
expect(result.affected).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('getParentFolderPath()', () => {
|
|
test('extracts parent folder from nested path', () => {
|
|
expect(WaypointUtils.getParentFolderPath('folder/subfolder/file.md')).toBe('folder/subfolder');
|
|
});
|
|
|
|
test('extracts parent folder from single level path', () => {
|
|
expect(WaypointUtils.getParentFolderPath('folder/file.md')).toBe('folder');
|
|
});
|
|
|
|
test('returns null for root level file', () => {
|
|
expect(WaypointUtils.getParentFolderPath('file.md')).toBe(null);
|
|
});
|
|
|
|
test('handles path with multiple slashes', () => {
|
|
expect(WaypointUtils.getParentFolderPath('a/b/c/d/file.md')).toBe('a/b/c/d');
|
|
});
|
|
|
|
test('handles empty string', () => {
|
|
expect(WaypointUtils.getParentFolderPath('')).toBe(null);
|
|
});
|
|
|
|
test('handles path ending with slash', () => {
|
|
expect(WaypointUtils.getParentFolderPath('folder/subfolder/')).toBe('folder/subfolder');
|
|
});
|
|
});
|
|
|
|
describe('getBasename()', () => {
|
|
test('extracts basename from file with extension', () => {
|
|
expect(WaypointUtils.getBasename('file.md')).toBe('file');
|
|
});
|
|
|
|
test('extracts basename from nested path', () => {
|
|
expect(WaypointUtils.getBasename('folder/subfolder/file.md')).toBe('file');
|
|
});
|
|
|
|
test('handles file with multiple dots', () => {
|
|
expect(WaypointUtils.getBasename('file.test.md')).toBe('file.test');
|
|
});
|
|
|
|
test('handles file without extension', () => {
|
|
expect(WaypointUtils.getBasename('folder/file')).toBe('file');
|
|
});
|
|
|
|
test('returns entire name when no extension or path', () => {
|
|
expect(WaypointUtils.getBasename('filename')).toBe('filename');
|
|
});
|
|
|
|
test('handles empty string', () => {
|
|
expect(WaypointUtils.getBasename('')).toBe('');
|
|
});
|
|
|
|
test('handles path with only extension', () => {
|
|
expect(WaypointUtils.getBasename('.md')).toBe('');
|
|
});
|
|
|
|
test('handles deeply nested path', () => {
|
|
expect(WaypointUtils.getBasename('a/b/c/d/e/file.md')).toBe('file');
|
|
});
|
|
|
|
test('handles hidden file (starts with dot)', () => {
|
|
expect(WaypointUtils.getBasename('.gitignore')).toBe('');
|
|
});
|
|
|
|
test('handles hidden file with extension', () => {
|
|
expect(WaypointUtils.getBasename('.config.json')).toBe('.config');
|
|
});
|
|
});
|
|
});
|