Full-project rename. `git grep -i aksrt` is now empty apart from the
DBMigrator 3.3.0 changelog note that documents the rename itself.
Code identifiers
- PHP namespace AKSRT\ -> Jio\ (composer PSR-4 + 378 declarations)
- global constants AKSRT_* -> JIO_* (VERSION/DIR/URI, PAY_TYPE_*, CAPTCHA_*)
- procedural functions, hooks, options, meta keys, table names,
cookies, query vars, ajax actions and nonces: aksrt_ -> jio_
- text domain aksrt -> jio; Theme Name -> Jio
- JS globals: aksrtData/aksrtAdminData/... -> jioData/jioAdminData/...,
window.AKSRTCaptcha & friends -> window.JioCaptcha, Aksrt* -> Jio*
- Vue components Aks*.vue -> Jio*.vue (15 files renamed + imports)
- CSS classes/data-attrs/custom properties: aksrt- -> jio-
- REST namespace aksrt/v1 -> jio/v1 (incl. payment gateway callbacks)
Storage layer
- DB tables {prefix}aksrt_* -> {prefix}jio_*; SCHEMA_VERSION 3.2.0 -> 3.3.0
so dbDelta creates the new names on install
- widget id_base aksrt_widget_* -> jio_widget_*
- uninstall.php drop list rebuilt from the actual migrator tables
(dropped 3 stale entries, added the 17 missing ones)
Removed
- hardcoded https://api.aksrt.com/version.json default update endpoint;
get_endpoint() now reads the 「更新端点」 setting and do_check() no-ops
when it is empty
Files renamed: inc/helpers/aksrt-*.php -> jio-*.php (54),
assets/admin/src/components/form/Aks*.vue -> Jio*.vue (15).
Build artifacts regenerated: assets/css/build, assets/js/runtime,
assets/admin/build.
Verified: audit-psr4-paths errors=0, audit-admin-contracts errors=0
(warnings=56, unchanged baseline), 813/813 widget-system tests,
44/44 phpunit, php -l clean across all PHP.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
141 lines
7.7 KiB
JavaScript
141 lines
7.7 KiB
JavaScript
/**
|
||
* 会员弹窗骨架屏 / 打开提速 / help_text 清理 / 启动优化契约。
|
||
*
|
||
* 硬约束:
|
||
* 1) 会员弹窗加载占位为完整布局仿形骨架(vipSkeleton()),复用全局
|
||
* jio-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", "jio-vip-modal-skeleton"\)/);
|
||
assert.match(vipJs, /skeleton\.setAttribute\("aria-busy", "true"\)/);
|
||
// 结构:用户框(头像+两行)、等级卡行、双列面板(权益+套餐区)。
|
||
assert.match(vipJs, /jio-vip-modal-skeleton__user/);
|
||
assert.match(vipJs, /jio-vip-modal-skeleton__avatar/);
|
||
assert.match(vipJs, /jio-vip-modal-skeleton__lines/);
|
||
assert.match(vipJs, /jio-vip-modal-skeleton__tabs/);
|
||
assert.match(vipJs, /jio-vip-modal-skeleton__tab/);
|
||
assert.match(vipJs, /jio-vip-modal-skeleton__pane/);
|
||
assert.match(vipJs, /jio-vip-modal-skeleton__info/);
|
||
assert.match(vipJs, /jio-vip-modal-skeleton__products/);
|
||
assert.match(vipJs, /jio-vip-modal-skeleton__button/);
|
||
// 线条/色块复用全局骨架类。
|
||
assert.match(vipJs, /jio-modal-runtime__skeleton-line is-wide/);
|
||
assert.match(vipJs, /jio-modal-runtime__skeleton-line is-short/);
|
||
assert.match(vipJs, /jio-modal-runtime__skeleton-block/);
|
||
// openRemote 传入自定义骨架,不再使用纯文本占位。
|
||
assert.match(vipJs, /skeleton: vipSkeleton\(\),/);
|
||
assert.doesNotMatch(vipJs, /加载中/);
|
||
});
|
||
|
||
test('骨架样式落在 vip-modal.css 并对齐 640px 双列阈值', () => {
|
||
assert.match(vipCss, /\.jio-vip-modal-skeleton \{/);
|
||
assert.match(vipCss, /\.jio-vip-modal-skeleton__avatar \{[\s\S]*?border-radius: 50%/);
|
||
assert.match(vipCss, /\.jio-vip-modal-skeleton__tab \{[\s\S]*?flex: 1/);
|
||
assert.match(vipCss, /\.jio-vip-modal-skeleton__button \{[\s\S]*?height: 38px/);
|
||
assert.match(vipCss, /animation: jio-modal-skeleton/);
|
||
assert.match(vipCss, /@media \(min-width: 640px\)[\s\S]*?\.jio-vip-modal-skeleton__pane \{[\s\S]*?grid-template-columns: 1fr 1\.2fr/);
|
||
});
|
||
|
||
test('弹窗打开提速:缓存 TTL 提升与意图预热', () => {
|
||
assert.match(vipJs, /cacheTtl: 60000,/);
|
||
// 预热与 openRemote 同一 cacheKey(method + JioRequest.url)。
|
||
assert.match(vipJs, /function vipModalCacheKey\(level\) \{/);
|
||
assert.match(vipJs, /"GET:" \+ window\.JioRequest\.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 styles ship once in the shared public bundle', () => {
|
||
const builder = read('tools', 'build-public-assets.mjs');
|
||
const bundle = read('assets', 'css', 'build', 'public.css');
|
||
assert.strictEqual((builder.match(/'assets\/css\/frontend\/header-userbox\.css'/g) || []).length, 1);
|
||
assert.strictEqual((bundle.match(/\/\* source: assets\/css\/frontend\/header-userbox\.css \*\//g) || []).length, 1);
|
||
assert.match(bundle, /\.jio-header-userbox\s*\{/);
|
||
assert.match(scripts, /wp_enqueue_style\(\s*'jio-public',[\s\S]*?\['jio-main'\]/);
|
||
assert.doesNotMatch(scripts, /\$enqueue_module\('header-userbox'\)/);
|
||
});
|