Files
shanxia2.0/web-admin/scripts/attachment-upload-reactivity.test.ts
T

119 lines
4.6 KiB
TypeScript

import test from 'node:test'
import assert from 'node:assert/strict'
import { readFileSync } from 'node:fs'
import { parse, compileScript } from '@vue/compiler-sfc'
import ts from 'typescript'
import * as vue from 'vue'
function compiledSfc(path: string, dependencies: Record<string, any>, inlineTemplate = true) {
const descriptor = parse(readFileSync(new URL(path, import.meta.url), 'utf8')).descriptor
const compiled = compileScript(descriptor, { id: path, inlineTemplate })
const source = ts.createSourceFile('compiled.ts', compiled.content, ts.ScriptTarget.Latest, true)
const env: Record<string, any> = Object.fromEntries(
Object.entries(vue).filter(([key]) => /^[a-zA-Z_$][\w$]*$/.test(key) && key !== 'default')
)
Object.assign(env, dependencies)
for (const statement of source.statements) {
if (!ts.isImportDeclaration(statement) || statement.importClause?.isTypeOnly) continue
const clause = statement.importClause
if (clause?.name) env[clause.name.text] = dependencies[clause.name.text] || {}
if (clause?.namedBindings && ts.isNamedImports(clause.namedBindings)) {
for (const item of clause.namedBindings.elements) {
if (item.isTypeOnly) continue
const name = (item.propertyName || item.name).text
env[item.name.text] =
statement.moduleSpecifier.getText(source) === '"vue"' ||
statement.moduleSpecifier.getText(source) === "'vue'"
? (vue as any)[name]
: dependencies[name]
}
}
}
const code = ts.transpile(
source.statements
.filter((s) => !ts.isImportDeclaration(s))
.map((s) => s.getText(source))
.join('\n')
.replace('export default', 'const component ='),
{ target: ts.ScriptTarget.ESNext }
)
return new Function(...Object.keys(env), `${code}; return component`)(...Object.values(env))
}
for (const outcome of ['success', 'failure'] as const)
test(`real attachment ${outcome} releases reactive external save guard`, async () => {
let resolve!: (value: any) => void, reject!: (reason: any) => void
const pending = new Promise((a, b) => {
resolve = a
reject = b
})
const events: string[] = []
const browser = { addEventListener() {}, removeEventListener() {} }
const Attachment = compiledSfc(
'../src/components/business/attachment-upload-field/index.vue',
{
useRoute: () => ({ path: '/pl/legal-use-document' }),
isMpWebviewPath: () => false,
fetchUploadAttachment: () => pending,
ElMessage: { warning: (v: string) => events.push(v), error: (v: string) => events.push(v) },
document: browser,
window: browser
},
false
)
Attachment.render = () => null
const upload = vue.ref<any>(),
model = vue.ref('')
// An external footer/computed consumes the exposed guard independently of v-model.
const savingDisabled = vue.computed(() => !!upload.value?.hasUploading())
const buttons: any[] = []
const renderer = vue.createRenderer<any, any>({
createElement: (tag: string) => {
const el: any = { tag }
if (tag === 'button') buttons.push(el)
return el
},
createText: () => ({}),
createComment: () => ({}),
setText() {},
setElementText() {},
parentNode: () => null,
nextSibling: () => null,
insert() {},
remove() {},
patchProp: (el: any, key: string, _prev: any, value: any) => {
el[key] = value
}
})
const app = renderer.createApp({
render: () =>
vue.h('section', [
vue.h(Attachment, {
ref: upload,
modelValue: model.value,
'onUpdate:modelValue': (value: string) => {
model.value = value
}
}),
vue.h('button', { disabled: savingDisabled.value }, '保存本段资料')
])
})
app.mount({})
await vue.nextTick()
assert.equal(buttons[0].disabled, false)
const operation = upload.value.$.setupState.handleAttachmentChange({
raw: new File(['synthetic'], 'proof.pdf', { type: 'application/pdf' })
})
await vue.nextTick()
assert.equal(buttons[0].disabled, true)
assert.equal(upload.value.hasUploading(), true)
if (outcome === 'success')
resolve({ fileUrl: 'legal/proof.pdf', fileName: 'proof.pdf', fileType: 'application/pdf' })
else reject(Error('上传失败'))
await operation
await vue.nextTick()
assert.equal(upload.value.hasUploading(), false)
assert.equal(buttons[0].disabled, false, '上传结束必须通知外部保存按钮更新')
assert.equal(model.value, outcome === 'success' ? 'legal/proof.pdf' : '')
app.unmount()
})