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') const { renderToStaticMarkup } = require('react-dom/server') function compile(file, overrides = {}, globals = {}) { const exports = {} vm.runInNewContext(ts.transpileModule(fs.readFileSync(require.resolve(file), 'utf8'), { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022, jsx: ts.JsxEmit.ReactJSX } }).outputText, { exports, require: name => overrides[name] ?? require(name), clearTimeout, setTimeout, ...globals }) return exports } const i18n = { t: key => key, useLocaleRender() {} } const block = compile('../src/renderer/src/components/ChatCodeBlock.tsx', { '../i18n': i18n }) const { MarkdownMessage } = compile('../src/renderer/src/components/ChatWidgets.tsx', { '../i18n': i18n, './ChatCodeBlock': block, './chatAttachments': {}, './ChatImageAttachments': {}, './ChatTextContextMenu': {} }) const render = (content, dark = true, extra = {}) => renderToStaticMarkup(React.createElement(MarkdownMessage, { content, dark, ...extra })) for (const dark of [true, false]) { test(`renders numbered/nested/task lists without losing the starting number (${dark})`, () => { const html = render('3. 설정 확인\n - 속도 제한\n - 조향 제한\n4. 결과 확인\n\n- [x] 완료\n- [ ] 대기', dark) assert.match(html, /
    { const html = render('| 모듈 | 속도 |\n| --- | ---: |\n| Control | `0.3 m/s` |', dark) assert.match(html, /data-chat-table/) assert.match(html, /tabindex="0" role="region" aria-label="표"/) assert.match(html, /overflow-x-auto/) assert.match(html, / { const html = render('```python\ndef speed():\n return 0.3\n```', dark) assert.match(html, /data-chat-code-block/) assert.ok(html.includes(`data-code-theme="${dark ? 'dark' : 'light'}"`)) assert.match(html, />Python<\/span>/) assert.match(html, /aria-label="코드 복사"/) assert.match(html, /hljs-keyword/) assert.match(html, /hljs-number/) assert.match(html, /\n { for (const language of ['', 'unknown-language']) { const html = render('```' + language + '\n unformatted \n```') assert.match(html, /data-chat-code-block/) assert.ok(html.includes(language || '일반 텍스트')) assert.ok(html.includes(' unformatted <value>')) assert.doesNotMatch(html, /hljs-/) } }) test('HTML-looking code is rendered as escaped text, never live elements', () => { const html = render('```html\n\n\n```') assert.doesNotMatch(html, / { const markdown = '설명\n\n```yaml\nspeed: 0.3\n```\n\n| 키 | 값 |\n| --- | --- |\n| speed | 0.3 |' const html = render(markdown, true, { fadeFrom: markdown.indexOf('| 키') }) assert.equal((html.match(/agent-response-fade/g) ?? []).length, 1) assert.match(html, /overflow-x-auto rounded-md agent-response-fade/) }) test('oversized code skips tokenization but still preserves all content', () => { const source = '# comment\n'.repeat(6000) const html = renderToStaticMarkup(React.createElement(block.ChatCodeBlock, { source, language: 'python', dark: true })) assert.doesNotMatch(html, /hljs-comment/) assert.equal((html.match(/# comment/g) ?? []).length, 6000) }) function elements(tree, predicate) { if (!React.isValidElement(tree)) return [] return [...(predicate(tree) ? [tree] : []), ...React.Children.toArray(tree.props.children).flatMap(child => elements(child, predicate))] } for (const fails of [false, true]) test(`code copy preserves the full source and reports clipboard ${fails ? 'failure' : 'success'}`, async () => { let state = 'idle', copied, timer const api = compile('../src/renderer/src/components/ChatCodeBlock.tsx', { '../i18n': i18n, react: { ...React, useState: () => [state, value => { state = value }], useMemo: fn => fn(), useRef: () => ({ current: null }), useEffect() {} } }, { navigator: { clipboard: { writeText: async text => { if (fails) throw Error('denied'); copied = text } } }, setTimeout: callback => { timer = callback; return 1 }, clearTimeout() {} }) const source = '\t# 한글 주석\n speed = 0.3\n\n' const renderBlock = () => api.ChatCodeBlock({ source, language: 'python', dark: true }) const button = elements(renderBlock(), element => element.type === 'button')[0] let stopped = false button.props.onClick({ stopPropagation() { stopped = true } }) await new Promise(resolve => setImmediate(resolve)) assert.equal(stopped, true) assert.equal(state, fails ? 'error' : 'copied') if (!fails) assert.equal(copied, source) assert.equal(elements(renderBlock(), element => element.type === 'button')[0].props['aria-label'], fails ? '코드를 복사하지 못했습니다.' : '복사됨') timer() assert.equal(state, 'idle') })