Files
X-Financial/web/tests/commercial-components-compile.test.mjs

141 lines
6.8 KiB
JavaScript
Raw Permalink Normal View History

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')
}