主题打包的是 Font Awesome 5,solid 字形必须带样式类 fas。全站有 18 处仍写成 FA4 的裸 fa fa-*,FA5 下并不存在对应字形,表现为图标位置空白;而 FA5-only 契约此前只扫描部分运行时文件,没有覆盖模板与渲染器,这些写法一直没被拦下。 同时发现一处真实的失效图标名:profile-level.php 用的是 fa-line-chart。这是 FA4 名称,FA5 把该系列重命名为 chart-*,正确写法是 fa-chart-line。 - 18 处 "fa fa-*" 补上 fas:首页模板、组件标记、评论表单、用户等级页、菜单 装饰、SchemaPayment 促销标签默认值、VIP 服务弹窗、ContentRenderer / GeneralRenderer、后台卡片样式校验、pay-vip.js - profile-level.php 的 fa-line-chart 修正为 fa-chart-line - 三处注释里的示例值同步为合法形态("fa fas fa-star"),避免被当成正确写法 抄走;credits.css 注释中直接写出旧字体族名的一处改为描述性表述,否则会被 FA5-only 契约当作违规命中 - legacy-navigation-contract:FA 资产断言改为跟随 aksrt_enqueue_fontawesome() 这一间接层(注册点已从脚本文件移到 inc/helpers/aksrt-icons.php),并把 fa-zibsvg-<key> 识别为项目自有 SVG sprite 族,而非 Font Awesome 字形 - vip-points-exchange-modal 契约同步断言 fa fas fa-angle-right 验证:改动前在干净 HEAD 上跑 node --test(已追踪用例)为 327 项 / 325 通过 / 2 失败,两项失败正是本次修复的失效图标名断言与 FA 版本号断言;改动后 333 项 / 333 通过(含 5 个本地专用用例共 370 项全部通过)。php -l 全部改动的 PHP 文件 通过。
115 lines
6.3 KiB
JavaScript
115 lines
6.3 KiB
JavaScript
/**
|
|
* 积分兑换会员弹窗契约(参考主题 zibpay_get_vip_points_exchange_modal 1:1 对齐)。
|
|
*
|
|
* 三条硬约束:
|
|
* 1) 服务端静态输出参考主题 refresh_modal 同构壳(`modal flex jc fade` >
|
|
* `modal-dialog modal-mini full-sm` > `modal-content` > `modal-body`),
|
|
* 入口只保留在会员购买弹窗内——/user/vip/ 页底部的独立入口已移除。
|
|
* 2) 静止态必须被 vip-modal.css 显式隐藏(.flex 覆盖回归护栏),打开态为
|
|
* flex 而非 block,且层级高于 runtime 宿主(100050),以复现参考主题在
|
|
* 会员弹窗之上再叠一层兑换弹窗的行为。
|
|
* 3) 旧的客户端自实现弹窗(renderPointsModal / aksrt-vip-points-option /
|
|
* catalog.vip_url)必须已彻底移除。
|
|
*/
|
|
const test = require('node:test');
|
|
const assert = require('node:assert');
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
|
|
const root = path.join(__dirname, '..', '..');
|
|
const read = (...seg) => fs.readFileSync(path.join(root, ...seg), 'utf8');
|
|
|
|
const modal = read('src', 'Content', 'VipServiceModal.php');
|
|
const catalogSource = read('src', 'Content', 'VipServicePurchase.php');
|
|
const footer = read('inc', 'helpers', 'aksrt-vip.php');
|
|
const vipJs = read('assets', 'js', 'pay-vip.js');
|
|
const creditsJs = read('assets', 'js', 'credits.js');
|
|
const vipCss = read('assets', 'css', 'frontend', 'vip-modal.css');
|
|
const vipTab = read('inc', 'helpers', 'user-center-renderers', 'vip-tab.php');
|
|
const creditsCss = read('assets', 'css', 'routes', 'user-center', 'credits.css');
|
|
|
|
test('弹窗为参考主题 refresh_modal 同构静态壳并带 data 钩子', () => {
|
|
assert.match(modal, /public function get_points_exchange_modal_html/);
|
|
assert.match(modal, /class="modal flex jc fade"[^']*data-aksrt-vip-points-modal/);
|
|
assert.match(modal, /class="modal-dialog modal-mini full-sm"/);
|
|
assert.match(modal, /class="modal-content"><div class="modal-body">/);
|
|
assert.match(modal, /data-dismiss="modal"/);
|
|
assert.match(modal, /#icon-close/);
|
|
assert.match(modal, /data-aksrt-vip-points-form/);
|
|
});
|
|
|
|
test('弹窗内容保持参考主题结构:标题/我的积分/卡片选择/立即兑换', () => {
|
|
// 头部:.mb10.touch + modal-title.flex.ac + vip_1 图标。
|
|
assert.match(modal, /'<div class="mb10 touch">'/);
|
|
assert.match(modal, /class="modal-title flex ac"/);
|
|
assert.match(modal, /#icon-vip_1/);
|
|
// 我的积分盒(点回用户中心积分页)。
|
|
assert.match(modal, /muted-box padding-h10 mb20/);
|
|
assert.match(modal, /我的积分/);
|
|
assert.match(modal, /#icon-points/);
|
|
assert.match(modal, /aksrt_get_user_center_tab_url\('credits'\)/);
|
|
// 卡片列表 + 参考主题 data-for/data-value 选中模型。
|
|
assert.match(modal, /请选择需兑换的会员/);
|
|
assert.match(modal, /flex hh exchange-card-box/);
|
|
assert.match(modal, /vip-card vip-theme/);
|
|
assert.match(modal, /data-for="product_id" data-value=/);
|
|
assert.match(modal, /flex jsb ac hh em16 mt10/);
|
|
// 表单:hidden 契约 + 立即兑换按钮 + 积分不足提示。
|
|
assert.match(modal, /name="order_type" value="4"/);
|
|
assert.match(modal, /name="action" value="points_initiate_pay"/);
|
|
assert.match(modal, /mt6 but jb-yellow btn-block radius padding-lg/);
|
|
assert.match(modal, /data-aksrt-vip-points-submit/);
|
|
assert.match(modal, /badg c-red btn-block/);
|
|
});
|
|
|
|
test('弹窗壳随 wp_footer 全局输出且不进入 catalog 载荷', () => {
|
|
assert.match(footer, /function aksrt_render_vip_points_exchange_modal/);
|
|
assert.match(footer, /add_action\('wp_footer', 'aksrt_render_vip_points_exchange_modal'\)/);
|
|
assert.match(footer, /VipService::get_instance\(\)->get_points_exchange_modal_html/);
|
|
// 兑换入口不再跳用户中心:catalog 里的 vip_url 已清除。
|
|
assert.doesNotMatch(catalogSource, /'vip_url'/);
|
|
assert.doesNotMatch(modal, /'vip_url'/);
|
|
});
|
|
|
|
test('弹窗壳静止态必须被 vip-modal.css 显式隐藏(.flex 覆盖回归护栏)', () => {
|
|
assert.match(vipCss, /\.modal\[data-aksrt-vip-points-modal\]\s*\{\s*display:\s*none;/);
|
|
assert.match(vipCss, /\.modal\[data-aksrt-vip-points-modal\]\.in\s*\{\s*display:\s*flex;/);
|
|
assert.doesNotMatch(vipCss, /\.modal\[data-aksrt-vip-points-modal\]\.in\s*\{\s*display:\s*block;/);
|
|
// 层级必须高于 runtime 宿主(.aksrt-modal-runtime 的 z-index: 100050)。
|
|
assert.match(vipCss, /\.modal\[data-aksrt-vip-points-modal\]\s*\{[^}]*z-index:\s*100060;/s);
|
|
assert.match(vipCss, /\.modal-backdrop\[data-aksrt-vip-points-backdrop\]\s*\{\s*z-index:\s*100055;/);
|
|
});
|
|
|
|
test('开关自包含:不依赖 jQuery/Bootstrap modal 运行时', () => {
|
|
assert.match(vipJs, /function openPointsModal\(trigger\)/);
|
|
assert.match(vipJs, /function closePointsModal\(\)/);
|
|
assert.doesNotMatch(vipJs, /\.modal\(\s*['"]show['"]/);
|
|
assert.doesNotMatch(vipJs, /hidden\.bs\.modal/);
|
|
assert.match(vipJs, /pointsBackdrop\.className = "modal-backdrop fade in"/);
|
|
assert.match(vipJs, /modal\.style\.display = "flex"/);
|
|
assert.doesNotMatch(vipJs, /modal\.style\.display = "block"/);
|
|
assert.match(vipJs, /void modal\.offsetWidth/);
|
|
});
|
|
|
|
test('会员弹窗内的入口为参考主题同款按钮并开本地弹窗', () => {
|
|
// 参考主题 zibpay_get_pay_uservip_modal 的 class 与图标顺序。
|
|
assert.match(vipJs, /node\("a", "btn-block but mb10 padding-lg c-green mini-radius"\)/);
|
|
assert.match(vipJs, /#icon-points-color/);
|
|
// 图标顺序与 class 串保持参考主题一致;FA5 样式类 `fas` 由图标契约(
|
|
// legacy-navigation-contract 的 FA5-only 约束)强制要求,故在此一并断言。
|
|
assert.match(vipJs, /fa fas fa-angle-right em12 ml10/);
|
|
assert.match(vipJs, /data-aksrt-vip-points-open/);
|
|
assert.match(vipJs, /openPointsModal\(pointsLink\)/);
|
|
});
|
|
|
|
test('旧的自实现兑换弹窗与独立入口已彻底移除', () => {
|
|
assert.doesNotMatch(vipJs, /renderPointsModal|exchangeFromModal|vipPointsPay/);
|
|
assert.doesNotMatch(vipJs, /aksrt-vip-points-option|aksrt-vip-points-modal__/);
|
|
assert.doesNotMatch(vipCss, /\.aksrt-vip-points-option|\.aksrt-vip-points-submit/);
|
|
// /user/vip/ 页底部不再有独立积分兑换入口。
|
|
assert.doesNotMatch(vipTab, /uc-vip-points-link/);
|
|
assert.doesNotMatch(creditsCss, /\.uc-vip-points-link/);
|
|
// 用户中心侧的旧兑换控制器([data-aksrt-vip-points],全站无对应 DOM)。
|
|
assert.doesNotMatch(creditsJs, /initVipPointsExchange|data-aksrt-vip-points/);
|
|
});
|