The user-center VIP tab rendered pay_user_vip_more in a muted box below the level cards, which the reference theme does not do: zibpay_user_page_tab_content_vip only renders pay_user_vip_desc in the status box, and pay_user_vip_more appears solely in the two modal builders (purchase and existing-member renew/upgrade). The page block stayed invisible while the setting defaulted to empty and surfaced once the zibll default copy landed. Remove the page-level block; the modal view model and pay-vip.js right-column rendering are untouched. Verification: regression test locks vip-tab.php free of the more block while the modal path keeps carrying it; node --test tests/**/*.test.cjs 635/635 pass; php -l clean.
159 lines
10 KiB
JavaScript
159 lines
10 KiB
JavaScript
/**
|
||
* VIP 后台设置弹窗文案字段契约(对照参考主题 zibll 修正)。
|
||
*
|
||
* 硬约束:
|
||
* 1) 弹窗文案字段更名为「会员介绍」(pay_user_vip_desc, input) 与
|
||
* 「开通会员更多内容」(pay_user_vip_more, textarea rows 4 +
|
||
* sanitize_type html),并从「会员等级」组移到分区末尾的
|
||
* 「界面文案」(modal-copy) 独立分组;
|
||
* 2) 文案默认值对齐 zibll(黄金会员/钻石会员、尊享专属特权、权益四项、
|
||
* 会员介绍与开通会员更多内容默认值),SPA schema / PHP schema /
|
||
* PaymentRuntime 回退三处一致——Settings::get_all() 不合并
|
||
* schema 默认值,运行时回退即实际默认值;
|
||
* 3) vip_renew_desc / vip_upgrade_desc 为死配置(AKSRT 无续费/升级
|
||
* 卡渲染面,全仓零消费方),两份 schema 与 PaymentRuntime 均已删除;
|
||
* 4) 权益与卡密说明字段保存时保留 HTML(sanitize_type html),
|
||
* 不再被 sanitize_textarea_field 剥掉标签。
|
||
*/
|
||
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 vipSchemaJs = read('assets', 'admin', 'src', 'schema', 'sections', 'payment', 'vip.js');
|
||
const schemaPhp = read('src', 'Admin', 'Schema', 'SchemaPayment.php');
|
||
const paymentRuntime = read('src', 'Settings', 'PaymentRuntime.php');
|
||
const rechargeJs = read('assets', 'admin', 'src', 'schema', 'sections', 'payment', 'recharge.js');
|
||
const vipTabPhp = read('inc', 'helpers', 'user-center-renderers', 'vip-tab.php');
|
||
const modalSource = read('src', 'Content', 'VipServiceModal.php');
|
||
const payVipJs = read('assets', 'js', 'pay-vip.js');
|
||
|
||
const DESC_DEFAULT = '开通VIP会员,享受会员专属折扣以及多项特权';
|
||
const MORE_DEFAULT = '<div>购买后不支持退款</div><div>VIP权益仅适用于本站</div><div>欢迎与站长联系</div>';
|
||
const EQUITY_DEFAULT = '<li>全站资源折扣购买</li><li>部分内容免费阅读</li><li>一对一技术指导</li><li>VIP用户专属QQ群</li>';
|
||
|
||
// 取两个标记之间的源码段(分组切片,避免断言误伤其它功能的同名键)。
|
||
function slice(source, from, to) {
|
||
const start = source.indexOf(from);
|
||
assert.notStrictEqual(start, -1, `missing ${from}`);
|
||
const end = source.indexOf(to, start + from.length);
|
||
return source.slice(start, end === -1 ? undefined : end);
|
||
}
|
||
|
||
test('弹窗文案字段更名为会员介绍/开通会员更多内容并改为正确类型', () => {
|
||
for (const [name, src] of [['vip.js', vipSchemaJs], ['SchemaPayment.php', schemaPhp]]) {
|
||
assert.ok(!src.includes('弹窗顶部会员说明') && !src.includes('弹窗底部补充说明 HTML'), `${name} 旧名称应已移除`);
|
||
assert.ok(src.includes('会员介绍') && src.includes('开通会员更多内容'), `${name} 应含新名称`);
|
||
}
|
||
// 会员介绍为单行 input(zibll text「一句话简介」);开通会员更多内容为 rows 4 的 HTML textarea。
|
||
assert.match(
|
||
vipSchemaJs,
|
||
/key: 'pay_user_vip_desc',\s*\n\s*label: '会员介绍',\s*\n\s*type: 'input',\s*\n\s*default: '开通VIP会员,享受会员专属折扣以及多项特权'/
|
||
);
|
||
assert.match(
|
||
vipSchemaJs,
|
||
/key: 'pay_user_vip_more',\s*\n\s*label: '开通会员更多内容',\s*\n\s*type: 'textarea',\s*\n\s*sanitize_type: 'html'[\s\S]*?rows: 4/
|
||
);
|
||
assert.match(
|
||
schemaPhp,
|
||
/'key' => 'pay_user_vip_desc', 'label' => '会员介绍', 'type' => 'input', 'default' => '开通VIP会员,享受会员专属折扣以及多项特权'/
|
||
);
|
||
assert.match(
|
||
schemaPhp,
|
||
/'key' => 'pay_user_vip_more', 'label' => '开通会员更多内容', 'type' => 'textarea', 'sanitize_type' => 'html'[\s\S]*?'rows' => 4/
|
||
);
|
||
// 帮助文本对齐 zibll 的 desc。
|
||
assert.ok(vipSchemaJs.includes('显示在开通界面顶部一句话简介,可以为会员权益简介或者活动介绍'));
|
||
assert.ok(vipSchemaJs.includes('显示在开通界面底部位置,可以为提醒事项、用户协议等,支持HTML代码'));
|
||
});
|
||
|
||
test('两个文案字段位于分区末尾的界面文案分组且不再属于会员等级组', () => {
|
||
const modalJs = vipSchemaJs.indexOf("key: 'modal-copy'");
|
||
const modalPhp = schemaPhp.indexOf("'key' => 'modal-copy'");
|
||
assert.notStrictEqual(modalJs, -1, 'vip.js 应含 modal-copy 分组');
|
||
assert.notStrictEqual(modalPhp, -1, 'SchemaPayment.php 应含 modal-copy 分组');
|
||
assert.ok(modalJs > vipSchemaJs.indexOf("key: 'points-exchange'"), 'vip.js modal-copy 应在分区末尾');
|
||
assert.ok(modalPhp > schemaPhp.indexOf("'key' => 'points-exchange'"), 'SchemaPayment.php modal-copy 应在分区末尾');
|
||
// modal-copy 之后不再出现其它分组/字段 key。
|
||
// modal-copy 之后(至分区末尾)不再出现其它分组/字段 key;dependsOn 引用的等级开关除外。
|
||
assert.doesNotMatch(vipSchemaJs.slice(modalJs), /key: '(?!modal-copy|pay_user_vip_desc|pay_user_vip_more|pay_user_vip_1_s|pay_user_vip_2_s)/);
|
||
const phpTail = slice(schemaPhp, "'key' => 'modal-copy'", "'revenue'");
|
||
assert.doesNotMatch(phpTail, /'key' => '(?!modal-copy|pay_user_vip_desc|pay_user_vip_more|pay_user_vip_1_s|pay_user_vip_2_s)/);
|
||
// 「会员等级」组不再包含这两个字段。
|
||
assert.doesNotMatch(slice(vipSchemaJs, "key: 'basic'", "key: 'benefit'"), /pay_user_vip_desc|pay_user_vip_more/);
|
||
assert.doesNotMatch(slice(schemaPhp, "'key' => 'basic'", "'key' => 'benefit'"), /pay_user_vip_desc|pay_user_vip_more/);
|
||
// 依赖:任一等级启用时显示(zibll pay_user_vip_1_s 依赖的双等级适配)。
|
||
assert.ok(
|
||
vipSchemaJs.includes("dependsOn: { any: [{ key: 'pay_user_vip_1_s', value: true }, { key: 'pay_user_vip_2_s', value: true }] }"),
|
||
'vip.js 应为 any 双等级依赖'
|
||
);
|
||
assert.ok(
|
||
schemaPhp.includes("'dependsOn' => ['any' => [['key' => 'pay_user_vip_1_s', 'value' => true], ['key' => 'pay_user_vip_2_s', 'value' => true]]]"),
|
||
'SchemaPayment.php 应为 any 双等级依赖'
|
||
);
|
||
});
|
||
|
||
test('续费/升级说明死配置已从两份 schema 与运行时删除', () => {
|
||
for (const [name, src] of [['vip.js', vipSchemaJs], ['SchemaPayment.php', schemaPhp], ['PaymentRuntime.php', paymentRuntime]]) {
|
||
assert.ok(!src.includes('vip_renew_desc'), `${name} 不应再含 vip_renew_desc`);
|
||
assert.ok(!src.includes('vip_upgrade_desc'), `${name} 不应再含 vip_upgrade_desc`);
|
||
}
|
||
});
|
||
|
||
test('文案默认值三处一致并对齐 zibll', () => {
|
||
// SPA defaults 块与字段级 default。
|
||
assert.ok(vipSchemaJs.includes("pay_user_vip_1_name: '黄金会员'"));
|
||
assert.ok(vipSchemaJs.includes("pay_user_vip_2_name: '钻石会员'"));
|
||
assert.ok(vipSchemaJs.includes("pay_user_vip_1_desc: '尊享专属特权'"));
|
||
assert.ok(vipSchemaJs.includes("pay_user_vip_2_desc: '尊享专属特权'"));
|
||
assert.ok(vipSchemaJs.includes(`pay_user_vip_desc: '${DESC_DEFAULT}'`));
|
||
assert.ok(vipSchemaJs.includes(`pay_user_vip_more: '${MORE_DEFAULT}'`));
|
||
assert.ok(vipSchemaJs.includes(`default: '${EQUITY_DEFAULT}'`));
|
||
// PHP schema 字段 default。
|
||
assert.ok(schemaPhp.includes("'default' => '黄金会员'"));
|
||
assert.ok(schemaPhp.includes("'default' => '钻石会员'"));
|
||
assert.ok(schemaPhp.includes(`'default' => '${DESC_DEFAULT}'`));
|
||
assert.ok(schemaPhp.includes(`'default' => '${MORE_DEFAULT}'`));
|
||
// PaymentRuntime 运行时回退(get_all 不合并 schema 默认值,回退即实际默认值)。
|
||
assert.ok(paymentRuntime.includes(`$desc = sanitize_text_field((string) ($settings['pay_user_vip_desc'] ?? __('${DESC_DEFAULT}', 'aksrt')))`));
|
||
assert.ok(paymentRuntime.includes(`$more = (string) ($settings['pay_user_vip_more'] ?? '${MORE_DEFAULT}')`));
|
||
assert.ok(paymentRuntime.includes("$name1 = __('黄金会员', 'aksrt');"));
|
||
assert.ok(paymentRuntime.includes("$name2 = __('钻石会员', 'aksrt');"));
|
||
assert.ok(paymentRuntime.includes("$settings['pay_user_vip_1_desc'] ?? __('尊享专属特权', 'aksrt')"));
|
||
assert.ok(paymentRuntime.includes("$settings['pay_user_vip_2_desc'] ?? __('尊享专属特权', 'aksrt')"));
|
||
assert.ok(paymentRuntime.includes(EQUITY_DEFAULT));
|
||
// 旧默认值不再出现在任何一处。
|
||
for (const [name, src] of [['vip.js', vipSchemaJs], ['SchemaPayment.php', schemaPhp], ['PaymentRuntime.php', paymentRuntime]]) {
|
||
assert.ok(!/尊享一级会员特权|尊享二级会员特权|免费下载部分资源|更高免费下载额度/.test(src), `${name} 旧默认值应已移除`);
|
||
}
|
||
});
|
||
|
||
test('权益与卡密说明字段保存时保留 HTML', () => {
|
||
assert.ok(vipSchemaJs.includes("key: 'pay_user_vip_1_equity', label: '一级会员权益 HTML', type: 'textarea', sanitize_type: 'html'"));
|
||
assert.ok(vipSchemaJs.includes("key: 'pay_user_vip_2_equity', label: '二级会员权益 HTML', type: 'textarea', sanitize_type: 'html'"));
|
||
assert.ok(schemaPhp.includes("'key' => 'pay_user_vip_1_equity', 'label' => '一级会员权益 HTML', 'type' => 'textarea', 'sanitize_type' => 'html'"));
|
||
assert.ok(schemaPhp.includes("'key' => 'pay_user_vip_2_equity', 'label' => '二级会员权益 HTML', 'type' => 'textarea', 'sanitize_type' => 'html'"));
|
||
assert.ok(rechargeJs.includes("key: 'pay_vip_pass_charge_desc', label: '卡密兑换会员说明', type: 'textarea', sanitize_type: 'html'"));
|
||
assert.ok(schemaPhp.includes("'key' => 'pay_vip_pass_charge_desc', 'label' => '卡密兑换会员说明', 'type' => 'textarea', 'sanitize_type' => 'html'"));
|
||
});
|
||
|
||
test('会员介绍运行时按单行文本净化', () => {
|
||
// 只收敛 VIP 设置函数:充值等其它设置的 $desc 与本文无关。
|
||
const vipSettings = slice(paymentRuntime, 'public static function get_payment_vip_settings', 'public static function get_payment_revenue_settings');
|
||
assert.ok(vipSettings.includes("$desc = sanitize_text_field((string) ($settings['pay_user_vip_desc']"));
|
||
assert.ok(!vipSettings.includes('$desc = sanitize_textarea_field'));
|
||
});
|
||
|
||
test('开通会员更多内容只在弹窗内渲染,不裸露在用户中心页面', () => {
|
||
// 参考主题 zibpay_user_page_tab_content_vip 只渲染 pay_user_vip_desc;
|
||
// pay_user_vip_more 仅出现在两个弹窗函数(购买/续费弹窗)中。
|
||
assert.ok(!vipTabPhp.includes("vip_settings['more']"), '用户中心 VIP 页不应渲染 more');
|
||
assert.ok(!vipTabPhp.includes('muted-box padding-10 mini-radius mt15'), '用户中心 VIP 页不应有 more 提示盒');
|
||
// 弹窗侧保持:视图模型透传 more + pay-vip.js 右列渲染。
|
||
assert.match(modalSource, /'more'\s+=>/);
|
||
assert.match(payVipJs, /catalog\.more/);
|
||
});
|