88 lines
4.5 KiB
JavaScript
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)
|
|
}
|
|
})
|