Files
shanxia2.0/web-admin/scripts/mobile-customer-attachments.test.ts
T
2026-10-22 16:24:46 +08:00

148 lines
5.2 KiB
TypeScript

import assert from 'node:assert/strict'
import { readFileSync } from 'node:fs'
import test from 'node:test'
import { computed, effectScope, nextTick, reactive, ref, watch } from 'vue'
import { parse } from '@vue/compiler-sfc'
import ts from 'typescript'
import { buildAttachmentUploadFormData } from '../src/utils/crm-upload'
const file = new URL('../src/views/m/crm/customer/CustomerAttachments.vue', import.meta.url)
const { descriptor } = parse(readFileSync(file, 'utf8'))
const ast = ts.createSourceFile(
'CustomerAttachments.ts',
descriptor.scriptSetup!.content,
ts.ScriptTarget.Latest,
true
)
// Run every setup statement, replacing only external imports and SFC macros.
const source = ast.statements
.filter((node) => !ts.isImportDeclaration(node))
.map((node) => node.getText(ast))
.join('\n')
function harness() {
let resolve!: (value: unknown) => void
let reject!: (error: Error) => void
const response = new Promise((done, fail) => {
resolve = done
reject = fail
})
const requests: FormData[] = []
const events: unknown[][] = []
const notices: string[] = []
const previews: unknown[] = []
let exposed: { hasUploading: () => boolean }
const scope = effectScope()
const bindings = {
computed,
nextTick,
reactive,
ref,
watch,
defineProps: () => ({}),
withDefaults: (props: object, defaults: object) => ({ ...defaults, ...props }),
defineEmits:
() =>
(...args: unknown[]) =>
events.push(args),
defineExpose: (value: typeof exposed) => {
exposed = value
},
showToast: (message: string) => notices.push(message),
showFailToast: (message: string) => notices.push(message),
buildAttachmentUploadFormData,
fetchUploadAttachment: (data: FormData) => {
requests.push(data)
return response
},
// Only external URL resolution and navigation are stubbed; Vue state and FormData stay real.
parseCrmAttachmentField: (value: string) => value.split(',').filter(Boolean),
resolveCrmAttachmentFiles: (urls: string[]) =>
urls.map((url) => ({ url, name: url.split('/').pop() })),
resolveCrmAttachmentUrl: (url: string) => `https://files.example/${url}`,
isCrmImageFile: ({ name }: { name: string }) => name.endsWith('.png'),
openCrmAttachmentPreviewInNewTab: (item: unknown) => previews.push(item),
downloadCrmAttachment: () => undefined
}
const code = ts.transpile(source, { target: ts.ScriptTarget.ESNext })
const instance = scope.run(() =>
new Function(
...Object.keys(bindings),
`${code}\nreturn { onPick, gridItems, fileList, uploading, onPreview }`
)(...Object.values(bindings))
)
return {
...instance,
resolve,
reject,
requests,
events,
notices,
previews,
hasUploading: () => exposed.hasUploading(),
stop: () => scope.stop()
}
}
async function startUpload(h: ReturnType<typeof harness>) {
const raw = new File(['fixture'], '客户附件.png', { type: 'image/png' })
const input = { files: [raw], value: 'selected' }
const pending = h.onPick({ target: input })
await nextTick()
// Prime the computed cache while transport is pending, just as the template does.
assert.equal(h.gridItems.value[0].uploading, true)
assert.equal(h.gridItems.value[0].url, '')
assert.equal(h.uploading.value, 1)
assert.equal(h.hasUploading(), true)
assert.equal(input.value, '')
assert.equal(h.requests.length, 1)
assert.equal(h.requests[0].get('bizType'), 'crm_customer_certificate')
assert.equal(h.requests[0].get('folder'), 'crm/customer')
assert.equal((h.requests[0].get('file') as File).name, raw.name)
return { pending }
}
test('pending customer upload refreshes the cached grid and enables preview on success', async () => {
const h = harness()
try {
const { pending } = await startUpload(h)
h.resolve({ data: { fileUrl: 'customer/saved.png' } })
await pending
await nextTick()
assert.equal(h.gridItems.value[0].uploading, false)
assert.equal(h.gridItems.value[0].url, 'https://files.example/customer/saved.png')
assert.equal(h.fileList.value[0].status, 'done')
assert.equal(h.uploading.value, 0)
assert.equal(h.hasUploading(), false)
assert.equal(h.requests.length, 1)
assert.deepEqual(h.events, [['update:modelValue', 'customer/saved.png']])
h.onPreview(h.gridItems.value[0])
assert.deepEqual(h.previews, [{ ossId: 'customer/saved.png', name: '客户附件.png' }])
assert.deepEqual(h.notices, [])
} finally {
h.stop()
}
})
test('rejected customer upload clears the cached grid uploading state without emitting a URL', async () => {
const h = harness()
try {
const { pending } = await startUpload(h)
h.reject(new Error('fixture upload failure'))
await pending
await nextTick()
assert.equal(h.gridItems.value[0].uploading, false)
assert.equal(h.gridItems.value[0].url, '')
assert.equal(h.fileList.value[0].status, 'failed')
assert.equal(h.uploading.value, 0)
assert.equal(h.hasUploading(), false)
assert.equal(h.requests.length, 1)
assert.deepEqual(h.events, [])
assert.deepEqual(h.notices, ['fixture upload failure'])
h.onPreview(h.gridItems.value[0])
assert.deepEqual(h.previews, [])
} finally {
h.stop()
}
})