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