const assert = require('node:assert/strict'); const { readFileSync } = require('node:fs'); const { test } = require('node:test'); const ts = require('typescript'); const source = ts.createSourceFile( 'MonitoringAccountList.tsx', readFileSync(`${__dirname}/../src/components/accounts/MonitoringAccountList.tsx`, 'utf8'), ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX, ); const code = source.getFullText(); function syncRenderer() { let meta; let renderer; function visit(node) { if (ts.isVariableDeclaration(node) && node.name.getText(source) === 'syncStatusMeta') { meta = node.initializer.getText(source); } if (ts.isObjectLiteralExpression(node) && node.properties.some( (property) => ts.isPropertyAssignment(property) && property.name.getText(source) === 'title' && property.initializer.getText(source) === "'同步状态'", )) { renderer = node.properties.find((property) => property.name?.getText(source) === 'render').initializer.getText(source); } ts.forEachChild(node, visit); } visit(source); assert.ok(meta && renderer, '同步列和状态定义必须存在'); const { outputText } = ts.transpileModule(`const syncStatusMeta = ${meta}; return (${renderer});`, { compilerOptions: { jsx: ts.JsxEmit.React, target: ts.ScriptTarget.ES2022 }, }); return new Function('React', 'Flex', 'Tooltip', 'Tag', 'Typography', 'dateTime', outputText)( require('react'), 'Flex', 'Tooltip', 'Tag', { Text: 'Text' }, (value) => value || '—', ); } function elements(node) { return node && typeof node === 'object' ? [node, ...[node.props?.children].flat(Infinity).flatMap(elements)] : []; } test('失败原因仅在悬停同步失败标签时显示,完整保留长错误', () => { const render = syncRenderer(); const reason = 'douyin browser request rejected with HTTP 502: ' + '执行页面请求超时'.repeat(80); for (const status of ['failed', 'blocked']) { const nodes = elements(render(null, { sync_status: status, sync_error: reason, last_sync_at: '2026-05-14' })); const tooltip = nodes.find((node) => node.type === 'Tooltip'); assert.ok(tooltip, '失败原因必须使用 Tooltip'); assert.equal(tooltip.props.title, reason); assert.equal(tooltip.props.children.type, 'Tag', '浮层仅绑定状态标签'); assert.ok(nodes.every((node) => node.props?.children !== reason), '错误不占用表格行空间'); } }); test('其他同步状态及无失败原因时不显示错误浮层', () => { const render = syncRenderer(); for (const status of ['idle', 'running', 'paused', 'failed', 'blocked', 'unknown', undefined]) { for (const sync_error of ['', null, undefined]) { const nodes = elements(render(null, { sync_status: status, sync_error })); const tag = nodes.find((node) => node.type === 'Tag'); assert.ok(tag); const tooltip = nodes.find((node) => node.type === 'Tooltip'); assert.ok(!tooltip || !tooltip.props.title); } } for (const status of ['idle', 'running', 'paused']) { const nodes = elements(render(null, { sync_status: status, sync_error: '上次失败' })); assert.ok(nodes.every((node) => node.type !== 'Tooltip' || !node.props.title)); } }); test('监控账号列表使用 Table,一行一个账号,保留详情入口和操作', () => { assert.match(code, / { assert.doesNotMatch(code, /title: '账号数据'/); for (const [title, value] of [ ['粉丝', 'account.follower_count'], ['关注', 'account.following_count'], ['作品', 'account.work_count ?? account.aweme_count ?? null'], ]) { assert.ok(code.includes(`title: '${title}',\n render: (_, account) => formatCount(${value}),`), `${title} 应为单独数值列`); } }); test('列表不展示冗长的平台账号,保留画像统计及同步异常', () => { assert.doesNotMatch(code, /row\.platform_account_key|account\.platform_account_key/); for (const field of ['follower_count', 'following_count', 'work_count', 'latest_published_at', 'last_sync_at', 'sync_error']) { assert.ok(code.includes(`account.${field}`), `${field} 应保留在表格中`); } });