Files
shanxia2.0/web-admin/scripts/mobile-transport-pickers.test.ts
T
2026-10-23 23:07:00 +08:00

305 lines
11 KiB
TypeScript

import assert from 'node:assert/strict'
import test from 'node:test'
import { readFileSync } from 'node:fs'
import { parse } from '@vue/compiler-sfc'
import ts from 'typescript'
import * as vue from 'vue'
const names = ['VehicleLibrary', 'CarrierLibrary', 'TransportAddress', 'Contract', 'Product']
const tick = async () => {
await vue.nextTick()
await Promise.resolve()
await Promise.resolve()
}
function deferred() {
let resolve: any, reject: any
const promise = new Promise((a, b) => {
resolve = a
reject = b
})
return { promise, resolve, reject }
}
async function harness(name: string, extra = {}) {
const props = vue.reactive({
modelValue: false,
selectableStatuses: ['approved', 'executing'],
...extra
})
const account = vue.reactive({ accessToken: 'a', getUserInfo: { id: 'a' } })
const calls: any[] = [],
details: any[] = [],
events: any[] = []
const request = (queue: any[]) => (params: any) => {
const d = deferred()
queue.push({ params, ...d })
return d.promise
}
const list = request(calls),
get = request(details)
const source = parse(
readFileSync(new URL(`../src/components/mp/Mp${name}SelectSheet.vue`, import.meta.url), 'utf8')
).descriptor.scriptSetup!.content
const ast = ts.createSourceFile('sheet.ts', source, ts.ScriptTarget.Latest, true)
const code = ast.statements
.filter((s) => !ts.isImportDeclaration(s))
.map((s) => s.getText(ast))
.join('\n')
.replace(/export type /g, 'type ')
let helper = {}
try {
helper = await import('../src/components/mp/useMobilePickerList')
} catch {
// The pre-fix SFC snapshot has no helper dependency.
}
const bindings = {
ref: vue.ref,
computed: vue.computed,
watch: vue.watch,
onScopeDispose: vue.onScopeDispose,
...helper,
defineProps: () => props,
withDefaults: (p: any) => p,
defineEmits:
() =>
(...args: any[]) =>
events.push(args),
defineOptions: () => {},
useUserStore: () => account,
useDictStore: () => ({ getLabel: (_: any, v: any) => v }),
ElMessage: { warning() {}, error() {} },
vehicleApi: { list },
carrierApi: { list },
transportAddressApi: { list },
listContract: list,
getContract: get,
fetchProductList: list,
fetchProductDetail: get,
pickBxPageResult: (r: any) => ({ list: r.rows || [], total: r.total || 0 })
}
const scope = vue.effectScope()
const h = scope.run(() =>
new Function(
...Object.keys(bindings),
ts.transpile(code, { target: ts.ScriptTarget.ESNext, module: ts.ModuleKind.None }) +
';return { switchTab: typeof switchTab === "undefined" ? undefined : switchTab, activeTab: typeof activeTab === "undefined" ? undefined : activeTab, rows, loading, keyword, fetchList, loadMore, innerVisible, error: typeof error === "undefined" ? undefined : error, handlePick: typeof handlePick === "undefined" ? undefined : handlePick, handleConfirm: typeof handleConfirm === "undefined" ? undefined : handleConfirm, selectedProducts: typeof selectedProducts === "undefined" ? undefined : selectedProducts, toggleSelect: typeof toggleSelect === "undefined" ? undefined : toggleSelect }'
)(...Object.values(bindings))
)
props.modelValue = true
await tick()
return { ...h, props, account, calls, details, events, stop: () => scope.stop() }
}
for (const name of names) {
test(`${name}: reversed requests and close/reopen cannot write old rows or finally`, async () => {
const h = await harness(name)
h.keyword.value = 'new'
const fresh = h.fetchList(true)
h.calls[0].resolve({ rows: [{ id: 'old' }], total: 1 })
await tick()
assert.deepEqual(h.rows.value, [])
assert.equal(h.loading.value, true)
h.calls.at(-1).resolve({ rows: [{ id: 'new' }], total: 1 })
await fresh
h.props.modelValue = false
await tick()
h.props.modelValue = true
await tick()
const old = h.calls.at(-1)
h.props.modelValue = false
await tick()
h.props.modelValue = true
await tick()
old.reject(new Error('old failure'))
await tick()
assert.equal(h.loading.value, true)
assert.equal(h.error.value, '')
h.stop()
})
test(`${name}: failed page retries same page, deduplicates, account invalidates`, async () => {
const h = await harness(name)
h.calls[0].resolve({ rows: [{ id: 'a' }], total: 4 })
await tick()
h.loadMore()
h.loadMore()
assert.equal(h.calls.length, 2)
h.calls[1].reject(new Error('offline'))
await tick()
assert.match(h.error.value, /offline/)
h.loadMore()
assert.equal(h.calls[2].params.page ?? h.calls[2].params.pageNum, 2)
h.calls[2].resolve({ rows: [{ id: 'a' }, { id: 'b' }], total: 4 })
await tick()
assert.deepEqual(
h.rows.value.map((r: any) => r.id),
['a', 'b']
)
h.keyword.value = 'pending'
h.account.accessToken = 'b'
await tick()
assert.deepEqual(h.rows.value, [])
h.stop()
const count = h.calls.length
await new Promise((r) => setTimeout(r, 350))
assert.equal(h.calls.length, count)
})
}
test('contract detail cannot emit or clear a newer confirmation after customer changes', async () => {
const h = await harness('Contract', { customerId: 'a' })
const old = h.handlePick({ id: 'one', status: 'approved' })
h.props.customerId = 'b'
await tick()
const fresh = h.handlePick({ id: 'two', status: 'approved' })
h.details[0].resolve({ id: 'one', status: 'approved', customerId: 'a' })
await old
assert.equal(h.events.length, 0)
h.details[1].resolve({
id: 'two',
status: 'approved',
customerId: 'b',
products: [{ id: 'line', productId: 'p' }]
})
await fresh
assert.equal(h.events[0][1].products[0].id, 'line')
h.stop()
})
test('restricted products expose detail failure and reject stale allowed IDs', async () => {
const h = await harness('Product', { allowedProductIds: ['a'] })
h.details[0].reject(new Error('offline'))
await tick()
assert.match(h.error.value, /offline/)
const retry = h.fetchList(true)
h.props.allowedProductIds = ['b']
await tick()
h.details[1].resolve({ id: 'a' })
await retry
assert.deepEqual(h.rows.value, [])
h.details[2].resolve({ id: 'b', productName: 'B' })
await tick()
await tick()
h.toggleSelect(h.rows.value[0])
h.keyword.value = 'missing'
const search = h.fetchList(true)
h.details[3].resolve({ id: 'b', productName: 'B' })
await search
assert.equal(h.selectedProducts.value[0].id, 'b')
h.innerVisible.value = false
assert.equal(
h.events.some((e: any[]) => e[0] === 'confirm'),
false
)
h.stop()
})
for (const name of names) {
test(`${name}: clear search restores results and unmount cancels pending debounce`, async () => {
const h = await harness(name)
h.calls[0].resolve({ rows: [{ id: 'a' }], total: 1 })
await tick()
h.keyword.value = 'absent'
const missing = h.fetchList(true)
h.calls[1].resolve({ rows: [], total: 0 })
await missing
assert.deepEqual(h.rows.value, [])
h.keyword.value = ''
const restore = h.fetchList(true)
assert.equal(h.calls[2].params.q ?? h.calls[2].params.keyword, undefined)
h.calls[2].resolve({ rows: [{ id: 'a' }], total: 1 })
await restore
assert.equal(h.rows.value[0].id, 'a')
h.keyword.value = 'later'
h.stop()
await new Promise((r) => setTimeout(r, 350))
assert.equal(h.calls.length, 3)
})
}
test('vehicle tab switch keeps self until close and reopen restores carrier', async () => {
const h = await harness('VehicleLibrary')
assert.equal(h.calls[0].params.vehicleType, 'carrier')
h.switchTab('self')
assert.equal(h.activeTab.value, 'self')
assert.equal(h.calls[1].params.vehicleType, 'self')
h.calls[0].resolve({ rows: [{ id: 'old-carrier' }], total: 1 })
await tick()
assert.deepEqual(h.rows.value, [])
h.calls[1].resolve({ rows: [{ id: 'self' }], total: 1 })
await tick()
assert.equal(h.rows.value[0].id, 'self')
h.innerVisible.value = false
h.props.modelValue = false
await tick()
h.props.modelValue = true
await tick()
assert.equal(h.activeTab.value, 'carrier')
assert.equal(h.calls.at(-1).params.vehicleType, 'carrier')
assert.equal(h.calls.length, 3)
h.stop()
})
test('restricted empty IDs never expand into full catalogue and confirm retains metadata', async () => {
const h = await harness('Product', { allowedProductIds: [] })
await tick()
assert.equal(h.calls.length, 0)
assert.equal(h.details.length, 0)
assert.equal(h.error.value, '')
h.props.allowedProductIds = ['p']
await tick()
h.details[0].resolve({
id: 'p',
productName: 'P',
brands: [{ id: 'brand' }],
aliases: [{ id: 'alias' }]
})
await tick()
await tick()
h.toggleSelect(h.rows.value[0])
h.handleConfirm()
const result = h.events.find((e: any[]) => e[0] === 'confirm')[1][0]
assert.equal(result.id, 'p')
assert.equal(result.brands[0].id, 'brand')
assert.equal(result.aliases[0].id, 'alias')
h.stop()
})
test('restricted response must match requested product ID', async () => {
const h = await harness('Product', { allowedProductIds: ['p'] })
h.details[0].resolve({ id: 'wrong' })
await tick()
await tick()
assert.deepEqual(h.rows.value, [])
assert.match(h.error.value, /不匹配/)
h.stop()
})
test('contract stale rejection cannot clear newer confirming state or emit', async () => {
const h = await harness('Contract', { customerId: 'a' })
const old = h.handlePick({ id: 'one', status: 'approved' })
h.innerVisible.value = false
h.props.modelValue = false
await tick()
h.props.modelValue = true
await tick()
const fresh = h.handlePick({ id: 'two', status: 'executing' })
h.details[0].reject(new Error('old'))
await old
await h.handlePick({ id: 'three', status: 'approved' })
assert.equal(h.details.length, 2)
h.details[1].resolve({ id: 'two', customerId: 'a', status: 'executing' })
await fresh
assert.equal(h.events.filter((e: any[]) => e[0] === 'confirm').length, 1)
h.stop()
})
test('equivalent account and allowed ID objects preserve selection; actual change and back invalidates', async () => {
const h = await harness('Product', { allowedProductIds: ['p'] })
h.details[0].resolve({ id: 'p' })
await tick()
await tick()
h.toggleSelect(h.rows.value[0])
h.account.getUserInfo = { id: 'a' }
h.props.allowedProductIds = ['p']
assert.equal(h.selectedProducts.value.length, 1)
assert.equal(h.details.length, 1)
h.account.accessToken = 'b'
h.account.accessToken = 'a'
assert.equal(h.selectedProducts.value.length, 0)
h.details[1].resolve({ id: 'p', productName: 'old-account' })
await tick()
await tick()
assert.deepEqual(h.rows.value, [])
h.stop()
})