Files
wordpress-theme-aksrt/tests/widget-system/post-list-quick-edit-contract.test.cjs
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

218 lines
10 KiB
JavaScript
Raw Permalink 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.
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 fields = read('src/Admin/Native/PostListEditFields.php')
const domain = read('src/Core/DomainModel.php')
const pay = read('inc/helpers/jio-pay.php')
const metabox = read('src/Core/PostMetaBox.php')
const resourceBox = read('src/Core/ResourceMetaBox.php')
test('quick and bulk edit boxes hook the post list with no topics column', () => {
// 参考主题把配置项挂在「专题」分类法列上;本项目没有专题,统一挂在互动数据列。
assert.match(list, /quick_edit_custom_box/)
assert.match(list, /bulk_edit_custom_box/)
assert.match(list, /COLUMN_STATS/)
// 代码里不得再出现专题分类法列(类文档块的说明除外,只看 namespace 之后的部分)。
const code = list.slice(list.indexOf('namespace Jio'))
assert.doesNotMatch(code, /taxonomy-topics/)
assert.doesNotMatch(code, /topics/)
})
test('every quick-edit field group is rendered', () => {
// 四组字段的入口函数与分组标识都要存在,缺一组该列就少一块配置。
assert.match(list, /render_edit_boxes/)
assert.match(list, /PostListEditFields::extended\(\)/)
assert.match(list, /PostListEditFields::pay\(\$this->currency_symbol\(\)\)/)
assert.match(list, /filter_fields_html\(\)/)
assert.match(list, /'pay'/)
assert.match(list, /'filter'/)
// 字段定义本体放在独立的 PostListEditFields,两个静态方法必须真的存在。
assert.match(fields, /function extended\(\): array/)
assert.match(fields, /function pay\(string \$currency\): array/)
})
test('quick-edit inputs are namespaced under jio_bulk_edit', () => {
// 字段名统一 jio_bulk_edit[{id}][{field}],避免与 WP 原生数据冲突。
assert.match(list, /jio_bulk_edit\[' \. \$id \. '\]\[' \. \$field\['id'\] \. '\]/)
// number 类型带运算子字段。
assert.match(list, /\[operation\]/)
assert.match(list, /\[val\]/)
})
test('quick-edit markup is emitted without kses stripping the controls', () => {
// select/option/input/fieldset/legend 不在 wp_kses_post 的文章白名单里,
// 一旦用它包上面板,所有控件会被剥成纯文本(表现为「新增的操作都不可用」)。
// 字段值均已在生成时 esc_attr/esc_html 转义,这里必须原样输出。
assert.doesNotMatch(list, /wp_kses_post\(\$this->edit_boxes_html\(\)\)/)
// 面板必须真的产生表单控件,而不是纯文本。
assert.match(list, /<select name=/)
assert.match(list, /<input type="text" name=/)
assert.match(list, /<fieldset/)
assert.match(list, /edit_boxes_html\(\)/)
})
test('field-group descriptions are emitted as pre-escaped HTML', () => {
// 组描述由调用方预先转义后整体传入(付费参数那组自带 <div class="c-yellow">),
// 渲染处若再 esc_html,标签会直接显示成文字。
assert.doesNotMatch(list, /esc_html\(\$desc\)/)
// 两个调用点都必须自带转义/HTML 包裹,不能传裸翻译串。
assert.match(list, /'<div class="c-yellow">' \. esc_html__\('批量设置付费参数/)
assert.match(list, /'<div class="px12 jio-bulk-edit-desc">' \. esc_html__\('让文章实现更加精细化/)
// 仍来自设置项的字段标题必须保持转义。
assert.match(list, /esc_html\(\$title_html\)/)
})
test('number fields support the full operation set', () => {
for (const op of ['ignore', 'set', 'plus', 'subtract', 'multiply', 'division']) {
assert.match(list, new RegExp(`value="${op}"`), `${op} operation exists`)
}
})
test('all control types used by the fields exist in the renderer', () => {
for (const type of ['number', 'checkbox', 'switcher', 'radio', 'select']) {
assert.match(list, new RegExp(`case '${type}':`), `${type} renderer exists`)
}
})
test('interaction quick-edit fields write to the keys the theme reads', () => {
// 阅读量/点赞数是唯一可批量设置的互动项,键必须与前端 helper 一致。
assert.match(fields, /'views'/)
assert.match(fields, /'like'/)
assert.match(metabox, /DomainModel::POST_VIEW_COUNT_KEY, max\(0, \(int\) \(\$raw\['views'\]/)
assert.match(metabox, /'_jio_like_count', max\(0, \(int\) \(\$raw\['like'\]/)
// 保存侧按这两个键分流到统计 meta。
assert.match(list, /stats_meta_key/)
})
test('article config quick-edit fields match the extension meta box keys', () => {
// 显示布局 + 两个开关必须与「文章扩展」metabox 的键名一致。
assert.match(metabox, /show_layout/)
assert.match(metabox, /no_article-navs/)
assert.match(metabox, /article_maxheight_xz/)
// 布局取值白名单:metabox 的 sanitize 是裸数组,快捷编辑提供同四值的单选。
assert.match(metabox, /\['false', 'no_sidebar', 'sidebar_left', 'sidebar_right'\]/)
assert.match(fields, /'false'\s+=> __\('跟随主题', 'jio'\)/)
assert.match(fields, /'no_sidebar'\s+=> __\('无侧边栏', 'jio'\)/)
assert.match(fields, /'sidebar_left'\s+=> __\('侧边栏靠左', 'jio'\)/)
assert.match(fields, /'sidebar_right'\s+=> __\('侧边栏靠右', 'jio'\)/)
// 保存侧再用同一份白名单收敛,避免快捷编辑写入未知布局。
assert.match(list, /\['false', 'no_sidebar', 'sidebar_left', 'sidebar_right'\]/)
// 落到 jio_post_meta 这个共享 bag 上,与 metabox 同一键(self::META_KEY)。
assert.match(metabox, /post_meta/)
assert.match(list, /PostMetaBox::META_KEY/)
})
test('pay quick-edit fields map onto the real pay meta schema', () => {
// 逐字段核对:快捷编辑写入的键必须存在于默认付费 meta / metabox 中。
const payKeys = [
'pay_type', 'pay_modo', 'pay_limit',
'points_price', 'vip1_points', 'vip2_points',
'price', 'original_price', 'vip1_price', 'vip2_price',
'pay_count', 'coupon_s', 'order_expired_s',
'order_expired_time', 'order_expired_unit', 'show_sales',
]
for (const key of payKeys) {
assert.match(domain, new RegExp(`'${key}'\\s*=>`), `pay meta has ${key}`)
assert.match(fields, new RegExp(`'${key}'`), `quick edit offers ${key}`)
}
})
test('pay quick-edit save sanitizes through the shared sanitizer', () => {
// 任何批量写入都必须走统一写入口 persist_pay_meta:先 sanitize_pay_meta,再回写派生键。
assert.match(list, /DomainModel::persist_pay_meta\(\$post_id, \$pay_meta\)/)
const persist = domain.slice(domain.indexOf('function persist_pay_meta('), domain.indexOf('function sanitize_downloads('))
assert.match(persist, /self::sanitize_pay_meta\(\$meta\)/)
assert.match(persist, /self::PAY_META_KEY, \$meta/)
assert.match(persist, /self::PAY_TYPE_KEY/)
assert.match(persist, /self::PAY_PRICE_KEY/)
assert.match(persist, /self::RESOURCE_MARKER_KEY/)
// _jio_pay_modo / _jio_pay_limit 没有任何读取方,已删除,不得再写。
assert.doesNotMatch(list, /'_jio_pay_modo'|'_jio_pay_limit'/)
assert.doesNotMatch(resourceBox, /'_jio_pay_modo'|'_jio_pay_limit'/)
})
test('pay prices go through the ledger, not raw floats', () => {
// 金额字段运算在「分」上进行,避免浮点误差。
assert.match(list, /balance_minor|price_to_minor/)
assert.match(list, /decimal_from_minor/)
assert.match(list, /\$pay_meta\[\$field_id\] = \\Jio\\Ledger\\AssetAmount::decimal_from_minor/)
})
test('quick-edit save ignores invalid operations and clamps negatives', () => {
assert.match(list, /apply_operation/)
assert.match(list, /case 'set'/)
assert.match(list, /case 'plus'/)
assert.match(list, /case 'subtract'/)
assert.match(list, /case 'multiply'/)
assert.match(list, /case 'division'/)
// 除法要防零。
assert.match(list, /\$current != 0 && \$value != 0/)
// 负值归零。
assert.match(list, /max\(0, \(int\) \$new\)/)
})
test('custom filter quick-edit fields reuse the settings dimensions', () => {
// 高级筛选项从同一份设置里动态生成,键名前缀与 metabox 保存一致。
assert.match(list, /get_post_filter_settings/)
assert.match(list, /custom_filter/)
assert.match(list, /'_jio_cf_' \. \$field_id/)
assert.match(metabox, /'_jio_cf_' \. \$key/)
})
test('quick-edit save is guarded by screen, autosave, revision and capability', () => {
assert.match(list, /DOING_AUTOSAVE/)
assert.match(list, /wp_is_post_revision/)
assert.match(list, /current_user_can\('edit_post', \$post_id\)/)
assert.match(list, /edit-post/)
// 只处理 post 类型,不动页面、CPT 与其他列表。
assert.match(list, /\$post->post_type !== 'post'/)
})
test('no legacy upstream field names or topic links survive', () => {
assert.doesNotMatch(list, /zib_bulk_edit/)
assert.doesNotMatch(list, /posts_zibpay/)
// 商品类型筛选字段的规范名是 pay_type,zibpay_type 是历史名。
assert.doesNotMatch(list, /zibpay_type/)
assert.doesNotMatch(list, /pay_cuont/)
// 本项目不使用「自动售卡」类目。
assert.doesNotMatch(list, /售卡/)
})
test('the old inline column code was fully migrated', () => {
// inc/core-hooks.php 不再有任何文章列表列/排序逻辑,全部由新类负责。
// (L295 的 pre_get_posts 属于前台查询,不是后台排序列增强;这里只锁列表增强的部分。)
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, /manage_edit-post_sortable_columns/)
assert.doesNotMatch(hooks, /jio_enqueue_fontawesome/)
assert.doesNotMatch(hooks, /jio_stats/)
})
test('the dead tab-pane and modal css blocks are removed', () => {
// 这些选择器无任何引用,已确认删除;文件仍保留 quick-edit 需要的轮子类。
const css = read('assets/css/admin-main.css')
assert.doesNotMatch(css, /jio-tab-pane/)
assert.doesNotMatch(css, /\.jio-modal\b/)
assert.doesNotMatch(css, /\.jio-modal-backdrop\b/)
// 快捷编辑卡片与依赖样式仍在。
assert.match(css, /\.jio-bulk-edit-box/)
assert.match(css, /\.jio-bulk-edit-desc/)
assert.match(css, /\.flex\s*\{/)
})
test('the quick-edit dependency script exists and toggles by data-dependency', () => {
assert.ok(fs.existsSync(path.join(root, 'assets/js/admin-post-list.js')))
const js = read('assets/js/admin-post-list.js')
assert.match(js, /data-dependency/)
assert.match(js, /jio_bulk_edit\[pay\]/)
assert.match(js, /inline-edit-row/)
})