mirror of
https://github.com/callumalpass/tasknotes.git
synced 2026-07-22 12:50:26 +00:00
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:
parent
9832645096
commit
c8a946eb27
15 changed files with 20 additions and 2670 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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()),
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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>;
|
||||
|
||||
|
|
|
|||
|
|
@ -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>;
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue