Files
wordpress-theme-aksrt/tests/admin/charge-cards-pagination.test.cjs
T
kate522 7b83893cf6 feat(admin): 支付中心列表页统一为 WP 原生分页
七个支付中心列表页(订单明细 / 售后服务 / 付费文章 / 优惠码 / 创作分成 /
提现审核 / 卡密管理)的分页收敛到共享助手 AdminListPagination,改用 WP 原生
paginate_links() 渲染。

- 基准 URL 显式以 page 参数 + 当前筛选参数拼装,不再让两个参数的
  add_query_arg('paged', ...) 退回 $_SERVER['REQUEST_URI']:链接不再随当前
  请求形态漂移,列条件也不再丢失
- 售后服务从写死的 limit => 100 改为 limit/offset 分页,总数接上原本零调用的
  AfterSaleService::count();after_sale_status 先按状态常量归一,保证 count()
  与 admin_list() 口径一致,分页条不说谎
- 每页 20 条,统一来自 AdminListPagination::DEFAULT_PER_PAGE
- 每页在查询前夹取页码,越界时回退到最后一页并重查,杜绝「空表且没有分页条」
  的死胡同;页码夹取与早退也一并收敛到助手
- 提现审核的翻页链接排除 retry_id / retry_key 两个一次性动作参数
- 订单明细的标签页与翻页链接共用同一份筛选参数,不再各写一份

清理死代码:五个页面里近乎复制的 render_pagination()(其中订单明细与提现审核
两份从未被调用)、两页重复的内联 paginate_links 块、卡密管理的 render_pagenavi()
及其 docblock、PayChargeCards::DEFAULT_PER_PAGE 常量,以及只服务自定义禁用
按钮的 .tablenav-pages a.disabled 规则。

验证:tests/admin/ 76 项通过(新增 pay-pages-pagination.test.cjs,重写
charge-cards-pagination.test.cjs);用 git show HEAD 取出原文件对比,订单明细
与售后服务两页在新旧实现下渲染输出逐字节相同。
2026-10-08 22:22:08 +08:00

106 lines
5.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use strict';
/**
* 「卡密管理」分页契约。
*
* 背景(两次演进):
* 1) 翻页链接此前写作 add_query_arg('paged', N) —— 只有两个参数时 WordPress 会落到
* $_SERVER['REQUEST_URI'],链接绑在当前请求上,随请求形态漂移,列条件也容易丢。
* 2) render_pagenavi() 的 URL 推导是一串 if/elseif。页码越界时(例如 per_page 缩小后
* URL 里还留着 paged=2,而总数只剩 1 页)三个分支一个都不命中,上一页/下一页
* 同时停在 '#':按钮既不打 disabled 也点不动,还停在「第 1 页」状态无法自洽。
* 极端情况下 offset 落到数据之外,表格直接变空态并且连分页条都不输出,只能手改 URL 退出。
* 3) ice_perpage 只在服务端被读取、默认每页条数偏大。条数小于默认值时永远只有一页,
* 四个按钮恒为 disabled —— 看上去就是「分页是坏的」。
*
* 现状:渲染已迁移到共享助手 AdminListPagination(WP 原生 paginate_links),
* 基准 URL 与页码夹取都在那里做。本文件只固化卡密管理仍特有的部分:
* - ice_perpage 仍作为 URL 参数被尊重,且工具栏不新增「每页显示」控件;
* - 越界页码回退到最后一页并重查,不留空态死胡同;
* - 筛选参数白名单化拼装,排序与每页条数随链接带走。
*/
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 page = fs.readFileSync(path.join(root, 'src/Admin/Native/PayChargeCards.php'), 'utf8');
const count = (src, pattern) => (src.match(pattern) || []).length;
const slice = (from, to) => {
const start = page.indexOf(from);
const end = page.indexOf(to);
if (start === -1 || end === -1 || end <= start) {
throw new Error(`切片失败:${from} → ${to}`);
}
return page.slice(start, end);
};
const list = slice('private function render_list(', 'private function render_add_form(');
const args = slice('private function list_query_args(', 'private function print_assets(');
test('渲染已迁移到共享助手 AdminListPagination', () => {
assert.match(
list,
/AdminListPagination::render\(self::PAGE_SLUG, \$count_all, \$paged, \$per_page, \$query_args\);/
);
// 自定义标记与专属常量都应清空。
assert.doesNotMatch(page, /render_pagenavi/);
assert.doesNotMatch(page, /first-page button|prev-page button|next-page button|last-page button/);
assert.doesNotMatch(page, /public const DEFAULT_PER_PAGE/);
assert.doesNotMatch(page, /self::DEFAULT_PER_PAGE/);
assert.match(page, /AdminListPagination::DEFAULT_PER_PAGE/);
// 只服务自定义禁用按钮的 CSS 规则一并删除。
assert.doesNotMatch(page, /\.tablenav-pages a\.disabled \{ pointer-events: none; \}/);
});
test('ice_perpage 仍作为 URL 参数被尊重,默认每页 20', () => {
assert.match(
list,
/\(int\) \(\$_REQUEST\['ice_perpage'\] \?\? AdminListPagination::DEFAULT_PER_PAGE\)/
);
// 明确约定:不在列表工具栏增加「每页显示」下拉/按钮。
assert.doesNotMatch(page, /per_page_selector/);
assert.doesNotMatch(page, /PER_PAGE_CHOICES/);
assert.doesNotMatch(page, /aksrt-perpage-form/);
assert.doesNotMatch(page, /name="ice_perpage"/);
assert.doesNotMatch(page, /order-header-right/);
// 但 ice_perpage 仍随链接带走(无界面入口,供 URL / 程序化调用)。
assert.match(args, /\$args\['ice_perpage'\] =/);
// 工具栏保持「过滤链接 + 搜索表单」两段式。
assert.match(list, /echo '<div class="order-header"><ul class="subsubsub">' \. \$but_html \. '<\/ul>' \. \$search \. '<\/div>';/);
});
test('越界页码回退到最后一页,不再出现无分页条的死胡同', () => {
assert.match(list, /\$max_page\s*=\s*max\(1, \(int\) \(\$result\['max_pages'\] \?\? 1\)\)/);
assert.match(list, /if \(!\$items && \$count_all > 0 && \$paged > \$max_page\)/);
assert.match(list, /\$paged\s*=\s*\$max_page;/);
// 回退之后必须重查,否则表格依旧是空的。
assert.equal(count(list, /\$svc->get_list\(/g), 2);
// 分页条必须在空态分支之后渲染,否则空态 return 会把分页条一起吞掉。
assert.ok(
list.indexOf('AdminListPagination::render(') > list.indexOf('未找到对应内容'),
'分页渲染必须晚于空态处理'
);
});
test('筛选参数白名单化拼装,排序与每页条数随链接带走', () => {
for (const key of ['status', 'type', 'other', 's', 'orderby', 'desc', 'ice_perpage']) {
assert.match(args, new RegExp(`\\$args\\['${key}'\\] =`), `缺少 ${key}`);
}
// 默认值不进 URL,避免链接里塞满无意义参数。
assert.match(args, /\$orderby !== 'modified_time'/);
assert.match(args, /\$order === 'ASC'/);
assert.match(args, /\$per_page !== AdminListPagination::DEFAULT_PER_PAGE/);
// paged 由 AdminListPagination 单独追加,不能混进基准参数里(否则会被自己覆盖)。
assert.doesNotMatch(args, /\$args\['paged'\]/);
});