const { test } = require('node:test') const assert = require('node:assert/strict') const fs = require('node:fs') const vm = require('node:vm') const ts = require('typescript') const React = require('react') function compile(path, overrides = {}) { const exports = {} const source = fs.readFileSync(require.resolve(path), 'utf8') vm.runInNewContext(ts.transpileModule(source, { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022, jsx: ts.JsxEmit.ReactJSX } }).outputText, { exports, document: { body: {} }, require: name => overrides[name] ?? require(name) }) return exports } const models = compile('../src/renderer/src/features/workspace/agentModels.ts') const options = [{ provider: 'OPENAI', model: 'gpt-6.1-sol', imagesEnabled: false }, { provider: 'QWEN', model: 'qwen3.8-flash', imagesEnabled: true }] const plain = value => JSON.parse(JSON.stringify(value)) test('normalizes enabled models, validates IDs and preserves old single-model servers', () => { const status = models.normalizeAgentStatus({ enabled: true, models: [options[1], options[0], options[0], { provider: 'FORGED', model: 'bad' }, { provider: 'GEMINI', model: '' }] }) assert.deepEqual(plain(status.models), options) assert.equal(status.selectionSupported, true) assert.deepEqual(plain(models.normalizeAgentStatus({ enabled: true, ...options[1] }).models), [options[1]]) assert.equal(models.normalizeAgentStatus({ enabled: true, ...options[1] }).selectionSupported, false) assert.equal(models.normalizeAgentStatus({ enabled: false, models: options }).models.length, 0) assert.equal(models.normalizeAgentStatus({ enabled: true, models: [] }).enabled, false) }) test('selects the saved provider only while it is available, and never invents one', () => { assert.equal(models.selectedAgentModel(options, 'QWEN'), options[1]) assert.equal(models.selectedAgentModel(options, 'ANTHROPIC'), options[0]) assert.equal(models.selectedAgentModel([options[0]], 'QWEN'), options[0]) assert.equal(models.selectedAgentModel([], 'QWEN'), null) }) function elements(root, predicate) { if (!React.isValidElement(root)) return [] return [...(predicate(root) ? [root] : []), ...React.Children.toArray(root.props.children).flatMap(child => elements(child, predicate))] } function selectorHarness(props) { const state = [], refs = [] let stateIndex = 0, refIndex = 0 const { AgentModelSelector } = compile('../src/renderer/src/components/AgentModelSelector.tsx', { '../i18n': { t: value => value }, '../features/workspace/agentModels': models, 'react-dom': { createPortal: child => child }, react: { ...React, useId: () => 'model-menu', useEffect() {}, useLayoutEffect() {}, useState(initial) { const index = stateIndex++; if (!(index in state)) state[index] = initial; return [state[index], value => { state[index] = typeof value === 'function' ? value(state[index]) : value }] }, useRef(initial) { return refs[refIndex++] ?? (refs[refIndex - 1] = { current: initial }) } } }) return { render(patch = {}) { stateIndex = 0; refIndex = 0; return AgentModelSelector({ ...props, ...patch }) } } } for (const dark of [true, false]) test(`model control lists only allowed providers and follows the theme (${dark})`, () => { let selected const h = selectorHarness({ models: options, provider: 'QWEN', dark, disabled: false, onChange: value => { selected = value } }) const tree = h.render() const trigger = elements(tree, node => node.type === 'button')[0] assert.equal(trigger.props['aria-haspopup'], 'menu') assert.equal(trigger.props['aria-expanded'], false) assert.equal(trigger.props.title, 'Qwen · qwen3.8-flash') assert.equal(elements(tree, node => node.props.role === 'menu').length, 0) assert.equal(elements(tree, node => node.type === 'select').length, 0) trigger.props.onClick() const menu = elements(h.render(), node => node.props.role === 'menu')[0] assert.match(menu.props.className, dark ? /bg-\[#252526\]/ : /bg-white/) assert.match(menu.props.className, /fixed/) const items = elements(menu, node => node.props.role === 'menuitemradio') assert.deepEqual(items.map(node => node.props.title), ['OpenAI · gpt-6.1-sol', 'Qwen · qwen3.8-flash']) assert.deepEqual(items.map(node => node.props['aria-checked']), [false, true]) assert.ok(items.every(node => elements(node, item => item.type === 'span' && item.props.className.includes('truncate')).length === 1)) items[0].props.onClick() assert.equal(selected, 'OPENAI') assert.equal(elements(h.render(), node => node.props.role === 'menu').length, 0) assert.ok(elements(h.render({ disabled: true }), node => node.type === 'button')[0].props.disabled) const empty = elements(h.render({ models: [], provider: null }), node => node.type === 'button')[0] assert.ok(empty.props.disabled) assert.equal(empty.props.title, '사용 가능한 모델 없음') }) test('model menu opens from arrow keys and closes when a request blocks the selector', () => { const h = selectorHarness({ models: options, provider: 'QWEN', dark: true, disabled: false, onChange() {} }) const trigger = elements(h.render(), node => node.type === 'button')[0] trigger.props.onKeyDown({ key: 'ArrowUp', preventDefault() {} }) assert.equal(elements(h.render(), node => node.props.role === 'menu').length, 1) assert.equal(elements(h.render({ disabled: true }), node => node.props.role === 'menu').length, 0) }) test('input footer places the model action before the attachment and clear controls', () => { const source = fs.readFileSync(require.resolve('../src/renderer/src/components/ChatWidgets.tsx'), 'utf8') const row = source.slice(source.indexOf('{(attachmentsEnabled || onClearConversation || footerAction)')) assert.ok(row.indexOf('{footerAction}') < row.indexOf('title={t(\'파일 첨부\')}')) assert.ok(row.indexOf('{footerAction}') < row.indexOf('title={t(\'대화내역 삭제\')}')) }) test('A-Copilot has a single managed label', () => { assert.equal(models.agentModelLabel({ provider: 'A_COPILOT', model: 'A-Copilot', imagesEnabled: false }), 'A-Copilot') assert.equal(models.agentModelLabel(options[0]), 'OpenAI · gpt-6.1-sol') })