mirror of
https://github.com/aaronsb/obsidian-mcp-plugin.git
synced 2026-07-22 06:45:14 +00:00
feat: add tests for searches with the tag operator
This commit is contained in:
parent
82423fcb6a
commit
0cc683431d
2 changed files with 134 additions and 0 deletions
|
|
@ -91,4 +91,35 @@ export class Setting {
|
|||
export function normalizePath(path: string): string {
|
||||
// Simple normalization for testing
|
||||
return path.replace(/\\/g, '/');
|
||||
}
|
||||
|
||||
// Minimal implementation of Obsidian's getAllTags utility for tests
|
||||
// Accepts a metadata cache-like object and returns a flat list of tags with leading '#'
|
||||
export function getAllTags(cache: any): string[] {
|
||||
if (!cache) return [];
|
||||
const out = new Set<string>();
|
||||
|
||||
// From cache.tags: [{ tag: '#foo' }, { tag: '#foo/bar' }]
|
||||
if (Array.isArray(cache.tags)) {
|
||||
for (const t of cache.tags) {
|
||||
const raw = typeof t === 'string' ? t : t?.tag;
|
||||
if (!raw) continue;
|
||||
const norm = raw.startsWith('#') ? raw : `#${raw}`;
|
||||
out.add(norm);
|
||||
}
|
||||
}
|
||||
|
||||
// From frontmatter.tags: 'foo', ['foo', 'bar'], or ['#foo/bar']
|
||||
const fmTags = cache.frontmatter?.tags;
|
||||
if (fmTags) {
|
||||
const list = Array.isArray(fmTags) ? fmTags : [fmTags];
|
||||
for (const rawItem of list) {
|
||||
if (!rawItem) continue;
|
||||
const raw = String(rawItem).trim();
|
||||
const norm = raw.startsWith('#') ? raw : `#${raw}`;
|
||||
out.add(norm);
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(out);
|
||||
}
|
||||
103
tests/search-tag-operator.test.ts
Normal file
103
tests/search-tag-operator.test.ts
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
import { ObsidianAPI } from '../src/utils/obsidian-api';
|
||||
import { App, TFile } from 'obsidian';
|
||||
|
||||
// Helper to create a mock TFile with required fields
|
||||
function makeFile(path: string): TFile {
|
||||
const f = Object.create(TFile.prototype);
|
||||
Object.assign(f, {
|
||||
path,
|
||||
name: path.split('/').pop()!,
|
||||
basename: path.split('/').pop()!.replace(/\.[^/.]+$/, ''),
|
||||
extension: path.split('.').pop() || 'md',
|
||||
stat: { size: 123, mtime: Date.now() }
|
||||
});
|
||||
return f as TFile;
|
||||
}
|
||||
|
||||
// Create a minimal mock app for tag-based search
|
||||
function makeMockApp(filesWithCaches: Array<{ file: TFile; cache: any }>): App {
|
||||
const files = filesWithCaches.map((x) => x.file);
|
||||
const cacheMap = new Map<string, any>(filesWithCaches.map(({ file, cache }) => [file.path, cache]));
|
||||
|
||||
const mockApp: any = {
|
||||
vault: {
|
||||
getFiles: jest.fn(() => files),
|
||||
},
|
||||
metadataCache: {
|
||||
getFileCache: jest.fn((file: TFile) => cacheMap.get(file.path)),
|
||||
},
|
||||
};
|
||||
|
||||
return mockApp as App;
|
||||
}
|
||||
|
||||
describe('search tag: operator', () => {
|
||||
test('matches files with exact tag and hierarchical children (no leading #)', async () => {
|
||||
const f1 = makeFile('notes/alpha.md');
|
||||
const f2 = makeFile('notes/beta.md');
|
||||
const f3 = makeFile('notes/gamma.md');
|
||||
|
||||
const app = makeMockApp([
|
||||
{
|
||||
file: f1,
|
||||
cache: {
|
||||
// content tags form
|
||||
tags: [{ tag: '#foo' }],
|
||||
},
|
||||
},
|
||||
{
|
||||
file: f2,
|
||||
cache: {
|
||||
// hierarchical child should match tag:foo
|
||||
tags: [{ tag: '#foo/bar' }],
|
||||
},
|
||||
},
|
||||
{
|
||||
file: f3,
|
||||
cache: {
|
||||
// different tag
|
||||
tags: [{ tag: '#bar' }],
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
const api = new ObsidianAPI(app);
|
||||
|
||||
const res = await api.searchPaginated('tag:foo');
|
||||
const paths = res.results.map((r) => r.path).sort();
|
||||
|
||||
expect(paths).toEqual(['notes/alpha.md', 'notes/beta.md']);
|
||||
});
|
||||
|
||||
test('matches with leading # and frontmatter tags', async () => {
|
||||
const f1 = makeFile('notes/frontmatter.md');
|
||||
const f2 = makeFile('notes/other.md');
|
||||
|
||||
const app = makeMockApp([
|
||||
{
|
||||
file: f1,
|
||||
cache: {
|
||||
frontmatter: {
|
||||
tags: ['foo', 'x/y'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
file: f2,
|
||||
cache: {
|
||||
frontmatter: {
|
||||
tags: ['bar'],
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
const api = new ObsidianAPI(app);
|
||||
|
||||
const res1 = await api.searchPaginated('tag:#foo');
|
||||
expect(res1.results.map((r) => r.path)).toEqual(['notes/frontmatter.md']);
|
||||
|
||||
const res2 = await api.searchPaginated('tag:x');
|
||||
expect(res2.results.map((r) => r.path)).toEqual(['notes/frontmatter.md']);
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue