- Expanded the interview configuration YAML to include new profiles for experience and insight, with detailed steps for capturing user input. - Updated the parsing logic to support new input types, including select options, enhancing user interaction during interviews. - Improved the rendering logic to ensure correct handling of section edges and types, aligning with the updated configuration structure. - Enhanced tests to validate the new configurations and rendering behavior, ensuring robustness in the interview process.
294 lines
8.6 KiB
TypeScript
294 lines
8.6 KiB
TypeScript
/**
|
|
* 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<string, Map<string, EdgeTypeHints>>([
|
|
[
|
|
"experience",
|
|
new Map<string, EdgeTypeHints>([
|
|
[
|
|
"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\]/);
|
|
});
|
|
});
|