/** * 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 = '
购买后不支持退款
VIP权益仅适用于本站
欢迎与站长联系
'; const EQUITY_DEFAULT = '
  • 全站资源折扣购买
  • 部分内容免费阅读
  • 一对一技术指导
  • VIP用户专属QQ群
  • '; // 取两个标记之间的源码段(分组切片,避免断言误伤其它功能的同名键)。 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/); });