Files
X-Financial/web/tests/commercial-components-compile.test.mjs
caoxiaozhu 787bc3a481 feat(platform): close AI expense value loop
Add tenant-safe value, telemetry, connector, commercial, and production-readiness foundations.
2026-07-17 14:14:08 +08:00

141 lines
6.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import assert from 'node:assert/strict'
import { readFileSync } from 'node:fs'
import test from 'node:test'
import { fileURLToPath } from 'node:url'
import { compileScript, compileTemplate, parse } from '@vue/compiler-sfc'
const componentFiles = [
'../src/components/commercial/CommercialWorkspace.vue',
'../src/components/commercial/CommercialAccountOverview.vue',
'../src/components/commercial/CommercialValueProofPanel.vue',
'../src/components/commercial/CommercialPricingScenarioPanel.vue',
'../src/components/commercial/CommercialMetricCard.vue',
'../src/components/commercial/CommercialAdminConsole.vue',
'../src/components/commercial/CommercialHistoryPanel.vue',
'../src/components/commercial/CommercialMutationDialog.vue'
]
for (const relativeFile of componentFiles) {
test(`${relativeFile} 可以独立编译`, () => {
const filename = fileURLToPath(new URL(relativeFile, import.meta.url))
const source = readFileSync(filename, 'utf8')
const parsed = parse(source, { filename })
assert.deepEqual(parsed.errors, [])
const id = `commercial-${relativeFile.replace(/\W+/gu, '-')}`
const script = compileScript(parsed.descriptor, { id })
const template = compileTemplate({
source: parsed.descriptor.template?.content || '',
filename,
id,
compilerOptions: { bindingMetadata: script.bindings }
})
assert.deepEqual(template.errors, [])
})
}
const workspace = readSource('../src/components/commercial/CommercialWorkspace.vue')
const valuePanel = readSource('../src/components/commercial/CommercialValueProofPanel.vue')
const pricingPanel = readSource('../src/components/commercial/CommercialPricingScenarioPanel.vue')
const metricCard = readSource('../src/components/commercial/CommercialMetricCard.vue')
const adminConsole = readSource('../src/components/commercial/CommercialAdminConsole.vue')
const mutationDialog = readSource('../src/components/commercial/CommercialMutationDialog.vue')
const historyPanel = readSource('../src/components/commercial/CommercialHistoryPanel.vue')
const styles = readSource('../src/components/commercial/commercial-workspace.css')
const overviewView = readSource('../src/views/OverviewView.vue')
const appShell = readSource('../src/views/AppShellRouteView.vue')
const topBarOverviewRange = readSource('../src/components/layout/useTopBarOverviewRange.js')
const overviewModel = readSource('../src/composables/useOverviewView.js')
test('商业工作台具有显式租户选择、双账分离、权限和完整空错状态', () => {
assert.match(workspace, /目标租户 ID/u)
assert.match(workspace, /平台管理员必须显式选择租户/u)
assert.match(workspace, /商业权益不能放宽安全规则/u)
assert.match(valuePanel, /客户价值账/u)
assert.match(valuePanel, /平台经营账/u)
assert.match(valuePanel, /不同币种不会混算/u)
assert.match(valuePanel, /!platformAdmin/u)
assert.match(valuePanel, /role="alert"/u)
assert.match(valuePanel, /这不是零成本或零价值/u)
})
test('无真实财务证据显示不可用和缺失输入,不含演示或回退指标', () => {
assert.match(metricCard, /commercial-unavailable-value">不可用/u)
assert.match(metricCard, /查看缺失证据/u)
assert.match(metricCard, /requiredInputs/u)
assert.doesNotMatch(workspace, /demo|mock|fallback/iu)
assert.doesNotMatch(valuePanel, /demo|mock|fallback/iu)
})
test('定价场景具有参数确认、分币种走廊、部分可用与不写套餐语义', () => {
assert.match(workspace, /CommercialPricingScenarioPanel/u)
for (const label of ['目标贡献毛利率', '最大价值分享比例', '证据截止时间as_of', '确认计算条件']) {
assert.match(pricingPanel, new RegExp(label, 'u'))
}
for (const label of ['可持续收费下限', '价值对齐收费上限', '成功费封顶', '推荐商业模式', '缺失输入']) {
assert.match(pricingPanel, new RegExp(label, 'u'))
}
assert.match(pricingPanel, /v-for="row in view\.rows"/u)
assert.match(pricingPanel, /row\.currency/u)
assert.match(pricingPanel, /evidenceStatus/u)
assert.match(pricingPanel, /不可用/u)
assert.match(pricingPanel, /不会自动写入套餐/u)
assert.match(pricingPanel, /aria-busy/u)
assert.match(pricingPanel, /role="alert"/u)
assert.match(pricingPanel, /inputmode="decimal"/u)
})
test('所有商业写操作都有操作确认、忙碌态、幂等和版本化提示', () => {
for (const label of ['创建套餐版本', '激活套餐版本', '创建订阅快照', '重新激活订阅', '变更订阅状态', '配置权益版本', '激活权益版本', '记录用量事件', '记录成本事件']) {
assert.match(readSource('../src/components/commercial/commercialWorkspaceModel.js'), new RegExp(label, 'u'))
}
assert.match(adminConsole, /二次确认/u)
assert.match(mutationDialog, /step === 'edit'/u)
assert.match(mutationDialog, /确认写入/u)
assert.match(mutationDialog, /幂等键/u)
assert.match(mutationDialog, /版本化快照/u)
assert.match(mutationDialog, /:disabled="busy"/u)
assert.match(mutationDialog, /role="alertdialog"/u)
})
test('五类历史有按需加载、刷新、空态与订阅状态操作入口', () => {
for (const label of ['套餐版本', '订阅历史', '权益版本', '用量事件', '成本事件']) {
assert.match(historyPanel, new RegExp(label, 'u'))
}
assert.match(historyPanel, /加载历史/u)
assert.match(historyPanel, /刷新历史/u)
assert.match(historyPanel, /这不是零用量或零成本/u)
assert.match(historyPanel, /manage-subscription/u)
assert.match(historyPanel, /变更状态/u)
assert.match(historyPanel, /恢复/u)
})
test('商业工作台满足键盘、触控、响应式和减少动画语义', () => {
assert.match(styles, /min-height: 44px/u)
assert.match(styles, /:focus-visible/u)
assert.match(styles, /@media \(max-width: 760px\)/u)
assert.match(styles, /@media \(prefers-reduced-motion: reduce\)/u)
assert.match(mutationDialog, /@keydown\.esc/u)
assert.match(mutationDialog, /aria-label="关闭对话框"/u)
assert.match(pricingPanel, /tabindex="-1"/u)
assert.match(pricingPanel, /:disabled="loading"/u)
})
test('商业工作台已接入分析看板且不会误加载财务看板', () => {
assert.match(overviewView, /CommercialWorkspace/u)
assert.match(overviewView, /activeDashboard === 'commercial'/u)
assert.match(overviewView, /currentUser: \{ type: Object/u)
assert.match(appShell, /:current-user="currentUser"/u)
assert.match(appShell, /'commercial'/u)
assert.match(topBarOverviewRange, /\{ label: '商业化管理', value: 'commercial' \}/u)
assert.match(overviewModel, /dashboard === 'commercial'/u)
assert.match(
overviewModel,
/activeDashboardKey\.value === 'commercial' \|\| activeDashboardKey\.value === 'value'/u
)
})
function readSource(relativeFile) {
return readFileSync(fileURLToPath(new URL(relativeFile, import.meta.url)), 'utf8')
}