yoi/web/workspace/src/lib/workspace-console/worker-console.ui.test.ts

192 lines
7.4 KiB
TypeScript

declare const Deno: {
test(name: string, fn: () => Promise<void> | void): void;
readTextFile(path: string | URL): Promise<string>;
};
function assert(condition: unknown, message: string): asserts condition {
if (!condition) {
throw new Error(message);
}
}
Deno.test("workspace Worker list lives on the dedicated Workers page", async () => {
const workspacePage = await Deno.readTextFile(
new URL("./../../routes/w/[workspaceId]/+page.svelte", import.meta.url),
);
const workersPage = await Deno.readTextFile(
new URL("./../../routes/w/[workspaceId]/workers/+page.svelte", import.meta.url),
);
const workersNav = await Deno.readTextFile(
new URL("../workspace-sidebar/WorkersNavSection.svelte", import.meta.url),
);
const sidebar = await Deno.readTextFile(
new URL("../workspace-sidebar/WorkspaceSidebar.svelte", import.meta.url),
);
assert(
workspacePage.includes('href={`/w/${workspaceId}/runtimes`}') &&
workspacePage.includes('href={`/w/${workspaceId}/workers`}'),
"top workspace page should link to Runtimes and Workers pages",
);
assert(
!workspacePage.includes("workerConsoleHref") &&
!workspacePage.includes("Open Console"),
"top workspace page should not own the Worker list",
);
assert(
workersPage.includes("workerConsoleHref(worker, data.workspaceId)") &&
workersPage.includes("<table class=\"workers-table\">") &&
workersPage.includes("Open Console"),
"dedicated Workers page should expose a table and attach action per Worker",
);
assert(
workersNav.includes('href={`/w/${workspaceId}/workers`}') &&
workersNav.includes("workerConsoleHref(worker, workspaceId)") &&
workersNav.includes("aria-current"),
"Workers sidebar should link to the Worker list page and target Console routes",
);
assert(
!sidebar.includes("CompanionNavSection") &&
sidebar.includes("WorkersNavSection"),
"standalone Companion/Console navigation should not remain canonical",
);
});
Deno.test("Worker Console uses protocol observation events without transcript fetch", async () => {
const consolePage = await Deno.readTextFile(
new URL("./../../routes/w/[workspaceId]/runtimes/[runtimeId]/workers/[workerId]/console/+page.svelte", import.meta.url),
);
assert(
consolePage.includes("seenObservationEventIds") &&
consolePage.includes("rememberObservationEvent(frame.envelope.event_id)") &&
consolePage.includes("projectConsole(observedEvents.map") &&
!consolePage.includes("/transcript") &&
!consolePage.includes("WorkerTranscriptProjection"),
"Console should render protocol observation replay/live events directly and dedupe repeated frames by event id",
);
});
Deno.test("Decodal source editor keeps imperative EditorView out of reactive state", async () => {
const editor = await Deno.readTextFile(
new URL("../workspace-settings/DecodalSourceEditor.svelte", import.meta.url),
);
assert(
editor.includes("let view: EditorView | null = null") &&
!editor.includes("$state<EditorView") &&
editor.includes("untrack(() => value)") &&
editor.includes("untrack(() => onChange)"),
"CodeMirror EditorView must not be reactive state; otherwise mount cleanup can loop forever",
);
});
Deno.test("workspace Runtime management pages expose Runtimes and Runtime-owned workdirs", async () => {
const sidebar = await Deno.readTextFile(
new URL("../workspace-sidebar/WorkspaceSidebar.svelte", import.meta.url),
);
const runtimesNav = await Deno.readTextFile(
new URL("../workspace-sidebar/RuntimesNavSection.svelte", import.meta.url),
);
const runtimesPage = await Deno.readTextFile(
new URL("./../../routes/w/[workspaceId]/runtimes/+page.svelte", import.meta.url),
);
const workdirsPage = await Deno.readTextFile(
new URL("./../../routes/w/[workspaceId]/runtimes/[runtimeId]/workdirs/+page.svelte", import.meta.url),
);
const workdirsLoad = await Deno.readTextFile(
new URL("./../../routes/w/[workspaceId]/runtimes/[runtimeId]/workdirs/+page.ts", import.meta.url),
);
assert(
sidebar.includes("RuntimesNavSection") &&
runtimesNav.includes('href={runtimesHref}') &&
runtimesNav.includes("/runtimes"),
"sidebar should expose Runtime management navigation",
);
assert(
runtimesPage.includes("Open workdirs") &&
runtimesPage.includes("runtimes-table") &&
runtimesPage.includes("/workdirs"),
"Runtimes page should table Runtimes and link to each Runtime's workdirs",
);
assert(
workdirsPage.includes("Workdirs") &&
workdirsPage.includes("workdirs-table") &&
workdirsLoad.includes("/working-directories"),
"Workdirs page should read Runtime-owned working-directory API while using workdir UI language",
);
});
Deno.test("workspace Worker sidebar links New to the dedicated create page", async () => {
const workersNav = await Deno.readTextFile(
new URL("../workspace-sidebar/WorkersNavSection.svelte", import.meta.url),
);
const newWorkerPage = await Deno.readTextFile(
new URL("./../../routes/w/[workspaceId]/workers/new/+page.svelte", import.meta.url),
);
assert(
workersNav.includes('href={`/w/${workspaceId}/workers/new`}') &&
!workersNav.includes('worker-launch-form') &&
!workersNav.includes('createWorker()'),
"Workers sidebar should link to the dedicated New Worker page instead of owning the form",
);
assert(
newWorkerPage.includes('worker-launch-form') &&
newWorkerPage.includes('buildBrowserCreateWorkerRequest') &&
newWorkerPage.includes("/workers/launch-options"),
"New Worker page should own launch options and creation form behavior",
);
});
Deno.test("Worker Console page is routed by runtime_id and worker_id through backend APIs", async () => {
const consolePage = await Deno.readTextFile(
new URL(
"./../../routes/w/[workspaceId]/runtimes/[runtimeId]/workers/[workerId]/console/+page.svelte",
import.meta.url,
),
);
const routeLoad = await Deno.readTextFile(
new URL(
"./../../routes/w/[workspaceId]/runtimes/[runtimeId]/workers/[workerId]/console/+page.ts",
import.meta.url,
),
);
assert(
routeLoad.includes("workspaceId") &&
routeLoad.includes("runtimeId") && routeLoad.includes("workerId"),
"route load should expose workspace and target ids",
);
assert(
consolePage.includes("workspaceApiPath(workspaceId, path)") &&
consolePage.includes(
"workerApiPath(`/runtimes/${encodeURIComponent(target.runtimeId)}/workers/${encodeURIComponent(target.workerId)}`)",
),
"Worker detail should use the scoped backend Worker detail API",
);
assert(
!consolePage.includes("/transcript") &&
consolePage.includes("/events/ws") && consolePage.includes("/input"),
"Console should use protocol observation WS and input APIs without a transcript API",
);
assert(
!consolePage.includes("/api/companion"),
"Console page must not use Companion-specific APIs",
);
assert(
consolePage.includes("function advanceReloadToken()") &&
consolePage.includes("nextReloadToken += 1") &&
!consolePage.includes("reloadToken += 1"),
"reload token advancement should not synchronously read and write the rune state",
);
assert(
consolePage.includes(
"advanceReloadToken();\n void loadConsoleData(target);",
) &&
!consolePage.includes("void refreshConsole();\n });\n\n $effect"),
"target-change effect should load data without depending on manual refresh state reads",
);
});