murashit_codex-panel/tests/features/turn-diff/turn-diff.test.ts
2026-06-27 23:43:56 +09:00

177 lines
7.1 KiB
TypeScript

// @vitest-environment jsdom
import type { WorkspaceLeaf } from "obsidian";
import { describe, expect, it, vi } from "vitest";
import { persistedTurnDiffViewState } from "../../../src/features/turn-diff/model";
import { renderTurnDiffView } from "../../../src/features/turn-diff/render.dom";
import { CodexTurnDiffView } from "../../../src/features/turn-diff/view.obsidian";
import { installObsidianDomShims } from "../../support/dom";
installObsidianDomShims();
describe("turn diff view decisions", () => {
it("renders the turn diff view with classified unified diff lines", () => {
const parent = document.createElement("div");
const copyDiff = vi.fn();
renderTurnDiffView(
parent,
{
threadId: "019e061e-0046-7653-a362-86de9a47cb5c",
turnId: "019e061f-0046-7653-a362-86de9a47cb5c",
cwd: "/vault/project",
files: ["src/main.ts"],
diff: "diff --git a/src/main.ts b/src/main.ts\n--- a/src/main.ts\n+++ b/src/main.ts\n@@\n-old\n+new\n context",
},
{ copyDiff },
);
expect(parent.querySelector(".codex-panel-turn-diff__title")?.textContent).toBe("Turn diff");
expect(parent.querySelector(".codex-panel-turn-diff__meta")?.textContent).toContain("019e061e");
const changedFilesSummary = parent.querySelector<HTMLElement>(".codex-panel-turn-diff__files summary");
expect(changedFilesSummary?.textContent).toBe("Changed files");
expect(changedFilesSummary?.tabIndex).toBe(-1);
expect(parent.querySelector(".codex-panel-turn-diff__files")?.textContent).toContain("src/main.ts");
expect(parent.querySelector(".codex-panel-diff__line--file")?.textContent).toBe("src/main.ts");
expect(parent.textContent).not.toContain("diff --git");
expect(parent.textContent).not.toContain("+++ b/src/main.ts");
expect(parent.textContent).not.toContain("-old");
expect(parent.textContent).not.toContain("+new");
expect(parent.textContent).toContain("old");
expect(parent.textContent).toContain("new");
expect(parent.querySelectorAll(".codex-panel-diff__line--hunk")).toHaveLength(1);
expect(parent.querySelectorAll(".codex-panel-diff__line--removed")).toHaveLength(1);
expect(parent.querySelectorAll(".codex-panel-diff__line--added")).toHaveLength(1);
parent.querySelector<HTMLButtonElement>(".codex-panel-turn-diff__copy")?.click();
expect(copyDiff).toHaveBeenCalled();
});
it("highlights changed English words inside adjacent removed and added lines", () => {
const parent = document.createElement("div");
renderTurnDiffView(parent, {
threadId: "thread",
turnId: "turn",
cwd: "/vault/project",
files: ["Note.md"],
diff: "diff --git a/Note.md b/Note.md\n@@\n-The quick brown fox\n+The quick red fox",
});
expect(parent.textContent).toContain("The quick brown fox");
expect(parent.textContent).toContain("The quick red fox");
expect(parent.querySelector(".codex-panel-diff__word--removed")?.textContent).toBe("brown");
expect(parent.querySelector(".codex-panel-diff__word--added")?.textContent).toBe("red");
});
it("highlights changed Japanese words with Intl.Segmenter", () => {
const parent = document.createElement("div");
renderTurnDiffView(parent, {
threadId: "thread",
turnId: "turn",
cwd: "/vault/project",
files: ["Note.md"],
diff: "diff --git a/Note.md b/Note.md\n@@\n-吾輩は猫である\n+吾輩は犬である",
});
expect(parent.textContent).toContain("吾輩は猫である");
expect(parent.textContent).toContain("吾輩は犬である");
expect(parent.querySelector(".codex-panel-diff__word--removed")?.textContent).toBe("猫");
expect(parent.querySelector(".codex-panel-diff__word--added")?.textContent).toBe("犬");
});
it("pairs changed words by line inside multi-line replacement blocks", () => {
const parent = document.createElement("div");
renderTurnDiffView(parent, {
threadId: "thread",
turnId: "turn",
cwd: "/vault/project",
files: ["Note.md"],
diff: [
"diff --git a/Note.md b/Note.md",
"@@",
"-これはdiffのテストです。",
"-今日は元気です。",
"-とても元気です。",
"+これはdiffのてすとです。",
"+きょうはげんきです。",
"+とてもげんきです。",
].join("\n"),
});
const removedHighlights = Array.from(parent.querySelectorAll(".codex-panel-diff__word--removed"), (element) => element.textContent);
const addedHighlights = Array.from(parent.querySelectorAll(".codex-panel-diff__word--added"), (element) => element.textContent);
expect(removedHighlights).toEqual(["テスト", "今日", "元気", "元気"]);
expect(addedHighlights).toEqual(["てすと", "きょう", "げんき", "げんき"]);
expect(removedHighlights).not.toContain("これはdiffのテスト");
});
it("falls back to line-level rendering for large intraline candidates", () => {
const parent = document.createElement("div");
const oldText = `start ${"old ".repeat(600)}end`;
const newText = `start ${"new ".repeat(600)}end`;
renderTurnDiffView(parent, {
threadId: "thread",
turnId: "turn",
cwd: "/vault/project",
files: ["Note.md"],
diff: `diff --git a/Note.md b/Note.md\n@@\n-${oldText}\n+${newText}`,
});
expect(parent.textContent).toContain(oldText);
expect(parent.textContent).toContain(newText);
expect(parent.querySelector(".codex-panel-diff__word")).toBeNull();
});
it("keeps unified diff text out of persisted turn diff view state", () => {
const persisted = persistedTurnDiffViewState({
threadId: "thread",
turnId: "turn",
cwd: "/vault/project",
files: ["src/main.ts"],
diff: "@@\n-old\n+new",
});
expect(persisted).toEqual({
threadId: "thread",
turnId: "turn",
cwd: "/vault/project",
files: ["src/main.ts"],
});
expect(persisted).not.toHaveProperty("diff");
});
it("renders restored turn diff metadata without unavailable diff text", () => {
const parent = document.createElement("div");
renderTurnDiffView(parent, null, {}, { threadId: "thread", turnId: "turn", cwd: "/vault/project", files: ["src/main.ts"] });
expect(parent.querySelector(".codex-panel-turn-diff__meta")?.textContent).toContain("thread / turn");
expect(parent.textContent).toContain("Turn diff is no longer available.");
expect(parent.querySelector(".codex-panel-turn-diff__copy")).toBeNull();
expect(parent.querySelector(".codex-panel-turn-diff__diff")).toBeNull();
});
it("unmounts the turn diff Preact root when the view closes", async () => {
const containerEl = document.createElement("div");
const view = new CodexTurnDiffView({ containerEl } as unknown as WorkspaceLeaf);
view.setDiffPayload({
threadId: "thread",
turnId: "turn",
cwd: "/vault/project",
files: ["src/main.ts"],
diff: "diff --git a/src/main.ts b/src/main.ts\n@@\n-old\n+new",
});
expect(view.contentEl.querySelector(".codex-panel-turn-diff__title")?.textContent).toBe("Turn diff");
await view.onClose();
expect(view.contentEl.childElementCount).toBe(0);
});
});