mirror of
https://github.com/murashit/codex-panel.git
synced 2026-07-22 06:57:10 +00:00
Harden tests and enable remaining TypeScript strictness
This commit is contained in:
parent
84980e25e3
commit
63086634df
8 changed files with 75 additions and 37 deletions
|
|
@ -13,6 +13,11 @@ import {
|
|||
parseSlashCommand,
|
||||
} from "../../src/composer/suggestions";
|
||||
|
||||
function expectPresent<T>(value: T | null | undefined): T {
|
||||
if (value === null || value === undefined) throw new Error("Expected value to be present");
|
||||
return value;
|
||||
}
|
||||
|
||||
function thread(overrides: Partial<Thread> = {}): Thread {
|
||||
return {
|
||||
id: "019abcde-0000-7000-8000-000000000001",
|
||||
|
|
@ -116,7 +121,7 @@ describe("composer suggestions", () => {
|
|||
replacement: "019abcde-0000-7000-8000-000000000001",
|
||||
appendSpaceOnInsert: true,
|
||||
});
|
||||
expect(applyComposerSuggestionInsertion("/resume codex", 13, suggestions[0])).toEqual({
|
||||
expect(applyComposerSuggestionInsertion("/resume codex", 13, expectPresent(suggestions[0]))).toEqual({
|
||||
value: "/resume 019abcde-0000-7000-8000-000000000001 ",
|
||||
cursor: 45,
|
||||
});
|
||||
|
|
@ -194,17 +199,19 @@ describe("composer suggestions", () => {
|
|||
});
|
||||
|
||||
it("adds a trailing space for slash command and skill insertions only", () => {
|
||||
const slash = activeComposerSuggestions("/sta", notes, [])[0];
|
||||
const skill = activeComposerSuggestions("$obs", notes, [
|
||||
{
|
||||
name: "obsidian-dataview-read",
|
||||
description: "Read Dataview results",
|
||||
path: "/vault/___/skills/obsidian-dataview-read/SKILL.md",
|
||||
scope: "local",
|
||||
enabled: true,
|
||||
} as never,
|
||||
])[0];
|
||||
const wikilink = activeComposerSuggestions("[[bet", notes, [])[0];
|
||||
const slash = expectPresent(activeComposerSuggestions("/sta", notes, [])[0]);
|
||||
const skill = expectPresent(
|
||||
activeComposerSuggestions("$obs", notes, [
|
||||
{
|
||||
name: "obsidian-dataview-read",
|
||||
description: "Read Dataview results",
|
||||
path: "/vault/___/skills/obsidian-dataview-read/SKILL.md",
|
||||
scope: "local",
|
||||
enabled: true,
|
||||
} as never,
|
||||
])[0],
|
||||
);
|
||||
const wikilink = expectPresent(activeComposerSuggestions("[[bet", notes, [])[0]);
|
||||
|
||||
expect(slash).toMatchObject({ replacement: "/status", appendSpaceOnInsert: true });
|
||||
expect(skill).toMatchObject({ replacement: "$obsidian-dataview-read", appendSpaceOnInsert: true });
|
||||
|
|
|
|||
|
|
@ -19,6 +19,11 @@ import type { DisplayItem } from "../../src/display/types";
|
|||
import type { ThreadItem } from "../../src/generated/app-server/v2/ThreadItem";
|
||||
import type { Turn } from "../../src/generated/app-server/v2/Turn";
|
||||
|
||||
function expectPresent<T>(value: T | null | undefined): T {
|
||||
if (value === null || value === undefined) throw new Error("Expected value to be present");
|
||||
return value;
|
||||
}
|
||||
|
||||
function commandItem(id: string, text: string, turnId: string): DisplayItem {
|
||||
return { id, kind: "command", role: "tool", text, turnId, command: text, cwd: "/vault", status: "completed" };
|
||||
}
|
||||
|
|
@ -835,10 +840,10 @@ describe("streaming updates target item identity without mutating history", () =
|
|||
];
|
||||
|
||||
const updated = appendAssistantDelta(items, "a1", "t1", " world");
|
||||
expect(updated[0].text).toBe("hello world");
|
||||
expect(updated[0]).toMatchObject({ copyText: "hello world" });
|
||||
expect(expectPresent(updated[0]).text).toBe("hello world");
|
||||
expect(expectPresent(updated[0])).toMatchObject({ copyText: "hello world" });
|
||||
expect(updated).toHaveLength(2);
|
||||
expect(items[0].text).toBe("hello");
|
||||
expect(expectPresent(items[0]).text).toBe("hello");
|
||||
expect(updated).not.toBe(items);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -483,8 +483,8 @@ describe("PanelController", () => {
|
|||
},
|
||||
} satisfies Extract<ServerNotification, { method: "hook/completed" }>);
|
||||
|
||||
expect(state.displayItems[0]).toMatchObject({ id: "hook-hook-1-1", kind: "hook" });
|
||||
expect(state.displayItems[0].turnId).toBeUndefined();
|
||||
expect(expectPresent(state.displayItems[0])).toMatchObject({ id: "hook-hook-1-1", kind: "hook" });
|
||||
expect(expectPresent(state.displayItems[0]).turnId).toBeUndefined();
|
||||
expect(expectPresent(state.pendingTurnStart).promptSubmitHookItemIds).toEqual(["hook-hook-1-1"]);
|
||||
});
|
||||
|
||||
|
|
@ -501,7 +501,7 @@ describe("PanelController", () => {
|
|||
} satisfies Extract<ServerNotification, { method: "hook/completed" }>);
|
||||
|
||||
expect(state.displayItems.map((item) => item.id)).toEqual(["local-user-1", "hook-hook-1-1"]);
|
||||
expect(state.displayItems[1].turnId).toBeUndefined();
|
||||
expect(expectPresent(state.displayItems[1]).turnId).toBeUndefined();
|
||||
expect(expectPresent(state.pendingTurnStart).promptSubmitHookItemIds).toEqual(["hook-hook-1-1"]);
|
||||
|
||||
controller.handleNotification({
|
||||
|
|
@ -628,7 +628,7 @@ describe("PanelController", () => {
|
|||
});
|
||||
|
||||
expect(state.pendingUserInputs).toHaveLength(1);
|
||||
controller.resolveUserInput(state.pendingUserInputs[0], { scope: "Narrow" });
|
||||
controller.resolveUserInput(expectPresent(state.pendingUserInputs[0]), { scope: "Narrow" });
|
||||
expect(respondToServerRequest).toHaveBeenCalledWith(42, { answers: { scope: { answers: ["Narrow"] } } });
|
||||
expect(state.pendingUserInputs).toEqual([]);
|
||||
expect(state.displayItems.at(-1)).toMatchObject({
|
||||
|
|
@ -656,7 +656,7 @@ describe("PanelController", () => {
|
|||
},
|
||||
});
|
||||
|
||||
controller.cancelUserInput(state.pendingUserInputs[0]);
|
||||
controller.cancelUserInput(expectPresent(state.pendingUserInputs[0]));
|
||||
expect(rejectServerRequest).toHaveBeenCalledWith(43, -32000, "User cancelled input request.");
|
||||
expect(state.pendingUserInputs).toEqual([]);
|
||||
expect(state.displayItems.at(-1)).toMatchObject({
|
||||
|
|
@ -795,7 +795,7 @@ describe("PanelController", () => {
|
|||
const controller = controllerForState(state, { respondToServerRequest });
|
||||
|
||||
controller.handleServerRequest(userInputRequest(55));
|
||||
controller.resolveUserInput(state.pendingUserInputs[0], { note: "Later" });
|
||||
controller.resolveUserInput(expectPresent(state.pendingUserInputs[0]), { note: "Later" });
|
||||
|
||||
expect(state.pendingUserInputs).toHaveLength(1);
|
||||
expect(state.displayItems).toEqual([
|
||||
|
|
@ -1223,8 +1223,8 @@ describe("PanelController", () => {
|
|||
text: "Auto-review approved: npm test",
|
||||
state: "completed",
|
||||
});
|
||||
const reviewItem = state.displayItems[0];
|
||||
expect("details" in reviewItem ? reviewItem.details?.[0] : null).toMatchObject({
|
||||
const reviewItem = expectPresent(state.displayItems[0]);
|
||||
expect("details" in reviewItem ? reviewItem.details[0] : null).toMatchObject({
|
||||
title: "Review",
|
||||
rows: expect.arrayContaining([{ key: "status", value: "approved" }]),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,6 +3,11 @@ import { describe, expect, it } from "vitest";
|
|||
import { isRollbackCandidateItem, rollbackCandidateFromItems } from "../../src/panel/rollback";
|
||||
import type { DisplayItem } from "../../src/display/types";
|
||||
|
||||
function expectPresent<T>(value: T | null | undefined): T {
|
||||
if (value === null || value === undefined) throw new Error("Expected value to be present");
|
||||
return value;
|
||||
}
|
||||
|
||||
describe("rollback candidate", () => {
|
||||
it("selects the first user message from the latest turn", () => {
|
||||
const items: DisplayItem[] = [
|
||||
|
|
@ -15,9 +20,9 @@ describe("rollback candidate", () => {
|
|||
const candidate = rollbackCandidateFromItems(items);
|
||||
|
||||
expect(candidate).toEqual({ turnId: "turn-2", itemId: "u2", text: "latest" });
|
||||
expect(isRollbackCandidateItem(items[2], candidate)).toBe(true);
|
||||
expect(isRollbackCandidateItem(items[0], candidate)).toBe(false);
|
||||
expect(isRollbackCandidateItem({ ...items[2], turnId: "turn-other" }, candidate)).toBe(false);
|
||||
expect(isRollbackCandidateItem(expectPresent(items[2]), candidate)).toBe(true);
|
||||
expect(isRollbackCandidateItem(expectPresent(items[0]), candidate)).toBe(false);
|
||||
expect(isRollbackCandidateItem({ ...expectPresent(items[2]), turnId: "turn-other" }, candidate)).toBe(false);
|
||||
});
|
||||
|
||||
it("returns null when there is no completed user turn in display items", () => {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,11 @@ import { installObsidianDomShims } from "./dom-test-helpers";
|
|||
|
||||
installObsidianDomShims();
|
||||
|
||||
function expectPresent<T>(value: T | null | undefined): T {
|
||||
if (value === null || value === undefined) throw new Error("Expected value to be present");
|
||||
return value;
|
||||
}
|
||||
|
||||
describe("view DOM helpers", () => {
|
||||
it("uses short signatures instead of embedding full text in data attributes", () => {
|
||||
const long = "x".repeat(20_000);
|
||||
|
|
@ -22,11 +27,12 @@ describe("view DOM helpers", () => {
|
|||
renderTextWithWikiLinks(parent, "See [[Target Note|label]] and [[Other]].", openLink);
|
||||
|
||||
const links = parent.querySelectorAll<HTMLAnchorElement>("a.internal-link");
|
||||
const firstLink = expectPresent(links[0]);
|
||||
expect(links).toHaveLength(2);
|
||||
expect(links[0].textContent).toBe("label");
|
||||
expect(links[0].getAttribute("href")).toBe("Target Note");
|
||||
expect(firstLink.textContent).toBe("label");
|
||||
expect(firstLink.getAttribute("href")).toBe("Target Note");
|
||||
|
||||
links[0].click();
|
||||
firstLink.click();
|
||||
expect(openLink).toHaveBeenCalledWith("Target Note");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -15,12 +15,25 @@ import { renderPendingRequestMessage } from "../../src/ui/pending-request-messag
|
|||
import { renderToolbar, toolbarSignature, type ToolbarViewModel } from "../../src/ui/toolbar";
|
||||
import { displayItemSignature } from "../../src/display/signature";
|
||||
import { implementPlanCandidateFromState } from "../../src/panel/message-renderer";
|
||||
import { messageRenderBlocks, syncMessageRenderBlocks } from "../../src/ui/message-stream";
|
||||
import { messageRenderBlocks as rawMessageRenderBlocks, syncMessageRenderBlocks } from "../../src/ui/message-stream";
|
||||
import { displayDiffLines, persistedTurnDiffViewState, renderTurnDiffView } from "../../src/ui/turn-diff";
|
||||
import { composerSuggestionScrollFixture, installObsidianDomShims, topLevelDetailsSummaries } from "./dom-test-helpers";
|
||||
|
||||
installObsidianDomShims();
|
||||
|
||||
function messageRenderBlocks(
|
||||
...args: Parameters<typeof rawMessageRenderBlocks>
|
||||
): [ReturnType<typeof rawMessageRenderBlocks>[number], ...ReturnType<typeof rawMessageRenderBlocks>] {
|
||||
const blocks = rawMessageRenderBlocks(...args);
|
||||
if (blocks.length === 0) throw new Error("Expected at least one message render block.");
|
||||
return blocks as [ReturnType<typeof rawMessageRenderBlocks>[number], ...ReturnType<typeof rawMessageRenderBlocks>];
|
||||
}
|
||||
|
||||
function expectPresent<T>(value: T | null | undefined): T {
|
||||
if (value === null || value === undefined) throw new Error("Expected value to be present");
|
||||
return value;
|
||||
}
|
||||
|
||||
function withMessageContentScrollHeight<T>(scrollHeight: number, fn: () => T): T {
|
||||
const descriptor = Object.getOwnPropertyDescriptor(HTMLElement.prototype, "scrollHeight");
|
||||
Object.defineProperty(HTMLElement.prototype, "scrollHeight", {
|
||||
|
|
@ -334,9 +347,9 @@ describe("message stream block identity and message actions", () => {
|
|||
|
||||
const rendered = blocks.map((block) => block.render());
|
||||
|
||||
expect(rendered[0].querySelector(".codex-panel__rollback-turn")).toBeNull();
|
||||
expect(rendered[1].querySelector(".codex-panel__rollback-turn")).toBeNull();
|
||||
const button = rendered[2].querySelector<HTMLButtonElement>(".codex-panel__rollback-turn");
|
||||
expect(expectPresent(rendered[0]).querySelector(".codex-panel__rollback-turn")).toBeNull();
|
||||
expect(expectPresent(rendered[1]).querySelector(".codex-panel__rollback-turn")).toBeNull();
|
||||
const button = expectPresent(rendered[2]).querySelector<HTMLButtonElement>(".codex-panel__rollback-turn");
|
||||
expect(button?.getAttribute("aria-label")).toBe("Rollback last turn");
|
||||
button?.click();
|
||||
expect(onRollbackItem).toHaveBeenCalledWith(expect.objectContaining({ id: "u2" }));
|
||||
|
|
@ -362,8 +375,8 @@ describe("message stream block identity and message actions", () => {
|
|||
});
|
||||
|
||||
const rendered = blocks.map((block) => block.render());
|
||||
const userButton = rendered[0].querySelector<HTMLButtonElement>(".codex-panel__copy-message");
|
||||
const assistantButton = rendered[1].querySelector<HTMLButtonElement>(".codex-panel__copy-message");
|
||||
const userButton = expectPresent(rendered[0]).querySelector<HTMLButtonElement>(".codex-panel__copy-message");
|
||||
const assistantButton = expectPresent(rendered[1]).querySelector<HTMLButtonElement>(".codex-panel__copy-message");
|
||||
|
||||
expect(userButton?.getAttribute("aria-label")).toBe("Copy message");
|
||||
expect(assistantButton?.getAttribute("aria-label")).toBe("Copy message");
|
||||
|
|
@ -2053,7 +2066,7 @@ describe("pending request renderer decisions", () => {
|
|||
});
|
||||
|
||||
expect(blocks.map((block) => block.key)).toEqual(["item:a1", "pending-requests"]);
|
||||
expect(blocks[1].render()).toBe(pending);
|
||||
expect(expectPresent(blocks[1]).render()).toBe(pending);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ describe("user input model", () => {
|
|||
|
||||
const input = expectPresent(toPendingUserInput(request));
|
||||
expect(input).toMatchObject({ requestId: 7, method: "item/tool/requestUserInput" });
|
||||
expect(questionDefaultAnswer(request.params.questions[0])).toBe("Recommended");
|
||||
expect(questionDefaultAnswer(expectPresent(request.params.questions[0]))).toBe("Recommended");
|
||||
expect(userInputResponse(input, { direction: "Recommended" })).toEqual({
|
||||
answers: { direction: { answers: ["Recommended"] } },
|
||||
});
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@
|
|||
"noUncheckedSideEffectImports": true,
|
||||
"strict": true,
|
||||
"noImplicitOverride": true,
|
||||
"exactOptionalPropertyTypes": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"isolatedModules": true,
|
||||
|
|
|
|||
Loading…
Reference in a new issue