mirror of
https://github.com/sotashimozono/obsidian-remote-ssh.git
synced 2026-07-22 17:10:32 +00:00
Add the first plugin scenario on top of the E-α harness. Drives
metadataCache.getFileCache() for every file in vault.getMarkdownFiles()
— the same shape Dataview's dv.pages('"folder"') query takes.
Reorg:
- Existing 4 harness fixtures moved to fixtures/harness/. Each
scenario gets its own subdirectory so getMarkdownFiles assertions
in one suite can't pick up another suite's fixtures. The harness
test now points at fixtures/harness/.
New:
- plugin/tests/compat/fixtures/dataview-pages/{task-one..five}.md —
4 tasks with frontmatter (status × priority × assignee × tag) plus
one with no frontmatter (negative-control).
- plugin/tests/compat/dataview.test.ts — 8 tests covering:
- getMarkdownFiles returns all 5 (including no-frontmatter)
- frontmatter scalars round-trip per file
- status / tag / assignee aggregations match expectations
- sum-priority aggregation
- dvPages folder-scope helper filters by prefix
docs/plugin-compatibility.md: Dataview row flipped from
"🟡 expected (check on first build)" to "✅ verified-by-harness (#124 F11)"
with a pointer to the test file.
Counts: 49 test files / 707 pass / 1 Windows-skipped.
F12 Templater, F13 Excalidraw, F14 Tasks, and the nightly compat
workflow remain as follow-up PRs in #124.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
135 lines
5.1 KiB
TypeScript
135 lines
5.1 KiB
TypeScript
import { describe, it, expect, beforeEach } from 'vitest';
|
|
import * as path from 'path';
|
|
import type { TFile } from 'obsidian';
|
|
import { CompatVault } from './CompatVault';
|
|
import { fixturesDir, loadFixtures } from './fixtures';
|
|
|
|
/**
|
|
* Phase E F11 — Dataview compat smoke.
|
|
*
|
|
* Drives the public `metadataCache.getFileCache()` API for every
|
|
* file in `vault.getMarkdownFiles()` — the same shape Dataview's
|
|
* `dv.pages('"folder"')` query takes. Asserts that the 5 fixture
|
|
* files (4 with frontmatter + 1 without) all show up and that
|
|
* downstream aggregations (count by status / by tag, sum priority,
|
|
* group by assignee) produce the right answers.
|
|
*
|
|
* If this suite breaks against a future Obsidian-API drift, the
|
|
* harness in CompatVault.ts needs updating before Dataview itself
|
|
* can be diagnosed.
|
|
*/
|
|
|
|
interface DataviewPage {
|
|
path: string;
|
|
frontmatter: Record<string, unknown> | undefined;
|
|
}
|
|
|
|
/** The shape Dataview's `dv.pages(...)` returns, narrowed to what F11 cares about. */
|
|
function dvPages(vault: CompatVault, folder: string): DataviewPage[] {
|
|
return vault.getMarkdownFiles()
|
|
.filter((f: TFile) => f.path.startsWith(folder + '/'))
|
|
.map((f: TFile) => ({
|
|
path: f.path,
|
|
frontmatter: vault.metadataCache.getFileCache(f)?.frontmatter as Record<string, unknown> | undefined,
|
|
}));
|
|
}
|
|
|
|
describe('Phase E F11 — Dataview compat smoke', () => {
|
|
let vault: CompatVault;
|
|
let pages: DataviewPage[];
|
|
|
|
beforeEach(async () => {
|
|
vault = new CompatVault();
|
|
const loaded = await loadFixtures(vault, path.join(fixturesDir(), 'dataview-pages'));
|
|
expect(loaded).toBe(5);
|
|
pages = vault.getMarkdownFiles().map(f => ({
|
|
path: f.path,
|
|
frontmatter: vault.metadataCache.getFileCache(f)?.frontmatter as Record<string, unknown> | undefined,
|
|
}));
|
|
});
|
|
|
|
it('lists every loaded page (5 of 5, including the no-frontmatter one)', () => {
|
|
const paths = pages.map(p => p.path).sort();
|
|
expect(paths).toEqual([
|
|
'task-five.md',
|
|
'task-four.md',
|
|
'task-one.md',
|
|
'task-three.md',
|
|
'task-two.md',
|
|
]);
|
|
});
|
|
|
|
it('returns frontmatter scalars for each task with a YAML block', () => {
|
|
const byPath = Object.fromEntries(pages.map(p => [p.path, p.frontmatter ?? null]));
|
|
expect(byPath['task-one.md']).toMatchObject({
|
|
status: 'open', priority: 1, assignee: 'alice', tag: 'work',
|
|
});
|
|
expect(byPath['task-two.md']).toMatchObject({
|
|
status: 'open', priority: 2, assignee: 'bob', tag: 'home',
|
|
});
|
|
expect(byPath['task-three.md']).toMatchObject({
|
|
status: 'done', priority: 3, assignee: 'alice', tag: 'work',
|
|
});
|
|
expect(byPath['task-four.md']).toMatchObject({
|
|
status: 'pending', priority: 4, assignee: 'bob', tag: 'home',
|
|
});
|
|
});
|
|
|
|
it('still appears for the page with no frontmatter (frontmatter is undefined)', () => {
|
|
const five = pages.find(p => p.path === 'task-five.md');
|
|
expect(five).toBeDefined();
|
|
expect(five!.frontmatter).toBeUndefined();
|
|
});
|
|
|
|
// ─── Dataview-shape aggregations ────────────────────────────────
|
|
|
|
it('aggregates count by status (the FROM ... GROUP BY status query)', () => {
|
|
const byStatus: Record<string, number> = {};
|
|
for (const p of pages) {
|
|
const s = (p.frontmatter?.status as string | undefined) ?? '(none)';
|
|
byStatus[s] = (byStatus[s] ?? 0) + 1;
|
|
}
|
|
expect(byStatus).toEqual({ open: 2, done: 1, pending: 1, '(none)': 1 });
|
|
});
|
|
|
|
it('aggregates count by tag (work vs home, ignores (none))', () => {
|
|
const byTag: Record<string, number> = {};
|
|
for (const p of pages) {
|
|
const t = p.frontmatter?.tag as string | undefined;
|
|
if (!t) continue;
|
|
byTag[t] = (byTag[t] ?? 0) + 1;
|
|
}
|
|
expect(byTag).toEqual({ work: 2, home: 2 });
|
|
});
|
|
|
|
it('sums priority across the 4 tasks with frontmatter (1+2+3+4 = 10)', () => {
|
|
const total = pages.reduce(
|
|
(sum, p) => sum + ((p.frontmatter?.priority as number | undefined) ?? 0),
|
|
0,
|
|
);
|
|
expect(total).toBe(10);
|
|
});
|
|
|
|
it('groups paths by assignee (alice has 2, bob has 2, unassigned has 1)', () => {
|
|
const byAssignee: Record<string, string[]> = {};
|
|
for (const p of pages) {
|
|
const a = (p.frontmatter?.assignee as string | undefined) ?? '(unassigned)';
|
|
(byAssignee[a] ??= []).push(p.path);
|
|
}
|
|
expect(byAssignee.alice.sort()).toEqual(['task-one.md', 'task-three.md']);
|
|
expect(byAssignee.bob.sort()).toEqual(['task-four.md', 'task-two.md']);
|
|
expect(byAssignee['(unassigned)']).toEqual(['task-five.md']);
|
|
});
|
|
|
|
it('dvPages folder-scope helper filters correctly', async () => {
|
|
// Drop a page outside the loaded namespace and confirm the
|
|
// helper's `startsWith(folder + '/')` filter excludes / includes
|
|
// by folder prefix.
|
|
await vault.create('elsewhere/note.md', '---\ntag: work\n---\n');
|
|
const inDataview = dvPages(vault, 'dataview-pages');
|
|
expect(inDataview.length).toBe(0); // Loaded paths don't have a `dataview-pages/` prefix
|
|
const inElsewhere = dvPages(vault, 'elsewhere');
|
|
expect(inElsewhere.length).toBe(1);
|
|
expect(inElsewhere[0].path).toBe('elsewhere/note.md');
|
|
});
|
|
});
|