Harden tests and enable remaining TypeScript strictness

This commit is contained in:
murashit 2026-05-23 07:28:46 +09:00
parent 84980e25e3
commit 63086634df
8 changed files with 75 additions and 37 deletions

View file

@ -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 });

View file

@ -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);
});

View file

@ -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" }]),
});

View file

@ -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", () => {

View file

@ -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");
});
});

View file

@ -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);
});
});

View file

@ -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"] } },
});

View file

@ -11,6 +11,8 @@
"noUncheckedSideEffectImports": true,
"strict": true,
"noImplicitOverride": true,
"exactOptionalPropertyTypes": true,
"noUncheckedIndexedAccess": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"isolatedModules": true,