fix(pl): 调整证件建档弹窗布局并按所选类型解析单个附件。
This commit is contained in:
1 parent
c16681a16f
commit
4dbd48d827
4 files changed
+478
-32
No files matched your search
@@ -0,0 +1,266 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import test from 'node:test'
|
||||
import { parse } from '@vue/compiler-sfc'
|
||||
import ts from 'typescript'
|
||||
import * as vue from 'vue'
|
||||
import * as model from '../src/views/pl/quota-cert/model'
|
||||
import { useClientColumnFilters } from '../src/composables/useClientColumnFilters'
|
||||
import { usePersistedListColumns } from '../src/composables/usePersistedListColumns'
|
||||
|
||||
function deferred() {
|
||||
let resolve!: (value: any) => void
|
||||
const promise = new Promise<any>((r) => {
|
||||
resolve = r
|
||||
})
|
||||
return { promise, resolve }
|
||||
}
|
||||
function harness() {
|
||||
const reads: string[] = [],
|
||||
writes: any[] = [],
|
||||
parses: any[] = [],
|
||||
messages: string[] = []
|
||||
let confirmSwitch = true
|
||||
let pendingOptions: ReturnType<typeof deferred> | undefined
|
||||
const env = {
|
||||
...Object.fromEntries(
|
||||
Object.entries(vue).filter(([key]) => /^[a-zA-Z_$][\w$]*$/.test(key) && key !== 'default')
|
||||
),
|
||||
...model,
|
||||
useClientColumnFilters,
|
||||
usePersistedListColumns,
|
||||
useListTableViewportHeight: () => ({ tableHeight: vue.ref(400) }),
|
||||
useRoute: () => ({ query: {} }),
|
||||
useUserStore: () => ({ accessToken: 'synthetic', getUserInfo: {} }),
|
||||
onMounted: () => {},
|
||||
onBeforeUnmount: () => {},
|
||||
fetchGetMenuCodes: async () => ({ all: true }),
|
||||
ElMessage: {
|
||||
warning: (s: string) => messages.push(s),
|
||||
success: (s: string) => messages.push(s)
|
||||
},
|
||||
ElMessageBox: {
|
||||
confirm: async () => {
|
||||
if (!confirmSwitch) throw new Error('cancel')
|
||||
}
|
||||
},
|
||||
quotaCertApi: {
|
||||
list: async (kind: string) => {
|
||||
reads.push(kind)
|
||||
return pendingOptions ? pendingOptions.promise : []
|
||||
},
|
||||
get: async (kind: string, id: string) => ({
|
||||
...model.newCert(kind as any),
|
||||
id,
|
||||
certNo: 'EXISTING',
|
||||
attachmentUrls: 'a.png,b.png'
|
||||
}),
|
||||
save: async (kind: string, body: any, id?: string) => {
|
||||
writes.push({ kind, body, id })
|
||||
},
|
||||
recognize: (kind: string, url: string) => {
|
||||
const pending = deferred()
|
||||
parses.push({ kind, url, ...pending })
|
||||
return pending.promise
|
||||
}
|
||||
}
|
||||
}
|
||||
const script = parse(
|
||||
readFileSync(new URL('../src/views/pl/quota-cert/index.vue', import.meta.url), 'utf8')
|
||||
).descriptor.scriptSetup!.content
|
||||
const source = ts.createSourceFile('page.ts', script, ts.ScriptTarget.Latest, true)
|
||||
const statements = source.statements.filter((s) => !ts.isImportDeclaration(s))
|
||||
const names = statements.flatMap((s) =>
|
||||
ts.isVariableStatement(s)
|
||||
? s.declarationList.declarations
|
||||
.filter((d) => ts.isIdentifier(d.name))
|
||||
.map((d) => d.name.getText(source))
|
||||
: ts.isFunctionDeclaration(s) && s.name
|
||||
? [s.name.text]
|
||||
: []
|
||||
)
|
||||
const code = ts.transpile(statements.map((s) => s.getText(source)).join('\n'), {
|
||||
target: ts.ScriptTarget.ESNext
|
||||
})
|
||||
const scope = vue.effectScope()
|
||||
const state = scope.run(() =>
|
||||
new Function(...Object.keys(env), `${code}; return {${names.join(',')}}`)(...Object.values(env))
|
||||
)
|
||||
state.formRef.value = { validate: async () => true, clearValidate: () => {} }
|
||||
return {
|
||||
state,
|
||||
reads,
|
||||
writes,
|
||||
parses,
|
||||
messages,
|
||||
deferOptions: () => {
|
||||
pendingOptions = deferred()
|
||||
return pendingOptions
|
||||
},
|
||||
cancelSwitch: () => {
|
||||
confirmSwitch = false
|
||||
},
|
||||
stop: () => scope.stop()
|
||||
}
|
||||
}
|
||||
|
||||
test('type changes immediately while options load and stale options cannot enter a later form', async () => {
|
||||
const h = harness(),
|
||||
s = h.state
|
||||
try {
|
||||
await s.edit()
|
||||
const options = h.deferOptions()
|
||||
const switching = s.changeFormKind('transport')
|
||||
assert.equal(s.formKind.value, 'transport')
|
||||
await s.changeFormKind('purchase')
|
||||
options.resolve([{ id: 'old-option' }])
|
||||
await switching
|
||||
assert.equal(s.formKind.value, 'purchase')
|
||||
assert.deepEqual(s.purchases.value, [])
|
||||
} finally {
|
||||
h.stop()
|
||||
}
|
||||
})
|
||||
|
||||
test('new form type changes without closing dialog or changing the list, and drives save', async () => {
|
||||
const h = harness(),
|
||||
s = h.state
|
||||
try {
|
||||
await s.edit()
|
||||
assert.equal(s.formKind?.value, 'purchase')
|
||||
await s.changeFormKind('transport')
|
||||
assert.equal(s.formKind.value, 'transport')
|
||||
assert.equal(s.kind.value, 'purchase')
|
||||
assert.equal(s.formVisible.value, true)
|
||||
assert.ok(s.formFields.value.some(([key]: string[]) => key === 'shipperName'))
|
||||
assert.ok(!s.formFields.value.some(([key]: string[]) => key === 'buyerName'))
|
||||
s.form.value.shipperName = '合成托运单位'
|
||||
await s.save()
|
||||
assert.equal(h.writes[0].kind, 'transport')
|
||||
assert.equal(h.writes[0].body.shipperName, '合成托运单位')
|
||||
assert.equal(h.writes[0].body.buyerName, undefined)
|
||||
assert.equal(s.kind.value, 'transport')
|
||||
} finally {
|
||||
h.stop()
|
||||
}
|
||||
})
|
||||
|
||||
test('switch clears old certificate data after confirmation, preserving attachments and mode', async () => {
|
||||
const h = harness(),
|
||||
s = h.state
|
||||
try {
|
||||
await s.edit()
|
||||
s.form.value.certNo = 'PURCHASE-1'
|
||||
s.form.value.entryMode = 'image_parse'
|
||||
s.form.value.attachmentUrls = 'one.png'
|
||||
s.form.value.remark = '保留备注'
|
||||
await s.changeFormKind('transport')
|
||||
assert.equal(s.form.value.certNo, '')
|
||||
assert.equal(s.form.value.attachmentUrls, 'one.png')
|
||||
assert.equal(s.form.value.entryMode, 'image_parse')
|
||||
assert.equal(s.form.value.remark, '保留备注')
|
||||
assert.equal(s.form.value.buyerName, undefined)
|
||||
} finally {
|
||||
h.stop()
|
||||
}
|
||||
})
|
||||
|
||||
test('cancel type switch and existing certificate edit preserve data and original type', async () => {
|
||||
const h = harness(),
|
||||
s = h.state
|
||||
try {
|
||||
await s.edit()
|
||||
s.form.value.certNo = 'KEEP'
|
||||
h.cancelSwitch()
|
||||
s.selectedFormKind.value = 'transport'
|
||||
await s.changeFormKind('transport')
|
||||
assert.equal(s.formKind.value, 'purchase')
|
||||
assert.equal(s.selectedFormKind.value, 'purchase')
|
||||
assert.equal(s.form.value.certNo, 'KEEP')
|
||||
await s.edit({ id: 'existing' })
|
||||
await s.changeFormKind('transport')
|
||||
assert.equal(s.formKind.value, 'purchase')
|
||||
assert.equal(s.form.value.attachmentUrls, 'a.png,b.png')
|
||||
} finally {
|
||||
h.stop()
|
||||
}
|
||||
})
|
||||
|
||||
test('single attachment is automatically selected, parsing uses form type and preserves manual edits', async () => {
|
||||
const h = harness(),
|
||||
s = h.state
|
||||
try {
|
||||
await s.edit()
|
||||
await s.changeFormKind('transport')
|
||||
s.form.value.attachmentUrls = 'one.png'
|
||||
await vue.nextTick()
|
||||
assert.equal(s.recognitionImage.value, 'one.png')
|
||||
s.form.value.certNo = 'MANUAL'
|
||||
s.edited.add('certNo')
|
||||
const parsing = s.recognize()
|
||||
assert.equal(h.parses.length, 1)
|
||||
assert.equal(h.parses[0].kind, 'transport')
|
||||
assert.equal(h.parses[0].url, 'one.png')
|
||||
h.parses[0].resolve({
|
||||
candidates: { certNo: 'OCR', shipperName: '合成托运单位' },
|
||||
invalidFields: {}
|
||||
})
|
||||
await parsing
|
||||
assert.equal(s.form.value.certNo, 'MANUAL')
|
||||
assert.equal(s.form.value.shipperName, '合成托运单位')
|
||||
} finally {
|
||||
h.stop()
|
||||
}
|
||||
})
|
||||
|
||||
test('missing, removed, multiple new attachments and uploads in progress never start parsing', async () => {
|
||||
const h = harness(),
|
||||
s = h.state
|
||||
try {
|
||||
await s.edit()
|
||||
const empty = s.recognize()
|
||||
assert.equal(h.parses.length, 0)
|
||||
await empty
|
||||
s.form.value.attachmentUrls = 'one.png'
|
||||
await vue.nextTick()
|
||||
s.form.value.attachmentUrls = ''
|
||||
await vue.nextTick()
|
||||
assert.equal(s.recognitionImage.value, '')
|
||||
await s.recognize()
|
||||
s.form.value.attachmentUrls = 'one.png,two.png'
|
||||
s.recognitionImage.value = 'one.png'
|
||||
await s.recognize()
|
||||
s.form.value.attachmentUrls = 'one.png'
|
||||
s.certAttachments.value = { hasUploading: () => true }
|
||||
await s.recognize()
|
||||
assert.equal(h.parses.length, 0)
|
||||
await s.save()
|
||||
assert.equal(h.writes.length, 0)
|
||||
} finally {
|
||||
h.stop()
|
||||
}
|
||||
})
|
||||
|
||||
test('closed or switched forms reject delayed recognition results', async () => {
|
||||
for (const action of ['close', 'switch']) {
|
||||
const h = harness(),
|
||||
s = h.state
|
||||
try {
|
||||
await s.edit()
|
||||
s.form.value.attachmentUrls = 'one.png'
|
||||
await vue.nextTick()
|
||||
const parsing = s.recognize()
|
||||
assert.equal(h.parses.length, 1)
|
||||
if (action === 'close') {
|
||||
s.formVisible.value = false
|
||||
await s.edit()
|
||||
} else await s.changeFormKind('transport')
|
||||
h.parses[0].resolve({ candidates: { certNo: 'OLD' }, invalidFields: {} })
|
||||
await parsing
|
||||
assert.equal(s.form.value.certNo, '')
|
||||
assert.equal(s.recognizing.value, false)
|
||||
} finally {
|
||||
h.stop()
|
||||
}
|
||||
}
|
||||
})
|
||||
Reference in new issue
Block a user