82 lines
4.3 KiB
JavaScript
82 lines
4.3 KiB
JavaScript
import test from 'node:test'
|
|||
|
|
import assert from 'node:assert/strict'
|
||
|
|
import { readFileSync } from 'node:fs'
|
||
|
|
import ts from 'typescript'
|
||
|
|
|
||
|
|
const source = readFileSync(new URL('./CompletionConfirmationDialog.vue', import.meta.url), 'utf8')
|
||
|
|
const script = source.match(/<script setup lang="ts">([\s\S]*?)<\/script>/)[1]
|
||
|
|
const js = ts.transpileModule(script, { compilerOptions: { target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.ESNext } }).outputText
|
||
|
|
.replace(/^import .*$/gm, '').replace(/^export \{\};?$/gm, '')
|
||
|
|
|
||
|
|
function harness(kind = 'disposal', overrides = {}) {
|
||
|
|
const props = { modelValue: true, kind, documentNo: 'CD/1' }
|
||
|
|
const calls = []; const events = []; const revoked = []
|
||
|
|
const api = {
|
||
|
|
getDiscard: async () => ({ summary: { status: 'PDA_COMPLETED' }, photos: [{ fileId: '101' }] }),
|
||
|
|
getRelabel: async () => ({ application: { status: 'CUSTOMER_CONFIRM_PENDING' }, completionEvidence: { photoFileIds: ['101'] } }),
|
||
|
|
downloadDiscardPhoto: async () => ({}), downloadRelabelCompletionPhoto: async () => ({}),
|
||
|
|
confirmDiscardCompletion: async no => calls.push(['discard', no]),
|
||
|
|
confirmRelabelCompletion: async no => calls.push(['relabel', no]), ...overrides,
|
||
|
|
}
|
||
|
|
const env = {
|
||
|
|
computed: fn => ({ get value() { return fn() } }), ref: value => ({ value }), watch: () => {}, onBeforeUnmount: () => {},
|
||
|
|
defineProps: () => props, defineEmits: () => (...args) => events.push(args),
|
||
|
|
useAuthStore: () => ({ hasPermission: () => true }), ElMessage: { success() {}, error() {} },
|
||
|
|
URL: { createObjectURL: () => 'blob:photo', revokeObjectURL: url => revoked.push(url) }, ...api,
|
||
|
|
}
|
||
|
|
const state = new Function(...Object.keys(env), js + '\nreturn { loadEvidence, loadPhoto, submit, clear, checked, canConfirm, urls, failed, error, busy };')(...Object.values(env))
|
||
|
|
return { ...state, props, calls, events, revoked }
|
||
|
|
}
|
||
|
|
|
||
|
|
for (const kind of ['disposal', 'labeling']) {
|
||
|
|
test(`${kind}: opening loads photos without confirming; only reviewed evidence can be submitted`, async () => {
|
||
|
|
const state = harness(kind)
|
||
|
|
await state.loadEvidence()
|
||
|
|
assert.deepEqual(state.calls, [])
|
||
|
|
await state.submit(); assert.deepEqual(state.calls, [])
|
||
|
|
state.checked.value = true
|
||
|
|
assert.equal(state.canConfirm.value, true)
|
||
|
|
await state.submit()
|
||
|
|
assert.deepEqual(state.calls, [[kind === 'disposal' ? 'discard' : 'relabel', 'CD/1']])
|
||
|
|
assert.deepEqual(state.events, [['confirmed', 'CD/1'], ['update:modelValue', false]])
|
||
|
|
state.clear(); assert.deepEqual(state.revoked, ['blob:photo'])
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
test('photo failure blocks confirmation and supports retry', async () => {
|
||
|
|
let reject = true
|
||
|
|
const state = harness('disposal', { downloadDiscardPhoto: async () => { if (reject) throw Error('offline'); return {} } })
|
||
|
|
await state.loadEvidence(); state.checked.value = true
|
||
|
|
assert.equal(state.failed.value['101'], true); assert.equal(state.canConfirm.value, false)
|
||
|
|
reject = false; await state.loadPhoto('101')
|
||
|
|
assert.equal(state.canConfirm.value, true)
|
||
|
|
})
|
||
|
|
|
||
|
|
test('closing while photos load prevents stale preview URLs', async () => {
|
||
|
|
let resolve
|
||
|
|
const state = harness('disposal', { downloadDiscardPhoto: () => new Promise(done => { resolve = done }) })
|
||
|
|
const pending = state.loadEvidence()
|
||
|
|
await Promise.resolve()
|
||
|
|
state.props.modelValue = false; state.clear(); resolve({}); await pending
|
||
|
|
assert.deepEqual(state.urls.value, {})
|
||
|
|
})
|
||
|
|
|
||
|
|
test('completed orders and missing evidence cannot be confirmed', async () => {
|
||
|
|
for (const detail of [{ summary: { status: 'COMPLETED' }, photos: [{ fileId: '101' }] }, { summary: { status: 'PDA_COMPLETED' }, photos: [] }]) {
|
||
|
|
const state = harness('disposal', { getDiscard: async () => detail })
|
||
|
|
await state.loadEvidence(); state.checked.value = true; await state.submit()
|
||
|
|
assert.equal(state.canConfirm.value, false); assert.deepEqual(state.calls, [])
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
test('failed submission preserves dialog and a pending submission is not sent twice', async () => {
|
||
|
|
let reject
|
||
|
|
const state = harness('disposal', { confirmDiscardCompletion: () => new Promise((_, fail) => { reject = fail }) })
|
||
|
|
await state.loadEvidence(); state.checked.value = true
|
||
|
|
const pending = state.submit()
|
||
|
|
assert.equal(state.busy.value, true); await state.submit()
|
||
|
|
reject(Error('offline')); await pending
|
||
|
|
assert.deepEqual(state.events, []); assert.equal(state.busy.value, false)
|
||
|
|
assert.equal(state.canConfirm.value, true)
|
||
|
|
})
|