Files
kate522andClaude Opus 5 427968a721 chore(branding): remove leftover upstream theme identifiers
The theme was renamed Zibll/子比 → AKSRT → Jio, but upstream branding
survived the sweeps in CSS classes, the SVG sprite prefix, a persisted
widget field ID, JS events, admin Vue class names, test infrastructure
and docblocks.

Renames:
- zib-widget / zib-widget-wrap / zib-slider → jio-widget-box /
  jio-widget-box-wrap / jio-slider-box (jio-widget is taken by the modern
  widget shell, jio-box-* by the card primitives)
- zib-partners-scroll + zps-* → jio-partners-scroll + jps-*
- zib-combo-info-card + zci-* → jio-combo-info-card + jci-*
- zibsvg- → jiosvg-, including the exported picker helpers and the
  fa-zibsvg- family-prefix guard
- zibpay_type → pay_type; the dual-read fallback in PostQuery collapses
  to a single key
- [zibajax="submit"] dropped (no PHP emitter); zib_ajax.success →
  jio_ajax.success
- aks-* admin Vue classes → jio-*
- zib_palette() → palette()

User-facing vendor traces are gone: the floating "more" button default no
longer ships a zibll.com link or 子比主题 marketing copy, the notice
widget ships no preset promo entries, and the four 官网教程 links are
removed from the post-list admin schema.

Test infrastructure renamed to neutral names (docker-compose.reference.yml,
Caddyfile, article-baseline.json, modern-widgets-contract.test.cjs) with
their DB/volume/env/slug identifiers following.

Kept intentionally, documented in tests/fixtures/README.md: the golden
fixture reference fingerprint (a SHA-256 record of an external directory,
read by validate-widget-golden / generate-widget-golden-fixture /
audit-widget-system), the doesNotMatch guards that prove the old tokens are
gone, and the reference theme's own meta key in the visual fixtures.

No backward-compat or data migration: saved widget configs holding old
icon keys or the old field ID silently lose them, per the project's
rewrite-over-compatibility rule.

Verified: 132/132 node contract tests, php tests/widget-system/run.php
(146 assertions), audit-admin-contracts, audit-psr4-paths and
validate-widget-golden all green; tracked bundles regenerated and in sync.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-11-02 00:06:54 +08:00

104 lines
5.6 KiB
JavaScript

/**
* 「收款接口显示顺序」契约(后台 sortable 控件 → 收银台支付方式顺序)。
*
* 背景:后台 收款接口 → 收款接口显示顺序 曾整列空白、保存后顺序被静默改写,
* 且没有任何前台代码消费该配置。本契约锁定四件事:
* 1) sortable 项使用 JioField 实际读取的 `label` 键(写 `title` 会渲染成空白);
* 2) 持久化必须保留用户提交的键顺序(键顺序即配置值本身);
* 3) 排序只保留一份实现,并且真的作用到收银台;
* 4) 相关的重复实现 / 死模块不回潮。
*/
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 schemaPhp = read('src', 'Admin', 'Schema', 'SchemaPayment.php');
const schemaJs = read('assets', 'admin', 'src', 'schema', 'sections', 'payment', 'gateway.js');
const aksField = read('assets', 'admin', 'src', 'components', 'form', 'JioField.vue');
const provider = read('src', 'Admin', 'SchemaProvider.php');
const pay = read('inc', 'helpers', 'jio-pay.php');
const gateway = read('src', 'Payment', 'Gateway.php');
const wallet = read('inc', 'helpers', 'user-center-renderers', 'credits-wallet.php');
const pointsRenderer = read('inc', 'helpers', 'user-center-renderers', 'credits-balance.php');
const vipPackages = read('src', 'Content', 'VipServicePackages.php');
const balanceCharge = read('src', 'Credits', 'BalanceServiceCharge.php');
test('sortable 项使用渲染组件读取的 label 键', () => {
// 渲染侧读取 item.label —— items 里写 title 会让每一行只剩上下箭头。
assert.match(aksField, /\{\{\s*item\.label\s*\}\}/);
assert.match(schemaJs, /items:\s*\[\s*\{ key: 'wechat', label: '微信' \}/);
assert.match(schemaJs, /\{ key: 'card_pass', label: '卡密' \}/);
assert.doesNotMatch(schemaJs, /title: '微信'/);
assert.match(schemaPhp, /\['key' => 'wechat', 'label' => '微信'\]/);
assert.match(schemaPhp, /\['key' => 'card_pass', 'label' => '卡密'\]/);
assert.doesNotMatch(schemaPhp, /'key' => 'wechat', 'title' => '微信'/);
});
test('sortable 持久化保留提交键顺序', () => {
const start = provider.indexOf("if ($type === 'sortable') {");
const end = provider.indexOf("if ($type === 'link') {", start);
assert.ok(start >= 0 && end > start, 'SchemaProvider 应存在 sortable 分支');
const block = provider.slice(start, end);
const resultLoopStart = block.indexOf('foreach ($value as $item_key');
assert.ok(resultLoopStart > 0, '结果必须按提交顺序(遍历 $value)构建');
const definitionsLoop = block.slice(0, resultLoopStart);
const resultLoop = block.slice(resultLoopStart);
// items 定义只用于白名单校验,不得据此产出结果键顺序。
assert.doesNotMatch(definitionsLoop, /\$items\[/);
assert.match(definitionsLoop, /\$definitions\[\$item_key\] = \$item_schema;/, 'items 定义应收敛为白名单');
// 键顺序即排序值:按提交顺序写入结果,且丢弃未定义的槽位键。
assert.match(resultLoop, /!isset\(\$definitions\[\$item_key\]\)/);
assert.match(resultLoop, /\$items\[\$item_key\] = \$this->sanitize_object_fields/);
});
test('排序只有一份实现,无调用方的重复实现已移除', () => {
assert.match(pay, /if \(!function_exists\('jio_sort_payment_methods'\)\)/);
assert.match(pay, /function jio_sort_payment_methods\(array \$methods\): array/);
assert.match(pay, /\$configured = is_array\(\$settings\) \? \(\$settings\['pay_sdk_order'\] \?\? \[\]\) : \[\];/);
// 槽位映射与后台 sortable 项一一对应,易支付子通道随所属渠道槽位。
assert.match(pay, /'wechat'\s+=> \['wechatpay', 'epay_wxpay'\]/);
assert.match(pay, /'alipay'\s+=> \['alipay', 'epay_alipay'\]/);
assert.match(pay, /'card_pass' => \['card_pass'\]/);
// 旧的无调用方实现(第二套排序逻辑)已删除。
assert.doesNotMatch(gateway, /function get_available\(/);
assert.doesNotMatch(gateway, /function apply_sdk_order\(/);
assert.doesNotMatch(gateway, /function get_status_summary\(/);
});
test('基础渠道列表与卡密渠道都经过同一排序实现', () => {
// 内容收银台 / 会员购买 / 余额充值共用基础列表。
assert.match(pay, /return jio_sort_payment_methods\(\$methods\);/);
assert.match(balanceCharge, /jio_sort_payment_methods\(\$methods\)/);
assert.match(vipPackages, /jio_sort_payment_methods\(\$methods\)/);
// 购买积分弹窗自建余额 + 卡密列表,同样排序。
assert.match(pointsRenderer, /jio_sort_payment_methods\(\$points_methods\)/);
// 卡密输入盒是 method 条件块,位置无关:卡密渠道可被移到任意槽位。
assert.match(wallet, /in_array\('card_pass', array_column\(\$methods, 'id'\), true\)/);
});
test('会员弹窗不再保留重复的支付方式列表回退分支', () => {
assert.doesNotMatch(vipPackages, /class_exists\('\\\\Jio\\\\Payment\\\\Gateway'\)/);
// 卡密渠道注入契约保持。
assert.match(vipPackages, /'id' => 'card_pass', 'name' => __\('卡密', 'jio'\)/);
});
test('未被引用的后台 schema 默认值死模块不回潮', () => {
assert.equal(
fs.existsSync(path.join(root, 'assets', 'admin', 'src', 'constants', 'settings-defaults.js')),
false,
'settings-defaults.js 无任何引用方,应保持删除'
);
const schemaIndex = read('assets', 'admin', 'src', 'schema', 'index.js');
assert.doesNotMatch(schemaIndex, /getSchemaDefaults|getSchemaSectionKeys|getDomainSchemas|CANONICAL_SCHEMA_DOMAINS/);
});