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