import assert from 'node:assert/strict'; import React, {createElement} from 'react'; import {renderToStaticMarkup} from 'react-dom/server'; import {readFileSync} from 'node:fs'; import {before, after, test} from 'node:test'; import {createServer} from 'vite'; let server, resolve, awaitsCapture, Guard, Provider, Device, Host; before(async () => { server = await createServer({appType:'custom', logLevel:'silent', server:{middlewareMode:true}}); ({workspaceLaunchProfile:resolve} = await server.ssrLoadModule('/src/core/observation/workspaceLaunch.ts')); ({planningAwaitsCapture:awaitsCapture, PlanningTestProvider:Provider} = await server.ssrLoadModule('/src/core/missions/PlanningTestContext.tsx')); ({PlanningCaptureGuard:Guard} = await server.ssrLoadModule('/src/components/missions/PlanningCaptureGuard.tsx')); ({DeviceWorkspace:Device} = await server.ssrLoadModule('/src/workspaces/DeviceWorkspace.tsx')); ({DevicePluginHostProvider:Host} = await server.ssrLoadModule('/src/core/device-plugins/DevicePluginHost.tsx')); }); after(async () => { await server?.close(); }); test('direct entry overrides an earlier planning profile for both shared surfaces', () => { for (const kind of ['device','spatial']) { assert.equal(resolve('planning',kind),'direct'); assert.equal(resolve('direct',kind),'direct'); assert.equal(resolve('direct',kind,'planning'),'planning'); assert.equal(resolve('planning',kind,'planning'),'planning'); } assert.equal(resolve('planning','missions'),'planning'); assert.equal(resolve('direct','missions'),'direct'); }); test('a prepared consumer cannot silently claim a direct capture; old/bound runs do not block it', () => { assert.equal(awaitsCapture(null),false); for (const state of ['completed','cancelled','interrupted','error']) { for (const query_session_id of [null,'recording']) assert.equal(awaitsCapture({state,query_session_id}),false); } for (const state of ['preparing','waiting','running']) { assert.equal(awaitsCapture({state,query_session_id:null}),true); assert.equal(awaitsCapture({state,query_session_id:'recording'}),false); } }); function withHooks(hooks, fn) { // Same bounded pre-effect harness used by observatoryHooks.test.mjs. const internals = React.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE; const previous = internals.H; internals.H = hooks; try { return fn(); } finally { internals.H = previous; } } test('retained completed run renders the ordinary connection, without issuing any commands', () => { let calls = 0; const child = createElement('div',null,'ORDINARY DEVICE'); const tree = withHooks({useContext:()=>({test:{state:'completed',query_session_id:'old'},finish:()=>{calls++;}})}, () => Guard({enabled:true,onResume:()=>{calls++;},children:child})); assert.match(renderToStaticMarkup(tree),/ORDINARY DEVICE/); assert.equal(calls,0); }); test('pending run handoff is explicit and only finishes the research, not the scanner', () => { let stopped = 0, resumed = 0; const context = {test:{state:'waiting',query_session_id:null,draft:{name:'PENDING'}},busy:false,error:null,finish:()=>{stopped++;}}; const child = createElement('div',null,'ORDINARY DEVICE'); const tree = withHooks({useContext:()=>context}, () => Guard({enabled:true,onResume:()=>{resumed++;},children:child})); assert.doesNotMatch(renderToStaticMarkup(tree),/ORDINARY DEVICE/); assert.equal(stopped,0); const actions = tree.props.children.at(-1).props.children; actions[0].props.onClick(); assert.equal(stopped,1); assert.equal(resumed,0); actions[1].props.onClick(); assert.equal(resumed,1); const planningTree = withHooks({useContext:()=>context}, () => Guard({enabled:false,onResume:()=>{},children:child})); assert.match(renderToStaticMarkup(planningTree),/ORDINARY DEVICE/); }); test('generic device catalog renders without any PlanningTestProvider', () => { const html = renderToStaticMarkup(createElement(Host,{plugins:[]}, createElement(Device,{onOpenSpatialScene:()=>{},onActivateAutomaticSpatialSource:()=>{}}))); assert.match(html,/Выберите модель устройства/); assert.doesNotMatch(html,/Профиль · Планирование|Подключение сканера · Планирование/); }); test('failed planner selection does not grant a successful launch', async () => { const writes = []; const context = withHooks({ useState:initial=>[typeof initial==='function'?initial():initial,value=>writes.push(value)], useRef:value=>({current:value}),useCallback:fn=>fn,useEffect:()=>{}, }, () => Provider({children:null}).props.value); const originalFetch = globalThis.fetch; try { globalThis.fetch = async () => new Response(JSON.stringify({detail:'Выбранный проход недоступен'}),{status:409}); assert.equal(await context.select('missing'),false); assert.ok(writes.includes('Выбранный проход недоступен')); assert.equal('selected' in context,false); assert.equal('resume' in context,false); } finally { globalThis.fetch = originalFetch; } }); test('composition owns explicit profile propagation; server polling cannot restore it', () => { const source = path => readFileSync(new URL('../src/'+path,import.meta.url),'utf8'); assert.doesNotMatch(source('workspaces/DeviceWorkspace.tsx'),/Planning|missions\//); assert.doesNotMatch(source('core/missions/PlanningTestContext.tsx'),/dismissed|sessionStorage|launchProfile/); assert.match(source('App.tsx'),/useState\('direct'\)/); assert.match(source('App.tsx'),/openView\("spatial-scene", 'planning'\)/); assert.match(source('App.tsx'),/openView\("spatial-scene", 'direct'\)/); assert.match(source('workspaces/spatial/SpatialWorkspace.tsx'),/openView\("spatial-scene", launchProfile\)/); assert.match(source('workspaces/Workspaces.tsx'),/props\.launchProfile === 'planning'/); assert.match(source('workspaces/missions/MissionPlannerWorkspace.tsx'),/openView\('local-device','planning'\)/); assert.match(source('workspaces/missions/MissionPlannerWorkspace.tsx'),/if\(await live.select\(project.id\)\)openView/); });