P1 排序图标跑到「销售数据」文字前面: 原因是注册了 manage_edit-post_sortable_columns。注册后 WordPress 会给 该列套上自己的排序指示器与 <a>,而列头 label 里已经自带一个 <a>, 浏览器解析嵌套 <a> 时把内层内容抛出,箭头因而显示到文字之前。 参考主题不注册该钩子,靠 label 链接 + pre_get_posts 落地排序,现改为 同一做法,同时删掉因此变成死代码的钩子与方法。 P2 快捷/批量编辑新增的配置项不可用,只显示为纯文本: render_edit_boxes() 用 wp_kses_post() 包裹面板输出。该函数按「文章 内容」白名单过滤,select/option/input/fieldset/legend/label 均不在 白名单内,标签被剥掉只剩文字,于是所有控件退化成纯文本。改为原样 输出,字段值在生成时已逐个 esc_attr/esc_html 转义。 同步更新两份契约测试:sales 列表头排序改为断言「不注册 sortable」, 并为快捷编辑新增一条防回归断言,锁住不得再用 wp_kses_post 包裹面板。 验证:node --test tests/widget-system/*.test.cjs 444 项全部通过 (原有 443 项 + 新增 1 项);php -l 无语法错误。
138 lines
6.6 KiB
JavaScript
138 lines
6.6 KiB
JavaScript
const test = require('node:test')
|
||
const assert = require('node:assert/strict')
|
||
const fs = require('node:fs')
|
||
const path = require('node:path')
|
||
|
||
const root = path.resolve(__dirname, '../..')
|
||
const read = file => fs.readFileSync(path.join(root, file), 'utf8')
|
||
|
||
const list = read('src/Admin/Native/PostListEnhance.php')
|
||
const domain = read('src/Core/DomainModel.php')
|
||
const tags = read('inc/helpers/aksrt-frontend-tags.php')
|
||
const pay = read('inc/helpers/aksrt-pay.php')
|
||
|
||
test('admin post list has the interaction stats column', () => {
|
||
assert.match(list, /manage_post_posts_columns/)
|
||
assert.match(list, /互动数据/)
|
||
assert.match(list, /manage_post_posts_custom_column/)
|
||
assert.match(list, /COLUMN_STATS/)
|
||
})
|
||
|
||
test('admin post list has the sales data column with a sortable header', () => {
|
||
// 表头「销售数据」必须是可点击排序链接,排序键为 sales_volume。
|
||
assert.match(list, /销售数据/)
|
||
assert.match(list, /'aksrt_sales'/)
|
||
assert.match(list, /orderby.*sales_volume/)
|
||
|
||
// 排序靠表头 label 里的链接 + pre_get_posts 落地,不注册 sortable_columns:
|
||
// 注册后 WordPress 会套自己的排序指示器与 <a>,与 label 里的 <a> 嵌套,
|
||
// 箭头会被解析到文字前面。
|
||
assert.doesNotMatch(list, /manage_edit-post_sortable_columns/)
|
||
assert.match(list, /pre_get_posts/)
|
||
assert.match(list, /'sales_volume'/)
|
||
assert.match(list, /RESOURCE_SALES_VOLUME_KEY/)
|
||
})
|
||
|
||
test('sales column renders pay type, price and both sales counts', () => {
|
||
// 未开启付费 → 提示;已开启 → 类型链接 + 价格 + 销量。
|
||
assert.match(list, /未开启付费/)
|
||
assert.match(list, /pay_type_labels\(\)/)
|
||
assert.match(list, /真实销量/)
|
||
assert.match(list, /get_content_sales_map/)
|
||
// 第一行取自 _aksrt_pay,第二行的销量取自 _aksrt_sales_volume。
|
||
assert.match(list, /aksrt_get_pay_meta/)
|
||
assert.match(list, /DomainModel::RESOURCE_SALES_VOLUME_KEY/)
|
||
// 真实销量链接必须跳到订单页(aksrt_orders)并按商品 ID 过滤。
|
||
assert.match(list, /'aksrt_orders'/)
|
||
assert.match(list, /'product_id'/)
|
||
})
|
||
|
||
test('interaction counts are read from the keys the theme actually writes', () => {
|
||
// 计数一律落在这几个 meta key 上:aksrt_post_views / _aksrt_like_count /
|
||
// _aksrt_favorite_count / _aksrt_res_download_count。列回调只能用同一套键,
|
||
// 否则全列恒为 0。旧实现读 'views' / 'like' / 'favorite',仓库里没有任何写入方。
|
||
assert.match(tags, /'_aksrt_like_count'/)
|
||
assert.match(tags, /'_aksrt_favorite_count'/)
|
||
assert.match(tags, /'aksrt_post_views'/)
|
||
|
||
assert.match(list, /aksrt_get_post_views\(\$post_id\)/)
|
||
assert.match(list, /aksrt_get_post_likes\(\$post_id\)/)
|
||
assert.match(list, /aksrt_get_post_favorites\(\$post_id\)/)
|
||
|
||
// 资源下载量只有 _aksrt_res_download_count 一个写入键。
|
||
assert.match(list, /DomainModel::RESOURCE_DOWNLOAD_COUNT_KEY/)
|
||
assert.match(domain, /RESOURCE_DOWNLOAD_COUNT_KEY\s*=\s*'_aksrt_res_download_count'/)
|
||
|
||
// 不得再直接读这些不存在的键('download_count' 只作为历史遗留键回退读取)。
|
||
const column = list.slice(list.indexOf('private function render_stats_column'), list.indexOf('private function render_sales_column'))
|
||
assert.doesNotMatch(column, /get_post_meta\(\$post_id, 'views'/)
|
||
assert.doesNotMatch(column, /get_post_meta\(\$post_id, 'like'/)
|
||
assert.doesNotMatch(column, /get_post_meta\(\$post_id, 'favorite'/)
|
||
})
|
||
|
||
test('admin stats column shares the aksrt_format_number abbreviation', () => {
|
||
// 后台列与前台必须同一套缩写口径(≥10000 → 1.2W+,≥1000 → 1.5K)。
|
||
assert.match(list, /aksrt_format_number\(\$n\)/)
|
||
const column = list.slice(list.indexOf('private function render_stats_column'), list.indexOf('private function render_sales_column'))
|
||
assert.doesNotMatch(column, /'万'/)
|
||
})
|
||
|
||
test('sortable meta maps the public orderby keys to the stored keys', () => {
|
||
assert.match(list, /'views'\s*=>\s*\\AKSRT\\Core\\DomainModel::POST_VIEW_COUNT_KEY/)
|
||
assert.match(list, /'like'\s*=>\s*'_aksrt_like_count'/)
|
||
assert.match(list, /'favorite'\s*=>\s*'_aksrt_favorite_count'/)
|
||
assert.match(list, /'download_count'\s*=>\s*\\AKSRT\\Core\\DomainModel::RESOURCE_DOWNLOAD_COUNT_KEY/)
|
||
// meta_key 必须是真实存储键,而不是用户可见的排序键。
|
||
assert.match(list, /\$query->set\('meta_key', \$this->stats_meta_key\(\$orderby\)\)/)
|
||
})
|
||
|
||
test('every counter meta key the admin column renders has a writer', () => {
|
||
// 逐键核对:列里展示的每项计数都必须在某个 helper 里真正被写入过。
|
||
// _aksrt_sales_volume 的写入方是付费订单流水(PayOrders),不在这份清单里,
|
||
// 由下一个用例单独覆盖。
|
||
const sources = [
|
||
'src/Admin/Native/PostListEnhance.php',
|
||
'inc/helpers/aksrt-frontend-tags.php',
|
||
'src/Core/PostMetaBox.php',
|
||
'src/Frontend/PageAdminSet.php',
|
||
].map(read).join('\n')
|
||
|
||
for (const key of ['aksrt_post_views', '_aksrt_like_count', '_aksrt_favorite_count']) {
|
||
assert.ok(sources.includes(`'${key}'`), `${key} is written somewhere`)
|
||
}
|
||
// 资源帖的浏览有独立键,aksrt_get_post_views() 取两者较大值。
|
||
assert.match(tags, /_aksrt_res_view_count/)
|
||
assert.match(read('inc/helpers/aksrt-resource.php'), /RESOURCE_VIEW_COUNT_KEY|_aksrt_res_view_count/)
|
||
|
||
// 资源下载走普通外链,主题内没有任何写入方,所以下载项只在 >0 时出现。
|
||
assert.match(list, /\$downloads <= 0/)
|
||
assert.match(list, /\$legacy_downloads/)
|
||
})
|
||
|
||
test('sales volume is written by the order pipeline', () => {
|
||
// _aksrt_sales_volume 的写入方在付费助手与订单处理器里,列本身只读。
|
||
assert.match(pay, /aksrt_bump_content_sales_volume/)
|
||
assert.match(pay, /RESOURCE_SALES_VOLUME_KEY/)
|
||
assert.match(read('src/Content/PayOrders.php'), /aksrt_bump_content_sales_volume/)
|
||
})
|
||
|
||
test('admin column icons stay behind the Font Awesome enqueue on edit.php', () => {
|
||
assert.match(list, /admin_enqueue_scripts[\s\S]*?edit\.php[\s\S]*?aksrt_enqueue_fontawesome\(\)/)
|
||
for (const klass of ['fa-eye', 'fa-thumbs-up', 'fa-star', 'fa-download']) {
|
||
assert.match(list, new RegExp(klass))
|
||
}
|
||
})
|
||
|
||
test('the old closure-based column implementation is gone from core-hooks', () => {
|
||
// 列与排序逻辑统一收在 src/Admin/Native/PostListEnhance.php,inc/core-hooks.php
|
||
// 不再重复注册,否则同一列会被渲染两次。
|
||
const hooks = read('inc/core-hooks.php')
|
||
assert.doesNotMatch(hooks, /manage_post_posts_columns/)
|
||
assert.doesNotMatch(hooks, /manage_post_posts_custom_column/)
|
||
assert.doesNotMatch(hooks, /aksrt_stats/)
|
||
})
|
||
|
||
test('PostListEnhance is wired up from functions.php', () => {
|
||
assert.match(read('functions.php'), /PostListEnhance::get_instance\(\)->init\(\)/)
|
||
})
|