Files
Mind-o-Mat/tests/indexer-edge-cases.test.ts
T
Jannik f2e8e59baf
CI / test (push) Canceled after 0s
CI / webapp (push) Canceled after 0s
v2.0.2: Watcher-Mode + Watch-Bug-Fix + Lint-Setup + Audit-Fix
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)
2026-09-05 12:29:48 +02:00

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');
});
});