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>
218 lines
10 KiB
JavaScript
218 lines
10 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 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/)
|
||
})
|