Files
wordpress-theme-aksrt/tests/admin/charge-cards-pagination.test.cjs
kate522andClaude Opus 5 35bb96e82f refactor: rename the theme brand and every identifier from AKSRT to Jio
Full-project rename. `git grep -i aksrt` is now empty apart from the
DBMigrator 3.3.0 changelog note that documents the rename itself.

Code identifiers
- PHP namespace AKSRT\ -> Jio\ (composer PSR-4 + 378 declarations)
- global constants AKSRT_* -> JIO_* (VERSION/DIR/URI, PAY_TYPE_*, CAPTCHA_*)
- procedural functions, hooks, options, meta keys, table names,
  cookies, query vars, ajax actions and nonces: aksrt_ -> jio_
- text domain aksrt -> jio; Theme Name -> Jio
- JS globals: aksrtData/aksrtAdminData/... -> jioData/jioAdminData/...,
  window.AKSRTCaptcha & friends -> window.JioCaptcha, Aksrt* -> Jio*
- Vue components Aks*.vue -> Jio*.vue (15 files renamed + imports)
- CSS classes/data-attrs/custom properties: aksrt- -> jio-
- REST namespace aksrt/v1 -> jio/v1 (incl. payment gateway callbacks)

Storage layer
- DB tables {prefix}aksrt_* -> {prefix}jio_*; SCHEMA_VERSION 3.2.0 -> 3.3.0
  so dbDelta creates the new names on install
- widget id_base aksrt_widget_* -> jio_widget_*
- uninstall.php drop list rebuilt from the actual migrator tables
  (dropped 3 stale entries, added the 17 missing ones)

Removed
- hardcoded https://api.aksrt.com/version.json default update endpoint;
  get_endpoint() now reads the 「更新端点」 setting and do_check() no-ops
  when it is empty

Files renamed: inc/helpers/aksrt-*.php -> jio-*.php (54),
assets/admin/src/components/form/Aks*.vue -> Jio*.vue (15).
Build artifacts regenerated: assets/css/build, assets/js/runtime,
assets/admin/build.

Verified: audit-psr4-paths errors=0, audit-admin-contracts errors=0
(warnings=56, unchanged baseline), 813/813 widget-system tests,
44/44 phpunit, php -l clean across all PHP.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-11-01 20:05:04 +08:00

115 lines
5.5 KiB
JavaScript

'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,四按钮标记由助手统一输出
* (与这里原来那套逐行一致),基准 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, '..', '..');
// PayChargeCards 已拆成 flat sibling traits;按方法名切片需要「类体 + trait 体」仍在同一文本里
// 保持原有相邻顺序,故按固定顺序拼接壳类与三个 trait(壳类 → List → Assets → Forms)。
const page = [
'src/Admin/Native/PayChargeCards.php',
'src/Admin/Native/PayChargeCardsList.php',
'src/Admin/Native/PayChargeCardsAssets.php',
'src/Admin/Native/PayChargeCardsForms.php',
].map((rel) => fs.readFileSync(path.join(root, rel), 'utf8')).join('\n');
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, /jio-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'\]/);
});