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}`), `${name} 缺少主题化标题`) assert.match(normal, /#47528C/i, `${name} 默认态颜色错误`) assert.match(active, /#F4005A/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)') // 文字与图标同色:color 必须等于 SVG 默认态色,selectedColor 必须等于选中态色, // 否则会出现「图标是藏青蓝/玫红、文字却是别的颜色」的割裂。 assert.equal(appJson.tabBar?.color, '#47528C', 'tabBar.color 必须与图标默认态同色(#47528C)') assert.equal(appJson.tabBar?.selectedColor, '#F4005A', 'tabBar.selectedColor 必须与图标选中态同色(#F4005A)') 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')