Files
danyang-match/ui-tests/tabbar-icons.mjs
T
WorkBuddy 45e84722e1 fix(miniapp): 修复底部 tabBar 图标全部消失(开发者工具误删 app.json 专属图标)
根因:微信开发者工具(>=1.05.2112301)的 ignoreDevUnusedFiles 会按 WXML/JS 依赖分析过滤
"无依赖"文件。tabBar 图标仅在 app.json 中声明,不属于任何页面 WXML/JS 依赖,被误判为无用
文件从开发包剥离,导致 5 个图标全部加载失败(渲染为破图占位)。

修复:
- 在 project.config.json 与 miniapp/project.config.json 的 setting 中显式关闭
  ignoreDevUnusedFiles / ignoreUploadUnusedFiles(false),阻止图标被过滤。
- 同步关闭本地 gitignore 的 project.private.config.json(DevTools 运行时实际读取项)。
- 预览通道 scripts/preview-miniapp.mjs:补全 /assets 绝对路径→相对路径重写,并手动复刻
  原生 tabBar(原生 tabBar 不在任何页面 WXML 中,静态预览需自行复刻)。
- 回归守卫 ui-tests/tabbar-icons.mjs 扩充为 5 段,覆盖根因(配置开关)+ 预览复刻。
- miniapp/assets/tabbar/DESIGN.md 增加根因警示,防止后续被误开开关。

验证:UI 契约 23/23 全绿(tabbar-icons.mjs 位于 [19/23]);CDP 渲染实测 127 张图 0 破图,
5 个 tabBar 图标 naturalW=81 全部加载,选中色 rgb(233,107,107)、未选 rgb(153,153,153),
与 app.json selectedColor/color 一致。
2026-09-23 15:57:04 +08:00

118 lines
5.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import assert from 'node:assert/strict'
import { readFileSync, existsSync } from 'node:fs'
import { join } from 'node:path'
const repoRoot = process.cwd()
const root = join(repoRoot, 'miniapp', 'assets', 'tabbar')
const icons = {
home: '爱心小屋',
discover: '双人缘分',
message: '爱心对话',
activity: '公益日历',
mine: '个人守护',
}
// ── 1. SVG 源文件:主题化标题 + 两态配色 ──────────────────────────────
for (const [name, title] of Object.entries(icons)) {
const normal = readFileSync(join(root, 'svg', `${name}.svg`), 'utf8')
const active = readFileSync(join(root, 'svg', `${name}-active.svg`), 'utf8')
assert.match(normal, new RegExp(`<title>${title}</title>`), `${name} 缺少主题化标题`)
assert.match(normal, /#999999/i, `${name} 默认态颜色错误`)
assert.match(active, /#E96B6B/i, `${name} 选中态颜色错误`)
// ── 2. PNG 规格:正方形、1x/2x/3x、透明 RGBA ────────────────────────
for (const suffix of ['', '_2x', '_3x']) {
const expectedSize = suffix === '' ? 81 : suffix === '_2x' ? 162 : 243
for (const state of ['', '-active']) {
const png = readFileSync(join(root, `${name}${state}${suffix}.png`))
assert.equal(png.readUInt32BE(16), expectedSize)
assert.equal(png.readUInt32BE(20), expectedSize)
assert.equal(png[25], 6, `${name}${state}${suffix} 必须为透明 RGBA PNG`)
}
}
}
// ── 3. app.json ↔ 磁盘一致性 ─────────────────────────────────────────
// 历史缺陷:tabBar 图标只被 app.json 引用(不经 WXML/JS),
// 一旦配置漂移,不会有任何 WXML 侧症状,只会「底部导航整排变破图」。
const appJsonPath = join(repoRoot, 'miniapp', 'app.json')
const appJson = JSON.parse(readFileSync(appJsonPath, 'utf8'))
const tabList = appJson.tabBar?.list ?? []
assert.equal(tabList.length, 5, 'tabBar.list 必须为 5 项(微信支持 2~5)')
for (const item of tabList) {
assert.ok(appJson.pages.includes(item.pagePath), `tabBar pagePath 未在 pages 中注册: ${item.pagePath}`)
for (const key of ['iconPath', 'selectedIconPath']) {
const rel = item[key]
assert.ok(rel, `tabBar ${item.pagePath} 缺少 ${key}`)
assert.ok(!rel.startsWith('/'), `tabBar ${key} 不应以 / 开头(微信按小程序根解析): ${rel}`)
assert.ok(
existsSync(join(repoRoot, 'miniapp', rel)),
`tabBar ${key} 指向不存在的文件: ${rel}`,
)
}
}
// ── 4. 「无依赖文件过滤」必须关闭(tabBar 图标整排变破图的根因)────────
// 微信开发者工具自 1.05.2112301 起默认开启该能力:它按 WXML/JS 依赖分析,
// app.json 的 tabBar 图标不被视为依赖 → 被从开发包中剔除 → 图标全部加载失败。
// 官方缓解方式即 ignoreDevUnusedFiles / ignoreUploadUnusedFiles 置 false。
const CONFIGS = [
'project.config.json',
'miniapp/project.config.json',
'project.private.config.json',
'miniapp/project.private.config.json',
]
const requiredOff = ['project.config.json', 'miniapp/project.config.json']
for (const rel of CONFIGS) {
const p = join(repoRoot, rel)
if (!existsSync(p)) continue // 私有配置被 gitignore,缺失属正常
const setting = JSON.parse(readFileSync(p, 'utf8')).setting ?? {}
assert.notEqual(
setting.ignoreDevUnusedFiles, true,
`${rel} 开启了无依赖文件过滤(ignoreDevUnusedFiles),会剔除 app.json 独占引用的 tabBar 图标,导致底部导航整排破图`,
)
assert.notEqual(
setting.ignoreUploadUnusedFiles, true,
`${rel} 开启了上传期无依赖文件过滤(ignoreUploadUnusedFiles),体验版/正式版会丢失 tabBar 图标`,
)
}
for (const rel of requiredOff) {
const p = join(repoRoot, rel)
assert.ok(existsSync(p), `缺少共享配置 ${rel}`)
const setting = JSON.parse(readFileSync(p, 'utf8')).setting ?? {}
assert.equal(
setting.ignoreDevUnusedFiles, false,
`${rel} 必须显式关闭 ignoreDevUnusedFiles(新克隆默认值为 true 会复现破图)`,
)
assert.equal(
setting.ignoreUploadUnusedFiles, false,
`${rel} 必须显式关闭 ignoreUploadUnusedFiles`,
)
}
// ── 5. 静态预览必须改写资源路径 ──────────────────────────────────────
// WXML/WXSS 写的是小程序根绝对路径 /assets/...,浏览器 file:// 打开会解析到
// 文件系统根 → 全部 404(表现为破图)。预览产出必须改写为相对路径,
// 并复刻 app.json 原生 tabBar(它不在任何 WXML 里)。
const previewSrc = readFileSync(join(repoRoot, 'scripts', 'preview-miniapp.mjs'), 'utf8')
assert.match(previewSrc, /const rewriteAssetUrls\s*=/, '预览脚本缺少资源路径改写器')
assert.match(
previewSrc, /writeFileSync\(outFile,\s*rewriteAssetUrls\(html\)/,
'预览脚本写盘时未应用资源路径改写 → file:// 下图片会全部 404',
)
assert.match(previewSrc, /function renderTabbar\(/, '预览脚本缺少原生 tabBar 复刻')
const previewHtml = join(repoRoot, 'artifacts', 'ui-redesign', 'miniapp', 'preview.html')
if (existsSync(previewHtml)) {
const html = readFileSync(previewHtml, 'utf8')
const absoluteSrc = (html.match(/src=["']\/assets\//g) || []).length
assert.equal(absoluteSrc, 0, `preview.html 仍有 ${absoluteSrc} 处根绝对资源路径,浏览器打开会破图`)
assert.match(html, /class="mini-tabbar"/, 'preview.html 缺少复刻的底部 tabBar')
}
console.log('Tabbar icon contracts passed')