152 lines
5.3 KiB
TypeScript
152 lines
5.3 KiB
TypeScript
import test from 'node:test'
|
|
import assert from 'node:assert/strict'
|
|
import { readFileSync } from 'node:fs'
|
|
import vm from 'node:vm'
|
|
import ts from 'typescript'
|
|
|
|
// 执行真实交付函数,仅替换浏览器宿主与 HTTP 边界。
|
|
const source = readFileSync(
|
|
new URL('../src/views/m/crm/contract/print/deliverContractFile.ts', import.meta.url),
|
|
'utf8'
|
|
)
|
|
const code = ts.transpileModule(
|
|
source.replace(/^import .*$/gm, '').replace('export async function', 'async function'),
|
|
{ compilerOptions: { target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.None } }
|
|
).outputText
|
|
function harness({
|
|
wechat = true,
|
|
marked = false,
|
|
env = true,
|
|
navigation = 'success',
|
|
delayedSdk = false,
|
|
pendingBridge = false
|
|
} = {}) {
|
|
const calls = { saved: 0, uploaded: 0, navigated: [] as string[] }
|
|
const events = new EventTarget()
|
|
const script = new EventTarget()
|
|
const mini = {
|
|
getEnv(cb: (value: { miniprogram: boolean }) => void) {
|
|
if (pendingBridge) {
|
|
// JSSDK 1.6.0:桥接就绪后才读取宿主注入的环境标记。
|
|
events.addEventListener('WeixinJSBridgeReady', () => cb({ miniprogram: marked }), {
|
|
once: true
|
|
})
|
|
return
|
|
}
|
|
setTimeout(() => cb({ miniprogram: env }), 1)
|
|
},
|
|
navigateTo(options: any) {
|
|
calls.navigated.push(options.url)
|
|
if (navigation === 'success') options.success()
|
|
if (navigation === 'fail') options.fail({ errMsg: 'page not found' })
|
|
}
|
|
}
|
|
const window: any = { wx: delayedSdk ? undefined : { miniProgram: mini } }
|
|
const context = vm.createContext({
|
|
window,
|
|
navigator: { userAgent: wechat ? 'iPhone MicroMessenger/8.0' : 'Safari' },
|
|
document: {
|
|
querySelector: () => (delayedSdk ? script : null),
|
|
addEventListener: events.addEventListener.bind(events),
|
|
removeEventListener: events.removeEventListener.bind(events)
|
|
},
|
|
setTimeout: (fn: () => void, ms: number) => setTimeout(fn, Math.min(ms, 30)),
|
|
clearTimeout,
|
|
File,
|
|
encodeURIComponent,
|
|
FileSaver: {
|
|
saveAs() {
|
|
calls.saved++
|
|
}
|
|
},
|
|
isInMiniProgramWebView: () => marked,
|
|
CRM_UPLOAD_BIZ_TYPE: { CONTRACT_ATTACHMENT: 'contract' },
|
|
buildAttachmentUploadFormData: (file: File) => file,
|
|
fetchUploadAttachment: async () => {
|
|
calls.uploaded++
|
|
return { data: { fileUrl: 'contracts/a.pdf' } }
|
|
},
|
|
fetchAttachmentDownloadUrl: async () => ({
|
|
data: { downloadUrl: 'https://res.example.com/a.pdf?signature=x' }
|
|
})
|
|
})
|
|
vm.runInContext(code + '\nglobalThis.deliver = deliverContractFile', context)
|
|
return {
|
|
calls,
|
|
deliver: (isActive?: () => boolean) =>
|
|
context.deliver(new Blob(['%PDF-1.3']), '测试合同.pdf', isActive),
|
|
ready() {
|
|
window.wx = { miniProgram: mini }
|
|
script.dispatchEvent(new Event('load'))
|
|
},
|
|
bridgeReady() {
|
|
marked = true
|
|
events.dispatchEvent(new Event('WeixinJSBridgeReady'))
|
|
}
|
|
}
|
|
}
|
|
test('iPhone WeChat without synchronous markers waits for getEnv and opens native document page', async () => {
|
|
const h = harness()
|
|
assert.equal(await h.deliver(), 'mini-program-requested')
|
|
assert.equal(h.calls.saved, 0)
|
|
assert.equal(h.calls.uploaded, 1)
|
|
assert.match(h.calls.navigated[0], /^\/pages\/common\/contract-document\?url=/)
|
|
})
|
|
test('ordinary browser still requests a browser download despite a JSSDK shell', async () => {
|
|
const h = harness({ wechat: false })
|
|
assert.equal(await h.deliver(), 'browser-download')
|
|
assert.equal(h.calls.saved, 1)
|
|
assert.equal(h.calls.uploaded, 0)
|
|
})
|
|
test('waits for the real JSSDK ordering: bridge ready then injected environment marker', async () => {
|
|
const h = harness({ pendingBridge: true })
|
|
const result = h.deliver()
|
|
assert.equal(h.calls.saved, 0)
|
|
assert.equal(h.calls.uploaded, 0)
|
|
h.bridgeReady()
|
|
assert.equal(await result, 'mini-program-requested')
|
|
assert.equal(h.calls.uploaded, 1)
|
|
assert.equal(h.calls.navigated.length, 1)
|
|
})
|
|
test('waits for an already loading JSSDK before delivering', async () => {
|
|
const h = harness({ delayedSdk: true })
|
|
const result = h.deliver()
|
|
h.ready()
|
|
assert.equal(await result, 'mini-program-requested')
|
|
assert.equal(h.calls.saved, 0)
|
|
})
|
|
test('unconfirmed WeChat environment does not silently fall back to browser downloads', async () => {
|
|
const h = harness({ env: false })
|
|
await assert.rejects(h.deliver(), /小程序/)
|
|
assert.equal(h.calls.saved, 0)
|
|
assert.equal(h.calls.uploaded, 0)
|
|
})
|
|
test('missing JSSDK times out without downloading or uploading', async () => {
|
|
const h = harness({ delayedSdk: true })
|
|
await assert.rejects(h.deliver(), /微信文档接口未就绪/)
|
|
h.ready()
|
|
assert.equal(h.calls.saved, 0)
|
|
assert.equal(h.calls.uploaded, 0)
|
|
})
|
|
test('closing while environment is pending prevents file upload and navigation', async () => {
|
|
const h = harness()
|
|
let active = true
|
|
const result = h.deliver(() => active)
|
|
active = false
|
|
assert.equal(await result, undefined)
|
|
assert.equal(h.calls.saved, 0)
|
|
assert.equal(h.calls.uploaded, 0)
|
|
assert.equal(h.calls.navigated.length, 0)
|
|
})
|
|
for (const navigation of ['fail', 'silent']) {
|
|
test(
|
|
`native navigation ${navigation} rejects instead of reporting success`,
|
|
{ timeout: 200 },
|
|
async () => {
|
|
const h = harness({ marked: true, navigation })
|
|
await assert.rejects(h.deliver(), /文档/)
|
|
assert.equal(h.calls.saved, 0)
|
|
}
|
|
)
|
|
}
|