Fix no-non-null-assertion lint

This commit is contained in:
murashit 2026-05-23 04:52:27 +09:00
parent 0614756acb
commit c12cf90c76
5 changed files with 55 additions and 37 deletions

View file

@ -11,6 +11,11 @@ import {
import type { ServerRequest } from "../../src/generated/app-server/ServerRequest";
import type { CommandExecutionApprovalDecision } from "../../src/generated/app-server/v2/CommandExecutionApprovalDecision";
function expectPresent<T>(value: T | null | undefined): T {
if (value === null || value === undefined) throw new Error("Expected value to be present");
return value;
}
describe("approval model", () => {
it("classifies command approvals and builds v2 decisions", () => {
const request: ServerRequest = {
@ -29,12 +34,11 @@ describe("approval model", () => {
proposedNetworkPolicyAmendments: [],
},
};
const approval = toPendingApproval(request);
const approval = expectPresent(toPendingApproval(request));
expect(approval).not.toBeNull();
expect(approvalTitle(approval!)).toBe("Command approval");
expect(approvalSummary(approval!)).toBe("npm run build");
expect(approvalResponse(approval!, "accept-session")).toEqual({ decision: "acceptForSession" });
expect(approvalTitle(approval)).toBe("Command approval");
expect(approvalSummary(approval)).toBe("npm run build");
expect(approvalResponse(approval, "accept-session")).toEqual({ decision: "acceptForSession" });
});
it("builds permission grants only for accept actions", () => {
@ -51,7 +55,7 @@ describe("approval model", () => {
permissions: { network: { enabled: true }, fileSystem: null },
},
};
const approval = toPendingApproval(request)!;
const approval = expectPresent(toPendingApproval(request));
expect(approvalResponse(approval, "decline")).toEqual({ permissions: {}, scope: "turn" });
expect(approvalResponse(approval, "accept")).toEqual({
@ -82,16 +86,16 @@ describe("approval model", () => {
availableDecisions: [networkDecision, "decline"],
},
};
const approval = toPendingApproval(request)!;
const approval = expectPresent(toPendingApproval(request));
const options = approvalActionOptions(approval);
expect(options.map((option) => option.label)).toEqual(["Allow network rule", "Deny"]);
expect(approvalResponse(approval, options[0]!.action)).toEqual({ decision: networkDecision });
expect(approvalResponse(approval, options[1]!.action)).toEqual({ decision: "decline" });
expect(approvalResponse(approval, expectPresent(options[0]).action)).toEqual({ decision: networkDecision });
expect(approvalResponse(approval, expectPresent(options[1]).action)).toEqual({ decision: "decline" });
});
it("shows approval reasons first in pending request summaries", () => {
const command = toPendingApproval({
const command = expectPresent(toPendingApproval({
id: 20,
method: "item/commandExecution/requestApproval",
params: {
@ -106,8 +110,8 @@ describe("approval model", () => {
proposedExecpolicyAmendment: null,
proposedNetworkPolicyAmendments: [],
},
})!;
const fileChange = toPendingApproval({
}));
const fileChange = expectPresent(toPendingApproval({
id: 21,
method: "item/fileChange/requestApproval",
params: {
@ -118,8 +122,8 @@ describe("approval model", () => {
reason: "Write outside workspace",
grantRoot: "/tmp/project",
},
})!;
const permissions = toPendingApproval({
}));
const permissions = expectPresent(toPendingApproval({
id: 22,
method: "item/permissions/requestApproval",
params: {
@ -131,7 +135,7 @@ describe("approval model", () => {
reason: "Need network",
permissions: { network: { enabled: true }, fileSystem: null },
},
})!;
}));
expect(approvalSummary(command)).toBe("Needs unsandboxed access\nnpm run build");
expect(approvalSummary(fileChange)).toBe("Write outside workspace\ngrant root: /tmp/project");
@ -139,7 +143,7 @@ describe("approval model", () => {
});
it("keeps approval details semantic and omits raw payloads", () => {
const approval = toPendingApproval({
const approval = expectPresent(toPendingApproval({
id: 23,
method: "item/permissions/requestApproval",
params: {
@ -151,7 +155,7 @@ describe("approval model", () => {
reason: "Need network",
permissions: { network: { enabled: true }, fileSystem: null },
},
})!;
}));
expect(approvalDetails(approval)).toEqual([
{ key: "reason", value: "Need network" },
@ -161,7 +165,7 @@ describe("approval model", () => {
});
it("omits empty approval detail rows", () => {
const approval = toPendingApproval({
const approval = expectPresent(toPendingApproval({
id: 24,
method: "item/commandExecution/requestApproval",
params: {
@ -176,7 +180,7 @@ describe("approval model", () => {
proposedExecpolicyAmendment: null,
proposedNetworkPolicyAmendments: [],
},
})!;
}));
expect(approvalDetails(approval)).toEqual([
{ key: "command", value: "npm test" },
@ -225,7 +229,7 @@ describe("approval model", () => {
];
for (const { request, acceptSession, cancel } of requests) {
const approval = toPendingApproval(request)!;
const approval = expectPresent(toPendingApproval(request));
expect(approvalResponse(approval, "accept-session")).toEqual(acceptSession);
expect(approvalResponse(approval, "cancel")).toEqual(cancel);
expect(approvalResponse(approval, "decline")).toEqual({ decision: "decline" });

View file

@ -23,6 +23,11 @@ function controllerForState(
});
}
function expectPresent<T>(value: T | null | undefined): T {
if (value === null || value === undefined) throw new Error("Expected value to be present");
return value;
}
describe("PanelController", () => {
describe("active turn routing", () => {
it("ignores item notifications for a different active thread", () => {
@ -624,8 +629,8 @@ describe("PanelController", () => {
const respondToServerRequest = vi.fn(() => true);
const controller = controllerForState(state, { respondToServerRequest });
controller.handleServerRequest(supportedApprovalRequests()[2]!);
controller.resolveApproval(state.approvals[0]!, "accept-session");
controller.handleServerRequest(expectPresent(supportedApprovalRequests()[2]));
controller.resolveApproval(expectPresent(state.approvals[0]), "accept-session");
expect(respondToServerRequest).toHaveBeenCalledWith(12, {
scope: "session",
@ -763,7 +768,7 @@ describe("PanelController", () => {
requestId: 50,
method: "item/commandExecution/requestApproval",
params: {
...supportedApprovalRequests()[0]!.params,
...expectPresent(supportedApprovalRequests()[0]).params,
threadId: "thread-active",
} as Extract<ServerRequest, { method: "item/commandExecution/requestApproval" }>["params"],
},
@ -834,7 +839,7 @@ describe("PanelController", () => {
{
requestId: 10,
method: "item/commandExecution/requestApproval",
params: supportedApprovalRequests()[0]!.params as Extract<
params: expectPresent(supportedApprovalRequests()[0]).params as Extract<
ServerRequest,
{ method: "item/commandExecution/requestApproval" }
>["params"],

View file

@ -32,6 +32,7 @@ describe("settings tab", () => {
vi.stubGlobal("HTMLDivElement", dom.window.HTMLDivElement);
vi.stubGlobal("HTMLInputElement", dom.window.HTMLInputElement);
vi.stubGlobal("HTMLSelectElement", dom.window.HTMLSelectElement);
vi.stubGlobal("Event", dom.window.Event);
withAppServerSessionMock.mockReset();
notices.length = 0;
});
@ -110,7 +111,7 @@ describe("settings tab", () => {
if (!shortcut) throw new Error("Missing send shortcut dropdown");
shortcut.value = "mod-enter";
shortcut.dispatchEvent(new shortcut.ownerDocument.defaultView!.Event("change"));
shortcut.dispatchEvent(new Event("change"));
await flushPromises();
expect(saveSettings).toHaveBeenCalledOnce();
@ -130,13 +131,13 @@ describe("settings tab", () => {
if (!toggle || !folder || !filename || !tags) throw new Error("Missing archive export controls");
toggle.checked = true;
toggle.dispatchEvent(new toggle.ownerDocument.defaultView!.Event("change"));
toggle.dispatchEvent(new Event("change"));
folder.value = "Saved Threads";
folder.dispatchEvent(new folder.ownerDocument.defaultView!.Event("change"));
folder.dispatchEvent(new Event("change"));
filename.value = "{{date}} {{title}}.md";
filename.dispatchEvent(new filename.ownerDocument.defaultView!.Event("change"));
filename.dispatchEvent(new Event("change"));
tags.value = "codex, archive";
tags.dispatchEvent(new tags.ownerDocument.defaultView!.Event("change"));
tags.dispatchEvent(new Event("change"));
await flushPromises();
expect(saveSettings).toHaveBeenCalledTimes(4);

View file

@ -1725,9 +1725,10 @@ describe("toolbar renderer decisions", () => {
expect(startRenameThread).toHaveBeenCalledWith("thread");
const input = parent.querySelector<HTMLInputElement>(".codex-panel__thread-rename-input");
expect(input?.value).toBe("Draft title");
input!.value = "New title";
input!.dispatchEvent(new Event("input", { bubbles: true }));
if (!input) throw new Error("Missing thread rename input");
expect(input.value).toBe("Draft title");
input.value = "New title";
input.dispatchEvent(new Event("input", { bubbles: true }));
expect(updateRenameDraft).toHaveBeenCalledWith("editing", "New title");
parent.querySelector<HTMLButtonElement>('[aria-label="Save thread name"]')?.click();
@ -1877,7 +1878,9 @@ describe("pending request renderer decisions", () => {
const buttons = [...parent.querySelectorAll<HTMLButtonElement>(".codex-panel__pending-request-button")];
expect(buttons.map((button) => button.textContent)).toEqual(["Allow network rule", "Deny"]);
buttons[0]!.click();
const allowButton = buttons[0];
if (!allowButton) throw new Error("Missing allow button");
allowButton.click();
expect(resolveApproval).toHaveBeenCalledWith(approval, {
kind: "command-decision",
decision: { applyNetworkPolicyAmendment: { network_policy_amendment: { host: "registry.npmjs.org", action: "allow" } } },

View file

@ -4,6 +4,11 @@ import { questionDefaultAnswer, toPendingUserInput, userInputResponse } from "..
import { pendingRequestsSignature } from "../../src/panel/request-state";
import type { ServerRequest } from "../../src/generated/app-server/ServerRequest";
function expectPresent<T>(value: T | null | undefined): T {
if (value === null || value === undefined) throw new Error("Expected value to be present");
return value;
}
describe("user input model", () => {
it("classifies requestUserInput and builds answers", () => {
const request: ServerRequest = {
@ -26,16 +31,16 @@ describe("user input model", () => {
},
};
const input = toPendingUserInput(request);
const input = expectPresent(toPendingUserInput(request));
expect(input).toMatchObject({ requestId: 7, method: "item/tool/requestUserInput" });
expect(questionDefaultAnswer(request.params.questions[0])).toBe("Recommended");
expect(userInputResponse(input!, { direction: "Recommended" })).toEqual({
expect(userInputResponse(input, { direction: "Recommended" })).toEqual({
answers: { direction: { answers: ["Recommended"] } },
});
});
it("signs pending request content and drafts deterministically", () => {
const input = toPendingUserInput({
const input = expectPresent(toPendingUserInput({
id: 7,
method: "item/tool/requestUserInput",
params: {
@ -53,7 +58,7 @@ describe("user input model", () => {
},
],
},
})!;
}));
const drafts = new Map([
["z", "last"],
["a", "first"],