94 lines
6.0 KiB
JavaScript
94 lines
6.0 KiB
JavaScript
// Run with node --test. Exercise the real SFC methods without a browser or API.
|
|
const { test } = require('node:test')
|
|
const assert = require('node:assert/strict')
|
|
const fs = require('node:fs')
|
|
const path = require('node:path')
|
|
const { parse } = require('@vue/compiler-sfc')
|
|
const source = fs.readFileSync(path.join(__dirname, '../../src/views/goauto/shopee-products/ShopeeProductDetailDrawer.vue'), 'utf8')
|
|
const script = parse(source).descriptor.script.content.replace(/import[\s\S]*?from ['"][^'"]+['"]/g, '').replace('export default', 'return')
|
|
const product = id => ({ id, specContextVersion: 'v1', specs: [{ name: '颜色', role: 'color', values: [{ name: '黑', source: 'import' }] }] })
|
|
const reply = id => ({ data: { product: product(id), autoMatchTimeoutSeconds: 130 } })
|
|
const deferred = () => { let resolve, reject; const promise = new Promise((yes, no) => { resolve = yes; reject = no }); return { promise, resolve, reject } }
|
|
function instance(get = async id => reply(id), post = async () => ({ data: {} })) {
|
|
const options = new Function('getShopeeProduct', 'autoMatchShopeeSpecMappings', 'createRequestId', 'ElMessage', 'PddProductDetailDrawer', script)(get, post, () => 'id', { success() {}, warning() {} }, {})
|
|
const vm = { modelValue: true, productId: 1, $emit() {}, $confirm: async () => {}, ...options.methods }
|
|
Object.assign(vm, options.data.call(vm))
|
|
Object.assign(vm, { loadedProductId: 1, detailDrawerOpen: true })
|
|
vm.detail.product = product(1); vm.detail.pddLoaded = true
|
|
// Focus method lifecycle, not the already-covered candidate predicates.
|
|
Object.defineProperty(vm, 'canAutoMatch', { get: () => !vm.detail.aiMatching && !vm.hasMappingChanges })
|
|
vm.hasMappingChanges = false
|
|
return vm
|
|
}
|
|
const tick = () => new Promise(resolve => setImmediate(resolve))
|
|
|
|
test('close/reopen same product refreshes, old result/finally cannot clear a new operation', async () => {
|
|
const first = deferred(), second = deferred(); let calls = 0, reads = 0
|
|
const vm = instance(async id => { reads++; return reply(id) }, () => (++calls === 1 ? first.promise : second.promise))
|
|
const old = vm.autoMatchMappings(); await tick()
|
|
vm.modelValue = false; vm.closeInternals()
|
|
assert.equal(vm.detail.aiMatching, false)
|
|
vm.modelValue = true; await vm.requestSwitch(1)
|
|
assert.equal(reads, 2)
|
|
const fresh = vm.autoMatchMappings(); await tick()
|
|
first.resolve({ data: { confirmedCount: 99 } }); await old
|
|
assert.equal(vm.detail.aiMatching, true)
|
|
assert.equal(vm.detail.matchMessage.includes('99'), false)
|
|
second.resolve({ data: { confirmedCount: 1 } }); await fresh
|
|
assert.equal(vm.detail.aiMatching, false)
|
|
})
|
|
test('duplicate clicks do not send a second POST and dynamic budget is forwarded', async () => {
|
|
const result = deferred(); let calls = 0, seconds
|
|
const vm = instance(undefined, (id, data, budget) => { calls++; seconds = budget; return result.promise })
|
|
const running = vm.autoMatchMappings(); await tick(); await vm.autoMatchMappings()
|
|
assert.equal(calls, 1); assert.equal(seconds, 130)
|
|
result.resolve({ data: {} }); await running; assert.equal(vm.detail.aiMatching, false)
|
|
})
|
|
for (const reason of ['timeout', 'Network Error', 'business failure']) {
|
|
test(`${reason}: one readback, no automatic POST retry`, async () => {
|
|
let reads = 0, posts = 0
|
|
const vm = instance(async id => { reads++; return reply(id) }, async () => { posts++; throw new Error(reason) })
|
|
await vm.autoMatchMappings()
|
|
assert.equal(reads, 2); assert.equal(posts, 1); assert.equal(vm.detail.aiMatching, false)
|
|
assert.match(vm.detail.matchMessage, /已刷新当前保存结果/)
|
|
})
|
|
}
|
|
test('readback failure ends busy with explicit unknown result', async () => {
|
|
let reads = 0
|
|
const vm = instance(async id => { if (++reads > 1) throw new Error('offline'); return reply(id) }, async () => { throw new Error('timeout') })
|
|
await vm.autoMatchMappings(); assert.equal(vm.detail.aiMatching, false); assert.match(vm.detail.matchMessage, /结果暂未确认/)
|
|
})
|
|
test('prepare failure or missing budget does not POST', async () => {
|
|
for (const get of [async () => { throw new Error('offline') }, async id => ({ data: { product: product(id) } })]) {
|
|
let posts = 0; const vm = instance(get, async () => { posts++ })
|
|
await vm.autoMatchMappings(); assert.equal(posts, 0); assert.equal(vm.detail.aiMatching, false)
|
|
}
|
|
})
|
|
test('switch while readback is pending never writes old data or messages', async () => {
|
|
const pending = deferred(); let reads = 0
|
|
const vm = instance(async id => { if (++reads === 2) return pending.promise; return reply(id) })
|
|
const old = vm.autoMatchMappings(); await tick()
|
|
vm.productId = 2; await vm.requestSwitch(2)
|
|
pending.resolve(reply(1)); await old
|
|
assert.equal(vm.detail.product.id, 2); assert.equal(vm.detail.matchMessage, ''); assert.equal(vm.detail.aiMatching, false)
|
|
})
|
|
test('manual drafts prevent matching and refused discard prevents refresh', async () => {
|
|
let calls = 0; const vm = instance(async id => { calls++; return reply(id) })
|
|
vm.hasMappingChanges = true; vm.$confirm = async () => { throw new Error('cancel') }
|
|
await vm.autoMatchMappings(); await vm.reload(); assert.equal(calls, 0)
|
|
})
|
|
|
|
test('API uses derived time plus margin only for auto-match; suggestions stay unchanged', async () => {
|
|
const apiSource = fs.readFileSync(path.join(__dirname, '../../src/api/goauto/shopee-products.js'), 'utf8').replace(/import[^\n]+\n/g, '').replace(/export function/g, 'function')
|
|
const calls = []
|
|
const api = new Function('request', apiSource + '\nreturn { autoMatchShopeeSpecMappings, suggestShopeeColorMappings, suggestShopeeSizeMappings };')(config => { calls.push(config); return Promise.resolve({}) })
|
|
for (const seconds of [16, 130, 370, 1210]) {
|
|
await api.autoMatchShopeeSpecMappings(1, {}, seconds)
|
|
assert.equal(calls.at(-1).timeout, (seconds + 10) * 1000)
|
|
}
|
|
await assert.rejects(api.autoMatchShopeeSpecMappings(1, {}, undefined))
|
|
assert.equal(calls.length, 4)
|
|
await api.suggestShopeeColorMappings(1, {}); assert.equal(calls.at(-1).timeout, 610000)
|
|
await api.suggestShopeeSizeMappings(1, {}); assert.equal(calls.at(-1).timeout, 610000)
|
|
})
|