feat(f7.1): sequence participant/message diff + rect emission (#22)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -55,9 +55,49 @@ export function parseSequence(source: string): SeqDiagram {
|
|||||||
return { header: header || "sequenceDiagram", participants: declared, statements };
|
return { header: header || "sequenceDiagram", participants: declared, statements };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replaced in Task 5 with the real diff/emit.
|
function hexToRgb(hex: string): string {
|
||||||
export function diffSequence(_beforeSrc: string, currentSrc: string): string {
|
const h = hex.replace("#", "");
|
||||||
void diffArrays;
|
const n = parseInt(h, 16);
|
||||||
void CW_COLORS;
|
return `${(n >> 16) & 255}, ${(n >> 8) & 255}, ${n & 255}`;
|
||||||
return currentSrc;
|
}
|
||||||
|
|
||||||
|
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");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,3 +22,44 @@ describe("parseSequence", () => {
|
|||||||
expect(d.statements).toEqual(["A->>B: hi"]);
|
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));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user