Remove unused MinimalNativeCache and update tests to use TaskManager

MinimalNativeCache was completely unused in production code - it was replaced
by TaskManager but never deleted. This commit removes ~1,910 lines of dead code.

Changes:
- Deleted src/utils/MinimalNativeCache.ts (1,910 lines)
- Deleted 4 dedicated MinimalNativeCache test files
- Updated 7 FilterService test files to use TaskManager instead
- Updated mock factories to reference TaskManager
- Updated comments in main.ts and FilterService.ts

Evidence it was unused:
- Zero imports in src/ (only in tests)
- Zero instantiations in production code
- main.ts uses TaskManager (this.cacheManager)
- FilterService uses TaskManager in production
- TaskManager explicitly does NOT cache (JIT design)

The TaskManager approach:
- Reads on-demand from Obsidian's native metadataCache
- No internal indexes or caching
- Event-driven to stay in sync
- Simpler and more reliable than MinimalNativeCache

Build Verified: ✓
Tests Updated: ✓

Total Impact: ~2,000 lines removed
This commit is contained in:
callumalpass 2025-11-15 22:24:04 +11:00
parent 9832645096
commit c8a946eb27
15 changed files with 20 additions and 2670 deletions

View file

@ -537,7 +537,7 @@ export default class TaskNotesPlugin extends Plugin {
// Initialize notification service
await this.notificationService.initialize();
// Ensure MinimalNativeCache project indexes are warmed up
// Warm up TaskManager indexes for better performance
await this.warmupProjectIndexes();
// Initialize and start auto-archive service
@ -702,7 +702,7 @@ export default class TaskNotesPlugin extends Plugin {
}
/**
* Warmup project indexes in MinimalNativeCache for better performance
* Warm up TaskManager indexes for better performance
*/
private async warmupProjectIndexes(): Promise<void> {
try {

View file

@ -2743,7 +2743,6 @@ export class FilterService extends EventEmitter {
/**
* Extract project names from a task project value, handling [[link]] format
* This mirrors the logic from MinimalNativeCache.extractProjectNamesFromValue
*/
private extractProjectNamesFromTaskValue(projectValue: string, sourcePath: string): string[] {
if (!projectValue || projectValue.trim() === "" || projectValue === '""') {

File diff suppressed because it is too large Load diff

View file

@ -389,7 +389,7 @@ export const PluginFactory = {
};
const mockCache = {
// Core MinimalNativeCache methods
// Core TaskManager methods
initialize: jest.fn(),
getAllTasks: jest.fn().mockResolvedValue([]),
getAllTaskPaths: jest.fn().mockReturnValue(new Set()),

View file

@ -4,7 +4,7 @@
*/
import { FilterService } from '../../../src/services/FilterService';
import { MinimalNativeCache } from '../../../src/utils/MinimalNativeCache';
import { TaskManager } from '../../../src/utils/TaskManager';
import { StatusManager } from '../../../src/services/StatusManager';
import { PriorityManager } from '../../../src/services/PriorityManager';
import { FilterQuery, TaskInfo } from '../../../src/types';
@ -13,7 +13,7 @@ import * as dateFns from 'date-fns';
describe('Issue #384: Scheduled grouping misclassifies today as past', () => {
let filterService: FilterService;
let mockCacheManager: jest.Mocked<MinimalNativeCache>;
let mockCacheManager: jest.Mocked<TaskManager>;
let mockStatusManager: jest.Mocked<StatusManager>;
let mockPriorityManager: jest.Mocked<PriorityManager>;
let startOfDaySpy: jest.SpyInstance;

View file

@ -3,7 +3,7 @@
*/
import { FilterService } from '../../../src/services/FilterService';
import { MinimalNativeCache } from '../../../src/utils/MinimalNativeCache';
import { TaskManager } from '../../../src/utils/TaskManager';
import { StatusManager } from '../../../src/services/StatusManager';
import { PriorityManager } from '../../../src/services/PriorityManager';
import { TaskInfo, FilterQuery } from '../../../src/types';
@ -27,7 +27,7 @@ describe('Date comparison logic', () => {
describe('FilterService - Issue 153 Fix Verification', () => {
let filterService: FilterService;
let mockCacheManager: jest.Mocked<MinimalNativeCache>;
let mockCacheManager: jest.Mocked<TaskManager>;
let mockStatusManager: jest.Mocked<StatusManager>;
let mockPriorityManager: jest.Mocked<PriorityManager>;

View file

@ -4,14 +4,14 @@
*/
import { FilterService } from '../../../src/services/FilterService';
import { MinimalNativeCache } from '../../../src/utils/MinimalNativeCache';
import { TaskManager } from '../../../src/utils/TaskManager';
import { StatusManager } from '../../../src/services/StatusManager';
import { PriorityManager } from '../../../src/services/PriorityManager';
import { TaskInfo, FilterQuery } from '../../../src/types';
describe('FilterService - Issue 153 Fixed', () => {
let filterService: FilterService;
let mockCacheManager: jest.Mocked<MinimalNativeCache>;
let mockCacheManager: jest.Mocked<TaskManager>;
let mockStatusManager: jest.Mocked<StatusManager>;
let mockPriorityManager: jest.Mocked<PriorityManager>;

View file

@ -1,5 +1,5 @@
import { FilterService } from '../../../src/services/FilterService';
import { MinimalNativeCache } from '../../../src/utils/MinimalNativeCache';
import { TaskManager } from '../../../src/utils/TaskManager';
import { StatusManager } from '../../../src/services/StatusManager';
import { PriorityManager } from '../../../src/services/PriorityManager';
import { MockObsidian, App } from '../../__mocks__/obsidian';
@ -10,11 +10,11 @@ function makeApp(): App { return MockObsidian.createMockApp(); }
function makeCache(app: App, settingsOverride: Partial<typeof DEFAULT_SETTINGS> = {}) {
const mapper = new FieldMapper(DEFAULT_FIELD_MAPPING);
const settings = { ...DEFAULT_SETTINGS, ...settingsOverride } as any;
const cache = new MinimalNativeCache(app as any, settings, mapper);
const cache = new TaskManager(app as any, settings, mapper);
cache.initialize();
return cache;
}
function makeFilterService(cache: MinimalNativeCache, plugin: any) {
function makeFilterService(cache: TaskManager, plugin: any) {
const status = new StatusManager([]);
const priority = new PriorityManager([]);
return new FilterService(cache, status, priority, plugin);

View file

@ -1,5 +1,5 @@
import { FilterService } from '../../../src/services/FilterService';
import { MinimalNativeCache } from '../../../src/utils/MinimalNativeCache';
import { TaskManager } from '../../../src/utils/TaskManager';
import { StatusManager } from '../../../src/services/StatusManager';
import { PriorityManager } from '../../../src/services/PriorityManager';
import { MockObsidian, App } from '../../__mocks__/obsidian';
@ -14,12 +14,12 @@ function makeApp(): App {
function makeCache(app: App, settingsOverride: Partial<typeof DEFAULT_SETTINGS> = {}) {
const mapper = new FieldMapper(DEFAULT_FIELD_MAPPING);
const settings = { ...DEFAULT_SETTINGS, ...settingsOverride } as any;
const cache = new MinimalNativeCache(app as any, settings, mapper);
const cache = new TaskManager(app as any, settings, mapper);
cache.initialize();
return cache;
}
function makeFilterService(cache: MinimalNativeCache, plugin: any) {
function makeFilterService(cache: TaskManager, plugin: any) {
const status = new StatusManager([]);
const priority = new PriorityManager([]);
return new FilterService(cache, status, priority, plugin);

View file

@ -1,5 +1,5 @@
import { FilterService } from '../../../src/services/FilterService';
import { MinimalNativeCache } from '../../../src/utils/MinimalNativeCache';
import { TaskManager } from '../../../src/utils/TaskManager';
import { StatusManager } from '../../../src/services/StatusManager';
import { PriorityManager } from '../../../src/services/PriorityManager';
import { MockObsidian, App } from '../../__mocks__/obsidian';
@ -10,11 +10,11 @@ function makeApp(): App { return MockObsidian.createMockApp(); }
function makeCache(app: App) {
const mapper = new FieldMapper(DEFAULT_FIELD_MAPPING);
const settings = { ...DEFAULT_SETTINGS, taskIdentificationMethod: 'property', taskPropertyName: 'isTask', taskPropertyValue: 'true' } as any;
const cache = new MinimalNativeCache(app as any, settings, mapper);
const cache = new TaskManager(app as any, settings, mapper);
cache.initialize();
return cache;
}
function makeFilterService(cache: MinimalNativeCache, plugin: any) {
function makeFilterService(cache: TaskManager, plugin: any) {
const status = new StatusManager([]);
const priority = new PriorityManager([]);
return new FilterService(cache, status, priority, plugin);

View file

@ -1,5 +1,5 @@
import { FilterService } from '../../../src/services/FilterService';
import { MinimalNativeCache } from '../../../src/utils/MinimalNativeCache';
import { TaskManager } from '../../../src/utils/TaskManager';
import { StatusManager } from '../../../src/services/StatusManager';
import { PriorityManager } from '../../../src/services/PriorityManager';
import { MockObsidian } from '../../__mocks__/obsidian';
@ -14,7 +14,7 @@ function createPluginWithUserFields(userFields: any[]) {
function makeFilterService(plugin: any) {
const app = MockObsidian.createMockApp();
const cache = new MinimalNativeCache(app as any, DEFAULT_SETTINGS);
const cache = new TaskManager(app as any, DEFAULT_SETTINGS);
const status = new StatusManager([]);
const priority = new PriorityManager([]);
return new FilterService(cache, status, priority, plugin);

View file

@ -1,151 +0,0 @@
/**
* MinimalNativeCache boolean property identification tests
*/
import { MinimalNativeCache } from '../../../src/utils/MinimalNativeCache';
import { MockObsidian } from '../../__mocks__/obsidian';
describe('MinimalNativeCache - boolean property identification', () => {
let app: any;
beforeEach(() => {
MockObsidian.reset();
app = MockObsidian.createMockApp();
});
function createFrontmatterContent(frontmatter: Record<string, any>, body: string = '\n') {
const yaml = require('yaml').stringify(frontmatter);
return `---\n${yaml}---\n${body}`;
}
it('recognizes notes when frontmatter boolean true matches setting value "true"', async () => {
// Arrange settings for property-based identification
const settings: any = {
taskIdentificationMethod: 'property',
taskPropertyName: 'isTask',
taskPropertyValue: 'true',
taskTag: 'task',
excludedFolders: '',
disableNoteIndexing: false,
storeTitleInFilename: false,
};
// Create a markdown file with boolean property set to true (unquoted)
const path = 'tasks/boolean-task.md';
const content = createFrontmatterContent({ title: 'Boolean Task', isTask: true });
MockObsidian.createTestFile(path, content);
// Ensure metadata cache has the frontmatter (some tests bypass vault events)
app.metadataCache.setCache(path, { frontmatter: { title: 'Boolean Task', isTask: true } });
const cache = new MinimalNativeCache(app, settings);
cache.initialize();
// Act
const paths = cache.getAllTaskPaths();
// Assert
expect(paths.has(path)).toBe(true);
});
it('does not recognize notes when frontmatter boolean false and setting value is "true"', async () => {
const settings: any = {
taskIdentificationMethod: 'property',
taskPropertyName: 'isTask',
taskPropertyValue: 'true',
taskTag: 'task',
excludedFolders: '',
disableNoteIndexing: false,
storeTitleInFilename: false,
};
const path = 'tasks/not-a-task.md';
const content = createFrontmatterContent({ title: 'Not a Task', isTask: false });
MockObsidian.createTestFile(path, content);
// Ensure metadata cache has the frontmatter
app.metadataCache.setCache(path, { frontmatter: { title: 'Not a Task', isTask: false } });
const cache = new MinimalNativeCache(app, settings);
cache.initialize();
const paths = cache.getAllTaskPaths();
expect(paths.has(path)).toBe(false);
});
it('recognizes when property is an array containing boolean true', async () => {
const settings: any = {
taskIdentificationMethod: 'property',
taskPropertyName: 'isTask',
taskPropertyValue: 'true',
taskTag: 'task',
excludedFolders: '',
disableNoteIndexing: false,
storeTitleInFilename: false,
};
const path = 'tasks/array-task.md';
const content = createFrontmatterContent({ title: 'Array Task', isTask: [false, true] });
MockObsidian.createTestFile(path, content);
// Ensure metadata cache has the frontmatter
app.metadataCache.setCache(path, { frontmatter: { title: 'Array Task', isTask: [false, true] } });
const cache = new MinimalNativeCache(app, settings);
cache.initialize();
// Act
const paths = cache.getAllTaskPaths();
// Assert
expect(paths.has(path)).toBe(true);
});
it('does not recognize when frontmatter boolean true and setting value is "false"', async () => {
const settings: any = {
taskIdentificationMethod: 'property',
taskPropertyName: 'isTask',
taskPropertyValue: 'false',
taskTag: 'task',
excludedFolders: '',
disableNoteIndexing: false,
storeTitleInFilename: false,
};
const path = 'tasks/boolean-task-false-setting.md';
const content = createFrontmatterContent({ title: 'Boolean Task', isTask: true });
MockObsidian.createTestFile(path, content);
app.metadataCache.setCache(path, { frontmatter: { title: 'Boolean Task', isTask: true } });
const cache = new MinimalNativeCache(app, settings);
cache.initialize();
const paths = cache.getAllTaskPaths();
expect(paths.has(path)).toBe(false);
});
it('recognizes notes when frontmatter boolean false matches setting value "false"', async () => {
const settings: any = {
taskIdentificationMethod: 'property',
taskPropertyName: 'isTask',
taskPropertyValue: 'false',
taskTag: 'task',
excludedFolders: '',
disableNoteIndexing: false,
storeTitleInFilename: false,
};
const path = 'tasks/boolean-false-task.md';
const content = createFrontmatterContent({ title: 'Boolean False Task', isTask: false });
MockObsidian.createTestFile(path, content);
app.metadataCache.setCache(path, { frontmatter: { title: 'Boolean False Task', isTask: false } });
const cache = new MinimalNativeCache(app, settings);
cache.initialize();
const paths = cache.getAllTaskPaths();
expect(paths.has(path)).toBe(true);
});
});

View file

@ -1,144 +0,0 @@
/**
* MinimalNativeCache excluded folders handling tests
*/
import { MinimalNativeCache } from '../../../src/utils/MinimalNativeCache';
import { MockObsidian } from '../../__mocks__/obsidian';
describe('MinimalNativeCache - excluded folders handling', () => {
let app: any;
beforeEach(() => {
MockObsidian.reset();
app = MockObsidian.createMockApp();
});
it('should handle excluded folders with trailing comma', async () => {
// Arrange settings with trailing comma in excludedFolders
const settings: any = {
taskIdentificationMethod: 'tag',
taskTag: 'task',
excludedFolders: 'archive,temp,', // Note the trailing comma
disableNoteIndexing: false,
storeTitleInFilename: false,
};
// Create the cache
const cache = new MinimalNativeCache(app, settings, null as any);
// Test isValidFile method
expect(cache.isValidFile('regular/file.md')).toBe(true);
expect(cache.isValidFile('archive/file.md')).toBe(false);
expect(cache.isValidFile('temp/file.md')).toBe(false);
expect(cache.isValidFile('other/file.md')).toBe(true);
});
it('should handle excluded folders without trailing comma', async () => {
// Arrange settings without trailing comma
const settings: any = {
taskIdentificationMethod: 'tag',
taskTag: 'task',
excludedFolders: 'archive,temp',
disableNoteIndexing: false,
storeTitleInFilename: false,
};
// Create the cache
const cache = new MinimalNativeCache(app, settings, null as any);
// Test isValidFile method
expect(cache.isValidFile('regular/file.md')).toBe(true);
expect(cache.isValidFile('archive/file.md')).toBe(false);
expect(cache.isValidFile('temp/file.md')).toBe(false);
expect(cache.isValidFile('other/file.md')).toBe(true);
});
it('should handle empty excluded folders', async () => {
// Arrange settings with empty excludedFolders
const settings: any = {
taskIdentificationMethod: 'tag',
taskTag: 'task',
excludedFolders: '',
disableNoteIndexing: false,
storeTitleInFilename: false,
};
// Create the cache
const cache = new MinimalNativeCache(app, settings, null as any);
// Test isValidFile method - all files should be valid when no exclusions
expect(cache.isValidFile('regular/file.md')).toBe(true);
expect(cache.isValidFile('archive/file.md')).toBe(true);
expect(cache.isValidFile('temp/file.md')).toBe(true);
expect(cache.isValidFile('other/file.md')).toBe(true);
});
it('should handle excluded folders with multiple trailing commas', async () => {
// Arrange settings with multiple trailing commas
const settings: any = {
taskIdentificationMethod: 'tag',
taskTag: 'task',
excludedFolders: 'archive,temp,,',
disableNoteIndexing: false,
storeTitleInFilename: false,
};
// Create the cache
const cache = new MinimalNativeCache(app, settings, null as any);
// Test isValidFile method
expect(cache.isValidFile('regular/file.md')).toBe(true);
expect(cache.isValidFile('archive/file.md')).toBe(false);
expect(cache.isValidFile('temp/file.md')).toBe(false);
expect(cache.isValidFile('other/file.md')).toBe(true);
});
it('should handle excluded folders with spaces and trailing comma', async () => {
// Arrange settings with spaces and trailing comma
const settings: any = {
taskIdentificationMethod: 'tag',
taskTag: 'task',
excludedFolders: ' archive , temp , ',
disableNoteIndexing: false,
storeTitleInFilename: false,
};
// Create the cache
const cache = new MinimalNativeCache(app, settings, null as any);
// Test isValidFile method
expect(cache.isValidFile('regular/file.md')).toBe(true);
expect(cache.isValidFile('archive/file.md')).toBe(false);
expect(cache.isValidFile('temp/file.md')).toBe(false);
expect(cache.isValidFile('other/file.md')).toBe(true);
});
it('should handle updateConfig with trailing comma', async () => {
// Arrange settings
const settings: any = {
taskIdentificationMethod: 'tag',
taskTag: 'task',
excludedFolders: 'initial',
disableNoteIndexing: false,
storeTitleInFilename: false,
};
// Create the cache
const cache = new MinimalNativeCache(app, settings, null as any);
// Update settings with trailing comma
cache.updateConfig(
'task',
'archive,temp,', // trailing comma
null as any,
false,
false
);
// Test isValidFile method after update
expect(cache.isValidFile('regular/file.md')).toBe(true);
expect(cache.isValidFile('archive/file.md')).toBe(false);
expect(cache.isValidFile('temp/file.md')).toBe(false);
expect(cache.isValidFile('other/file.md')).toBe(true);
});
});

View file

@ -1,325 +0,0 @@
/**
* MinimalNativeCache.getTaskInfo() validation tests
* Tests for issue #953 - Subtasks incorrectly identified as TaskNotes
*/
import { MinimalNativeCache } from '../../../src/utils/MinimalNativeCache';
import { TaskNotesSettings } from '../../../src/types/settings';
import { TFile } from 'obsidian';
// Mock FilterUtils
jest.mock('../../../src/utils/FilterUtils', () => ({
FilterUtils: {
matchesHierarchicalTagExact: jest.fn((tag: string, taskTag: string) => {
return tag.toLowerCase() === taskTag.toLowerCase();
}),
},
}));
describe('MinimalNativeCache.getTaskInfo() - Task Identification Validation', () => {
let cache: MinimalNativeCache;
let mockApp: any;
let mockFile: any;
let mockFieldMapper: any;
beforeEach(() => {
// Create a mock file object that will pass instanceof TFile check
mockFile = Object.create(TFile.prototype);
Object.defineProperty(mockFile, 'path', { value: 'test/note.md', writable: true });
Object.defineProperty(mockFile, 'basename', { value: 'note', writable: true });
Object.defineProperty(mockFile, 'extension', { value: 'md', writable: true });
// Mock FieldMapper to return task info
mockFieldMapper = {
mapFromFrontmatter: jest.fn((frontmatter: any, path: string, storeTitleInFilename: boolean) => ({
title: frontmatter.title || 'Untitled',
status: frontmatter.status || 'open',
priority: frontmatter.priority || 'normal',
due: frontmatter.due,
scheduled: frontmatter.scheduled,
tags: frontmatter.tags || [],
contexts: frontmatter.contexts || [],
projects: frontmatter.projects || frontmatter.Parent ? [frontmatter.Parent] : [],
blockedBy: [],
timeEntries: [],
})),
};
mockApp = {
vault: {
getAbstractFileByPath: jest.fn().mockReturnValue(mockFile),
on: jest.fn(),
getMarkdownFiles: jest.fn().mockReturnValue([]),
},
metadataCache: {
getFileCache: jest.fn(),
getFirstLinkpathDest: jest.fn().mockReturnValue(null), // For dependency resolution
on: jest.fn(),
},
};
});
describe('Tag-based identification', () => {
beforeEach(() => {
const settings: TaskNotesSettings = {
taskTag: 'task',
taskIdentificationMethod: 'tag',
excludedFolders: '',
disableNoteIndexing: false,
storeTitleInFilename: false,
} as TaskNotesSettings;
cache = new MinimalNativeCache(mockApp, settings, mockFieldMapper);
});
test('should return TaskInfo for file with task tag', async () => {
// Arrange
const frontmatter = {
tags: ['task', 'project'],
title: 'Valid Task',
status: 'open',
};
mockApp.metadataCache.getFileCache.mockReturnValue({ frontmatter });
// Act
const result = await cache.getTaskInfo('test/note.md');
// Assert
expect(result).not.toBeNull();
expect(result?.title).toBe('Valid Task');
});
test('should return null for file WITHOUT task tag (issue #953)', async () => {
// Arrange - Note with Parent property but NO task tag
const frontmatter = {
tags: ['note', 'reference'],
title: 'Random Note',
Parent: '[[Existing_Task]]', // Has parent link but not a task
};
mockApp.metadataCache.getFileCache.mockReturnValue({ frontmatter });
// Act
const result = await cache.getTaskInfo('test/note.md');
// Assert - Should return null because it lacks the task tag
expect(result).toBeNull();
});
test('should return null for file with no tags', async () => {
// Arrange
const frontmatter = {
title: 'Note without tags',
Parent: '[[Some_Task]]',
};
mockApp.metadataCache.getFileCache.mockReturnValue({ frontmatter });
// Act
const result = await cache.getTaskInfo('test/note.md');
// Assert
expect(result).toBeNull();
});
test('should return null for file with empty tags array', async () => {
// Arrange
const frontmatter = {
tags: [],
title: 'Note with empty tags',
};
mockApp.metadataCache.getFileCache.mockReturnValue({ frontmatter });
// Act
const result = await cache.getTaskInfo('test/note.md');
// Assert
expect(result).toBeNull();
});
});
describe('Property-based identification', () => {
beforeEach(() => {
const settings: TaskNotesSettings = {
taskTag: 'task',
taskIdentificationMethod: 'property',
taskPropertyName: 'isTask',
taskPropertyValue: 'true',
excludedFolders: '',
disableNoteIndexing: false,
storeTitleInFilename: false,
} as TaskNotesSettings;
cache = new MinimalNativeCache(mockApp, settings, mockFieldMapper);
});
test('should return TaskInfo for file with matching property', async () => {
// Arrange
const frontmatter = {
isTask: true,
title: 'Valid Task',
status: 'open',
};
mockApp.metadataCache.getFileCache.mockReturnValue({ frontmatter });
// Act
const result = await cache.getTaskInfo('test/note.md');
// Assert
expect(result).not.toBeNull();
expect(result?.title).toBe('Valid Task');
});
test('should return null for file WITHOUT matching property (issue #953)', async () => {
// Arrange - Note with Parent but no isTask property
const frontmatter = {
title: 'Random Note',
Parent: '[[Existing_Task]]',
// isTask property is missing
};
mockApp.metadataCache.getFileCache.mockReturnValue({ frontmatter });
// Act
const result = await cache.getTaskInfo('test/note.md');
// Assert - Should return null because it lacks the required property
expect(result).toBeNull();
});
test('should return null for file with property set to false', async () => {
// Arrange
const frontmatter = {
isTask: false,
title: 'Not a Task',
Parent: '[[Some_Task]]',
};
mockApp.metadataCache.getFileCache.mockReturnValue({ frontmatter });
// Act
const result = await cache.getTaskInfo('test/note.md');
// Assert
expect(result).toBeNull();
});
test('should handle string "true" in frontmatter', async () => {
// Arrange
const frontmatter = {
isTask: 'true', // String instead of boolean
title: 'Task with string property',
};
mockApp.metadataCache.getFileCache.mockReturnValue({ frontmatter });
// Act
const result = await cache.getTaskInfo('test/note.md');
// Assert
expect(result).not.toBeNull();
});
});
describe('Edge cases', () => {
beforeEach(() => {
const settings: TaskNotesSettings = {
taskTag: 'task',
taskIdentificationMethod: 'tag',
excludedFolders: '',
disableNoteIndexing: false,
storeTitleInFilename: false,
} as TaskNotesSettings;
cache = new MinimalNativeCache(mockApp, settings, mockFieldMapper);
});
test('should return null for non-existent file', async () => {
// Arrange
mockApp.vault.getAbstractFileByPath.mockReturnValue(null);
// Act
const result = await cache.getTaskInfo('nonexistent.md');
// Assert
expect(result).toBeNull();
});
test('should return null for file without frontmatter', async () => {
// Arrange
mockApp.metadataCache.getFileCache.mockReturnValue({});
// Act
const result = await cache.getTaskInfo('test/note.md');
// Assert
expect(result).toBeNull();
});
test('should return null when metadata cache returns null', async () => {
// Arrange
mockApp.metadataCache.getFileCache.mockReturnValue(null);
// Act
const result = await cache.getTaskInfo('test/note.md');
// Assert
expect(result).toBeNull();
});
});
describe('Consistency with getCachedTaskInfoSync', () => {
test('async and sync methods should return same result for valid task', async () => {
// Arrange
const settings: TaskNotesSettings = {
taskTag: 'task',
taskIdentificationMethod: 'tag',
excludedFolders: '',
disableNoteIndexing: false,
storeTitleInFilename: false,
} as TaskNotesSettings;
cache = new MinimalNativeCache(mockApp, settings, mockFieldMapper);
const frontmatter = {
tags: ['task'],
title: 'Test Task',
status: 'open',
};
mockApp.metadataCache.getFileCache.mockReturnValue({ frontmatter });
// Act
const asyncResult = await cache.getTaskInfo('test/note.md');
const syncResult = cache.getCachedTaskInfoSync('test/note.md');
// Assert - Both should return TaskInfo
expect(asyncResult).not.toBeNull();
expect(syncResult).not.toBeNull();
expect(asyncResult?.title).toBe(syncResult?.title);
});
test('async and sync methods should both return null for non-task', async () => {
// Arrange
const settings: TaskNotesSettings = {
taskTag: 'task',
taskIdentificationMethod: 'tag',
excludedFolders: '',
disableNoteIndexing: false,
storeTitleInFilename: false,
} as TaskNotesSettings;
cache = new MinimalNativeCache(mockApp, settings, mockFieldMapper);
const frontmatter = {
tags: ['note'],
title: 'Not a Task',
Parent: '[[Some_Task]]',
};
mockApp.metadataCache.getFileCache.mockReturnValue({ frontmatter });
// Act
const asyncResult = await cache.getTaskInfo('test/note.md');
const syncResult = cache.getCachedTaskInfoSync('test/note.md');
// Assert - Both should return null
expect(asyncResult).toBeNull();
expect(syncResult).toBeNull();
});
});
});

View file

@ -1,119 +0,0 @@
import { MinimalNativeCache } from '../../src/utils/MinimalNativeCache';
import { TaskNotesSettings } from '../../src/types/settings';
// Mock FilterUtils
jest.mock('../../src/utils/FilterUtils', () => ({
FilterUtils: {
matchesHierarchicalTagExact: jest.fn((tag: string, taskTag: string) => {
return tag.toLowerCase() === taskTag.toLowerCase();
}),
},
}));
describe('MinimalNativeCache - isTaskFile with non-string tags', () => {
let cache: MinimalNativeCache;
let mockApp: any;
let settings: TaskNotesSettings;
beforeEach(() => {
mockApp = {
metadataCache: {
on: jest.fn(),
},
vault: {
on: jest.fn(),
},
};
settings = {
taskTag: 'task',
taskIdentificationMethod: 'tag',
excludedFolders: '',
disableNoteIndexing: false,
storeTitleInFilename: false,
} as TaskNotesSettings;
cache = new MinimalNativeCache(mockApp, settings);
});
test('handles frontmatter with valid string tags', () => {
const frontmatter = {
tags: ['task', 'project', 'important'],
};
const result = (cache as any).isTaskFile(frontmatter);
expect(result).toBe(true);
});
test('handles frontmatter with number in tags array', () => {
const frontmatter = {
tags: ['task', 123, 'project'],
};
const result = (cache as any).isTaskFile(frontmatter);
expect(result).toBe(true);
});
test('handles frontmatter with boolean in tags array', () => {
const frontmatter = {
tags: [true, 'task', false],
};
const result = (cache as any).isTaskFile(frontmatter);
expect(result).toBe(true);
});
test('handles frontmatter with mixed non-string types', () => {
const frontmatter = {
tags: [123, true, 'task', null, undefined, 'project'],
};
const result = (cache as any).isTaskFile(frontmatter);
expect(result).toBe(true);
});
test('returns false when all tags are non-strings', () => {
const frontmatter = {
tags: [123, true, null, undefined, 456],
};
const result = (cache as any).isTaskFile(frontmatter);
expect(result).toBe(false);
});
test('handles frontmatter with object in tags array', () => {
const frontmatter = {
tags: [{ nested: 'value' }, 'task'],
};
const result = (cache as any).isTaskFile(frontmatter);
expect(result).toBe(true);
});
test('handles frontmatter with array in tags array', () => {
const frontmatter = {
tags: [['nested', 'array'], 'task'],
};
const result = (cache as any).isTaskFile(frontmatter);
expect(result).toBe(true);
});
test('handles empty tags array', () => {
const frontmatter = {
tags: [],
};
const result = (cache as any).isTaskFile(frontmatter);
expect(result).toBe(false);
});
test('handles nested tag hierarchy with non-strings', () => {
const frontmatter = {
tags: ['task', 123, 'other/tag'],
};
const result = (cache as any).isTaskFile(frontmatter);
expect(result).toBe(true);
});
});