const test = require('node:test') const assert = require('node:assert/strict') const fs = require('node:fs') const { createRequire } = require('node:module') const { resolve } = require('node:path') const req = createRequire(resolve(__dirname, '../package.json')) const vue = req('vue') const ts = req('typescript') const { parse } = req('@vue/compiler-sfc') const path = resolve( __dirname, '../src/components/core/layouts/art-header-bar/widget/WechatNoticeDialog.vue' ) const flush = async () => { await Promise.resolve() await vue.nextTick() await Promise.resolve() } const deferred = () => { let resolve, reject const promise = new Promise((a, b) => { resolve = a reject = b }) return { promise, resolve, reject } } function mount(overrides = {}) { assert.ok(fs.existsSync(path), '微信通知绑定弹窗尚未实现') const source = parse(fs.readFileSync(path, 'utf8')).descriptor.scriptSetup.content const js = ts .transpileModule(source, { compilerOptions: { target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.ESNext }, transformers: { before: [ (context) => (node) => ts.visitNode(node, function visit(n) { if (ts.isImportDeclaration(n)) return undefined return ts.visitEachChild(n, visit, context) }) ] } }) .outputText.replace(/export \{\};?/g, '') .replace(/Object.defineProperty\(exports,[^;]+;/g, '') .replace(/exports\.__esModule[^;]+;/g, '') const props = vue.reactive({ visible: true }) const timers = new Map() let timerId = 0 let unmount const env = { ...vue, defineProps: () => props, defineEmits: () => (_, v) => { props.visible = v }, onBeforeUnmount: (fn) => { unmount = fn }, setTimeout: (fn, delay) => { timers.set(++timerId, { fn, delay }) return timerId }, clearTimeout: (id) => timers.delete(id), ElMessage: { success() {} }, ElMessageBox: { confirm: async () => {} }, fetchWechatNoticeBinding: async () => ({ enabled: true, bound: false, subscribed: false }), startWechatNoticeBinding: async () => ({ sessionId: 's1', authorizeUrl: 'https://open.weixin.qq.com/connect/oauth2/authorize?state=abc', expiresIn: 60 }), fetchWechatNoticeBindingSession: async () => ({ status: 'waiting', subscribed: false }), confirmWechatNoticeBinding: async () => {}, deleteWechatNoticeBinding: async () => {}, ...overrides } const scope = vue.effectScope() const state = scope.run(() => new Function( ...Object.keys(env), `${js}\nreturn {binding,session,error,loading,starting,submitting,unbinding,canConfirm,loadBinding,startBinding,confirmBinding,unbind,closeDialog}` )(...Object.values(env)) ) return { ...state, props, timers, runTimer: (delay) => { const entry = [...timers].find(([, t]) => t.delay === delay) assert.ok(entry, `missing timer ${delay}`) timers.delete(entry[0]) return entry[1].fn() }, destroy: () => { unmount() scope.stop() } } } test('未启用时不生成二维码', async () => { let starts = 0 const vm = mount({ fetchWechatNoticeBinding: async () => ({ enabled: false, bound: false, subscribed: false }), startWechatNoticeBinding: () => { starts++ } }) await flush() await vm.startBinding() assert.equal(starts, 0) vm.destroy() }) test('轮询串行,未关注的扫码不能确认绑定', async () => { const pending = deferred() const vm = mount({ fetchWechatNoticeBindingSession: () => pending.promise }) await flush() await vm.startBinding() const poll = vm.runTimer(3000) assert.equal( [...vm.timers.values()].some((t) => t.delay === 3000), false ) pending.resolve({ status: 'scanned', subscribed: false, verificationCode: '123456' }) await poll assert.equal(vm.canConfirm.value, false) assert.equal( [...vm.timers.values()].some((t) => t.delay === 3000), false ) vm.destroy() }) test('核对已关注微信的验证码后允许确认,成功后重新读取绑定', async () => { let confirmed = 0 const vm = mount({ fetchWechatNoticeBinding: async () => ({ enabled: true, bound: confirmed > 0, subscribed: confirmed > 0 }), fetchWechatNoticeBindingSession: async () => ({ status: 'scanned', subscribed: true, verificationCode: '123456' }), confirmWechatNoticeBinding: async (id) => { assert.equal(id, 's1') confirmed++ } }) await flush() await vm.startBinding() await vm.runTimer(3000) assert.equal(vm.canConfirm.value, true) await vm.confirmBinding() assert.equal(confirmed, 1) assert.equal(vm.binding.value.bound, true) assert.equal(vm.session.value, null) vm.destroy() }) test('关闭后忽略仍在返回的生成二维码请求', async () => { const pending = deferred() const vm = mount({ startWechatNoticeBinding: () => pending.promise }) await flush() const request = vm.startBinding() vm.closeDialog() await flush() pending.resolve({ sessionId: 'old', authorizeUrl: 'https://open.weixin.qq.com', expiresIn: 60 }) await request assert.equal(vm.session.value, null) assert.equal(vm.timers.size, 0) vm.destroy() }) test('关闭并重开后,旧轮询结果不能污染新弹窗', async () => { const pending = deferred() const vm = mount({ fetchWechatNoticeBindingSession: () => pending.promise }) await flush() await vm.startBinding() const poll = vm.runTimer(3000) vm.closeDialog() await flush() vm.props.visible = true await flush() pending.resolve({ status: 'scanned', subscribed: true, verificationCode: 'old' }) await poll assert.equal(vm.session.value, null) assert.equal(vm.canConfirm.value, false) vm.destroy() }) test('过期后停止轮询,不能提交确认', async () => { let confirms = 0 const vm = mount({ confirmWechatNoticeBinding: () => { confirms++ } }) await flush() await vm.startBinding() vm.runTimer(60000) assert.equal(vm.session.value.status, 'expired') assert.equal(vm.timers.size, 0) await vm.confirmBinding() assert.equal(confirms, 0) vm.destroy() }) test('轮询失败显示可恢复提示并停止轮询', async () => { const vm = mount({ fetchWechatNoticeBindingSession: async () => { throw new Error('network') } }) await flush() await vm.startBinding() await vm.runTimer(3000) assert.ok(vm.error.value) assert.equal( [...vm.timers.values()].some((t) => t.delay === 3000), false ) assert.equal(vm.canConfirm.value, false) vm.destroy() }) test('确认解绑后忽略此前仍在返回的扫码轮询', async () => { const pendingPoll = deferred() const pendingDelete = deferred() const vm = mount({ fetchWechatNoticeBinding: async () => ({ enabled: true, bound: true, subscribed: true }), fetchWechatNoticeBindingSession: () => pendingPoll.promise, deleteWechatNoticeBinding: () => pendingDelete.promise }) await flush() await vm.startBinding() const poll = vm.runTimer(3000) const deletion = vm.unbind() await flush() pendingPoll.resolve({ status: 'scanned', subscribed: true, verificationCode: 'stale' }) await poll assert.equal(vm.session.value, null) pendingDelete.resolve() await deletion vm.destroy() }) test('取消解绑不发送删除请求', async () => { let deletes = 0 const vm = mount({ fetchWechatNoticeBinding: async () => ({ enabled: true, bound: true, subscribed: true }), ElMessageBox: { confirm: async () => { throw 'cancel' } }, deleteWechatNoticeBinding: async () => { deletes++ } }) await flush() await vm.unbind() assert.equal(deletes, 0) assert.equal(vm.unbinding.value, false) vm.destroy() })