Files
X-Financial/web/tests/ai-conversation-html-renderer.test.mjs

101 lines
4.8 KiB
JavaScript
Raw Normal View History

import assert from 'node:assert/strict'
import test from 'node:test'
import { renderAiConversationHtml } from '../src/utils/aiConversationHtmlRenderer.js'
test('AI conversation renderer turns business copy into spacious semantic HTML', () => {
const rendered = renderAiConversationHtml([
'### 出差申请办理确认',
'',
'**我已在您的输入中提取到关键信息**,如下表所示:',
'',
'> **前置查询结果**:我已查询您名下可关联的差旅申请单,当前未查到可关联单据。',
'',
'> **需要您确认**:发起新的出差申请属于业务操作,需要您手动确认后我再继续办理。',
'',
'点击下方 **确认发起出差申请** 后,我会继续完成:',
'',
'- **单据重叠核查**:检查同一时间段是否已有申请单,避免重复申请。',
'- **预算与审批预审**:查看部门预算影响,判断是否可能增加预算管理者审核。'
].join('\n'))
assert.match(rendered, /<div class="ai-html-flow">/)
assert.match(rendered, /<h3 class="ai-html-title">出差申请办理确认<\/h3>/)
assert.match(rendered, /<section class="ai-html-focus-grid" aria-label="重点信息">/)
assert.match(rendered, /<article class="ai-html-focus-card">[\s\S]*前置查询结果[\s\S]*当前未查到可关联单据/)
assert.match(rendered, /<article class="ai-html-focus-card">[\s\S]*需要您确认[\s\S]*需要您手动确认后我再继续办理/)
assert.match(rendered, /<ul class="ai-html-steps">[\s\S]*单据重叠核查[\s\S]*预算与审批预审/)
assert.doesNotMatch(rendered, /<blockquote>/)
assert.doesNotMatch(rendered, /<ul>\s*<li><strong>/)
})
test('AI conversation renderer supports tables and escapes unsafe HTML', () => {
const rendered = renderAiConversationHtml([
'### 查询结果',
'',
'| 字段 | 内容 |',
'| --- | --- |',
'| 事由 | 辅助 <script>alert(1)</script> 部署 |',
'| 地点 | 上海 |'
].join('\n'))
assert.match(rendered, /<div class="ai-html-table-wrap">/)
assert.match(rendered, /<th>字段<\/th>/)
assert.match(rendered, /&lt;script&gt;alert\(1\)&lt;\/script&gt;/)
assert.doesNotMatch(rendered, /<script>/)
})
test('AI conversation renderer renders application detail action links as buttons', () => {
const rendered = renderAiConversationHtml([
'| 单据编号 | 操作 |',
'| --- | --- |',
'| AP-OVERLAP | [查看](#ai-open-application-detail:AP-OVERLAP) |'
].join('\n'))
assert.match(rendered, /class="ai-html-action-link ai-html-action-link-application"/)
assert.match(rendered, /data-ai-action="open-application-detail"/)
assert.match(rendered, /href="#ai-open-application-detail:AP-OVERLAP"/)
assert.doesNotMatch(rendered, /target="_blank"[\s\S]{0,120}#ai-open-application-detail/)
})
test('AI conversation renderer renders document detail action links as buttons', () => {
const rendered = renderAiConversationHtml('[查看单据](#ai-open-document-detail:CL-20260221001)')
assert.match(rendered, /class="ai-html-action-link ai-html-action-link-document"/)
assert.match(rendered, /data-ai-action="open-document-detail"/)
assert.match(rendered, /href="#ai-open-document-detail:CL-20260221001"/)
assert.doesNotMatch(rendered, /target="_blank"[\s\S]{0,120}#ai-open-document-detail/)
})
test('AI conversation renderer renders images as html and rejects unsafe image sources', () => {
const rendered = renderAiConversationHtml([
'### 图片材料',
'',
'![票据预览](https://example.com/receipt.png)',
'',
'内联图片:![危险](javascript:alert(1))'
].join('\n'))
assert.match(rendered, /<figure class="ai-html-image-frame">/)
assert.match(rendered, /<img class="ai-html-image" src="https:\/\/example\.com\/receipt\.png" alt="票据预览" loading="lazy" \/>/)
assert.match(rendered, /<figcaption class="ai-html-image-caption">票据预览<\/figcaption>/)
assert.doesNotMatch(rendered, /javascript:alert/)
})
test('AI conversation renderer keeps separated step bullets in one numbered sequence', () => {
const rendered = renderAiConversationHtml([
'点击下方 **确认发起出差申请** 后,我会继续完成:',
'',
'- **单据重叠核查**:检查同一时间段是否已有申请单,避免重复申请。',
'',
'- **预算与审批预审**:查看部门预算影响,判断是否可能增加预算管理者审核。',
'',
'- **申请表生成**:预审完成后,再展示完整申请表并自动预填已识别信息。'
].join('\n'))
assert.equal((rendered.match(/class="ai-html-steps"/g) || []).length, 1)
assert.match(rendered, /<span class="ai-html-step-index">1<\/span>[\s\S]*单据重叠核查/)
assert.match(rendered, /<span class="ai-html-step-index">2<\/span>[\s\S]*预算与审批预审/)
assert.match(rendered, /<span class="ai-html-step-index">3<\/span>[\s\S]*申请表生成/)
})