mirror of
https://github.com/murashit/codex-panel.git
synced 2026-07-22 06:57:10 +00:00
test: focus architecture checks on durable invariants
This commit is contained in:
parent
bbd981f255
commit
8ae6aab1fb
41 changed files with 191 additions and 1819 deletions
|
|
@ -195,6 +195,7 @@
|
|||
"scripts/**/*.mjs",
|
||||
"src/**/*.{css,ts,tsx}",
|
||||
"tests/**/*.{mjs,ts,tsx}",
|
||||
"!tests/fixtures/grit-policy",
|
||||
"!src/generated"
|
||||
]
|
||||
},
|
||||
|
|
|
|||
|
|
@ -31,6 +31,8 @@ Use focused scripts while iterating, but run `npm run check` before handoff. CI
|
|||
|
||||
Keep rule suppressions local and include the Obsidian-specific reason when a native Obsidian UI pattern intentionally diverges from a generic browser rule.
|
||||
|
||||
Grit policy fixtures protect matcher semantics rather than diagnostic wording, source locations, or the exact shape of Biome configuration. Change the checked-in valid and invalid fixtures only when a policy's accepted or rejected source shape intentionally changes.
|
||||
|
||||
## Generated and Loaded Files
|
||||
|
||||
`main.js`, `styles.css`, `data.json`, and `node_modules/` are ignored by Git. `main.js` and `styles.css` are still the files Obsidian loads, so run `npm run build` before live Obsidian validation if you have not already run `npm run check` after the source change.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"entry": ["src/main.ts", "scripts/**/*.mjs", "*.config.{ts,mjs}", "tests/**/*.test.{ts,tsx}"],
|
||||
"project": ["src/**/*.{ts,tsx}", "tests/**/*.{ts,tsx}", "scripts/**/*.mjs", "*.config.{ts,mjs}"],
|
||||
"ignore": ["src/generated/**"],
|
||||
"ignore": ["src/generated/**", "tests/fixtures/grit-policy/**"],
|
||||
"ignoreBinaries": ["codex", "taskkill"]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -133,155 +133,6 @@ describe("ChatPanelShell", () => {
|
|||
});
|
||||
});
|
||||
|
||||
it("keeps composer rendering off thread stream updates until turn presence changes", async () => {
|
||||
const store = createChatStateStore();
|
||||
const container = document.createElement("div");
|
||||
document.body.appendChild(container);
|
||||
const parts = shellParts();
|
||||
const renderComposerState = vi.fn(parts.composer.presenter.renderState.bind(parts.composer.presenter));
|
||||
parts.composer.presenter.renderState = renderComposerState;
|
||||
|
||||
await act(async () => {
|
||||
renderChatPanelShell(container, { ...shellProps(store), parts });
|
||||
await settleShellEffects();
|
||||
});
|
||||
renderComposerState.mockClear();
|
||||
|
||||
await act(async () => {
|
||||
store.dispatch({
|
||||
type: "thread-stream/system-item-added",
|
||||
item: { id: "system-1", kind: "system", role: "system", text: "Status only." },
|
||||
});
|
||||
await settleShellEffects();
|
||||
});
|
||||
expect(renderComposerState).not.toHaveBeenCalled();
|
||||
|
||||
await act(async () => {
|
||||
store.dispatch({
|
||||
type: "thread-stream/item-added",
|
||||
item: {
|
||||
id: "assistant-1",
|
||||
turnId: "turn-1",
|
||||
kind: "dialogue",
|
||||
dialogueKind: "assistantResponse",
|
||||
dialogueState: "completed",
|
||||
role: "assistant",
|
||||
text: "Turn response.",
|
||||
},
|
||||
});
|
||||
await settleShellEffects();
|
||||
});
|
||||
expect(renderComposerState).toHaveBeenCalledTimes(1);
|
||||
|
||||
await act(async () => {
|
||||
unmountChatPanelShell(container);
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps toolbar rendering off turn updates until busy state changes", async () => {
|
||||
const store = createChatStateStore();
|
||||
const container = document.createElement("div");
|
||||
document.body.appendChild(container);
|
||||
const toolbarConnected = vi.fn(() => false);
|
||||
|
||||
await act(async () => {
|
||||
renderChatPanelShell(container, { ...shellProps(store), parts: shellParts({ toolbarConnected }) });
|
||||
await settleShellEffects();
|
||||
});
|
||||
toolbarConnected.mockClear();
|
||||
|
||||
await act(async () => {
|
||||
store.dispatch({
|
||||
type: "turn/optimistic-started",
|
||||
item: { id: "local-user", kind: "dialogue", dialogueKind: "user", role: "user", text: "hello" },
|
||||
pendingTurnStart: { anchorItemId: "local-user", promptSubmitHookItemIds: [] },
|
||||
});
|
||||
await settleShellEffects();
|
||||
});
|
||||
expect(toolbarConnected).toHaveBeenCalledTimes(1);
|
||||
toolbarConnected.mockClear();
|
||||
|
||||
await act(async () => {
|
||||
store.dispatch({
|
||||
type: "turn/start-acknowledged",
|
||||
turnId: "turn-1",
|
||||
items: [{ id: "local-user", turnId: "turn-1", kind: "dialogue", dialogueKind: "user", role: "user", text: "hello" }],
|
||||
});
|
||||
await settleShellEffects();
|
||||
});
|
||||
expect(toolbarConnected).not.toHaveBeenCalled();
|
||||
|
||||
await act(async () => {
|
||||
unmountChatPanelShell(container);
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps goal rendering off active thread updates until goal state changes", async () => {
|
||||
const store = createChatStateStore();
|
||||
const container = document.createElement("div");
|
||||
document.body.appendChild(container);
|
||||
const goalSendShortcut = vi.fn(() => "enter" as const);
|
||||
|
||||
await act(async () => {
|
||||
renderChatPanelShell(container, { ...shellProps(store), parts: shellParts({ goalSendShortcut }) });
|
||||
await settleShellEffects();
|
||||
});
|
||||
goalSendShortcut.mockClear();
|
||||
|
||||
await act(async () => {
|
||||
store.dispatch({
|
||||
type: "active-thread/token-usage-set",
|
||||
tokenUsage: tokenUsageFixture(),
|
||||
});
|
||||
await settleShellEffects();
|
||||
});
|
||||
expect(goalSendShortcut).not.toHaveBeenCalled();
|
||||
|
||||
await act(async () => {
|
||||
unmountChatPanelShell(container);
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps thread stream rendering off active thread updates until stream thread fields change", async () => {
|
||||
const store = createChatStateStore();
|
||||
const container = document.createElement("div");
|
||||
document.body.appendChild(container);
|
||||
const parts = shellParts();
|
||||
const renderThreadStreamState = vi.fn(parts.threadStream.renderState.bind(parts.threadStream));
|
||||
parts.threadStream.renderState = renderThreadStreamState;
|
||||
|
||||
await act(async () => {
|
||||
renderChatPanelShell(container, { ...shellProps(store), parts });
|
||||
await settleShellEffects();
|
||||
});
|
||||
renderThreadStreamState.mockClear();
|
||||
|
||||
await act(async () => {
|
||||
store.dispatch({
|
||||
type: "active-thread/token-usage-set",
|
||||
tokenUsage: tokenUsageFixture(),
|
||||
});
|
||||
await settleShellEffects();
|
||||
});
|
||||
expect(renderThreadStreamState).not.toHaveBeenCalled();
|
||||
|
||||
await act(async () => {
|
||||
store.dispatch({ type: "ui/disclosure-set", bucket: "goalObjectiveExpanded", id: "thread", open: true });
|
||||
await settleShellEffects();
|
||||
});
|
||||
expect(renderThreadStreamState).not.toHaveBeenCalled();
|
||||
|
||||
await act(async () => {
|
||||
store.dispatch({ type: "active-thread/cwd-set", cwd: "/workspace" });
|
||||
await settleShellEffects();
|
||||
});
|
||||
expect(renderThreadStreamState).toHaveBeenCalledTimes(1);
|
||||
|
||||
await act(async () => {
|
||||
unmountChatPanelShell(container);
|
||||
});
|
||||
});
|
||||
|
||||
it("removes and restores the toolbar without losing composer or thread viewport state", async () => {
|
||||
const store = createChatStateStore();
|
||||
store.dispatch({ type: "composer/draft-set", draft: "toolbar continuity" });
|
||||
|
|
@ -653,14 +504,6 @@ function toolbarActionsFixture(): ChatPanelShellParts["toolbar"]["actions"] {
|
|||
};
|
||||
}
|
||||
|
||||
function tokenUsageFixture() {
|
||||
return {
|
||||
total: { totalTokens: 10, inputTokens: 7, cachedInputTokens: 0, outputTokens: 3, reasoningOutputTokens: 0 },
|
||||
last: { totalTokens: 10, inputTokens: 7, cachedInputTokens: 0, outputTokens: 3, reasoningOutputTokens: 0 },
|
||||
modelContextWindow: 100,
|
||||
};
|
||||
}
|
||||
|
||||
async function settleShellEffects(): Promise<void> {
|
||||
await Promise.resolve();
|
||||
await Promise.resolve();
|
||||
|
|
|
|||
4
tests/fixtures/grit-policy/invalid/src/app-server/protocol/escape.ts
vendored
Normal file
4
tests/fixtures/grit-policy/invalid/src/app-server/protocol/escape.ts
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
import type { Client } from "../connection/client";
|
||||
import type { Root } from "../root";
|
||||
|
||||
export type Escape = Client | Root;
|
||||
4
tests/fixtures/grit-policy/invalid/src/domain/root.ts
vendored
Normal file
4
tests/fixtures/grit-policy/invalid/src/domain/root.ts
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
import "obsidian";
|
||||
|
||||
export { value } from "./owner";
|
||||
export * from "./barrel";
|
||||
3
tests/fixtures/grit-policy/invalid/src/features/chat/app-server/escape.ts
vendored
Normal file
3
tests/fixtures/grit-policy/invalid/src/features/chat/app-server/escape.ts
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
import type { Panel } from "../panel/toolbar-actions";
|
||||
|
||||
export type Escape = Panel;
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
import type { Catalog } from "../../../../../../app-server/protocol/catalog";
|
||||
|
||||
export type Escape = Catalog;
|
||||
5
tests/fixtures/grit-policy/invalid/src/features/chat/application/escape.ts
vendored
Normal file
5
tests/fixtures/grit-policy/invalid/src/features/chat/application/escape.ts
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import type { Protocol } from "../../../../app-server/protocol/catalog";
|
||||
import type { Host } from "../host/contracts";
|
||||
import type { Workspace } from "../../../../workspace/panel-coordinator";
|
||||
|
||||
export type Escape = Protocol | Host | Workspace;
|
||||
1
tests/fixtures/grit-policy/invalid/src/features/chat/application/state/escape.ts
vendored
Normal file
1
tests/fixtures/grit-policy/invalid/src/features/chat/application/state/escape.ts
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
export const checkedAt = Date.now();
|
||||
3
tests/fixtures/grit-policy/invalid/src/features/chat/host/escape.ts
vendored
Normal file
3
tests/fixtures/grit-policy/invalid/src/features/chat/host/escape.ts
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
import type { View } from "../presentation/thread-stream/view-model";
|
||||
|
||||
export type Escape = View;
|
||||
3
tests/fixtures/grit-policy/invalid/src/features/chat/panel/escape.ts
vendored
Normal file
3
tests/fixtures/grit-policy/invalid/src/features/chat/panel/escape.ts
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
import type { Runtime } from "../host/session-runtime";
|
||||
|
||||
export type Escape = Runtime;
|
||||
6
tests/fixtures/grit-policy/invalid/src/features/chat/presentation/escape.ts
vendored
Normal file
6
tests/fixtures/grit-policy/invalid/src/features/chat/presentation/escape.ts
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { signal } from "@preact/signals";
|
||||
import type { State } from "../application/state/store";
|
||||
import type { ReadModel } from "../panel/shell-read-model";
|
||||
|
||||
export const escaped = signal(1);
|
||||
export type Escape = State | ReadModel | Signal<string>;
|
||||
10
tests/fixtures/grit-policy/invalid/src/features/chat/ui/escape.tsx
vendored
Normal file
10
tests/fixtures/grit-policy/invalid/src/features/chat/ui/escape.tsx
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import type { State } from "../application/state/store";
|
||||
import { addDomEventListener } from "../../../../shared/dom/events.dom";
|
||||
import { renderPreactRoot } from "../../../../shared/dom/preact-root.dom";
|
||||
|
||||
export function Escape(props: { state: State }): JSX.Element {
|
||||
document.body.append(document.createElement("div"));
|
||||
void addDomEventListener;
|
||||
void renderPreactRoot;
|
||||
return <input defaultValue={String(props.state)} />;
|
||||
}
|
||||
4
tests/fixtures/grit-policy/invalid/src/features/selection-rewrite/escape.ts
vendored
Normal file
4
tests/fixtures/grit-policy/invalid/src/features/selection-rewrite/escape.ts
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
import type { Connection } from "../../app-server/connection/client";
|
||||
import type { Workspace } from "../../workspace/panel-coordinator";
|
||||
|
||||
export type Escape = Connection | Workspace;
|
||||
1
tests/fixtures/grit-policy/invalid/src/features/threads/escape.tsx
vendored
Normal file
1
tests/fixtures/grit-policy/invalid/src/features/threads/escape.tsx
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
export const misplaced = 1;
|
||||
3
tests/fixtures/grit-policy/invalid/src/features/threads/workflows/escape.ts
vendored
Normal file
3
tests/fixtures/grit-policy/invalid/src/features/threads/workflows/escape.ts
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
import type { Client } from "../../../app-server/connection/client";
|
||||
|
||||
export type Escape = Client;
|
||||
5
tests/fixtures/grit-policy/invalid/src/plugin-runtime.ts
vendored
Normal file
5
tests/fixtures/grit-policy/invalid/src/plugin-runtime.ts
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
class Runner {
|
||||
constructor(readonly stop: () => void) {}
|
||||
}
|
||||
|
||||
export const runner = new Runner(() => runner.stop());
|
||||
7
tests/fixtures/grit-policy/invalid/src/settings/escape.ts
vendored
Normal file
7
tests/fixtures/grit-policy/invalid/src/settings/escape.ts
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import type { Generated } from "../generated/app-server/types";
|
||||
|
||||
export async function read(client: { request(method: string): Promise<void> }): Promise<void> {
|
||||
await client.request("config/read");
|
||||
}
|
||||
|
||||
export type Escape = Generated;
|
||||
7
tests/fixtures/grit-policy/invalid/src/shared/runtime/escape.ts
vendored
Normal file
7
tests/fixtures/grit-policy/invalid/src/shared/runtime/escape.ts
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import type { Feature } from "../../features/escape";
|
||||
|
||||
export function first<T>(iterator: Iterator<T>): T {
|
||||
return iterator.next().value as T;
|
||||
}
|
||||
|
||||
export type EscapedFeature = Feature;
|
||||
4
tests/fixtures/grit-policy/invalid/src/styles/escape.css
vendored
Normal file
4
tests/fixtures/grit-policy/invalid/src/styles/escape.css
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
#codex-panel-fixture * {
|
||||
color: #fff;
|
||||
padding: 12px;
|
||||
}
|
||||
3
tests/fixtures/grit-policy/invalid/src/workspace/escape.ts
vendored
Normal file
3
tests/fixtures/grit-policy/invalid/src/workspace/escape.ts
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
import type { ChatState } from "../features/chat/application/state/store";
|
||||
|
||||
export type Escape = ChatState;
|
||||
3
tests/fixtures/grit-policy/valid/src/app-server/protocol/safe.ts
vendored
Normal file
3
tests/fixtures/grit-policy/valid/src/app-server/protocol/safe.ts
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export interface ProtocolValue {
|
||||
id: string;
|
||||
}
|
||||
3
tests/fixtures/grit-policy/valid/src/app-server/services/safe.ts
vendored
Normal file
3
tests/fixtures/grit-policy/valid/src/app-server/services/safe.ts
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export function serviceValue(): string {
|
||||
return "safe";
|
||||
}
|
||||
1
tests/fixtures/grit-policy/valid/src/domain/example/safe.ts
vendored
Normal file
1
tests/fixtures/grit-policy/valid/src/domain/example/safe.ts
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
export type DomainValue = string;
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
export function convert(value: string): string {
|
||||
return value;
|
||||
}
|
||||
3
tests/fixtures/grit-policy/valid/src/features/chat/application/state/safe.ts
vendored
Normal file
3
tests/fixtures/grit-policy/valid/src/features/chat/application/state/safe.ts
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export interface ChatStateValue {
|
||||
ready: boolean;
|
||||
}
|
||||
1
tests/fixtures/grit-policy/valid/src/features/chat/host/safe.ts
vendored
Normal file
1
tests/fixtures/grit-policy/valid/src/features/chat/host/safe.ts
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
export type HostValue = string;
|
||||
1
tests/fixtures/grit-policy/valid/src/features/chat/panel/safe.ts
vendored
Normal file
1
tests/fixtures/grit-policy/valid/src/features/chat/panel/safe.ts
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
export type PanelValue = string;
|
||||
1
tests/fixtures/grit-policy/valid/src/features/chat/presentation/safe.ts
vendored
Normal file
1
tests/fixtures/grit-policy/valid/src/features/chat/presentation/safe.ts
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
export type PresentationValue = string;
|
||||
3
tests/fixtures/grit-policy/valid/src/features/chat/ui/safe.ts
vendored
Normal file
3
tests/fixtures/grit-policy/valid/src/features/chat/ui/safe.ts
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export function label(value: string): string {
|
||||
return value.trim();
|
||||
}
|
||||
3
tests/fixtures/grit-policy/valid/src/features/chat/ui/safe.tsx
vendored
Normal file
3
tests/fixtures/grit-policy/valid/src/features/chat/ui/safe.tsx
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export function SafeView(): JSX.Element {
|
||||
return <span>Safe</span>;
|
||||
}
|
||||
1
tests/fixtures/grit-policy/valid/src/features/selection-rewrite/safe.ts
vendored
Normal file
1
tests/fixtures/grit-policy/valid/src/features/selection-rewrite/safe.ts
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
export type RewriteValue = string;
|
||||
1
tests/fixtures/grit-policy/valid/src/features/threads/workflows/safe.ts
vendored
Normal file
1
tests/fixtures/grit-policy/valid/src/features/threads/workflows/safe.ts
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
export type WorkflowValue = string;
|
||||
3
tests/fixtures/grit-policy/valid/src/plugin-runtime.ts
vendored
Normal file
3
tests/fixtures/grit-policy/valid/src/plugin-runtime.ts
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export function createRuntime(): object {
|
||||
return {};
|
||||
}
|
||||
1
tests/fixtures/grit-policy/valid/src/settings/safe.ts
vendored
Normal file
1
tests/fixtures/grit-policy/valid/src/settings/safe.ts
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
export type SettingsValue = string;
|
||||
3
tests/fixtures/grit-policy/valid/src/shared/runtime/safe.ts
vendored
Normal file
3
tests/fixtures/grit-policy/valid/src/shared/runtime/safe.ts
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export function identity<T>(value: T): T {
|
||||
return value;
|
||||
}
|
||||
4
tests/fixtures/grit-policy/valid/src/styles/safe.css
vendored
Normal file
4
tests/fixtures/grit-policy/valid/src/styles/safe.css
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
.codex-panel-fixture {
|
||||
color: var(--text-normal);
|
||||
padding: var(--size-4-2);
|
||||
}
|
||||
1
tests/fixtures/grit-policy/valid/src/workspace/safe.ts
vendored
Normal file
1
tests/fixtures/grit-policy/valid/src/workspace/safe.ts
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
export type WorkspaceValue = string;
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -26,5 +26,6 @@
|
|||
"types": ["node", "vitest"],
|
||||
"lib": ["ES2022", "DOM"]
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx", "tests/**/*.ts", "tests/**/*.tsx", "vitest.config.ts"]
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx", "tests/**/*.ts", "tests/**/*.tsx", "vitest.config.ts"],
|
||||
"exclude": ["tests/fixtures/grit-policy/**"]
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue