import assert from "node:assert/strict"; import { readFile } from "node:fs/promises"; import { after, before, test } from "node:test"; import { createElement } from "react"; import { renderToStaticMarkup } from "react-dom/server"; import { createServer } from "vite"; let server; let xgridsK1Manifest; let xgridsK1ObservationSources; let openObservationSource; let shouldRestartObservationSource; let consumeCameraLeaseRetry; let resetCameraLeaseRetryBudget; let cameraTransportRecoveryIsCurrent; let cameraTransportCanOpen; let cameraPendingQueueCanAccept; let cameraTransportCloseRecoveryMessage; let cameraPlaybackRecoveryFailureStage; let createCameraStartupWatchdog; let cameraStartupWatchdogRecoveryMessage; let CAMERA_FIRST_MEDIA_TIMEOUT_MS; let CAMERA_FIRST_PLAYABLE_FRAME_TIMEOUT_MS; let liveCameraPlaybackAuthorityIdentity; let initialCameraPlaybackRecoveryState; let reduceCameraPlaybackRecovery; let cameraTransportCallbackIsCurrent; let cameraBrowserTransportIdentity; let liveRerunRecoveryAuthorityIdentity; let initialObservationWindowRect; let FloatingObservationWindow; let ObservationTimeline; let shouldCaptureWorkspacePointer; let normalizeTimelineRange; let timelineOffsetSeconds; let formatTimelineDuration; let normalizeAccumulationSeconds; let formatAccumulationDuration; let resolveRerunSourceUrl; let resolveRecordedBlueprintUrl; let fetchRecordedBlueprintRrd; let recordedCameraJournalContract; let resolveRecordedPerceptionUrl; let fetchRecordedPerceptionRrd; let resolveRecordedPerceptionViewerSourceUrl; let probeRecordedPerceptionViewerSource; let resolveRecordedPointColorsUrl; let fetchRecordedPointColorsRrd; let recordedPointColorKey; let isRecordedPlaybackFullyBuffered; let recordedObservationSources; let selectRecordedMediaEpoch; let recordedMediaLocalTime; before(async () => { server = await createServer({ appType: "custom", logLevel: "silent", server: { middlewareMode: true }, }); ({ xgridsK1Manifest } = await server.ssrLoadModule( "@xgrids-k1/frontend/manifest.ts", )); ({ xgridsK1ObservationSources } = await server.ssrLoadModule( "@xgrids-k1/frontend/observationSources.ts", )); ({ openObservationSource, shouldRestartObservationSource } = await server.ssrLoadModule( "/src/core/observation/layoutPolicy.ts", )); ({ consumeCameraLeaseRetry, resetCameraLeaseRetryBudget, cameraTransportRecoveryIsCurrent, cameraTransportCanOpen, cameraPendingQueueCanAccept, cameraTransportCloseRecoveryMessage, cameraPlaybackRecoveryFailureStage, createCameraStartupWatchdog, cameraStartupWatchdogRecoveryMessage, CAMERA_FIRST_MEDIA_TIMEOUT_MS, CAMERA_FIRST_PLAYABLE_FRAME_TIMEOUT_MS, } = await server.ssrLoadModule("/src/components/MseFmp4WebSocketPlayer.tsx")); ({ liveCameraPlaybackAuthorityIdentity, initialCameraPlaybackRecoveryState, reduceCameraPlaybackRecovery, cameraTransportCallbackIsCurrent, cameraBrowserTransportIdentity, } = await server.ssrLoadModule( "/src/core/observation/liveCameraRecovery.ts", )); ({ FloatingObservationWindow, initialObservationWindowRect, shouldCaptureWorkspacePointer, } = await server.ssrLoadModule("/src/components/FloatingObservationWindow.tsx")); ({ liveRerunRecoveryAuthorityIdentity } = await server.ssrLoadModule( "/src/core/observation/liveReceiverWatchdog.ts", )); ({ ObservationTimeline, normalizeTimelineRange, timelineOffsetSeconds, formatTimelineDuration, normalizeAccumulationSeconds, formatAccumulationDuration, } = await server.ssrLoadModule("/src/components/ObservationTimeline.tsx")); ({ resolveRerunSourceUrl, resolveRecordedBlueprintUrl, fetchRecordedBlueprintRrd, recordedCameraJournalContract, resolveRecordedPerceptionUrl, fetchRecordedPerceptionRrd, resolveRecordedPerceptionViewerSourceUrl, probeRecordedPerceptionViewerSource, resolveRecordedPointColorsUrl, fetchRecordedPointColorsRrd, recordedPointColorKey, isRecordedPlaybackFullyBuffered, } = await server.ssrLoadModule( "/src/components/RerunViewport.tsx", )); ({ recordedObservationSources } = await server.ssrLoadModule( "/src/core/observation/recordedObservationSources.ts", )); ({ selectRecordedMediaEpoch, recordedMediaLocalTime } = await server.ssrLoadModule( "/src/components/RecordedFmp4Player.tsx", )); }); test("follow toggles retain the current recorded camera journal", () => { const before = recordedCameraJournalContract({ activeView: "spatial", viewResetGeneration: 0, planView: false, followTrajectory: false, }); const afterFollowToggle = recordedCameraJournalContract({ activeView: "spatial", viewResetGeneration: 0, planView: false, followTrajectory: true, }); const afterPlanToggle = recordedCameraJournalContract({ activeView: "spatial", viewResetGeneration: 0, planView: true, followTrajectory: true, }); const afterReset = recordedCameraJournalContract({ activeView: "spatial", viewResetGeneration: 1, planView: false, followTrajectory: false, }); assert.equal(afterFollowToggle, before); assert.notEqual(afterPlanToggle, before); assert.notEqual(afterReset, before); }); test("observation camera windows tile from the bottom-left above the live timeline", () => { const bounds = { width: 1280, height: 720 }; const left = initialObservationWindowRect(0, 2, bounds); const right = initialObservationWindowRect(1, 2, bounds); assert.equal(left.y, right.y); assert.ok(left.x + left.width < right.x); assert.equal(left.x, 18); assert.ok(right.x + right.width <= bounds.width - 18); assert.ok(right.y + right.height <= bounds.height - 64); }); test("live camera window keeps its player mounted before viewport measurement", () => { const source = { id: "k1:sensor.camera.right", sourceId: "sensor.camera.right", label: "Правая камера", description: "Live camera", modality: "video", availability: "streaming", transport: "websocket", endpointLabel: "Локальный видеопоток", previewUrl: null, delivery: { id: "camera-preview-4", kind: "mse-fmp4-websocket", url: "/camera-preview/4", mediaType: 'video/mp4; codecs="avc1.641028"', }, activation: { groupId: "k1:device-session:camera.preview.decoder", maxActive: 1, selected: true, controllable: false, }, binding: { deviceId: "k1-a", deviceSessionId: "device-session", acquisitionId: "acquisition-quick", }, capabilities: { defaultVisible: true, overlay: true, resizable: true, timelineMode: "live-only", }, }; const noop = () => undefined; const markup = renderToStaticMarkup(createElement(FloatingObservationWindow, { source, index: 0, count: 1, boundsRef: { current: null }, maximized: false, active: true, onRectChange: noop, onMaximizedChange: noop, onActivate: noop, onClose: noop, })); assert.match(markup, /