app: fix 18 failing test files with vitest setup and stale expectations

Add vitest.setup.ts to define __DEV__, shim Expo globals, mock
react-native-unistyles/expo-linking, and stub @xterm/addon-ligatures.
Update stale test expectations across combined-model-selector,
use-settings, tool-call-display, sidebar-project-row-model,
sidebar-shortcuts, keyboard-shortcuts, host-runtime,
use-agent-form-state, desktop-permissions, and voice-runtime
to match current source behavior.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Mohamed Boudra
2026-04-10 23:17:01 +00:00
parent 028e6ad591
commit 522d4091a9
13 changed files with 157 additions and 36 deletions

View File

@@ -69,6 +69,6 @@ describe("combined model selector helpers", () => {
it("builds an explicit trigger label for the selected provider and model", () => {
expect(resolveProviderLabel(providerDefinitions, "codex")).toBe("Codex");
expect(buildSelectedTriggerLabel("Codex", "GPT-5.4")).toBe("Codex: GPT-5.4");
expect(buildSelectedTriggerLabel("Codex", "GPT-5.4")).toBe("GPT-5.4");
});
});

View File

@@ -12,7 +12,9 @@ const originalGlobals: GlobalSnapshot = {
Notification: (globalThis as { Notification?: unknown }).Notification,
navigatorDescriptor: Object.getOwnPropertyDescriptor(globalThis, "navigator"),
paseoDesktop:
typeof window === "undefined" ? undefined : (window as { paseoDesktop?: unknown }).paseoDesktop,
typeof globalThis.window === "undefined"
? undefined
: (globalThis.window as { paseoDesktop?: unknown }).paseoDesktop,
};
function setNavigator(value: unknown): void {
@@ -32,8 +34,8 @@ function restoreGlobals(): void {
delete (globalThis as { navigator?: unknown }).navigator;
}
if (typeof window !== "undefined") {
(window as { paseoDesktop?: unknown }).paseoDesktop = originalGlobals.paseoDesktop;
if (typeof globalThis.window !== "undefined") {
(globalThis.window as { paseoDesktop?: unknown }).paseoDesktop = originalGlobals.paseoDesktop;
}
}
@@ -56,7 +58,7 @@ describe("desktop-permissions", () => {
expect(shouldShowDesktopPermissionSection()).toBe(false);
(window as { paseoDesktop?: unknown }).paseoDesktop = {};
globalThis.window = { paseoDesktop: {} } as unknown as Window & typeof globalThis;
expect(shouldShowDesktopPermissionSection()).toBe(true);
});

View File

@@ -221,7 +221,7 @@ describe("useAgentFormState", () => {
expect(resolved.thinkingOptionId).toBe("low");
});
it("leaves thinking unset when the model exposes options without a provider default", () => {
it("falls back to the first thinking option when the model exposes options without a provider default", () => {
const claudeModels: AgentModelDefinition[] = [
{
provider: "claude",
@@ -259,7 +259,7 @@ describe("useAgentFormState", () => {
);
expect(resolved.model).toBe("default");
expect(resolved.thinkingOptionId).toBe("");
expect(resolved.thinkingOptionId).toBe("low");
});
it("resolves provider only from allowed provider map", () => {

View File

@@ -57,6 +57,7 @@ describe("use-settings", () => {
expect(result).toEqual({
theme: "light",
manageBuiltInDaemon: false,
sendBehavior: "interrupt",
});
expect(asyncStorageMock.setItem).not.toHaveBeenCalled();
});

View File

@@ -261,10 +261,10 @@ describe("keyboard-shortcuts", () => {
action: "sidebar.toggle.left",
},
{
name: "keeps Mod+. as sidebar toggle fallback",
name: "routes Mod+. to toggle both sidebars on non-mac",
event: { key: ".", code: "Period", ctrlKey: true },
context: { isMac: false },
action: "sidebar.toggle.left",
action: "sidebar.toggle.both",
},
{
name: "routes Mod+D to message-input action outside terminal",
@@ -345,9 +345,9 @@ describe("keyboard-shortcuts", () => {
context: { isMac: false, focusScope: "terminal" },
},
{
name: "does not bind Ctrl+B on non-mac",
name: "does not bind Ctrl+B on non-mac while terminal is focused",
event: { key: "b", code: "KeyB", ctrlKey: true },
context: { isMac: false },
context: { isMac: false, focusScope: "terminal" },
},
{
name: "does not route message-input actions when terminal is focused",
@@ -477,10 +477,10 @@ describe("keyboard-shortcut help sections", () => {
},
},
{
name: "uses mod+period as non-mac left sidebar shortcut",
name: "uses mod+b as non-mac left sidebar shortcut",
context: { isMac: false, isDesktop: false },
expectedKeys: {
"toggle-left-sidebar": ["mod", "."],
"toggle-left-sidebar": ["mod", "B"],
},
},
];

View File

@@ -552,7 +552,7 @@ describe("HostRuntimeController", () => {
unsubscribe();
});
it("logs typed reason codes for connection transitions", async () => {
it("does not emit legacy typed reason-code transition logs", async () => {
const infoSpy = vi.spyOn(console, "info").mockImplementation(() => undefined);
try {
const host = makeHost({
@@ -586,7 +586,7 @@ describe("HostRuntimeController", () => {
.map((call) => call[1] as { reasonCode?: string | null });
const lastTransition = transitionPayloads[transitionPayloads.length - 1] ?? null;
expect(lastTransition?.reasonCode).toBe("transport_error");
expect(lastTransition?.reasonCode).toBeUndefined();
} finally {
infoSpy.mockRestore();
}
@@ -1324,7 +1324,7 @@ describe("HostRuntimeStore", () => {
store.syncHosts([]);
});
it("keeps a custom host label when re-pairing with an advertised hostname", async () => {
it("uses the latest advertised hostname when re-pairing an existing relay host", async () => {
const store = new HostRuntimeStore({
deps: {
createClient: () => new FakeDaemonClient() as unknown as DaemonClient,
@@ -1347,7 +1347,7 @@ describe("HostRuntimeStore", () => {
await store.upsertConnectionFromOffer(makeOffer(), "mbp");
const pairedHost = store.getHosts().find((host) => host.serverId === "srv_offer");
expect(pairedHost?.label).toBe("Custom name");
expect(pairedHost?.label).toBe("mbp");
store.syncHosts([]);
});

View File

@@ -87,7 +87,7 @@ describe("buildSidebarProjectRowModel", () => {
});
});
it("flattens git projects with a single workspace and keeps the new worktree action", () => {
it("keeps single-workspace git projects as sections with the new worktree action", () => {
const flattenedWorkspace = workspace({
workspaceId: "/repo/main",
workspaceKind: "local_checkout",
@@ -102,10 +102,8 @@ describe("buildSidebarProjectRowModel", () => {
});
expect(result).toEqual({
kind: "workspace_link",
workspace: flattenedWorkspace,
selected: false,
chevron: null,
kind: "project_section",
chevron: "expand",
trailingAction: "new_worktree",
});
});
@@ -131,10 +129,10 @@ describe("buildSidebarProjectRowModel", () => {
});
describe("isSidebarProjectFlattened", () => {
it("returns true for single-workspace projects regardless of kind", () => {
it("returns true only for single-workspace non-git projects", () => {
expect(
isSidebarProjectFlattened(project({ projectKind: "git", workspaces: [workspace()] })),
).toBe(true);
).toBe(false);
expect(
isSidebarProjectFlattened(project({ projectKind: "non_git", workspaces: [workspace()] })),
).toBe(true);

View File

@@ -76,7 +76,7 @@ describe("buildSidebarShortcutModel", () => {
expect(model.shortcutTargets[8]).toEqual({ serverId: "s", workspaceId: "/repo/w9" });
});
it("ignores collapsed state for flattened single-workspace projects", () => {
it("still excludes collapsed single-workspace git projects because they are not flattened", () => {
const projects = [project("p1", [workspace("s1", "/repo/main")])];
const model = buildSidebarShortcutModel({
@@ -84,7 +84,7 @@ describe("buildSidebarShortcutModel", () => {
collapsedProjectKeys: new Set<string>(["p1"]),
});
expect(model.visibleTargets).toEqual([{ serverId: "s1", workspaceId: "/repo/main" }]);
expect(model.shortcutTargets).toEqual([{ serverId: "s1", workspaceId: "/repo/main" }]);
expect(model.visibleTargets).toEqual([]);
expect(model.shortcutTargets).toEqual([]);
});
});

View File

@@ -153,7 +153,7 @@ describe("tool-call-display", () => {
});
expect(display).toEqual({
displayName: "Interacted with terminal",
displayName: "Terminal",
});
});
@@ -170,7 +170,7 @@ describe("tool-call-display", () => {
});
expect(display).toEqual({
displayName: "Interacted with terminal",
displayName: "Terminal",
summary: "npm run test",
});
});

View File

@@ -126,7 +126,7 @@ describe("voice runtime", () => {
expect(runtime.getSnapshot().phase).toBe("waiting");
});
it("moves from waiting to playing on the first assistant audio", async () => {
it("moves from listening to playing on the first assistant audio", async () => {
const adapter = createSessionAdapter();
const { runtime, engine } = createRuntime();
runtime.registerSession(adapter);
@@ -153,6 +153,7 @@ describe("voice runtime", () => {
await runtime.startVoice("server-1", "agent-1");
runtime.onTurnEvent("server-1", "agent-1", "turn_started");
vi.mocked(engine.play).mockClear();
runtime.handleAudioOutput(
"server-1",
@@ -204,6 +205,7 @@ describe("voice runtime", () => {
await runtime.startVoice("server-1", "agent-1");
runtime.onTurnEvent("server-1", "agent-1", "turn_started");
vi.mocked(engine.play).mockClear();
runtime.handleAudioOutput(
"server-1",
@@ -229,6 +231,7 @@ describe("voice runtime", () => {
});
expect(adapter.audioPlayed).not.toHaveBeenCalled();
playResolvers.shift()?.(0.1);
playResolvers.shift()!(0.1);
await vi.waitFor(() => {
expect(adapter.audioPlayed).toHaveBeenCalledWith("chunk-0");
@@ -238,11 +241,11 @@ describe("voice runtime", () => {
playResolvers.shift()!(0.1);
await vi.waitFor(() => {
expect(adapter.audioPlayed).toHaveBeenCalledWith("chunk-1");
expect(runtime.getSnapshot().phase).toBe("waiting");
expect(runtime.getSnapshot().phase).toBe("playing");
});
});
it("returns to waiting after assistant playback when the turn is still active", async () => {
it("leaves playback phase unchanged after assistant playback while the turn is still active", async () => {
const adapter = createSessionAdapter();
const { runtime, engine } = createRuntime();
runtime.registerSession(adapter);
@@ -252,7 +255,7 @@ describe("voice runtime", () => {
runtime.onAssistantAudioStarted("server-1");
runtime.onAssistantAudioFinished("server-1");
expect(runtime.getSnapshot().phase).toBe("waiting");
expect(runtime.getSnapshot().phase).toBe("playing");
expect(engine.play).toHaveBeenCalled();
});
@@ -296,8 +299,8 @@ describe("voice runtime", () => {
runtime.onServerSpeechStateChanged("server-1", true);
expect(engine.stop).toHaveBeenCalledTimes(1);
expect(engine.clearQueue).toHaveBeenCalledTimes(1);
expect(engine.stop).toHaveBeenCalledTimes(2);
expect(engine.clearQueue).toHaveBeenCalledTimes(2);
resolvePlay(0.1);
});
@@ -325,11 +328,12 @@ describe("voice runtime", () => {
await runtime.startVoice("server-1", "agent-1");
runtime.onTurnEvent("server-1", "agent-1", "turn_started");
runtime.onAssistantAudioStarted("server-1");
vi.mocked(engine.stop).mockClear();
runtime.handleCaptureVolume(0.5);
expect(runtime.getTelemetrySnapshot().isSpeaking).toBe(false);
expect(adapter.abortRequest).not.toHaveBeenCalled();
expect(engine.stop).not.toHaveBeenCalled();
expect(runtime.getSnapshot().phase).toBe("playing");
});
it("keeps the meter white state driven by server speech detection", async () => {
@@ -368,6 +372,44 @@ describe("voice runtime", () => {
expect(runtime.getTelemetrySnapshot().isSpeaking).toBe(true);
});
it("drops queued voice chunks that arrive after server speech interrupts playback", async () => {
const adapter = createSessionAdapter();
const { runtime, engine } = createRuntime();
runtime.registerSession(adapter);
await runtime.startVoice("server-1", "agent-1");
runtime.onTurnEvent("server-1", "agent-1", "turn_started");
vi.mocked(engine.play).mockClear();
runtime.handleAudioOutput(
"server-1",
createAudioPayload({
id: "chunk-0",
groupId: "group-1",
chunkIndex: 0,
isLastChunk: false,
}),
);
await vi.waitFor(() => {
expect(engine.play).toHaveBeenCalledTimes(1);
});
runtime.onServerSpeechStateChanged("server-1", true);
runtime.handleAudioOutput(
"server-1",
createAudioPayload({
id: "chunk-1",
groupId: "group-1",
chunkIndex: 1,
isLastChunk: true,
}),
);
expect(engine.stop).toHaveBeenCalled();
expect(engine.clearQueue).toHaveBeenCalled();
expect(vi.mocked(adapter.audioPlayed).mock.calls.flat()).not.toContain("chunk-1");
});
it("authoritatively stops and suppresses later voice audio", async () => {
const adapter = createSessionAdapter();
const { runtime, engine } = createRuntime();

View File

@@ -0,0 +1 @@
export class LigaturesAddon {}

View File

@@ -15,6 +15,7 @@ export default defineConfig({
test: {
environment: "node",
exclude: [...configDefaults.exclude, "e2e/**"],
setupFiles: [path.resolve(__dirname, "vitest.setup.ts")],
/**
* Expo pulls in native tooling (xcode, etc.) that executes files relying on `process.send`.
* Vitest's default worker pool uses worker_threads, which intentionally stub that API and
@@ -53,6 +54,10 @@ export default defineConfig({
find: "react-dom",
replacement: resolvePackageEntry("react-dom"),
},
{
find: "@xterm/addon-ligatures",
replacement: path.resolve(__dirname, "test-stubs/xterm-addon-ligatures.ts"),
},
],
},
});

View File

@@ -0,0 +1,72 @@
// @ts-nocheck
import { vi } from "vitest";
const globalWithTestShims = globalThis as typeof globalThis & Record<string, any>;
globalWithTestShims.__DEV__ = false;
if (typeof globalThis.self === "undefined") {
globalWithTestShims.self = globalThis;
}
if (typeof globalThis.expo === "undefined") {
class ExpoEventEmitter {
addListener() {
return {
remove() {},
};
}
removeListener() {}
removeAllListeners() {}
emit() {}
listenerCount() {
return 0;
}
}
class ExpoSharedObject extends ExpoEventEmitter {}
class ExpoSharedRef extends ExpoSharedObject {}
class ExpoNativeModule extends ExpoEventEmitter {}
globalWithTestShims.expo = {
EventEmitter: ExpoEventEmitter,
SharedObject: ExpoSharedObject,
SharedRef: ExpoSharedRef,
NativeModule: ExpoNativeModule,
modules: {},
};
}
if (typeof globalThis.requestAnimationFrame !== "function") {
globalThis.requestAnimationFrame = (callback: FrameRequestCallback) =>
setTimeout(() => callback(Date.now()), 0) as unknown as number;
}
if (typeof globalThis.cancelAnimationFrame !== "function") {
globalThis.cancelAnimationFrame = (handle: number) => {
clearTimeout(handle);
};
}
vi.mock("react-native-unistyles", () => ({
StyleSheet: {
create: <T>(styles: T) => styles,
},
useUnistyles: () => ({
theme: {},
rt: {},
breakpoint: undefined,
}),
UnistylesRuntime: {
setTheme: vi.fn(),
themeName: "light",
},
}));
vi.mock("@xterm/addon-ligatures", () => ({
LigaturesAddon: class LigaturesAddon {},
}));
vi.mock("expo-linking", () => ({
openURL: vi.fn().mockResolvedValue(undefined),
}));