2026-05-20 21:00:47 +08:00
|
|
|
|
import assert from 'node:assert/strict'
|
|
|
|
|
|
import { readFileSync } from 'node:fs'
|
|
|
|
|
|
import test from 'node:test'
|
|
|
|
|
|
import { fileURLToPath } from 'node:url'
|
|
|
|
|
|
|
|
|
|
|
|
import {
|
|
|
|
|
|
buildAiAdviceViewModel,
|
|
|
|
|
|
buildAttachmentInsightViewModel,
|
|
|
|
|
|
buildAttachmentRiskCards
|
|
|
|
|
|
} from '../src/views/scripts/travelRequestDetailInsights.js'
|
|
|
|
|
|
|
|
|
|
|
|
const detailViewTemplate = readFileSync(
|
|
|
|
|
|
fileURLToPath(new URL('../src/views/TravelRequestDetailView.vue', import.meta.url)),
|
|
|
|
|
|
'utf8'
|
|
|
|
|
|
)
|
|
|
|
|
|
const detailViewScript = readFileSync(
|
|
|
|
|
|
fileURLToPath(new URL('../src/views/scripts/TravelRequestDetailView.js', import.meta.url)),
|
|
|
|
|
|
'utf8'
|
|
|
|
|
|
)
|
2026-05-21 10:57:06 +08:00
|
|
|
|
const detailViewStyle = readFileSync(
|
|
|
|
|
|
fileURLToPath(new URL('../src/assets/styles/views/travel-request-detail-view.css', import.meta.url)),
|
|
|
|
|
|
'utf8'
|
|
|
|
|
|
)
|
2026-05-20 21:00:47 +08:00
|
|
|
|
const requestsComposableScript = readFileSync(
|
|
|
|
|
|
fileURLToPath(new URL('../src/composables/useRequests.js', import.meta.url)),
|
|
|
|
|
|
'utf8'
|
|
|
|
|
|
)
|
|
|
|
|
|
const approvalCenterTemplate = readFileSync(
|
|
|
|
|
|
fileURLToPath(new URL('../src/views/ApprovalCenterView.vue', import.meta.url)),
|
|
|
|
|
|
'utf8'
|
|
|
|
|
|
)
|
|
|
|
|
|
const approvalCenterScript = readFileSync(
|
|
|
|
|
|
fileURLToPath(new URL('../src/views/scripts/ApprovalCenterView.js', import.meta.url)),
|
|
|
|
|
|
'utf8'
|
|
|
|
|
|
)
|
|
|
|
|
|
const returnReasonDialog = readFileSync(
|
|
|
|
|
|
fileURLToPath(new URL('../src/components/shared/ReturnReasonDialog.vue', import.meta.url)),
|
|
|
|
|
|
'utf8'
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
const attachmentMeta = {
|
|
|
|
|
|
file_name: 'taxi-invoice.pdf',
|
|
|
|
|
|
media_type: 'application/pdf',
|
|
|
|
|
|
previewable: true,
|
|
|
|
|
|
document_info: {
|
|
|
|
|
|
document_type: 'taxi_receipt',
|
|
|
|
|
|
document_type_label: '出租车/网约车票据',
|
|
|
|
|
|
fields: [
|
|
|
|
|
|
{ label: '金额', value: '121.54' },
|
|
|
|
|
|
{ label: '日期', value: '2026-03-04' }
|
|
|
|
|
|
]
|
|
|
|
|
|
},
|
|
|
|
|
|
requirement_check: {
|
|
|
|
|
|
matches: false,
|
|
|
|
|
|
message: '附件类型与当前费用项目不匹配。'
|
|
|
|
|
|
},
|
|
|
|
|
|
analysis: {
|
|
|
|
|
|
severity: 'high',
|
|
|
|
|
|
label: '高风险',
|
|
|
|
|
|
headline: '票据类型不匹配',
|
|
|
|
|
|
summary: '交通票据挂在办公费明细下。',
|
|
|
|
|
|
points: ['票据识别为出租车/网约车票据', '当前费用项目为办公费'],
|
|
|
|
|
|
suggestion: '把费用项目调整为交通费,或更换为办公用品票据。'
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
test('attachment insight exposes recognition fields and rule basis', () => {
|
|
|
|
|
|
const insight = buildAttachmentInsightViewModel(attachmentMeta, {
|
|
|
|
|
|
name: '办公费',
|
|
|
|
|
|
itemType: 'office'
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
assert.equal(insight.documentTypeLabel, '出租车/网约车票据')
|
|
|
|
|
|
assert.equal(insight.requirementLabel, '不符合当前费用类型')
|
|
|
|
|
|
assert.deepEqual(insight.fields, ['金额:121.54', '日期:2026-03-04'])
|
|
|
|
|
|
assert.ok(insight.ruleBasis.some((item) => item.includes('附件类型与当前费用项目不匹配')))
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
test('AI advice card splits every attachment risk point with basis and suggestion', () => {
|
|
|
|
|
|
const riskCards = buildAttachmentRiskCards({
|
|
|
|
|
|
expenseItems: [
|
|
|
|
|
|
{
|
|
|
|
|
|
id: 'item-1',
|
|
|
|
|
|
name: '办公费',
|
|
|
|
|
|
invoiceId: 'taxi-invoice.pdf'
|
|
|
|
|
|
}
|
|
|
|
|
|
],
|
|
|
|
|
|
attachmentMetaByItemId: {
|
|
|
|
|
|
'item-1': attachmentMeta
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
const advice = buildAiAdviceViewModel({
|
|
|
|
|
|
completionItems: [],
|
|
|
|
|
|
riskCards
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
assert.equal(riskCards.length, 2)
|
|
|
|
|
|
assert.equal(advice.badge, '优先整改')
|
|
|
|
|
|
assert.equal(advice.riskCards.length, 2)
|
|
|
|
|
|
assert.ok(advice.riskCards.every((card) => card.ruleBasis.length > 0))
|
|
|
|
|
|
assert.ok(advice.riskCards.every((card) => card.suggestion.includes('费用项目调整为交通费')))
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2026-05-21 10:57:06 +08:00
|
|
|
|
test('AI advice view model exposes grouped completion and risk sections', () => {
|
|
|
|
|
|
const advice = buildAiAdviceViewModel({
|
|
|
|
|
|
completionItems: ['补充业务地点', '补充报销金额'],
|
|
|
|
|
|
riskCards: [
|
|
|
|
|
|
{
|
|
|
|
|
|
id: 'risk-1',
|
|
|
|
|
|
tone: 'high',
|
|
|
|
|
|
label: '高风险',
|
|
|
|
|
|
title: '票据类型不匹配',
|
|
|
|
|
|
risk: '交通票据挂在办公费明细下。',
|
|
|
|
|
|
ruleBasis: ['附件类型与当前费用项目不匹配。'],
|
|
|
|
|
|
suggestion: '把费用项目调整为交通费。'
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
assert.equal(advice.sections.length, 2)
|
|
|
|
|
|
assert.deepEqual(
|
|
|
|
|
|
advice.sections.map((section) => ({ title: section.title, kind: section.kind })),
|
|
|
|
|
|
[
|
|
|
|
|
|
{ title: '建议补充字段', kind: 'completion' },
|
|
|
|
|
|
{ title: '已知存在风险', kind: 'risk' }
|
|
|
|
|
|
]
|
|
|
|
|
|
)
|
|
|
|
|
|
assert.deepEqual(advice.sections[0].items, ['补充业务地点', '补充报销金额'])
|
|
|
|
|
|
assert.equal(advice.sections[1].items.length, 1)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
test('AI advice view model omits empty sections', () => {
|
|
|
|
|
|
const completionOnlyAdvice = buildAiAdviceViewModel({
|
|
|
|
|
|
completionItems: ['补充业务地点'],
|
|
|
|
|
|
riskCards: []
|
|
|
|
|
|
})
|
|
|
|
|
|
const riskOnlyAdvice = buildAiAdviceViewModel({
|
|
|
|
|
|
completionItems: [],
|
|
|
|
|
|
riskCards: [
|
|
|
|
|
|
{
|
|
|
|
|
|
id: 'risk-1',
|
|
|
|
|
|
tone: 'medium',
|
|
|
|
|
|
label: '中风险',
|
|
|
|
|
|
title: '说明不完整',
|
|
|
|
|
|
risk: '缺少业务背景。',
|
|
|
|
|
|
ruleBasis: ['系统预审规则命中该风险提示。'],
|
|
|
|
|
|
suggestion: '补充说明。'
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
assert.deepEqual(completionOnlyAdvice.sections.map((section) => section.title), ['建议补充字段'])
|
|
|
|
|
|
assert.deepEqual(riskOnlyAdvice.sections.map((section) => section.title), ['已知存在风险'])
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
test('AI advice template renders grouped section titles with completion before risk', () => {
|
|
|
|
|
|
assert.match(detailViewTemplate, /v-if="aiAdvice\.sections\.length" class="validation-sections"/)
|
|
|
|
|
|
assert.match(detailViewTemplate, /v-for="section in aiAdvice\.sections"/)
|
|
|
|
|
|
assert.match(detailViewTemplate, /validation-section--\$\{section\.kind\}/)
|
|
|
|
|
|
assert.match(detailViewTemplate, /<h4 class="validation-section-title">\{\{ section\.title \}\}<\/h4>/)
|
|
|
|
|
|
assert.match(detailViewTemplate, /v-if="section\.kind === 'completion'" class="validation-list"/)
|
|
|
|
|
|
assert.match(detailViewTemplate, /v-else class="risk-advice-list"/)
|
|
|
|
|
|
assert.ok(
|
|
|
|
|
|
detailViewTemplate.indexOf("section.kind === 'completion'") < detailViewTemplate.indexOf('risk-advice-card')
|
|
|
|
|
|
)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
test('AI advice risk section uses compact card styling hooks', () => {
|
|
|
|
|
|
assert.match(detailViewTemplate, /class="\['risk-advice-card', card\.tone\]"/)
|
|
|
|
|
|
assert.match(detailViewStyle, /\.validation-card \{\s*border: 1px solid #e5e7eb;/)
|
|
|
|
|
|
assert.match(detailViewStyle, /\.validation-section--risk \.risk-advice-card \{\s*display: grid;\s*gap: 8px;\s*padding: 12px 12px 11px;/)
|
|
|
|
|
|
assert.match(detailViewStyle, /\.validation-section--risk \.risk-advice-meta ul,\s*\.validation-section--risk \.risk-advice-meta p \{\s*margin: 0;/)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2026-05-20 21:00:47 +08:00
|
|
|
|
test('AI advice shows only the latest manual return while preserving return count context', () => {
|
|
|
|
|
|
const riskCards = buildAttachmentRiskCards({
|
|
|
|
|
|
claimRiskFlags: [
|
|
|
|
|
|
{
|
|
|
|
|
|
source: 'manual_return',
|
|
|
|
|
|
severity: 'medium',
|
|
|
|
|
|
label: '人工退回',
|
|
|
|
|
|
message: '第一次退回:缺少附件。',
|
|
|
|
|
|
reason: '缺少附件。',
|
|
|
|
|
|
return_count: 1,
|
|
|
|
|
|
return_stage: '直属领导审批',
|
|
|
|
|
|
risk_points: ['附件缺失或不清晰']
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
source: 'manual_return',
|
|
|
|
|
|
severity: 'medium',
|
|
|
|
|
|
label: '人工退回',
|
|
|
|
|
|
message: '第二次退回:超标说明不完整。',
|
|
|
|
|
|
reason: '超标说明不完整。',
|
|
|
|
|
|
return_count: 2,
|
|
|
|
|
|
return_stage: '财务审批',
|
|
|
|
|
|
risk_points: ['超出制度标准或缺少超标说明']
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
assert.equal(riskCards.length, 1)
|
|
|
|
|
|
assert.equal(riskCards[0].risk, '第二次退回:超标说明不完整。')
|
|
|
|
|
|
assert.ok(riskCards[0].ruleBasis.some((item) => item.includes('累计退回 2 次')))
|
|
|
|
|
|
assert.ok(riskCards[0].ruleBasis.some((item) => item.includes('财务审批')))
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
test('expense attachment actions keep preview as the only recognition entry point', () => {
|
|
|
|
|
|
assert.match(detailViewTemplate, /:aria-label="resolveAttachmentPreviewTitle\(item\)"/)
|
|
|
|
|
|
assert.match(detailViewScript, /return fileName \? `预览附件:\$\{fileName\}` : '预览附件'/)
|
|
|
|
|
|
assert.doesNotMatch(detailViewTemplate, /aria-label="识别附件"/)
|
|
|
|
|
|
assert.doesNotMatch(detailViewTemplate, /点击识别按钮/)
|
|
|
|
|
|
assert.doesNotMatch(detailViewScript, /recognizeExpenseAttachment/)
|
|
|
|
|
|
assert.doesNotMatch(detailViewScript, /recognizingExpenseId/)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2026-05-21 10:57:06 +08:00
|
|
|
|
test('expense detail table shows the amount total below detail rows', () => {
|
2026-05-20 21:00:47 +08:00
|
|
|
|
assert.match(detailViewTemplate, /<div class="detail-expense-table">/)
|
|
|
|
|
|
assert.match(detailViewTemplate, /当前还没有费用明细/)
|
|
|
|
|
|
assert.doesNotMatch(detailViewTemplate, /class="total-row"/)
|
2026-05-21 10:57:06 +08:00
|
|
|
|
assert.match(detailViewTemplate, /class="expense-total-under-table"[\s\S]*金额合计[\s\S]*\{\{ expenseTotal \}\}/)
|
2026-05-20 21:00:47 +08:00
|
|
|
|
assert.doesNotMatch(detailViewTemplate, /\{\{ uploadedExpenseCount \}\} 项已关联票据/)
|
|
|
|
|
|
assert.doesNotMatch(detailViewTemplate, /\{\{ expenseSummaryText \}\}/)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2026-05-21 10:57:06 +08:00
|
|
|
|
test('additional note is shown above expense details as travel purpose text', () => {
|
|
|
|
|
|
assert.ok(detailViewTemplate.indexOf('<h3>附加说明</h3>') < detailViewTemplate.indexOf('<h3>费用明细</h3>'))
|
|
|
|
|
|
assert.match(detailViewTemplate, /用于说明本次出差或办事目的/)
|
|
|
|
|
|
assert.match(detailViewTemplate, /v-if="canEditDetailNote" class="detail-note-editor"/)
|
|
|
|
|
|
assert.match(detailViewTemplate, /v-else class="detail-note readonly"/)
|
|
|
|
|
|
assert.match(detailViewTemplate, /v-model="detailNoteEditor"/)
|
|
|
|
|
|
assert.match(detailViewTemplate, /提交后将作为明确说明展示/)
|
|
|
|
|
|
assert.match(detailViewScript, /const canEditDetailNote = computed\(\(\) => isDraftRequest\.value\)/)
|
|
|
|
|
|
assert.match(detailViewScript, /function normalizeDetailNoteDraftValue\(value\)/)
|
|
|
|
|
|
assert.match(detailViewScript, /const detailNoteSource = computed\(\(\) => normalizeDetailNoteDraftValue\(request\.value\.note\)\)/)
|
|
|
|
|
|
assert.match(detailViewScript, /updateExpenseClaim\(request\.value\.claimId/)
|
|
|
|
|
|
assert.match(detailViewScript, /emit\('request-updated', \{ claimId: request\.value\.claimId \}\)/)
|
|
|
|
|
|
assert.match(detailViewScript, /暂无附加说明。请补充本次出差或办事事由/)
|
|
|
|
|
|
assert.match(detailViewScript, /去北京客户现场出差,拜访 XX 客户并处理项目验收事项/)
|
|
|
|
|
|
assert.match(detailViewStyle, /\.detail-note-editor textarea/)
|
|
|
|
|
|
assert.match(detailViewStyle, /\.detail-note\.readonly/)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
test('ticket item types and system allowance row are visible but read only', () => {
|
|
|
|
|
|
assert.match(detailViewScript, /value: 'train_ticket', label: '火车票'/)
|
|
|
|
|
|
assert.match(detailViewScript, /value: 'flight_ticket', label: '机票'/)
|
|
|
|
|
|
assert.match(detailViewScript, /value: 'hotel_ticket', label: '住宿票'/)
|
|
|
|
|
|
assert.match(detailViewScript, /value: 'ride_ticket', label: '乘车'/)
|
|
|
|
|
|
assert.match(detailViewScript, /value: 'travel_allowance', label: '出差补贴'/)
|
|
|
|
|
|
assert.match(detailViewScript, /const SYSTEM_GENERATED_EXPENSE_TYPES = new Set\(\['travel_allowance'\]\)/)
|
|
|
|
|
|
assert.match(detailViewTemplate, /'system-generated-row': item\.isSystemGenerated/)
|
|
|
|
|
|
assert.match(detailViewTemplate, /v-if="item\.isSystemGenerated" class="system-row-lock"/)
|
|
|
|
|
|
assert.match(detailViewTemplate, /v-if="item\.isSystemGenerated" class="system-attachment-note"/)
|
|
|
|
|
|
assert.match(detailViewScript, /系统自动计算的补贴行不能手动编辑/)
|
|
|
|
|
|
assert.match(detailViewScript, /系统自动计算的补贴行不能删除/)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
test('travel item date caption distinguishes departure return and trip events', () => {
|
|
|
|
|
|
assert.match(detailViewTemplate, /<span>\{\{ item\.dayLabel \}\}<\/span>/)
|
|
|
|
|
|
assert.match(detailViewScript, /const LONG_DISTANCE_TRAVEL_EXPENSE_TYPES = new Set\(\['train_ticket', 'flight_ticket'\]\)/)
|
|
|
|
|
|
assert.match(detailViewScript, /function buildTravelTimeLabelMap\(items, requestModel\)/)
|
|
|
|
|
|
assert.match(detailViewScript, /labels\.set\(item\.id, '出发时间'\)/)
|
|
|
|
|
|
assert.match(detailViewScript, /labels\.set\(item\.id, '返回时间'\)/)
|
|
|
|
|
|
assert.match(detailViewScript, /return '乘车时间'/)
|
|
|
|
|
|
assert.match(detailViewScript, /return '住宿时间'/)
|
|
|
|
|
|
assert.match(requestsComposableScript, /function buildTravelTimeLabelMap\(items, claim\)/)
|
|
|
|
|
|
assert.match(requestsComposableScript, /return claim\?\.expense_type === 'travel' \? '出行时间' : '业务发生时间'/)
|
|
|
|
|
|
assert.doesNotMatch(detailViewScript, /第 \$\{index \+ 1\} 项/)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2026-05-20 21:00:47 +08:00
|
|
|
|
test('expense detail table shows each item filled time from item creation time', () => {
|
|
|
|
|
|
assert.match(detailViewTemplate, /<th class="col-filled-at">填写时间<\/th>/)
|
|
|
|
|
|
assert.match(detailViewTemplate, /<td class="expense-filled-at col-filled-at">[\s\S]*\{\{ item\.filledAt \}\}/)
|
|
|
|
|
|
assert.match(detailViewTemplate, /<span>条款填写时间<\/span>/)
|
|
|
|
|
|
assert.match(detailViewScript, /function formatExpenseFilledTime\(value\)/)
|
|
|
|
|
|
assert.match(detailViewScript, /source\?\.filledAt[\s\S]*source\?\.created_at/)
|
|
|
|
|
|
assert.match(detailViewScript, /expenseTableColumnCount = computed\(\s*\(\) => 6 \+ \(isEditableRequest\.value \? 1 : 0\)/)
|
|
|
|
|
|
assert.match(requestsComposableScript, /filledAt: formatDateTime\(item\?\.created_at\) \|\| '待同步'/)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
test('expense item upload remains limited to one receipt per detail row', () => {
|
|
|
|
|
|
assert.match(detailViewTemplate, /ref="expenseUploadInput"[\s\S]*type="file"/)
|
|
|
|
|
|
assert.doesNotMatch(detailViewTemplate, /\bmultiple\b/)
|
|
|
|
|
|
assert.equal(
|
2026-05-21 10:57:06 +08:00
|
|
|
|
(detailViewTemplate.match(/v-if="isEditableRequest && !item\.invoiceId && !item\.isSystemGenerated"/g) || []).length,
|
2026-05-20 21:00:47 +08:00
|
|
|
|
2
|
|
|
|
|
|
)
|
|
|
|
|
|
assert.match(detailViewScript, /const attachments = invoiceId \? \[attachmentName \|\| invoiceId\] : \[\]/)
|
2026-05-21 10:57:06 +08:00
|
|
|
|
assert.match(detailViewScript, /attachmentStatus: isSystemGenerated \? '无需附件' : attachments\.length \? '已关联票据' : '未上传'/)
|
2026-05-20 21:00:47 +08:00
|
|
|
|
assert.match(detailViewScript, /if \(item\?\.invoiceId\) \{[\s\S]*每条费用明细只能关联一张单据/)
|
|
|
|
|
|
assert.match(detailViewScript, /const fileCount = fileList\?\.length \|\| 0/)
|
|
|
|
|
|
assert.match(detailViewScript, /fileCount > 1[\s\S]*一条费用明细只能上传一张单据/)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2026-05-21 09:28:33 +08:00
|
|
|
|
test('expense item upload patches OCR amount into the visible detail row', () => {
|
|
|
|
|
|
assert.match(detailViewScript, /const recognizedItemAmount = Number\(payload\?\.item_amount \?\? payload\?\.itemAmount\)/)
|
|
|
|
|
|
assert.match(detailViewScript, /itemPatch\.itemAmount = recognizedItemAmount/)
|
|
|
|
|
|
assert.match(detailViewScript, /itemPatch\.amount = formatCurrency\(recognizedItemAmount\)/)
|
|
|
|
|
|
assert.match(detailViewScript, /expenseEditor\.itemAmount = String\(recognizedItemAmount\)/)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2026-05-20 21:00:47 +08:00
|
|
|
|
test('return reason dialog is wired into approval and detail return actions', () => {
|
|
|
|
|
|
assert.match(returnReasonDialog, /missing_attachment/)
|
|
|
|
|
|
assert.match(returnReasonDialog, /invoice_mismatch/)
|
|
|
|
|
|
assert.match(returnReasonDialog, /reason_codes/)
|
|
|
|
|
|
assert.match(approvalCenterTemplate, /<TravelRequestDetailView/)
|
|
|
|
|
|
assert.doesNotMatch(approvalCenterTemplate, /<ReturnReasonDialog/)
|
|
|
|
|
|
assert.match(detailViewTemplate, /<ReturnReasonDialog/)
|
|
|
|
|
|
assert.doesNotMatch(approvalCenterScript, /returnExpenseClaim/)
|
|
|
|
|
|
assert.match(detailViewScript, /returnExpenseClaim\(request\.value\.claimId, payload\)/)
|
|
|
|
|
|
assert.doesNotMatch(approvalCenterScript, /审批中心退回/)
|
|
|
|
|
|
assert.doesNotMatch(detailViewScript, /详情页退回/)
|
|
|
|
|
|
})
|