import { beforeAll, describe, expect, it, vi } from "vitest";
import type { IncomingEvent } from "../desktop/src/protocol";
vi.mock("@tauri-apps/api/core", () => ({ invoke: vi.fn() }));
vi.mock("@tauri-apps/api/event", () => ({ listen: vi.fn() }));
vi.mock("@tauri-apps/api/window", () => ({
getCurrentWindow: vi.fn(() => ({ onCloseRequested: vi.fn() })),
}));
vi.mock("@tauri-apps/plugin-dialog", () => ({ open: vi.fn(), save: vi.fn() }));
vi.mock("@tauri-apps/plugin-process", () => ({ relaunch: vi.fn() }));
vi.mock("@tauri-apps/plugin-updater", () => ({ check: vi.fn(), Update: class {} }));
vi.mock("@tauri-apps/plugin-opener", () => ({ openUrl: vi.fn() }));
vi.mock("../desktop/src/CommandPalette", () => ({
CommandPalette: () => null,
Toast: () => null,
buildCommands: vi.fn(() => []),
useCommandPalette: vi.fn(() => ({ open: false, setOpen: vi.fn() })),
}));
vi.mock("../desktop/src/Markdown", () => ({
WorkspaceProvider: ({ children }: { children?: unknown }) => children ?? null,
}));
vi.mock("../desktop/src/ui/thread", () => ({
ActivePlanTaskCard: () => null,
AssistantMsg: () => null,
CheckpointApprovalCard: () => null,
ChoiceApprovalCard: () => null,
ConfirmApprovalCard: () => null,
PathAccessApprovalCard: () => null,
PlanApprovalCard: () => null,
PlanBanner: () => null,
RevisionApprovalCard: () => null,
TurnDivider: () => null,
UserMsg: () => null,
}));
type ChatMessage = Awaited<typeof import("../desktop/src/App")>["ChatMessage"];
type AppState = Parameters<Awaited<typeof import("../desktop/src/App")>["applyIncoming"]>[0];
type ApplyIncoming = Awaited<typeof import("../desktop/src/App")>["applyIncoming"];
let applyIncoming: ApplyIncoming;
beforeAll(async () => {
({ applyIncoming } = await import("../desktop/src/App"));
});
function makeState(messages: ChatMessage[] = []): AppState {
return {
ready: true,
needsSetup: false,
busy: false,
model: "deepseek-v4-flash",
currentSession: "demo",
messages,
pendingConfirms: [],
pendingPathAccess: [],
pendingChoices: [],
pendingPlans: [],
pendingCheckpoints: [],
pendingRevisions: [],
activePlan: null,
usage: {
totalCostUsd: 0,
totalPromptTokens: 0,
totalCompletionTokens: 0,
cacheHitTokens: 0,
cacheMissTokens: 0,
lastCallCacheHit: null,
lastCallCacheMiss: null,
reservedTokens: 0,
liveLogTokens: 0,
},
sessions: [],
settings: null,
qq: null,
balance: null,
mentionResults: null,
mentionPreview: null,
mcpSpecs: [],
mcpBridged: false,
skills: [],
sessionFiles: [],
memory: [],
jobs: [],
activeSkill: null,
queuedSends: [],
retryNonce: 0,
};
}
describe("desktop incoming QQ/user message rendering", () => {
it("appends remote user.message into the desktop transcript and marks the tab busy", () => {
const state = makeState([{ kind: "assistant", turn: 1, segments: [], pending: false }]);
const next = applyIncoming(state, {
type: "user.message",
id: 42,
ts: "2026-05-19T12:00:00Z",
turn: 0,
text: "hello from qq",
} as IncomingEvent);
expect(next.busy).toBe(true);
expect(next.messages.at(-1)).toEqual({
kind: "user",
text: "hello from qq",
clientId: "remote-42",
turn: 2,
});
});
it("elides old heavy assistant segments during long live desktop sessions", () => {
const big = "long-session-payload\n".repeat(500);
const messages: ChatMessage[] = Array.from({ length: 250 }, (_, i) => ({
kind: "assistant",
turn: i + 1,
pending: false,
segments: [
{ kind: "reasoning", text: `${big}reasoning-${i}` },
{ kind: "text", text: `${big}text-${i}` },
{
kind: "tool",
callId: `tool-${i}`,
name: "read_file",
args: JSON.stringify({ path: `file-${i}.txt`, content: `${big}args-${i}` }),
startedAt: 0,
result: `${big}result-${i}`,
ok: true,
},
],
}));
const next = applyIncoming(makeState(messages), {
type: "model.turn.started",
turn: 999,
model: "deepseek-v4-flash",
} as IncomingEvent);
const oldest = next.messages[0];
expect(oldest?.kind).toBe("assistant");
if (oldest?.kind !== "assistant") return;
expect(oldest.segments[0]?.kind).toBe("reasoning");
expect(oldest.segments[1]?.kind).toBe("text");
expect(oldest.segments[2]?.kind).toBe("tool");
expect(oldest.segments[0]?.text).toMatch(/^\[elided/);
expect(oldest.segments[1]?.text).toMatch(/^\[elided/);
if (oldest.segments[2]?.kind === "tool") {
expect(oldest.segments[2].args).toMatch(/^\[elided/);
expect(oldest.segments[2].result).toMatch(/^\[elided/);
}
const recent = next.messages[240];
expect(recent?.kind).toBe("assistant");
if (recent?.kind !== "assistant") return;
expect(recent.segments[1]?.kind).toBe("text");
expect(recent.segments[1]?.text).toContain("text-240");
});
});