diff --git a/src/mermaidSequenceDiff.ts b/src/mermaidSequenceDiff.ts index e3f3ed7..bfcdc9a 100644 --- a/src/mermaidSequenceDiff.ts +++ b/src/mermaidSequenceDiff.ts @@ -55,9 +55,49 @@ export function parseSequence(source: string): SeqDiagram { return { header: header || "sequenceDiagram", participants: declared, statements }; } -// Replaced in Task 5 with the real diff/emit. -export function diffSequence(_beforeSrc: string, currentSrc: string): string { - void diffArrays; - void CW_COLORS; - return currentSrc; +function hexToRgb(hex: string): string { + const h = hex.replace("#", ""); + const n = parseInt(h, 16); + return `${(n >> 16) & 255}, ${(n >> 8) & 255}, ${n & 255}`; +} + +function rectWrap(stmt: string, hex: string): string[] { + return [` rect rgb(${hexToRgb(hex)})`, ` ${stmt}`, ` end`]; +} + +export function diffSequence(beforeSrc: string, currentSrc: string): string { + const before = parseSequence(beforeSrc); + const current = parseSequence(currentSrc); + + const out: string[] = [current.header]; + + // Participants: keep current declarations, then re-declare removed ones (ghosts). + for (const p of current.participants) out.push(` participant ${p}`); + const curSet = new Set(current.participants); + for (const p of before.participants) { + if (!curSet.has(p)) out.push(` participant ${p}`); + } + + // Message stream diff via LCS over statements; pair adjacent removed+added as changed. + const parts = diffArrays(before.statements, current.statements); + for (let n = 0; n < parts.length; n++) { + const ch = parts[n]; + if (!ch.added && !ch.removed) { + for (const s of ch.value) out.push(` ${s}`); + continue; + } + if (ch.removed) { + const next = parts[n + 1]; + const addVals = next?.added ? next.value : []; + const paired = Math.min(ch.value.length, addVals.length); + for (let k = 0; k < paired; k++) out.push(...rectWrap(addVals[k], CW_COLORS.changed)); + for (let k = paired; k < ch.value.length; k++) out.push(...rectWrap(ch.value[k], CW_COLORS.removed)); + for (let k = paired; k < addVals.length; k++) out.push(...rectWrap(addVals[k], CW_COLORS.added)); + if (next?.added) n++; + continue; + } + // lone added run + for (const s of ch.value) out.push(...rectWrap(s, CW_COLORS.added)); + } + return out.join("\n"); } diff --git a/test/mermaidSequenceDiff.test.ts b/test/mermaidSequenceDiff.test.ts index accfc26..25bfcfa 100644 --- a/test/mermaidSequenceDiff.test.ts +++ b/test/mermaidSequenceDiff.test.ts @@ -22,3 +22,44 @@ describe("parseSequence", () => { expect(d.statements).toEqual(["A->>B: hi"]); }); }); + +import { diffSequence } from "../src/mermaidSequenceDiff"; + +const rectCount = (s: string, rgb: string) => s.split(`rect rgb(${rgb})`).length - 1; + +describe("diffSequence", () => { + it("wraps an added message in a green rect", () => { + const before = "sequenceDiagram\n A->>B: hi"; + const current = "sequenceDiagram\n A->>B: hi\n B->>C: on"; + const out = diffSequence(before, current); + expect(rectCount(out, "46, 160, 67")).toBe(1); // CW_COLORS.added as rgb + expect(out).toContain("B->>C: on"); + }); + + it("ghosts a removed message back in a grey rect", () => { + const before = "sequenceDiagram\n A->>B: hi\n B->>C: gone"; + const current = "sequenceDiagram\n A->>B: hi"; + const out = diffSequence(before, current); + expect(out).toContain("B->>C: gone"); + expect(rectCount(out, "128, 128, 128")).toBe(1); + }); + + it("re-declares a removed participant so it still appears", () => { + const before = "sequenceDiagram\n participant A\n participant B\n participant C\n A->>C: x"; + const current = "sequenceDiagram\n participant A\n participant B\n A->>B: y"; + const out = diffSequence(before, current); + expect(out).toMatch(/participant C/); + }); + + it("keeps an unchanged message outside any rect", () => { + const doc = "sequenceDiagram\n A->>B: hi"; + const out = diffSequence(doc, doc); + expect(out).not.toContain("rect rgb"); + }); + + it("is deterministic", () => { + const before = "sequenceDiagram\n A->>B: hi"; + const current = "sequenceDiagram\n A->>B: hi\n B->>A: bye"; + expect(diffSequence(before, current)).toBe(diffSequence(before, current)); + }); +});