import assert from "node:assert/strict"; import { readFile } from "node:fs/promises"; import { after, before, test } from "node:test"; import { createServer } from "vite"; let server; let decodeObservationWorkspaceLayoutProfile; let encodeObservationWorkspaceLayoutProfile; let fetchObservationWorkspaceLayoutProfile; let normalizeObservationWindowRect; let projectObservationLayoutSnapshot; let saveObservationWorkspaceLayoutProfile; let WorkspaceLayoutApiError; let WorkspaceLayoutContractError; let observationPresentationSourceAfterLayoutApply; let visibleSourceIdsAfterRecordedCatalogActivation; before(async () => { server = await createServer({ appType: "custom", logLevel: "silent", server: { middlewareMode: true }, }); ({ decodeObservationWorkspaceLayoutProfile, encodeObservationWorkspaceLayoutProfile, fetchObservationWorkspaceLayoutProfile, normalizeObservationWindowRect, projectObservationLayoutSnapshot, saveObservationWorkspaceLayoutProfile, WorkspaceLayoutApiError, WorkspaceLayoutContractError, } = await server.ssrLoadModule("/src/core/observation/workspaceLayout.ts")); ({ observationPresentationSourceAfterLayoutApply, visibleSourceIdsAfterRecordedCatalogActivation, } = await server.ssrLoadModule("/src/core/observation/useObservationLayout.ts")); }); after(async () => { await server?.close(); }); function wireProfile(overrides = {}) { return { version: 2, revision: 7, workspace_id: "observation.spatial", scene_settings: { projection: "3d", point_size: 2.5, color_mode: "intensity", palette: "turbo", custom_color: "#35d7c1", accumulation_seconds: 12, show_points: true, show_trajectory: true, show_grid: true, show_labels: false, show_camera_frustums: true, }, visible_source_ids: ["spatial.point-cloud.live", "camera.left"], active_floating_source_id: "camera.left", window_rects: { "camera.left": { x: 0.1, y: 0.1, width: 0.4, height: 0.4 }, "camera.right": { x: 0.55, y: 0.1, width: 0.4, height: 0.4 }, }, viewport_size: { width: 1_000, height: 500 }, ...overrides, }; } test("workspace layout v2 excludes transient tool-window state", () => { const wire = wireProfile(); const profile = decodeObservationWorkspaceLayoutProfile(wire); assert.deepEqual(profile.visibleSourceIds, ["spatial.point-cloud.live", "camera.left"]); assert.equal(profile.sceneSettings.pointSize, 2.5); assert.deepEqual(encodeObservationWorkspaceLayoutProfile(profile), wire); assert.equal("tool_windows" in encodeObservationWorkspaceLayoutProfile(profile), false); }); test("workspace layout rejects unknown fields, transient transport data and invalid schema values", () => { assert.throws( () => decodeObservationWorkspaceLayoutProfile({ ...wireProfile(), source_url: "ws://192.168.68.52:9877", }), WorkspaceLayoutContractError, ); assert.throws( () => decodeObservationWorkspaceLayoutProfile(wireProfile({ version: 1 })), /Неподдерживаемая версия/, ); assert.throws( () => decodeObservationWorkspaceLayoutProfile(wireProfile({ revision: 1.5 })), /revision/, ); assert.throws( () => decodeObservationWorkspaceLayoutProfile(wireProfile({ visible_source_ids: ["../camera"], active_floating_source_id: null, })), /стабильный идентификатор/, ); assert.throws( () => decodeObservationWorkspaceLayoutProfile(wireProfile({ window_rects: { "camera.left": { x: 0.8, y: 0, width: 0.4, height: 0.5 } }, })), /выходит за нормализованные границы/, ); assert.throws( () => decodeObservationWorkspaceLayoutProfile({ ...wireProfile(), tool_windows: { sources_open: true, display_open: true, layers_open: true, order: ["sources", "display", "layers"], }, }), /неверная схема/, ); assert.throws( () => decodeObservationWorkspaceLayoutProfile(wireProfile({ scene_settings: { ...wireProfile().scene_settings, point_size: Number.POSITIVE_INFINITY }, })), /point_size/, ); }); test("restored desired layout survives an empty catalog and reveals only known stable sources later", () => { const profile = decodeObservationWorkspaceLayoutProfile(wireProfile()); const snapshot = { visibleSourceIds: profile.visibleSourceIds, activeFloatingSourceId: profile.activeFloatingSourceId, windowRects: profile.windowRects, viewportSize: profile.viewportSize, }; const empty = projectObservationLayoutSnapshot(snapshot, new Set(), { width: 500, height: 1_000 }); assert.deepEqual(empty, { visibleSourceIds: [], activeFloatingSourceId: null, windowRects: {}, }); assert.deepEqual(snapshot.visibleSourceIds, ["spatial.point-cloud.live", "camera.left"]); const cameraAppeared = projectObservationLayoutSnapshot( snapshot, new Set(["camera.left"]), { width: 500, height: 1_000 }, ); assert.deepEqual(cameraAppeared.visibleSourceIds, ["camera.left"]); assert.equal(cameraAppeared.activeFloatingSourceId, "camera.left"); assert.deepEqual(cameraAppeared.windowRects["camera.left"], { x: 50, y: 100, width: 200, height: 400, }); }); test("window rectangles normalize once and project proportionally into a different viewport", () => { assert.deepEqual( normalizeObservationWindowRect( { x: 100, y: 50, width: 400, height: 200 }, { width: 1_000, height: 500 }, ), { x: 0.1, y: 0.1, width: 0.4, height: 0.4 }, ); }); test("fullscreen presentation survives the viewport resize it causes", () => { assert.equal( observationPresentationSourceAfterLayoutApply("camera.left", "preserve"), "camera.left", ); assert.equal( observationPresentationSourceAfterLayoutApply("camera.left", "reset"), null, ); }); test("opening a recorded catalog reveals its sealed cameras beside the point cloud", () => { const source = (id, modality, transport = "recording") => ({ id, modality, transport, availability: "available", previewUrl: null, delivery: modality === "video" ? { kind: "recorded-fmp4-manifest" } : null, activation: null, capabilities: { defaultVisible: true, overlay: modality === "video", }, }); const sources = [ source("recorded.spatial.primary", "point-cloud"), source("recorded.camera.left", "video"), source("recorded.camera.right", "video"), source("live.camera", "video", "websocket"), ]; assert.deepEqual( visibleSourceIdsAfterRecordedCatalogActivation( ["recorded.spatial.primary"], sources, ), ["recorded.spatial.primary", "recorded.camera.left", "recorded.camera.right"], ); }); test("workspace layout API uses the canonical endpoint and optimistic revision", async () => { const calls = []; const current = decodeObservationWorkspaceLayoutProfile(wireProfile()); const saved = await saveObservationWorkspaceLayoutProfile(current, { fetcher: async (input, init) => { calls.push({ input: String(input), init, body: JSON.parse(String(init.body)) }); return new Response(JSON.stringify(wireProfile({ revision: 8 })), { status: 200, headers: { "Content-Type": "application/json" }, }); }, }); assert.equal(calls.length, 1); assert.equal(calls[0].input, "/api/v1/workspace-layouts/observation.spatial"); assert.equal(calls[0].init.method, "PUT"); assert.equal(calls[0].init.headers["If-Match"], '"7"'); assert.equal(calls[0].body.revision, 7); assert.equal(calls[0].body.workspace_id, "observation.spatial"); assert.equal(saved.revision, 8); }); test("workspace layout API treats 404 as no profile and exposes revision conflicts", async () => { assert.equal( await fetchObservationWorkspaceLayoutProfile({ fetcher: async () => new Response(null, { status: 404 }), }), null, ); const current = decodeObservationWorkspaceLayoutProfile(wireProfile()); await assert.rejects( saveObservationWorkspaceLayoutProfile(current, { fetcher: async () => new Response(JSON.stringify({ detail: "revision mismatch" }), { status: 412, headers: { "Content-Type": "application/json" }, }), }), (error) => error instanceof WorkspaceLayoutApiError && error.conflict && error.status === 412 && error.message === "revision mismatch", ); }); test("scene tool windows are route-scoped and toolbar actions are not duplicated", async () => { const appSource = await readFile(new URL("../src/App.tsx", import.meta.url), "utf8"); const utilityStart = appSource.indexOf("const contentActions"); const utilityEnd = appSource.indexOf("const header", utilityStart); const utilitySource = appSource.slice(utilityStart, utilityEnd); assert.match(appSource, /open=\{spatialWorkspaceActive && sourceWindowOpen\}/); assert.match(appSource, /open=\{spatialWorkspaceActive && displayWindowOpen\}/); assert.match(appSource, /open=\{spatialWorkspaceActive && layerInspectorOpen\}/); assert.match( appSource, /if \(spatialWorkspaceActive\) return;[\s\S]*setSceneWindowOrder\(\[\]\)/, ); assert.doesNotMatch(utilitySource, /Настроить визуальный движок/); assert.doesNotMatch(utilitySource, /Настроить отображение/); assert.doesNotMatch(utilitySource, /Открыть слои/); });