199 lines
8.4 KiB
JavaScript
199 lines
8.4 KiB
JavaScript
import assert from 'node:assert/strict'
|
|
import { readFileSync } from 'node:fs'
|
|
import { createRequire } from 'node:module'
|
|
import test from 'node:test'
|
|
|
|
// Reuse the monorepo's Vue runtime to exercise the page's actual watchers.
|
|
const require = createRequire(new URL('../../web-admin/package.json', import.meta.url))
|
|
const { ref, reactive, computed, watch, nextTick, effectScope } = require('vue')
|
|
const { parse, compileTemplate } = require('@vue/compiler-sfc')
|
|
const source = readFileSync(new URL('../pages/pl/vehicle-recharge-apply/form.vue', import.meta.url), 'utf8')
|
|
const { descriptor } = parse(source)
|
|
const script = descriptor.scriptSetup.content.replace(/^import .+ from .+$/gm, '')
|
|
const cardNo = '0012345678901234567'
|
|
const flush = async () => { await nextTick(); await Promise.resolve(); await nextTick() }
|
|
|
|
function createPage(t, { lookup = async () => ({ cardNo }), detail } = {}) {
|
|
const hooks = {}
|
|
const calls = []
|
|
const scope = effectScope()
|
|
const context = {
|
|
ref, reactive, computed, watch,
|
|
onLoad: (fn) => { hooks.load = fn },
|
|
onUnload: (fn) => { hooks.unload = fn },
|
|
PAGE_PATHS: {}, resolveMinioUrl: (url) => url,
|
|
vehicleRechargeApplyApi: {
|
|
lookupFuelCard: (plate) => { calls.push(plate); return lookup(plate) },
|
|
get: async () => detail
|
|
},
|
|
fetchVehicleOptions: async () => [],
|
|
chooseAndUploadImages: async () => [], previewImages: () => {},
|
|
useUserStore: () => ({ userInfo: { id: 'driver-1' } }),
|
|
getSafeAreaStyle: () => ({}), uni: { showToast: () => {} },
|
|
setTimeout, clearTimeout
|
|
}
|
|
const page = scope.run(() => new Function(...Object.keys(context), `${script}\nreturn {
|
|
form, readonly, imageUrls, buildPayload, onTypeChange, confirmVehiclePick,
|
|
display: typeof fuelCardDisplay === 'undefined' ? undefined : fuelCardDisplay
|
|
}`)(...Object.values(context)))
|
|
t.after(() => { hooks.unload?.(); scope.stop() })
|
|
return { ...page, hooks, calls }
|
|
}
|
|
|
|
test('fuel selection distinguishes empty plate, loading and exact string card number', async (t) => {
|
|
let resolve
|
|
const page = createPage(t, { lookup: () => new Promise((done) => { resolve = done }) })
|
|
page.onTypeChange({ detail: { value: 1 } })
|
|
await flush()
|
|
assert.equal(page.display?.value, '请先选择车牌')
|
|
assert.equal(page.calls.length, 0)
|
|
page.confirmVehiclePick({ licensePlate: '鄂A12345' })
|
|
await flush()
|
|
assert.equal(page.display.value, '查询中…')
|
|
resolve({ cardNo })
|
|
await flush()
|
|
assert.equal(page.display.value, cardNo)
|
|
assert.deepEqual(page.calls, ['鄂A12345'])
|
|
})
|
|
|
|
test('unbound and failed queries have different visible results', async (t) => {
|
|
const page = createPage(t, { lookup: async (plate) => {
|
|
if (plate === '鄂A失败') throw new Error('Network unavailable')
|
|
return { cardNo: null }
|
|
} })
|
|
page.form.rechargeType = 'fuel'
|
|
page.form.licensePlate = '鄂A未绑'
|
|
await flush()
|
|
assert.equal(page.display?.value, '未绑定油卡')
|
|
page.form.licensePlate = '鄂A失败'
|
|
await flush()
|
|
assert.equal(page.display.value, '油卡查询失败')
|
|
})
|
|
|
|
test('later plate wins over stale successful and failed responses', async (t) => {
|
|
const pending = []
|
|
const page = createPage(t, { lookup: () => new Promise((resolve, reject) => pending.push({ resolve, reject })) })
|
|
page.form.rechargeType = 'fuel'
|
|
for (const plate of ['鄂A旧卡', '鄂A失败', '鄂A新卡']) {
|
|
page.form.licensePlate = plate
|
|
await flush()
|
|
}
|
|
assert.equal(pending.length, 3)
|
|
pending[2].resolve({ cardNo })
|
|
await flush()
|
|
pending[0].resolve({ cardNo: '9999999999999999999' })
|
|
pending[1].reject(new Error('stale failure'))
|
|
await flush()
|
|
assert.equal(page.display?.value, cardNo)
|
|
})
|
|
|
|
test('malformed responses cannot be mistaken for an unbound card or a rounded number', async (t) => {
|
|
const page = createPage(t, { lookup: async (plate) =>
|
|
plate === '鄂A缺字段' ? {} : { cardNo: 1234567890123456789 }
|
|
})
|
|
page.form.rechargeType = 'fuel'
|
|
for (const plate of ['鄂A缺字段', '鄂A数字']) {
|
|
page.form.licensePlate = plate
|
|
await flush()
|
|
assert.equal(page.display.value, '油卡查询失败')
|
|
}
|
|
})
|
|
|
|
test('clearing the plate resets the card and invalidates its pending response', async (t) => {
|
|
let resolve
|
|
const page = createPage(t, { lookup: () => new Promise((done) => { resolve = done }) })
|
|
page.form.rechargeType = 'fuel'
|
|
page.form.licensePlate = '鄂A12345'
|
|
await flush()
|
|
page.form.licensePlate = ''
|
|
await flush()
|
|
resolve({ cardNo })
|
|
await flush()
|
|
assert.equal(page.display.value, '请先选择车牌')
|
|
})
|
|
|
|
test('ETC and shanxia do not query; switching away invalidates an in-flight query', async (t) => {
|
|
let resolve
|
|
const page = createPage(t, { lookup: () => new Promise((done) => { resolve = done }) })
|
|
page.form.licensePlate = '鄂A12345'
|
|
await flush()
|
|
page.onTypeChange({ detail: { value: 2 } })
|
|
await flush()
|
|
assert.equal(page.form.rechargeType, 'shanxia')
|
|
assert.equal(page.calls.length, 0)
|
|
page.onTypeChange({ detail: { value: 1 } })
|
|
await flush()
|
|
assert.equal(page.calls.length, 1)
|
|
page.onTypeChange({ detail: { value: 0 } })
|
|
await flush()
|
|
resolve({ cardNo })
|
|
await flush()
|
|
assert.equal(page.display?.value, '')
|
|
})
|
|
|
|
for (const status of ['draft', 'approved']) {
|
|
test(`${status} detail load queries the current bound card without changing submission fields`, async (t) => {
|
|
const page = createPage(t, { detail: {
|
|
id: 'apply-1', applicantId: 'driver-1', licensePlate: '鄂A12345', rechargeType: 'fuel',
|
|
status, applyDate: '2026-10-25', odometerKm: 100, remark: '保留备注', attachmentUrls: 'proof.jpg'
|
|
} })
|
|
await page.hooks.load({ id: 'apply-1' })
|
|
await flush()
|
|
assert.equal(page.display?.value, cardNo)
|
|
assert.equal(page.readonly.value, status !== 'draft')
|
|
assert.deepEqual(page.buildPayload(), {
|
|
licensePlate: '鄂A12345', rechargeType: 'fuel', applyDate: '2026-10-25', odometerKm: 100,
|
|
attachmentUrls: 'proof.jpg', remark: '保留备注', applicantId: 'driver-1'
|
|
})
|
|
})
|
|
}
|
|
|
|
test('page unload stops lookup updates and new requests', async (t) => {
|
|
let resolve
|
|
const page = createPage(t, { lookup: () => new Promise((done) => { resolve = done }) })
|
|
page.form.rechargeType = 'fuel'
|
|
page.form.licensePlate = '鄂A12345'
|
|
await flush()
|
|
assert.equal(typeof page.hooks.unload, 'function')
|
|
page.hooks.unload()
|
|
const before = page.display.value
|
|
resolve({ cardNo })
|
|
await flush()
|
|
assert.equal(page.display.value, before)
|
|
page.form.licensePlate = '鄂A54321'
|
|
await flush()
|
|
assert.equal(page.calls.length, 1)
|
|
})
|
|
|
|
test('template provides a readonly fuel-only field and compiles', () => {
|
|
assert.match(descriptor.template.content, /v-if="form.rechargeType === 'fuel'"[\s\S]*?绑定油卡卡号[\s\S]*?:value="fuelCardDisplay"[\s\S]*?disabled/)
|
|
const compiled = compileTemplate({ source: descriptor.template.content, filename: 'form.vue', id: 'fuel-card-test' })
|
|
assert.deepEqual(compiled.errors, [])
|
|
})
|
|
|
|
test('lookup uses the authenticated db client and limits the request to own vehicles', async () => {
|
|
let request
|
|
const dbSource = readFileSync(new URL('../utils/dbRequest.js', import.meta.url), 'utf8')
|
|
.replace(/^import .+ from .+$/gm, '').replace(/^export default db$/m, '')
|
|
.replace(/^export /gm, '')
|
|
const db = new Function('uni', 'TOKEN_KEY', 'REFRESH_TOKEN_KEY', 'USER_INFO_KEY', 'resolveApiBase', `${dbSource}\nreturn db`)(
|
|
{ getStorageSync: () => 'driver-token', request: (options) => {
|
|
request = options
|
|
options.success({ statusCode: 200, data: { code: 200, data: { licensePlate: '鄂A12345', cardNo } } })
|
|
} }, 'token', 'refresh', 'user', () => 'https://example.test/api'
|
|
)
|
|
const apiSource = readFileSync(new URL('../api/pl.js', import.meta.url), 'utf8')
|
|
.replace(/^import .+ from .+$/gm, '').replace(/^export (?:default .*|\{.*\})$/gm, '')
|
|
.replace(/^export /gm, '')
|
|
const api = new Function('db', 'fetchVehicleOptions', `${apiSource}\nreturn vehicleRechargeApplyApi`)(db, () => {})
|
|
assert.equal(typeof api.lookupFuelCard, 'function')
|
|
const data = await api.lookupFuelCard('鄂A12345')
|
|
const url = new URL(request.url)
|
|
assert.equal(url.pathname, '/api/transport/vehicle-recharge-apply/fuel-card')
|
|
assert.equal(url.searchParams.get('licensePlate'), '鄂A12345')
|
|
assert.equal(url.searchParams.get('mineVehicles'), 'true')
|
|
assert.equal(request.method, 'GET')
|
|
assert.equal(request.header.Authorization, 'Bearer driver-token')
|
|
assert.deepEqual(data, { licensePlate: '鄂A12345', cardNo })
|
|
})
|