import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
type EventSourceInstance = {
url: string;
onmessage: ((msg: MessageEvent) => void) | null;
onerror: (() => void) | null;
close: () => void;
};
const settingsResponse = {
reasoningEffort: "high",
editMode: "review",
budgetUsd: null,
model: "deepseek-v4-pro",
webSearchEngine: "bing",
subagentModels: {},
baseUrl: "",
apiKey: "",
};
function jsonResponse(body: unknown): Response {
return {
status: 200,
text: async () => JSON.stringify(body),
} as Response;
}
async function loadBridge(options?: {
overview?: Record<string, unknown>;
sessions?: Record<string, unknown>;
}) {
vi.resetModules();
let overview = options?.overview ?? {
cwd: "E:/proj",
model: "deepseek-v4-pro",
version: "0.52.0",
stats: {
totalCostUsd: 0.123456,
cacheHitRatio: 0.75,
lastPromptTokens: 1200,
cacheHitTokens: 900,
cacheMissTokens: 300,
totalCompletionTokens: 150,
balance: [{ currency: "CNY", total_balance: "88.5" }],
},
};
let sessions = options?.sessions ?? {
currentSession: "code-20260526120000",
sessions: [
{
name: "code-20260526120000",
messageCount: 4,
mtime: Date.UTC(2026, 4, 26, 12, 0, 0),
summary: "fresh session",
},
],
};
vi.stubGlobal("document", {
documentElement: { dataset: {} },
querySelector: (selector: string) => {
if (selector === 'meta[name="reasonix-mode"]') {
return { getAttribute: () => "server" };
}
if (selector === 'meta[name="reasonix-token"]') {
return { getAttribute: () => "testtoken" };
}
return null;
},
});
const eventSources: EventSourceInstance[] = [];
class FakeEventSource {
onmessage: ((msg: MessageEvent) => void) | null = null;
onerror: (() => void) | null = null;
constructor(public url: string) {
eventSources.push(this);
}
close() {}
}
vi.stubGlobal("EventSource", FakeEventSource);
const fetchMock = vi.fn(async (url: string) => {
if (url.includes("/api/settings")) return jsonResponse(settingsResponse);
if (url.includes("/api/sessions")) return jsonResponse(sessions);
if (url.includes("/api/overview")) return jsonResponse(overview);
return jsonResponse({});
});
vi.stubGlobal("fetch", fetchMock);
const bridge = await import("../dashboard/src/lib/tauri-bridge");
const events: Record<string, any>[] = [];
await bridge.listen("rpc:event", (event) => {
events.push(JSON.parse(event.payload.data));
});
await bridge.invoke("rpc_spawn");
await vi.waitFor(() => expect(events.some((event) => event.type === "$ready")).toBe(true));
return {
bridge,
events,
fetchMock,
eventSources,
setOverview: (next: Record<string, unknown>) => {
overview = next;
},
setSessions: (next: Record<string, unknown>) => {
sessions = next;
},
};
}
describe("dashboard server bridge refresh", () => {
beforeEach(() => {
vi.useFakeTimers();
});
afterEach(() => {
vi.useRealTimers();
vi.unstubAllGlobals();
});
it("emits the current server session in the session snapshot", async () => {
const { events } = await loadBridge();
expect(events).toContainEqual(
expect.objectContaining({
type: "$sessions",
currentSession: "code-20260526120000",
items: [
expect.objectContaining({
name: "code-20260526120000",
messageCount: 4,
summary: "fresh session",
}),
],
}),
);
});
it("emits absolute usage stats from overview so the token counter can recover after reconnect", async () => {
const { events } = await loadBridge();
expect(events).toContainEqual(
expect.objectContaining({
type: "$session_usage",
totalCostUsd: 0.123456,
totalPromptTokens: 1200,
totalCompletionTokens: 150,
cacheHitTokens: 900,
cacheMissTokens: 300,
}),
);
});
it("polls snapshots so sessions created outside the open page appear without reload", async () => {
const { events, setOverview, setSessions } = await loadBridge();
events.length = 0;
setSessions({
currentSession: "code-20260526120500",
sessions: [
{
name: "code-20260526120500",
messageCount: 2,
mtime: Date.UTC(2026, 4, 26, 12, 5, 0),
summary: "new external session",
},
],
});
setOverview({
cwd: "E:/proj",
model: "deepseek-v4-pro",
version: "0.52.0",
stats: {
totalCostUsd: 0.2,
cacheHitTokens: 1600,
cacheMissTokens: 400,
totalCompletionTokens: 250,
balance: [{ currency: "CNY", total_balance: "91" }],
},
});
await vi.advanceTimersByTimeAsync(5000);
expect(events).toContainEqual(
expect.objectContaining({
type: "$sessions",
currentSession: "code-20260526120500",
items: [expect.objectContaining({ name: "code-20260526120500" })],
}),
);
expect(events).toContainEqual(
expect.objectContaining({
type: "$session_usage",
totalPromptTokens: 2000,
totalCompletionTokens: 250,
}),
);
});
});