Files
wordpress-theme-aksrt/tests/widget-system/payment-withdrawal-channels-contract.test.cjs
T
kate522 8c4aabb9a4 fix(withdrawal): wire the channel hint to the actual withdrawal modal
The previous commit put the channel-switching code in the wrong init block.
credits.js initializes two modals side by side — the points recharge dialog and
the withdrawal dialog — and the snippet landed in the recharge one, which has
no [data-jio-withdraw-method] element at all. Both symptoms followed from that:
methodField resolved to null so no change listener was ever bound, and the
initial sync never ran, leaving the note paragraph permanently hidden.

The block now lives inside initWithdraw(), and the stray copy is gone. The
behaviour itself was already correct — switching channels swaps the account
placeholder and shows or hides the channel note — it just never executed.

Verified in a headless browser against the real markup and stylesheet: the
placeholder starts as the first channel's hint and the note is hidden; switching
to the second channel swaps the placeholder and reveals the note as
display:block; switching back hides it again as display:none.

The contract test previously only checked that the code existed, which is why
it missed a placement bug that made the whole block dead. It now asserts
syncChannelHint appears exactly once, sits after initWithdraw(), and does not
run past the next function definition. 873 tests pass.
2026-11-04 19:37:56 +08:00

89 lines
4.2 KiB
JavaScript

/**
* 提现收款渠道契约。
*
* 渠道由后台「提现策略 → 收款渠道」的拖拽结构定义,可自定义名称与提示,
* 不再固定为支付宝/微信两个勾选项。前台下拉、后端校验与后台审核页都读同一份配置。
*/
'use strict';
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const test = require('node:test');
const root = path.resolve(__dirname, '..', '..');
const read = (file) => fs.readFileSync(path.join(root, file), 'utf8');
test('收款渠道在两侧 schema 都是可拖拽的 repeater', () => {
const php = read('src/Admin/Schema/SchemaPayment.php');
const js = read('assets/admin/src/schema/sections/payment/withdrawal.js');
for (const source of [php, js]) {
assert.match(source, /affiliate_payout_channels/);
assert.match(source, /(?:type:\s*'repeater'|'type'\s*=>\s*'repeater')/);
// 结构:key / label / account_hint / note / enabled
for (const field of ['key', 'label', 'account_hint', 'note', 'enabled']) {
assert.match(source, new RegExp(`key:\\s*'${field}'|'key'\\s*=>\\s*'${field}'`), field);
}
}
// 不再是勾选列表。
assert.doesNotMatch(php, /affiliate_payout_channels[^\n]*'type'\s*=>\s*'checkbox'/);
assert.doesNotMatch(js, /affiliate_payout_channels[^\n]*type:\s*'checkbox'/);
});
test('运行时按自定义渠道解析,未配置时回落内置两项', () => {
const runtime = read('src/Settings/PaymentRuntime.php');
assert.match(runtime, /public static function payout_channels\(\$raw\): array/);
assert.match(runtime, /public static function enabled_payout_channels\(\): array/);
assert.match(runtime, /public static function payout_channel\(string \$key\): \?array/);
// 不再把渠道白名单硬编码成 alipay/wechat。
assert.doesNotMatch(runtime, /array_intersect\(\['alipay', 'wechat'\]/);
// 默认渠道由 schema 层唯一定义(纯数据,不依赖运行时),运行时引用它。
assert.match(read('src/Admin/Schema/SchemaPayment.php'), /public static function default_payout_channels\(\): array/);
assert.match(runtime, /SchemaPayment::default_payout_channels\(\)/);
});
test('前台弹窗与后端校验都按渠道 key 工作', () => {
const modal = read('inc/helpers/user-center-renderers/withdraw-modal.php');
const ledger = read('src/Payment/CommissionLedger.php');
const client = read('assets/js/credits.js');
// 弹窗按配置渲染,携带自定义名称与提示。
assert.doesNotMatch(modal, /array_intersect\(\['alipay', 'wechat'\]/);
assert.match(modal, /data-hint="<\?php echo esc_attr/);
assert.match(modal, /data-note="<\?php echo esc_attr/);
assert.match(modal, /data-jio-withdraw-note/);
// 切换渠道时同步提示文案,且必须挂在提现弹窗的初始化里
// (同一文件还有充值弹窗,挂错块会让整段逻辑变成死代码)。
assert.equal((client.match(/function syncChannelHint\(\)/g) || []).length, 1);
const initAt = client.indexOf('function initWithdraw()');
const syncAt = client.indexOf('function syncChannelHint()');
const nextFnAt = client.indexOf(String.fromCharCode(10) + String.fromCharCode(9) + 'function ', syncAt + 1);
assert.ok(initAt >= 0 && syncAt > initAt, 'syncChannelHint must live inside initWithdraw()');
assert.ok(nextFnAt === -1 || syncAt < nextFnAt, 'syncChannelHint must not leak past initWithdraw()');
assert.match(client, /methodField\.addEventListener\('change', syncChannelHint\)/);
// 后端只校验渠道存在且启用,不再限定内置两项。
assert.match(ledger, /\$channel_keys = array_map/);
assert.doesNotMatch(ledger, /in_array\(\$method,\s*\[?'alipay'/);
});
test('后台提现审核读取同一份渠道配置', () => {
const page = read('src/Admin/Native/PayWithdrawals.php');
const list = read('src/Admin/Native/PayWithdrawalsList.php');
// 渠道名来自设置,而不是硬编码映射。
assert.match(page, /private function method_labels\(\): array/);
assert.match(page, /get_withdrawal_settings\(\)/);
assert.doesNotMatch(page, /'alipay' => __\('支付宝'/);
assert.match(page, /private function method_notes\(\): array/);
// 审核弹窗展示渠道说明。
assert.match(list, /'method_note' => __\('渠道说明'/);
});