/** * 会员弹窗骨架屏 / 打开提速 / help_text 清理 / 启动优化契约。 * * 硬约束: * 1) 会员弹窗加载占位为完整布局仿形骨架(vipSkeleton()),复用全局 * aksrt-modal-runtime__skeleton-line/-block 与 shimmer 动画, * VIP 专属形状样式落在 assets/css/frontend/vip-modal.css; * 2) 弹窗数据缓存 TTL 60s,悬停/聚焦入口按等级意图预热,预热与 * openRemote 共用 cacheKey 与 flight; * 3) 弹窗底部提示句与整条 help_text 管线已移除,顶部说明 desc 保留; * 4) 积分/钱包余额合并为一次 LedgerService::points_and_balance(); * 5) 访问统计写库移到 shutdown,wp_head 只做访客判定与 setcookie; * 6) header-userbox 样式仅登录用户入队。 */ 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'); // 取某个方法体(到下一个同类方法声明为止),避免断言误伤其它功能的同名键。 function sliceMethod(source, signature, nextSignature) { const start = source.indexOf(signature); assert.notStrictEqual(start, -1, `missing ${signature}`); const end = source.indexOf(nextSignature, start + signature.length); return source.slice(start, end === -1 ? undefined : end); } const vipJs = read('assets', 'js', 'pay-vip.js'); const vipCss = read('assets', 'css', 'frontend', 'vip-modal.css'); const paymentRuntime = read('src', 'Settings', 'PaymentRuntime.php'); const modal = read('src', 'Content', 'VipServiceModal.php'); const catalogSource = read('src', 'Content', 'VipServicePurchase.php'); const ledger = read('src', 'Ledger', 'LedgerService.php'); const stats = read('src', 'Core', 'StatsTracker.php'); const scripts = read('src', 'Core', 'ScriptsPart1b.php'); test('会员弹窗使用完整布局仿形骨架屏', () => { assert.match(vipJs, /function vipSkeleton\(\) \{/); assert.match(vipJs, /node\("div", "aksrt-vip-modal-skeleton"\)/); assert.match(vipJs, /skeleton\.setAttribute\("aria-busy", "true"\)/); // 结构:用户框(头像+两行)、等级卡行、双列面板(权益+套餐区)。 assert.match(vipJs, /aksrt-vip-modal-skeleton__user/); assert.match(vipJs, /aksrt-vip-modal-skeleton__avatar/); assert.match(vipJs, /aksrt-vip-modal-skeleton__lines/); assert.match(vipJs, /aksrt-vip-modal-skeleton__tabs/); assert.match(vipJs, /aksrt-vip-modal-skeleton__tab/); assert.match(vipJs, /aksrt-vip-modal-skeleton__pane/); assert.match(vipJs, /aksrt-vip-modal-skeleton__info/); assert.match(vipJs, /aksrt-vip-modal-skeleton__products/); assert.match(vipJs, /aksrt-vip-modal-skeleton__button/); // 线条/色块复用全局骨架类。 assert.match(vipJs, /aksrt-modal-runtime__skeleton-line is-wide/); assert.match(vipJs, /aksrt-modal-runtime__skeleton-line is-short/); assert.match(vipJs, /aksrt-modal-runtime__skeleton-block/); // openRemote 传入自定义骨架,不再使用纯文本占位。 assert.match(vipJs, /skeleton: vipSkeleton\(\),/); assert.doesNotMatch(vipJs, /加载中/); }); test('骨架样式落在 vip-modal.css 并对齐 640px 双列阈值', () => { assert.match(vipCss, /\.aksrt-vip-modal-skeleton \{/); assert.match(vipCss, /\.aksrt-vip-modal-skeleton__avatar \{[\s\S]*?border-radius: 50%/); assert.match(vipCss, /\.aksrt-vip-modal-skeleton__tab \{[\s\S]*?flex: 1/); assert.match(vipCss, /\.aksrt-vip-modal-skeleton__button \{[\s\S]*?height: 38px/); assert.match(vipCss, /animation: aksrt-modal-skeleton/); assert.match(vipCss, /@media \(min-width: 640px\)[\s\S]*?\.aksrt-vip-modal-skeleton__pane \{[\s\S]*?grid-template-columns: 1fr 1\.2fr/); }); test('弹窗打开提速:缓存 TTL 提升与意图预热', () => { assert.match(vipJs, /cacheTtl: 60000,/); // 预热与 openRemote 同一 cacheKey(method + AksrtRequest.url)。 assert.match(vipJs, /function vipModalCacheKey\(level\) \{/); assert.match(vipJs, /"GET:" \+ window\.AksrtRequest\.url\("\/pay\/vip\/modal", \{ vip_level: level \}\)/); assert.match(vipJs, /cache\.acquire\(vipModalCacheKey\(level\)/); assert.match(vipJs, /ttl: 60000,/); assert.match(vipJs, /tags: \["vip"\]/); // 悬停/聚焦触发,绑定进 bindOnce。 assert.match(vipJs, /\["pointerenter", "focusin"\]/); assert.match(vipJs, /function bindPrefetch\(\) \{/); assert.match(vipJs, /document\.addEventListener\("click", onDocClick, true\);\s*bindPrefetch\(\);/); // 每等级每页只预热一次。 assert.match(vipJs, /prefetchedLevels\[level\] = true;/); }); test('弹窗底部提示与 help_text 管线已彻底移除', () => { assert.doesNotMatch(vipJs, /help_text/); // 仅收敛 VIP 设置函数:充值/网关设置的 help_text 与本文无关,保持原样。 const vipSettings = sliceMethod( paymentRuntime, 'public static function get_payment_vip_settings', 'public static function get_payment_revenue_settings' ); assert.doesNotMatch(vipSettings, /help_text/); assert.doesNotMatch(vipSettings, /开通后立即生效/); assert.doesNotMatch(modal, /help_text/); const viewModel = sliceMethod(modal, 'public function get_modal_view_model', 'public function get_points_exchange_modal_html'); assert.doesNotMatch(viewModel, /help_text/); assert.doesNotMatch(catalogSource, /help_text/); // 顶部说明 desc 仍保留(后台 pay_user_vip_desc 继续生效)。 assert.match(vipJs, /catalog\.desc/); assert.match(paymentRuntime, /'desc'\s+=> \$desc,/); }); test('积分与钱包余额合并为一次 LedgerService 读取', () => { assert.match(ledger, /public function points_and_balance\(int \$user_id\): array/); // 仅收敛 get_catalog:购买/履约路径的余额读取与本文无关,保持原样。 const getCatalog = sliceMethod(catalogSource, 'public function get_catalog', 'private function find_catalog_package'); assert.match(getCatalog, /points_and_balance\(\$user_id\)/); assert.match(getCatalog, /'user_points'\s+=> \$balances\['points'\]/); assert.match(getCatalog, /'wallet_balance'\s+=> \$balances\['balance'\]/); assert.doesNotMatch(getCatalog, /LedgerService::get_instance\(\)->balance\(/); assert.doesNotMatch(getCatalog, /balance_decimal/); }); test('访问统计写库移出渲染关键路径', () => { assert.match(stats, /add_action\('wp_head', \[self::get_instance\(\), 'track_visit'\], 1\)/); assert.match(stats, /add_action\('shutdown'/); assert.match(stats, /public function record_stats\(string \$option_key, bool \$is_unique\): void/); // wp_head 阶段只做判定与 setcookie,不再读写 option。 const trackVisit = stats.slice(stats.indexOf('public function track_visit'), stats.indexOf('public function record_stats')); assert.doesNotMatch(trackVisit, /update_option|get_option/); assert.match(trackVisit, /setcookie\(\$visitor_cookie/); // 落库与月初清理收敛到 record_stats。 const recordStats = stats.slice(stats.indexOf('public function record_stats')); assert.match(recordStats, /update_option\(\$option_key, \$stats, false\)/); assert.match(recordStats, /cleanup_old_stats\(\)/); }); test('header-userbox 样式仅登录用户入队', () => { assert.match(scripts, /if \(is_user_logged_in\(\)\) \{\s*\$enqueue_module\('header-userbox'\);/); });