Files
shanxia2.0/miniprogram/scripts/test-vehicle-recharge-fuel-card.mjs

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