Files
lms-fe/scripts/test-ros-connections.cjs

88 lines
4.5 KiB
JavaScript

const { test } = require('node:test')
const assert = require('node:assert/strict')
const fs = require('node:fs')
const path = require('node:path')
const vm = require('node:vm')
const ts = require('typescript')
const exportsObject = {}
const source = fs.readFileSync(path.resolve(__dirname, '../src/renderer/src/features/workspace/WorkspaceRosConnectionsView.tsx'), 'utf8')
vm.runInNewContext(ts.transpileModule(source, {
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022, jsx: ts.JsxEmit.ReactJSX }
}).outputText, { exports: exportsObject, require: () => ({}) })
const { rosGraphConnections } = exportsObject
const endpoint = (node, type = 'sensor_msgs/msg/CompressedImage') => ({ node, type })
test('shows image and custom topics without module definitions', () => {
const connections = rosGraphConnections({ topics: [
{ name: '/camera/image/compressed', types: ['sensor_msgs/msg/CompressedImage'], publishers: [endpoint('/camera')], subscribers: [endpoint('/bridge')] },
{ name: '/custom/new_topic', types: ['custom_msgs/msg/Value'], publishers: [], subscribers: [endpoint('/custom', 'custom_msgs/msg/Value')] },
] })
assert.equal(connections.length, 2)
assert.equal(connections[0].topic.name, '/camera/image/compressed')
assert.equal(connections[0].topic.type, 'sensor_msgs/msg/CompressedImage')
assert.equal(connections[0].active, true)
assert.equal(connections[1].publishers.length, 0)
assert.equal(connections[1].active, false)
})
test('empty and failed queries never synthesize topics or nodes', () => {
assert.equal(rosGraphConnections({ topics: [] }).length, 0)
assert.equal(rosGraphConnections({ topics: [], message: 'SSH timeout' }).length, 0)
})
test('keeps discovered topics without endpoints and deduplicates node entries', () => {
const connections = rosGraphConnections({ topics: [
{ name: '/z', types: [], publishers: [], subscribers: [] },
{ name: '/a', types: ['sensor_msgs/msg/CompressedImage'], publishers: [endpoint('/camera'), endpoint('/camera')], subscribers: [] },
] })
assert.equal(connections.length, 2)
assert.equal(connections[0].topic.name, '/a')
assert.equal(connections[0].publishers.length, 1)
assert.equal(connections[1].topic.name, '/z')
assert.equal(connections.every((connection) => !connection.active), true)
})
test('module selection and guessed topic mappings are removed from the view', () => {
assert.doesNotMatch(source, /selectedModuleKeys|moduleTopics|fallbackPublishers|fallbackSubscribers|onToggleModule|ListChecks|ListX/)
})
test('active toggle filters only topics, preserves totals and hides filtered details', () => {
const graph = { topics: [
{ name: '/active', types: [], publishers: [endpoint('/pub')], subscribers: [endpoint('/sub')] },
{ name: '/waiting', types: [], publishers: [endpoint('/pub')], subscribers: [] },
] }
function render(activeOnly) {
const states = [graph, false, activeOnly, '/waiting', true, 320]
let index = 0
const api = {}
const jsx = (type, props, key) => ({ type, props, key })
vm.runInNewContext(ts.transpileModule(source, {
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022, jsx: ts.JsxEmit.ReactJSX }
}).outputText, { exports: api, require: (name) => {
if (name === 'react') return { useState: () => [states[index++], () => {}], useMemo: fn => fn(), useEffect: () => {} }
if (name === 'react/jsx-runtime') return { jsx, jsxs: jsx }
if (name === '../../i18n') return { t: value => value, useLocaleRender: () => {} }
return {}
} })
const tree = api.default({ dark: false, modules: [{ name: 'Running module', state: 'running' }], remoteWorkspace: false, rosDomainId: '77' })
const nodes = []
function visit(node) {
if (Array.isArray(node)) return node.forEach(visit)
if (!node || typeof node !== 'object') return
nodes.push(node)
visit(node.props?.children)
}
visit(tree)
return nodes
}
for (const activeOnly of [false, true]) {
const nodes = render(activeOnly)
assert.equal(nodes.filter(node => node.type === 'button' && node.key?.startsWith('/')).length, activeOnly ? 1 : 2)
const toggle = nodes.find(node => node.props?.title === '활성 연결만 보기')
assert.equal(toggle.props['aria-pressed'], activeOnly)
assert.equal(toggle.props.children.at(-1), 1)
assert.equal(nodes.find(node => node.type?.name === 'RuntimeLane').props.modules.length, 1)
assert.equal(nodes.some(node => node.type?.name === 'TopicDetailDrawer'), !activeOnly)
}
})