mirror of
https://github.com/murashit/codex-panel.git
synced 2026-07-22 06:57:10 +00:00
Remove protocol thread cwd from panel state and operations. Require the immutable vault root wherever path presentation needs a workspace base.
140 lines
5.2 KiB
TypeScript
140 lines
5.2 KiB
TypeScript
import { describe, expect, it, vi } from "vitest";
|
|
import type { Thread } from "../../../../../src/domain/threads/model";
|
|
import { activeThreadId, createChatState } from "../../../../../src/features/chat/application/state/root-reducer";
|
|
import { createChatStateStore } from "../../../../../src/features/chat/application/state/store";
|
|
import { createActiveThreadIdentitySync } from "../../../../../src/features/chat/application/threads/active-thread-identity-sync";
|
|
|
|
function thread(id: string, name: string | null = null): Thread {
|
|
return {
|
|
id,
|
|
preview: "",
|
|
createdAt: 0,
|
|
updatedAt: 0,
|
|
name,
|
|
archived: false,
|
|
provenance: { kind: "interactive" },
|
|
};
|
|
}
|
|
|
|
function createIdentitySyncHarness() {
|
|
const stateStore = createChatStateStore(createChatState());
|
|
const host = {
|
|
stateStore,
|
|
invalidateThreadWork: vi.fn(),
|
|
resetThreadTurnPresence: vi.fn(),
|
|
notifyActiveThreadIdentityChanged: vi.fn(),
|
|
};
|
|
return { sync: createActiveThreadIdentitySync(host), host, stateStore };
|
|
}
|
|
|
|
describe("createActiveThreadIdentitySync", () => {
|
|
it("clears active thread identity as a complete archive transaction", () => {
|
|
const { sync, host, stateStore } = createIdentitySyncHarness();
|
|
stateStore.dispatch({
|
|
type: "active-thread/resumed",
|
|
approvalPolicyKnown: true,
|
|
sandboxPolicyKnown: true,
|
|
permissionProfileKnown: true,
|
|
approvalPolicy: null,
|
|
sandboxPolicy: null,
|
|
activePermissionProfile: null,
|
|
thread: thread("thread"),
|
|
model: null,
|
|
reasoningEffort: null,
|
|
serviceTier: null,
|
|
approvalsReviewer: null,
|
|
});
|
|
|
|
sync.applyThreadArchiveToActiveIdentity("thread");
|
|
|
|
expect(activeThreadId(stateStore.getState())).toBeNull();
|
|
expect(host.invalidateThreadWork).toHaveBeenCalledOnce();
|
|
expect(host.resetThreadTurnPresence).toHaveBeenCalledWith(false);
|
|
expect(host.notifyActiveThreadIdentityChanged).toHaveBeenCalledOnce();
|
|
});
|
|
|
|
it("ignores archive notifications for non-active threads without identity side effects", () => {
|
|
const { sync, host, stateStore } = createIdentitySyncHarness();
|
|
stateStore.dispatch({
|
|
type: "active-thread/resumed",
|
|
approvalPolicyKnown: true,
|
|
sandboxPolicyKnown: true,
|
|
permissionProfileKnown: true,
|
|
approvalPolicy: null,
|
|
sandboxPolicy: null,
|
|
activePermissionProfile: null,
|
|
thread: thread("active"),
|
|
model: null,
|
|
reasoningEffort: null,
|
|
serviceTier: null,
|
|
approvalsReviewer: null,
|
|
});
|
|
|
|
sync.applyThreadArchiveToActiveIdentity("other");
|
|
|
|
expect(activeThreadId(stateStore.getState())).toBe("active");
|
|
expect(host.invalidateThreadWork).not.toHaveBeenCalled();
|
|
expect(host.resetThreadTurnPresence).not.toHaveBeenCalled();
|
|
expect(host.notifyActiveThreadIdentityChanged).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("clears pending restored thread identity when that thread is archived", () => {
|
|
const { sync, host, stateStore } = createIdentitySyncHarness();
|
|
stateStore.dispatch({ type: "panel/restored-thread-applied", threadId: "thread", fallbackTitle: "Restored" });
|
|
|
|
sync.applyThreadArchiveToActiveIdentity("thread");
|
|
|
|
expect(activeThreadId(stateStore.getState())).toBeNull();
|
|
expect(host.invalidateThreadWork).toHaveBeenCalledOnce();
|
|
expect(stateStore.getState().panelThread.kind).toBe("empty");
|
|
expect(host.resetThreadTurnPresence).toHaveBeenCalledWith(false);
|
|
expect(host.notifyActiveThreadIdentityChanged).toHaveBeenCalledOnce();
|
|
});
|
|
|
|
it("routes active thread rename notifications through active identity refresh", () => {
|
|
const { sync, host, stateStore } = createIdentitySyncHarness();
|
|
stateStore.dispatch({
|
|
type: "active-thread/resumed",
|
|
approvalPolicyKnown: true,
|
|
sandboxPolicyKnown: true,
|
|
permissionProfileKnown: true,
|
|
approvalPolicy: null,
|
|
sandboxPolicy: null,
|
|
activePermissionProfile: null,
|
|
thread: thread("thread", "Old"),
|
|
model: null,
|
|
reasoningEffort: null,
|
|
serviceTier: null,
|
|
approvalsReviewer: null,
|
|
});
|
|
|
|
sync.applyThreadRenameToActiveIdentity("thread", "New");
|
|
|
|
expect(host.notifyActiveThreadIdentityChanged).toHaveBeenCalledOnce();
|
|
});
|
|
|
|
it("routes pending restored thread rename notifications through active identity refresh", () => {
|
|
const { sync, host, stateStore } = createIdentitySyncHarness();
|
|
stateStore.dispatch({ type: "panel/restored-thread-applied", threadId: "thread", fallbackTitle: "Old" });
|
|
|
|
sync.applyThreadRenameToActiveIdentity("thread", "New");
|
|
|
|
expect(stateStore.getState().panelThread).toEqual({
|
|
kind: "awaiting-resume",
|
|
threadId: "thread",
|
|
fallbackTitle: "New",
|
|
provenance: null,
|
|
});
|
|
expect(host.notifyActiveThreadIdentityChanged).toHaveBeenCalledOnce();
|
|
});
|
|
|
|
it("ignores rename notifications for inactive and unrestored threads without identity side effects", () => {
|
|
const { sync, host, stateStore } = createIdentitySyncHarness();
|
|
stateStore.dispatch({ type: "thread-list/applied", threads: [thread("thread", "Old")] });
|
|
|
|
sync.applyThreadRenameToActiveIdentity("other", "New");
|
|
|
|
expect(stateStore.getState().threadList.listedThreads[0]?.name).toBe("Old");
|
|
expect(host.notifyActiveThreadIdentityChanged).not.toHaveBeenCalled();
|
|
});
|
|
});
|