Phase 6 (Bonus): Watcher-Mode (--watch): - src/cli/ingest.ts: --watch Flag parsed - watchInbox(): chokidar-basiert (robuster als node:fs.watch auf Windows) Debounce 1s, awaitWriteFinish, ignoreInitial, filter Verarbeitet/Problemfaelle Auf Ctrl+C wird sauber beendet - Bug-Fix: 'return' bei files.length===0 war vor dem Watch-Block -> bei leerer Inbox mit --watch startet Watcher trotzdem ESLint + Prettier: - .eslintrc.cjs (deprecated eslintrc-Format, USE_FLAT_CONFIG=false) - .prettierrc mit Standard-Format - package.json scripts: lint, format, format:check - Lint-Fehler in Tests gefixt (try/catch, unused imports) npm audit fix --force: - esbuild 0.24.2 -> neuere Version (via Vite-Update) - vite, vitest auf v5 (breaking change akzeptiert) - 5 vulnerabilities -> 0 Verifiziert: - 50/50 Tests gruen - Watcher E2E: Notiz in 00_Inbox/ waehrend --watch -> Wiki erstellt, verarbeitet - Lint: 0 Fehler (nur Deprecation-Warnung fuer eslintrc-Format) Dependencies: chokidar (neu)
141 lines
5.0 KiB
TypeScript
141 lines
5.0 KiB
TypeScript
// Edge-Case-Tests fuer den Indexer
|
|
// Phase 2.5-erweitert
|
|
|
|
import { describe, it, expect, afterEach } from 'vitest';
|
|
import { execSync } from 'node:child_process';
|
|
import { mkdtempSync, mkdirSync, writeFileSync, rmSync, readFileSync } from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import { join } from 'node:path';
|
|
|
|
const TOOL = 'C:/GitHub/Mind-o-Mat';
|
|
const CLI = `node ${TOOL}/bin/mindomat.mjs`;
|
|
|
|
function setupVault(files: Record<string, string> = {}, includeKonfig = true): string {
|
|
const vault = mkdtempSync(join(tmpdir(), 'mindomat-edge-'));
|
|
for (const d of [
|
|
'00_Inbox', '00_Inbox/Verarbeitet', '00_Inbox/Problemfaelle',
|
|
'01_Daily', '10_Wiki', '10_Wiki/Seiten',
|
|
'20_Projekte', '90_Templates',
|
|
'99_System', '99_System/Skripte', '99_System/Logs', '99_System/Cache',
|
|
]) {
|
|
mkdirSync(join(vault, d), { recursive: true });
|
|
}
|
|
if (includeKonfig) {
|
|
writeFileSync(join(vault, '99_System/Konfig.md'),
|
|
'---\nveraltet_schwellwert_monate: 12\n---\n');
|
|
}
|
|
for (const [rel, content] of Object.entries(files)) {
|
|
const full = join(vault, rel);
|
|
mkdirSync(join(full, '..'), { recursive: true });
|
|
writeFileSync(full, content);
|
|
}
|
|
return vault;
|
|
}
|
|
|
|
function runIndexer(vault: string): void {
|
|
execSync(`${CLI} index --vault "${vault}"`, { stdio: 'pipe' });
|
|
}
|
|
|
|
const vaults: string[] = [];
|
|
afterEach(() => {
|
|
for (const v of vaults) {
|
|
rmSync(v, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
describe('Indexer Edge Cases', () => {
|
|
it('leerer Vault: keine Notizen, leere Outputs', () => {
|
|
const vault = setupVault({}, false);
|
|
vaults.push(vault);
|
|
runIndexer(vault);
|
|
|
|
const landkarte = JSON.parse(readFileSync(join(vault, 'landkarte.json'), 'utf-8'));
|
|
expect(landkarte.nodes.length).toBe(0);
|
|
expect(landkarte.edges.length).toBe(0);
|
|
|
|
const sysIndex = readFileSync(join(vault, '99_System/Index.md'), 'utf-8');
|
|
expect(sysIndex).toContain('Vault-Katalog');
|
|
expect(sysIndex).toContain('0 Notizen');
|
|
});
|
|
|
|
it('Notiz ohne Frontmatter: Typ wird aus Pfad erkannt, Frontmatter-Titel fehlt', () => {
|
|
const vault = setupVault({
|
|
'01_Daily/2026-09-05_Plain.md': '# Reiner Markdown\n\nKein Frontmatter.',
|
|
});
|
|
vaults.push(vault);
|
|
runIndexer(vault);
|
|
|
|
const landkarte = JSON.parse(readFileSync(join(vault, 'landkarte.json'), 'utf-8'));
|
|
expect(landkarte.nodes.length).toBe(1);
|
|
// 01_Daily/ mit YYYY-MM-DD-Pattern wird korrekt als 'daily' typisiert
|
|
expect(landkarte.nodes[0].type).toBe('daily');
|
|
// title faellt auf vollen Pfad ohne .md zurueck
|
|
expect(landkarte.nodes[0].title).toBe('01_Daily/2026-09-05_Plain');
|
|
});
|
|
|
|
it('Unicode im Titel wird korrekt verarbeitet', () => {
|
|
const vault = setupVault({
|
|
'00_Inbox/2026-09-05_Kaffee_ÄÖÜ.md':
|
|
'---\ntitle: Größere Übung\ncreated: 2026-09-05\n---\n\n# Größere Übung\n\nß und ö.',
|
|
});
|
|
vaults.push(vault);
|
|
runIndexer(vault);
|
|
|
|
const landkarte = JSON.parse(readFileSync(join(vault, 'landkarte.json'), 'utf-8'));
|
|
expect(landkarte.nodes[0].title).toBe('Größere Übung');
|
|
});
|
|
|
|
it('tote Wikilinks erscheinen in Index.md', () => {
|
|
const vault = setupVault({
|
|
'00_Inbox/2026-09-05_A.md':
|
|
'---\ntitle: A\n---\n\nVerweist auf [[B_Datei_Nicht_Da]].',
|
|
});
|
|
vaults.push(vault);
|
|
runIndexer(vault);
|
|
|
|
const sysIndex = readFileSync(join(vault, '99_System/Index.md'), 'utf-8');
|
|
expect(sysIndex).toContain('Verwaiste Links');
|
|
expect(sysIndex).toContain('B_Datei_Nicht_Da');
|
|
});
|
|
|
|
it('zirkulaere Wikilinks (A->B->A) ergeben 2 Edges', () => {
|
|
const vault = setupVault({
|
|
'00_Inbox/2026-09-05_A.md': '---\ntitle: A\n---\n\nVerweist auf [[B]].',
|
|
'00_Inbox/2026-09-05_B.md': '---\ntitle: B\n---\n\nVerweist auf [[A]].',
|
|
});
|
|
vaults.push(vault);
|
|
runIndexer(vault);
|
|
|
|
const landkarte = JSON.parse(readFileSync(join(vault, 'landkarte.json'), 'utf-8'));
|
|
// 2 Knoten, 2 Edges (A->B und B->A), dedupliziert bleibt 2
|
|
expect(landkarte.nodes.length).toBe(2);
|
|
expect(landkarte.edges.length).toBe(2);
|
|
});
|
|
|
|
it('mehrfache Wikilinks zum gleichen Ziel werden dedupliziert', () => {
|
|
const vault = setupVault({
|
|
'00_Inbox/2026-09-05_A.md': '---\ntitle: A\n---\n\n[[B]] und nochmal [[B]] und [[B]].',
|
|
'00_Inbox/2026-09-05_B.md': '---\ntitle: B\n---\n\nInhalt.',
|
|
});
|
|
vaults.push(vault);
|
|
runIndexer(vault);
|
|
|
|
const landkarte = JSON.parse(readFileSync(join(vault, 'landkarte.json'), 'utf-8'));
|
|
// 3 Wikilinks im Text, aber nur 1 Edge (dedupliziert)
|
|
expect(landkarte.edges.length).toBe(1);
|
|
});
|
|
|
|
it('Wikilink mit Anzeige-Text [[A|Alias]] wird korrekt extrahiert', () => {
|
|
const vault = setupVault({
|
|
'00_Inbox/2026-09-05_A.md': '---\ntitle: A\n---\n\nSiehe [[B_Datei|Zeige dies]].',
|
|
'00_Inbox/2026-09-05_B_Datei.md': '---\ntitle: B_Datei\n---\n\nInhalt.',
|
|
});
|
|
vaults.push(vault);
|
|
runIndexer(vault);
|
|
|
|
const landkarte = JSON.parse(readFileSync(join(vault, 'landkarte.json'), 'utf-8'));
|
|
expect(landkarte.edges.length).toBe(1);
|
|
expect(landkarte.edges[0].to).toBe('B_Datei.md');
|
|
});
|
|
});
|