Przeglądaj źródła

test(cfc-frontend): family.signature.landscape 契约级单测 S-cfc-51

Sisyphus Agent 2 dni temu
rodzic
commit
c781787a53

+ 177 - 0
cfc-frontend/__tests__/pages/signature/signature.landscape.contract.test.js

@@ -0,0 +1,177 @@
+/**
+ * 契约级单测 —— 签名页横屏适配(能力键 family.signature.landscape)
+ *
+ * 溯源:
+ *   SRS:  docs/superpowers/specs/2026-11-07-signature-page-landscape.md
+ *         §3 AC-R2(签名页为横屏全屏;加载时切横屏、离开时恢复竖屏,H5 不支持时优雅降级)
+ *         §5 WI-2(进入/退出横屏适配:微信 wx.setPageOrientation 切横屏;H5 降级不切横屏)
+ *   设计锚点: pages/signature/signature.vue 的 enterLandscape / restorePortrait 接线
+ *            (onLoad / onUnload / doBack)+ wx.setPageOrientation('landscape' / 'portrait')
+ *   台账: tests/test-cases/signature.md(TC-LANDSCAPE-01~08)
+ *
+ * 覆盖(独立契约,与 signature.canvas2d.test.js / signature.landscape.superseded.js 不重名):
+ *   TC-LANDSCAPE-01  enterLandscape / restorePortrait 方法存在
+ *   TC-LANDSCAPE-02  onLoad 进入即切横屏(调 enterLandscape)
+ *   TC-LANDSCAPE-03  onUnload 退出恢复竖屏(调 restorePortrait)
+ *   TC-LANDSCAPE-04  doBack 在 navigateBack 前恢复竖屏
+ *   TC-LANDSCAPE-05  enterLandscape 调 wx.setPageOrientation({ orientation: 'landscape' })
+ *   TC-LANDSCAPE-06  restorePortrait 调 wx.setPageOrientation({ orientation: 'portrait' })
+ *   TC-LANDSCAPE-07  H5(无 wx)降级不切横屏且不抛错
+ *   TC-LANDSCAPE-08  doBack 防重复返回(_backed 标志),onUnload 恢复竖屏
+ *
+ * 本文件只读源码并求值 script 段,不修改任何业务代码。
+ */
+
+const fs = require('fs')
+const path = require('path')
+
+const PAGE = path.join(__dirname, '../../../pages/signature/signature.vue')
+
+function readScript(file) {
+  const src = fs.readFileSync(file, 'utf8')
+  const m = src.match(/<script>([\s\S]*?)<\/script>/)
+  if (!m) throw new Error('no <script> in ' + file)
+  return m[1]
+}
+
+// import 语句 -> 同名空桩变量(保留脚本内引用,避免 ReferenceError)
+function stubImports(code) {
+  return code.replace(/import\s+([\s\S]*?)\s+from\s+['"][^'"]+['"];?/g, function (_all, clause) {
+    const ids = String(clause)
+      .replace(/[{}]/g, ' ')
+      .split(',')
+      .map(function (x) {
+        const parts = x.trim().split(/\s+as\s+/)
+        return parts[parts.length - 1].trim()
+      })
+      .filter(function (x) {
+        return /^[A-Za-z_$][\w$]*$/.test(x)
+      })
+    return ids
+      .map(function (id) {
+        return 'var ' + id + ' = function () {};'
+      })
+      .join(' ')
+  })
+}
+
+function loadOptions(file, sandbox) {
+  let code = stubImports(readScript(file)).replace(/export\s+default/, 'var __opts__ =')
+  code = code + '\nreturn __opts__'
+  const names = Object.keys(sandbox)
+  // eslint-disable-next-line no-new-func
+  const fn = new Function(names.join(','), code)
+  return fn.apply(
+    null,
+    names.map(function (n) {
+      return sandbox[n]
+    })
+  )
+}
+
+// 行为级测试用页面实例构造器:可注入 wx / window / uni 桩
+function makePageWith(sandbox) {
+  const base = {
+    uni: { getWindowInfo: function () { return { windowHeight: 390, windowWidth: 844 } } },
+    wx: undefined,
+    window: undefined,
+  }
+  const merged = Object.assign({}, base, sandbox || {})
+  if (sandbox && sandbox.uni) {
+    merged.uni = Object.assign({}, base.uni, sandbox.uni)
+  }
+  const opts = loadOptions(PAGE, merged)
+  const data = typeof opts.data === 'function' ? opts.data() : opts.data
+  const vm = Object.assign({}, data, opts.methods)
+  vm.$refs = {}
+  return vm
+}
+
+describe('签名页横屏适配契约 · family.signature.landscape', function () {
+  const pageScript = readScript(PAGE)
+
+  test('TC-LANDSCAPE-01 enterLandscape / restorePortrait 方法存在', function () {
+    expect(pageScript).toMatch(/enterLandscape\s*:/)
+    expect(pageScript).toMatch(/restorePortrait\s*:/)
+    // 两方法须为真实函数(行为可调)
+    const vm = makePageWith({ wx: undefined })
+    expect(typeof vm.enterLandscape).toBe('function')
+    expect(typeof vm.restorePortrait).toBe('function')
+  })
+
+  test('TC-LANDSCAPE-02 onLoad 进入即切横屏(调 enterLandscape)', function () {
+    expect(pageScript).toMatch(/onLoad\s*:\s*function[\s\S]*?enterLandscape\(\)/)
+  })
+
+  test('TC-LANDSCAPE-03 onUnload 退出恢复竖屏(调 restorePortrait)', function () {
+    expect(pageScript).toMatch(/onUnload\s*:\s*function[\s\S]*?restorePortrait\(\)/)
+  })
+
+  test('TC-LANDSCAPE-04 doBack 在 navigateBack 前恢复竖屏', function () {
+    expect(pageScript).toMatch(/doBack\s*:\s*function[\s\S]*?restorePortrait\(\)[\s\S]*?navigateBack\(\)/)
+  })
+
+  test('TC-LANDSCAPE-05 enterLandscape 调 wx.setPageOrientation landscape', function () {
+    const calls = []
+    const vm = makePageWith({
+      wx: { setPageOrientation: function (cfg) { calls.push(cfg) } }
+    })
+    vm.enterLandscape()
+    expect(calls.length).toBe(1)
+    expect(calls[0].orientation).toBe('landscape')
+    // 旋转后重算画布高度(complete 回调)
+    expect(typeof calls[0].complete).toBe('function')
+  })
+
+  test('TC-LANDSCAPE-06 restorePortrait 调 wx.setPageOrientation portrait', function () {
+    const calls = []
+    const vm = makePageWith({
+      wx: { setPageOrientation: function (cfg) { calls.push(cfg) } }
+    })
+    vm.restorePortrait()
+    expect(calls.length).toBe(1)
+    expect(calls[0].orientation).toBe('portrait')
+  })
+
+  test('TC-LANDSCAPE-07 H5(无 wx)降级不切横屏且不抛错', function () {
+    let setCalled = false
+    const vm = makePageWith({
+      wx: {
+        setPageOrientation: function () { setCalled = true }
+      }
+    })
+    // H5:wx 为 undefined
+    const h5 = makePageWith({ wx: undefined })
+    expect(function () { h5.enterLandscape() }).not.toThrow()
+    expect(function () { h5.restorePortrait() }).not.toThrow()
+    // 降级路径不得调用 setPageOrientation(不切横屏)
+    expect(setCalled).toBe(false)
+  })
+
+  test('TC-LANDSCAPE-08 doBack 防重复返回 + onUnload 恢复竖屏', function () {
+    const seq = []
+    const vm = makePageWith({
+      wx: { setPageOrientation: function (cfg) { seq.push('orient:' + cfg.orientation) } },
+      uni: {
+        getWindowInfo: function () { return { windowHeight: 390 } },
+        navigateBack: function () { seq.push('navigateBack') }
+      }
+    })
+    vm.doBack()
+    expect(seq).toEqual(['orient:portrait', 'navigateBack'])
+    // 防重复返回
+    vm.doBack()
+    expect(seq).toEqual(['orient:portrait', 'navigateBack'])
+    // onUnload 独立恢复竖屏(onUnload 走 opts.onUnload,单独构造)
+    const seq2 = []
+    const opts = loadOptions(PAGE, {
+      wx: { setPageOrientation: function (cfg) { seq2.push('orient:' + cfg.orientation) } },
+      uni: { getWindowInfo: function () { return { windowHeight: 390 } } }
+    })
+    const data = typeof opts.data === 'function' ? opts.data() : opts.data
+    const vm2 = Object.assign({}, data, opts.methods)
+    vm2.$refs = {}
+    opts.onUnload.call(vm2)
+    expect(seq2).toEqual(['orient:portrait'])
+  })
+})