import assert from 'node:assert/strict' import test from 'node:test' const webSource = new URL('../../unreal_tran_web/src/features/', import.meta.url) const fox = { sceneId: 'fox-scene-object', name: 'Fox', semantic: '实训装备', operable: true, interactionId: 'scene:fox', capabilities: ['select', 'inspect'], modelPartTargets: [{ sceneId: 'fox-scene-object', interactionId: 'scene:fox/part/mesh', sourceInteractionId: 'model:fox-mesh', editorId: 'fox-mesh', name: 'Fox mesh', operable: true, visible: true, capabilities: ['select', 'inspect'], }], } const projectWith = objects => ({ scenario: { publishedSceneId: 'content-scene-160', sceneResource: { sceneId: 'content-scene-160', snapshot: { objects } }, }, }) for (const runtime of ['guide-legacy/demo-src', 'virtual-training-legacy/upstream']) { const { TRAINING_PROJECT_TARGETS, createTrainingProject, getTrainingProjectTargets, listTrainingSceneTargets, normalizeTrainingProject, validateTrainingProject, } = await import(new URL(`${runtime}/training-project-io.js`, webSource)) test(`${runtime}: binding a published scene replaces all demo-machine targets`, () => { assert.deepEqual(getTrainingProjectTargets(projectWith([fox])).map(item => item.id), ['scene:fox', 'scene:fox/part/mesh']) assert.deepEqual(getTrainingProjectTargets(projectWith([])), []) assert.equal(getTrainingProjectTargets({}).length, TRAINING_PROJECT_TARGETS.length) const reservedId = TRAINING_PROJECT_TARGETS[0].id assert.deepEqual(getTrainingProjectTargets(projectWith([{ ...fox, interactionId: reservedId, modelPartTargets: [] }])).map(item => item.id), [reservedId]) }) test(`${runtime}: hidden and deleted roots exclude their parts and split children`, () => { for (const disabled of [{ visible: false }, { deleted: true }, { softDeleted: true }]) { const root = { ...fox, ...disabled, splitParts: [{ ...fox, sceneId: 'split', interactionId: 'scene:split' }] } assert.deepEqual(listTrainingSceneTargets({ objects: [root] }), []) const normalized = normalizeTrainingProject({ ...createTrainingProject(), ...projectWith([root]) }) assert.deepEqual(getTrainingProjectTargets(normalized), []) assert.equal(normalized.scenario.sceneResource.snapshot.objects[0].visible, false) } }) test(`${runtime}: part visibility and soft-delete overrides keep only the valid root`, () => { for (const disabled of [{ visible: false }, { deleted: true }, { softDeleted: true }]) { const root = { ...fox, modelPartTargets: [{ ...fox.modelPartTargets[0], ...disabled }] } assert.deepEqual(listTrainingSceneTargets({ objects: [root] }).map(item => item.id), ['scene:fox']) } const root = { ...fox, partOverrides: { 'scene:fox/part/mesh': { softDeleted: true } } } assert.deepEqual(listTrainingSceneTargets({ objects: [root] }).map(item => item.id), ['scene:fox']) }) test(`${runtime}: formal semantic target ids follow backend operable/visible/deleted rules`, () => { const formal = { id: 'fox-formal', name: 'Fox', semantic: { operable: true, interactionId: 'scene:formal-fox', deviceId: 'FOX-001', capabilities: ['inspect'] }, } const objects = [formal, { ...formal, id: 'hidden', visible: false }, { ...formal, id: 'deleted', deleted: true }, { id: 'not-operable', semantic: { interactionId: 'scene:locked', operable: false } }, { id: 'missing-id', semantic: { operable: true } }, ] const targets = listTrainingSceneTargets({ objects }) assert.deepEqual(targets.map(item => item.id), ['scene:formal-fox']) assert.deepEqual(targets[0].modes, ['inspect']) assert.equal(targets[0].targetRef.deviceId, 'FOX-001') const normalized = normalizeTrainingProject({ ...createTrainingProject(), ...projectWith(objects) }) assert.deepEqual(getTrainingProjectTargets(normalized).map(item => item.id), ['scene:formal-fox']) }) test(`${runtime}: old demo steps stay invalid after binding, then real scene selections validate`, () => { const source = { ...createTrainingProject(), ...projectWith([fox]) } const invalid = validateTrainingProject(source) assert.ok(invalid.errors.some(item => item.path?.endsWith('.targetId'))) source.steps = [{ ...source.steps[0], targetId: 'scene:fox', targetRef: { interactionId: 'scene:fox' }, mode: 'inspect' }] const normalized = normalizeTrainingProject(source) const result = validateTrainingProject(normalized) assert.equal(result.errors.some(item => item.path?.endsWith('.targetId')), false) assert.equal(normalized.steps[0].targetRef.sceneObjectId, 'fox-scene-object') }) }