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) } })