test-wechat-binding-return.mjs 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657
  1. import assert from 'node:assert/strict'
  2. import { readFileSync } from 'node:fs'
  3. import { runInNewContext } from 'node:vm'
  4. import test from 'node:test'
  5. function page(wx, bridgeReady = true) {
  6. const html = readFileSync(new URL('../../backend/app/templates/wechat-binding-mobile.html', import.meta.url), 'utf8')
  7. const script = html.match(/<script nonce="__NONCE__">([\s\S]*?)<\/script>/)[1]
  8. const nodes = Object.fromEntries(['return-button', 'return-hint', 'wechat-sdk'].map(id => [id, { textContent: '', handlers: {}, addEventListener(name, fn) { this.handlers[name] = fn } }]))
  9. const timers = [], events = {}, calls = []
  10. const window = { WeixinJSBridge: bridgeReady ? {} : undefined, wx: wx === false ? undefined : { miniProgram: { navigateBack: args => calls.push(args), ...wx } } }
  11. runInNewContext(script, { window, document: { getElementById: id => nodes[id], addEventListener: (name, fn) => { events[name] = fn } },
  12. setTimeout: fn => { timers.push(fn); return timers.length }, clearTimeout() {} })
  13. return { nodes, timers, events, calls, window }
  14. }
  15. test('mobile callback bridge returns to previous page without a login token or home redirect', () => {
  16. const p = page({})
  17. assert.equal(p.calls.length, 1)
  18. assert.equal(p.calls[0].delta, 1)
  19. p.nodes['return-button'].handlers.click()
  20. assert.equal(p.calls.length, 1)
  21. })
  22. test('missing SDK keeps manual return hint and loaded SDK can recover', () => {
  23. const p = page(false)
  24. p.timers.forEach(fn => fn())
  25. assert.match(p.nodes['return-hint'].textContent, /左上角/)
  26. p.window.wx = { miniProgram: { navigateBack: args => p.calls.push(args) } }
  27. p.nodes['wechat-sdk'].handlers.load()
  28. assert.equal(p.calls.length, 1)
  29. })
  30. test('bridge failure or silent timeout permits retry with return button', () => {
  31. for (const failure of ['fail', 'timeout']) {
  32. const p = page({})
  33. if (failure === 'fail') p.calls[0].fail(); else p.timers.forEach(fn => fn())
  34. assert.match(p.nodes['return-hint'].textContent, /左上角/)
  35. p.nodes['return-button'].handlers.click()
  36. assert.equal(p.calls.length, 2)
  37. }
  38. })
  39. test('late WeixinJSBridge readiness never queues duplicate navigation', () => {
  40. const p = page({}, false)
  41. p.nodes['wechat-sdk'].handlers.load()
  42. p.nodes['return-button'].handlers.click()
  43. p.timers.forEach(fn => fn())
  44. assert.equal(p.calls.length, 0)
  45. p.window.WeixinJSBridge = {}
  46. p.events.WeixinJSBridgeReady()
  47. assert.equal(p.calls.length, 1)
  48. p.timers.forEach(fn => fn())
  49. p.events.WeixinJSBridgeReady()
  50. assert.equal(p.calls.length, 1)
  51. })