import test from 'node:test' import assert from 'node:assert/strict' import { createStatisticsLoader } from '../src/views/pl/quota-statistics/loader' test('fixed contract skips global list and retains each decimal balance', async () => { const calls: string[] = [] const loader = createStatisticsLoader({ contracts: async () => { calls.push('global') }, contract: async (id: string) => { calls.push(id) return { certificates: [{ availableQty: '0.10000001' }, { availableQty: '7.2' }] } } } as any) const result = await loader.load({ mode: 'contract', contractId: 'c1', allowed: true }) assert.deepEqual(calls, ['c1']) assert.deepEqual( result.certificates.map((c: any) => c.availableQty), ['0.10000001', '7.2'] ) }) test('permission denied never requests statistics', async () => { const loader = createStatisticsLoader( new Proxy( {}, { get: () => () => { throw Error('unexpected request') } } ) as any ) assert.equal(await loader.load({ mode: 'contract', allowed: false }), undefined) }) test('context invalidation discards pending response', async () => { let resolve!: (v: any) => void const loader = createStatisticsLoader({ contract: () => new Promise((r) => { resolve = r }) } as any) const pending = loader.load({ mode: 'contract', contractId: 'old', allowed: true }) loader.invalidate() resolve({ id: 'old' }) assert.equal(await pending, undefined) }) import { readFileSync } from 'node:fs' import { parse } from '@vue/compiler-sfc' import ts from 'typescript' import * as vue from 'vue' import { plRoutes } from '../src/router/modules/pl' import { usePersistedListColumns } from '../src/composables/usePersistedListColumns' import { useServerColumnFilters } from '../src/composables/useServerColumnFilters' const flush = async () => { for (let i = 0; i < 30; i++) await Promise.resolve() } function deferred() { let resolve!: (value: any) => void, reject!: (error: any) => void const promise = new Promise((a, b) => { resolve = a reject = b }) return { promise, resolve, reject } } function harness(fixed?: string) { const calls: any[] = [], permissions: any[] = [], unmount: Function[] = [] const props = vue.reactive({ mode: 'contract', contractId: fixed, active: true, embedded: !!fixed }) const user = vue.reactive({ accessToken: 'token', getUserInfo: { tenantNo: 'a' } }) const queue = (method: string, args: any[]) => { const pending = deferred() calls.push({ method, args, ...pending }) return pending.promise } const api = Object.fromEntries( ['contracts', 'contract', 'certificates', 'certificate'].map((method) => [ method, (...args: any[]) => queue(method, args) ]) ) const env = { ...Object.fromEntries( Object.entries(vue).filter(([key]) => /^[a-zA-Z_$][\w$]*$/.test(key) && key !== 'default') ), defineProps: () => props, withDefaults: (v: any) => v, useUserStore: () => user, fetchGetMenuCodes: () => { const p = deferred() permissions.push(p) return p.promise }, useListTableViewportHeight: () => ({ tableHeight: vue.ref(400) }), usePersistedListColumns, useServerColumnFilters, createStatisticsLoader, quotaStatisticsApi: api, onBeforeUnmount: (fn: Function) => unmount.push(fn) } const src = ts.createSourceFile( 'panel.ts', parse( readFileSync( new URL('../src/views/pl/quota-statistics/StatisticsPanel.vue', import.meta.url), 'utf8' ) ).descriptor.scriptSetup!.content, ts.ScriptTarget.Latest, true ) const code = ts.transpile( src.statements .filter((s) => !ts.isImportDeclaration(s)) .map((s) => s.getText(src)) .join('\n'), { target: ts.ScriptTarget.ESNext } ) const scope = vue.effectScope() const state = scope.run(() => new Function( ...Object.keys(env), `${code}; return { rows, fixedDetail, details, load, loadDetail, allowed, canEdit }` )(...Object.values(env)) ) return { state, calls, permissions, props, user, stop: () => { unmount.forEach((fn) => fn()) scope.stop() } } } test('five leaves retain IDs and individual permissions', () => { const group = plRoutes[0].children!.find((r) => r.id === 'PL_GROUP_CERT')! assert.deepEqual( group.children!.map((r) => r.path), ['quota-cert', 'contract-quota', 'certificate-usage', 'plan-cert', 'legal-use-document'] ) assert.equal(group.children![0].id, 'PL_QUOTA_CERT') assert.equal(group.children![3].id, 'PL00000000000000000000000000015') assert.deepEqual( group.children!.map((r) => r.meta!.menuCode), [ 'pl:quota-cert:list', 'pl:quota-cert:list', 'pl:quota-cert:list', 'pl:plan-cert:list', 'pl:legal-use-document:list' ] ) }) test('actual fixed panel loads only current contract and drops old response', async () => { const h = harness('one') h.permissions[0].resolve({ menuCodes: ['pl:quota-cert:list'] }) await flush() assert.equal(h.calls[0].method, 'contract') assert.deepEqual(h.calls[0].args, ['one']) h.props.contractId = 'two' h.permissions[1].resolve({ all: true }) await flush() h.calls[1].resolve({ id: 'two', certificates: [] }) await flush() h.calls[0].resolve({ id: 'one', certificates: [] }) await flush() assert.equal(h.state.fixedDetail.value.id, 'two') h.stop() }) for (const change of ['tenant', 'logout', 'unmount']) test(`actual panel invalidates data on ${change}`, async () => { const h = harness() h.permissions[0].resolve({ all: true }) await flush() if (change === 'tenant') h.user.getUserInfo.tenantNo = 'b' if (change === 'logout') h.user.accessToken = '' if (change === 'unmount') h.stop() h.calls[0].resolve({ list: [{ id: 'stale' }], total: 1 }) await flush() assert.deepEqual(h.state.rows.value, []) assert.equal(h.state.allowed.value, false) h.stop() }) test('proof-only permission cannot request quota', async () => { const h = harness() h.permissions[0].resolve({ menuCodes: ['pl:legal-use-document:list'] }) await flush() assert.equal(h.calls.length, 0) h.stop() }) test('expand failure clears stale data and retry works', async () => { const h = harness() h.permissions[0].resolve({ all: true }) await flush() h.calls[0].resolve({ list: [{ id: 'c' }], total: 1 }) await flush() const first = h.state.loadDetail({ id: 'c' }) h.calls[1].resolve({ id: 'c', certificates: [] }) await first assert.equal(h.state.details.c.data.id, 'c') const failure = h.state.loadDetail({ id: 'c' }) h.calls[2].reject(Error('failed')) await failure assert.equal(h.state.details.c.data, undefined) assert.equal(h.state.details.c.error, true) const retry = h.state.loadDetail({ id: 'c' }) h.calls[3].resolve({ id: 'c', certificates: [] }) await retry assert.equal(h.state.details.c.data.id, 'c') h.stop() }) test('statistics API maps each view to the server endpoint and preserves response decimals', async () => { const calls: any[] = [] const raw = { list: [{ availableQty: '1.00000001' }], total: 1 } const source = ts.createSourceFile( 'api.ts', readFileSync(new URL('../src/api/pl/quota-cert.ts', import.meta.url), 'utf8'), ts.ScriptTarget.Latest, true ) const code = ts.transpile( source.statements .filter((s) => !ts.isImportDeclaration(s)) .map((s) => s.getText(source).replace(/^export /, '')) .join('\n'), { target: ts.ScriptTarget.ESNext } ) const api = new Function('request', 'pickBxData', `${code};return quotaStatisticsApi`)( { get: (url: string, options: any) => { calls.push({ url, options }) return Promise.resolve(raw) } }, (v: any) => v ) assert.equal( (await api.contracts({ page: 2, keyword: '合同' })).list[0].availableQty, '1.00000001' ) await api.certificates({ certType: 'transport', pageSize: 20 }) await api.contract('c') await api.certificate('purchase', 'p') assert.deepEqual( calls.map((c) => c.url), [ '/transport/quota-certs/statistics/contracts', '/transport/quota-certs/statistics/certificates', '/transport/quota-certs/contracts/c/statistics', '/transport/quota-certs/purchase/p/statistics' ] ) assert.deepEqual(calls[1].options.params, { certType: 'transport', pageSize: 20 }) }) import * as statisticsLabels from '../src/views/pl/quota-statistics/labels' test('contract type tags translate all server values and legacy framework', () => { const template = parse( readFileSync( new URL('../src/views/pl/quota-statistics/StatisticsPanel.vue', import.meta.url), 'utf8' ) ).descriptor.template!.content const expression = template.match( /v-else-if="column.prop === 'contractType'"[^>]*>\s*\{\{([\s\S]*?)\}\}/ )![1] const renderLabel = new Function( 'row', ...Object.keys(statisticsLabels), `return (${expression})` ) for (const [contractType, label] of Object.entries({ sale: '销售合同', purchase: '采购合同', framework_sale: '框架销售合同', framework_purchase: '框架采购合同', framework: '框架合同', '': '—' })) { assert.equal(renderLabel({ contractType }, ...Object.values(statisticsLabels)), label) } })