/** * Tests für WP-26 Interview-Wizard Renderer * Prüft Section-Types, Block-IDs, automatische Edge-Vorschläge und Selbstreferenz-Prüfung */ import { describe, it, expect } from "vitest"; import { renderProfileToMarkdown, type RenderAnswers, type RenderOptions } from "./renderer"; import type { InterviewProfile } from "./types"; import type { GraphSchema, EdgeTypeHints } from "../mapping/graphSchema"; import { Vocabulary } from "../vocab/Vocabulary"; import { parseEdgeVocabulary } from "../vocab/parseEdgeVocabulary"; describe("WP-26 Interview Renderer", () => { // Mock Vocabulary const mockVocabularyText = `| System-Typ (Canonical) | Inverser Typ | Erlaubte Aliasse (User) | Beschreibung | |------------------------|--------------|-------------------------|-------------| | resulted_in | resulted_from| führt zu, bewirkt | Forward edge | | resulted_from | resulted_in | stammt aus, kommt von | Inverse edge | | related_to | related_to | verbunden mit | Bidirectional |`; const mockVocabulary = new Vocabulary(parseEdgeVocabulary(mockVocabularyText)); // Mock GraphSchema const mockGraphSchema: GraphSchema = { schema: new Map>([ [ "experience", new Map([ [ "insight", { typical: ["resulted_in"], prohibited: [], }, ], ]), ], ]), }; const mockOptions: RenderOptions = { graphSchema: mockGraphSchema, vocabulary: mockVocabulary, noteType: "experience", }; it("sollte keine Selbstreferenz bei automatischen Edges generieren", () => { const profile: InterviewProfile = { key: "test", label: "Test", note_type: "experience", steps: [ { type: "capture_text", key: "context", section: "## Kontext", section_type: "experience", generate_block_id: true, }, { type: "capture_text", key: "insight", section: "## Einsicht", section_type: "insight", generate_block_id: true, }, ], }; const answers: RenderAnswers = { collectedData: new Map([ ["context", "Kontext-Text"], ["insight", "Einsicht-Text"], ]), loopContexts: new Map(), }; const result = renderProfileToMarkdown(profile, answers, mockOptions); // Prüfe, dass keine Selbstreferenz vorhanden ist const contextSection = result.match(/## Kontext[\s\S]*?## Einsicht/)?.[0]; expect(contextSection).toBeDefined(); // Prüfe, dass keine Edge auf sich selbst zeigt const selfReference = contextSection?.match(/\[\[#\^context\]\]/); expect(selfReference).toBeNull(); // Prüfe, dass Edges generiert wurden const edges = result.match(/> \[!edge\]/g); expect(edges?.length).toBeGreaterThan(0); }); it("sollte Edge-Types aus graph_schema verwenden", () => { const profile: InterviewProfile = { key: "test", label: "Test", note_type: "experience", steps: [ { type: "capture_text", key: "experience", section: "## Erfahrung", section_type: "experience", generate_block_id: true, }, { type: "capture_text", key: "insight", section: "## Einsicht", section_type: "insight", generate_block_id: true, }, ], }; const answers: RenderAnswers = { collectedData: new Map([ ["experience", "Erfahrungs-Text"], ["insight", "Einsicht-Text"], ]), loopContexts: new Map(), }; const result = renderProfileToMarkdown(profile, answers, mockOptions); // Debug: Zeige das Ergebnis console.log("Render-Ergebnis:", result); // Prüfe, dass Edges generiert wurden const edges = result.match(/> \[!edge\]/g); expect(edges?.length).toBeGreaterThan(0); // Prüfe, dass "resulted_in" oder "resulted_from" verwendet wird (aus graph_schema) // Falls graph_schema nicht verfügbar ist, wird "related_to" verwendet (Fallback) const hasResultedEdge = result.match(/resulted_(in|from)/); const hasRelatedEdge = result.match(/related_to/); // Mindestens eine Edge sollte vorhanden sein expect(hasResultedEdge || hasRelatedEdge).toBeTruthy(); // Wenn graph_schema verfügbar ist, sollte "resulted_in" oder "resulted_from" verwendet werden if (mockOptions.graphSchema) { expect(hasResultedEdge).toBeTruthy(); } }); it("sollte Backlinks automatisch generieren", () => { const profile: InterviewProfile = { key: "test", label: "Test", note_type: "experience", steps: [ { type: "capture_text", key: "context", section: "## Kontext", section_type: "experience", generate_block_id: true, }, { type: "capture_text", key: "insight", section: "## Einsicht", section_type: "insight", generate_block_id: true, }, ], }; const answers: RenderAnswers = { collectedData: new Map([ ["context", "Kontext-Text"], ["insight", "Einsicht-Text"], ]), loopContexts: new Map(), }; const result = renderProfileToMarkdown(profile, answers, mockOptions); // Prüfe, dass beide Edge-Types vorhanden sind (Forward und Backward) const forwardEdge = result.match(/resulted_in/); const backwardEdge = result.match(/resulted_from/); expect(forwardEdge).toBeDefined(); expect(backwardEdge).toBeDefined(); }); it("sollte keine Selbstreferenz bei expliziten Referenzen erlauben", () => { const profile: InterviewProfile = { key: "test", label: "Test", note_type: "experience", steps: [ { type: "capture_text", key: "context", section: "## Kontext", section_type: "experience", block_id: "context", references: [ { block_id: "context", // Selbstreferenz - sollte verhindert werden edge_type: "related_to", }, ], }, ], }; const answers: RenderAnswers = { collectedData: new Map([ ["context", "Kontext-Text"], ]), loopContexts: new Map(), }; const result = renderProfileToMarkdown(profile, answers, mockOptions); // Prüfe, dass keine Selbstreferenz generiert wurde const selfReference = result.match(/\[\[#\^context\]\]/); // Sollte null sein, da Selbstreferenz verhindert wird expect(selfReference).toBeNull(); }); it("sollte Section-Type-Callout direkt nach Heading platzieren", () => { const profile: InterviewProfile = { key: "test", label: "Test", note_type: "experience", steps: [ { type: "capture_text", key: "context", section: "## Kontext", section_type: "experience", generate_block_id: true, }, ], }; const answers: RenderAnswers = { collectedData: new Map([ ["context", "Kontext-Text"], ]), loopContexts: new Map(), }; const result = renderProfileToMarkdown(profile, answers, mockOptions); // Prüfe, dass Section-Type-Callout direkt nach Heading steht const sectionPattern = /## Kontext[\s\S]*?\n> \[!section\] experience/; expect(result).toMatch(sectionPattern); }); it("sollte Edges am Ende der Sektion platzieren", () => { const profile: InterviewProfile = { key: "test", label: "Test", note_type: "experience", steps: [ { type: "capture_text", key: "context", section: "## Kontext", section_type: "experience", generate_block_id: true, }, { type: "capture_text", key: "insight", section: "## Einsicht", section_type: "insight", generate_block_id: true, }, ], }; const answers: RenderAnswers = { collectedData: new Map([ ["context", "Kontext-Text"], ["insight", "Einsicht-Text"], ]), loopContexts: new Map(), }; const result = renderProfileToMarkdown(profile, answers, mockOptions); // Prüfe, dass Edges am Ende der Einsicht-Sektion stehen (nach dem Text; Abstract-Wrapper nutzt >>) const insightSection = result.match(/## Einsicht[\s\S]*?Einsicht-Text\n\n([\s\S]*?\[!edge\][\s\S]*)/)?.[1]; expect(insightSection).toBeDefined(); expect(insightSection).toMatch(/>+ \[!edge\]/); }); });