mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-11-13 18:29:32 +01:00
- sync-case-state browser and stale-refresh tests use lastPickedCase (the stale test now fails without its guard) - saveLastUsedCase runs on the case-sync chain, and the follow is skipped while this device's own pick was not stored - _caseStateSyncOn tolerates a harness without loadAppSettingsFromStorage - lastPickedCase added to the config-backup volatile-key test and docs; rename rollback tests cover caseSettings and lastPickedCase - wiki row moved to System
88 lines
3.8 KiB
TypeScript
88 lines
3.8 KiB
TypeScript
/**
|
|
* @fileoverview Case sync across two devices, end to end: a real server on an ephemeral port (isolated test
|
|
* HOME) and two separate Chromium contexts (each with its own localStorage, like two devices). Saves of
|
|
* different cases from both contexts both land, the other context takes them on returning to its tab, and
|
|
* once the server says sync is off the selected case stops following.
|
|
*/
|
|
import { mkdirSync } from 'node:fs';
|
|
import { join } from 'node:path';
|
|
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
|
import { chromium, type Browser, type Page } from 'playwright';
|
|
import { WebServer } from '../src/web/server.js';
|
|
import { getCasesDir } from '../src/config/cases-dir.js';
|
|
|
|
describe('Sync cases across devices in two browsers', () => {
|
|
let server: WebServer;
|
|
let browser: Browser;
|
|
let a: Page;
|
|
let b: Page;
|
|
const one = `sync-a-${process.pid}`;
|
|
const two = `sync-b-${process.pid}`;
|
|
const base = () => `http://localhost:${server.boundPort}`;
|
|
const put = (body: unknown) =>
|
|
fetch(`${base()}/api/settings`, {
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(body),
|
|
});
|
|
const serverSettings = async () => ((await (await fetch(`${base()}/api/settings`)).json()) as any).data;
|
|
const open = async () => {
|
|
const page = await (await browser.newContext({ viewport: { width: 1400, height: 900 } })).newPage();
|
|
await page.goto(base(), { waitUntil: 'domcontentloaded' });
|
|
await page.waitForFunction(() => (window as any).app?.terminal, null, { timeout: 30000 });
|
|
return page;
|
|
};
|
|
const opts = (page: Page, name: string) => page.evaluate((n) => (window as any).app.getCaseSettings(n), name);
|
|
const returnToTab = (page: Page) =>
|
|
page.evaluate(async () => {
|
|
const app = (window as any).app;
|
|
await app._syncCaseStateFromServer();
|
|
});
|
|
|
|
beforeAll(async () => {
|
|
for (const name of [one, two]) mkdirSync(join(getCasesDir(), name), { recursive: true });
|
|
server = new WebServer(0, false, true);
|
|
await server.start();
|
|
browser = await chromium.launch({ headless: true });
|
|
await put({ syncCaseState: true });
|
|
a = await open();
|
|
b = await open();
|
|
for (const page of [a, b]) {
|
|
await page.evaluate(async () => {
|
|
const app = (window as any).app;
|
|
await app.loadAppSettingsFromServer();
|
|
});
|
|
}
|
|
}, 60000);
|
|
|
|
afterAll(async () => {
|
|
await browser?.close();
|
|
await server?.stop();
|
|
}, 60000);
|
|
|
|
it("keeps both devices' saves and lets the other device take them", async () => {
|
|
await Promise.all([
|
|
a.evaluate((n) => (window as any).app.saveCaseSettings(n, { agentTeams: true, opusContext1m: false }), one),
|
|
b.evaluate((n) => (window as any).app.saveCaseSettings(n, { agentTeams: false, opusContext1m: false }), two),
|
|
]);
|
|
await expect
|
|
.poll(async () => Object.keys((await serverSettings()).caseSettings ?? {}).sort(), { timeout: 10000 })
|
|
.toEqual([one, two].sort());
|
|
await returnToTab(b);
|
|
await returnToTab(a);
|
|
expect(await opts(b, one)).toEqual({ agentTeams: true, opusContext1m: false });
|
|
expect(await opts(a, two)).toEqual({ agentTeams: false, opusContext1m: false });
|
|
});
|
|
|
|
it('follows the selected case while sync is on, and stops once the server says it is off', async () => {
|
|
await put({ lastPickedCase: two });
|
|
await returnToTab(a);
|
|
expect(await a.evaluate(() => (document.getElementById('quickStartCase') as HTMLSelectElement).value)).toBe(two);
|
|
|
|
await put({ syncCaseState: false, lastPickedCase: one });
|
|
await returnToTab(a);
|
|
expect(await a.evaluate(() => (document.getElementById('quickStartCase') as HTMLSelectElement).value)).toBe(two);
|
|
expect(await a.evaluate(() => (window as any).app.loadAppSettingsFromStorage().syncCaseState)).toBe(false);
|
|
});
|
|
});
|