fix(admin): replicate reference advanced-filter settings page 1:1
- Rewrite post_list/filter schema (PHP + JS) with the reference theme's
field set, order, defaults and dependencies; topics fields excluded
(no topics taxonomy), AKSRT private filter_* toggles dropped
- Support dynamic checkbox options in AksField (options as formData key)
so cat_custom_filter.custom_filters lists the custom_filter dimensions
- Map PostDisplay filter settings to the new keys without legacy fallback;
cat_custom_filter now carries lists/dropdown fieldsets and array filters
- Wire the archive chain to reference semantics: cat_custom_filter
overrides per-category filter projects, custom_filter_show gates the
default display, dimension chip groups render on category/tag archives
- Filter archive queries (main query + AJAX endpoint) by _aksrt_cf_{key}
meta with exact-quoted LIKE matching
- Add advanced-filter settings reference contract test
This commit is contained in:
@@ -0,0 +1,167 @@
|
||||
/**
|
||||
* 高级筛选设置页参考主题 1:1 契约。
|
||||
*
|
||||
* 参考:zibll inc/options/admin-options.php「高级筛选」区块(5251-5424 行)与
|
||||
* 前台链路(zib-category.php zib_get_ajax_option_menu / zib_get_option_custom_filter_but、
|
||||
* zib-post.php zib_sift_posts_per_page)。
|
||||
*/
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
const ROOT = path.resolve(__dirname, '..', '..');
|
||||
const read = (rel) => fs.readFileSync(path.join(ROOT, rel), 'utf8');
|
||||
|
||||
const js = read('assets/admin/src/schema/sections/post-list/filter.js');
|
||||
const php = read('src/Admin/Schema/SchemaPostList.php');
|
||||
const postDisplay = read('src/Settings/PostDisplay.php');
|
||||
const settings = read('src/Settings/Settings.php');
|
||||
const termMeta = read('src/Core/TermMeta.php');
|
||||
const filtersHelper = read('inc/helpers/aksrt-archive-filters.php');
|
||||
const categoryTpl = read('category.php');
|
||||
const tagTpl = read('tag.php');
|
||||
const archiveJs = read('assets/js/core/archive.js');
|
||||
const globalController = read('src/Admin/GlobalController.php');
|
||||
|
||||
// 参考主题字段键(顺序即参考主题定义顺序)。
|
||||
const REFERENCE_KEYS = ['custom_filter_show', 'custom_filter', 'cat_custom_filter'];
|
||||
// AKSRT 私有旧字段(参考主题无此配置,须移除避免双路)。
|
||||
const LEGACY_KEYS = [
|
||||
'filter_enabled',
|
||||
'filter_categories',
|
||||
'filter_tags',
|
||||
'filter_sort',
|
||||
'filter_ajax_enabled',
|
||||
'filter_cat_show_cat',
|
||||
'filter_cat_show_tag',
|
||||
'filter_tag_show_cat',
|
||||
'filter_tag_show_tag',
|
||||
];
|
||||
// 参考主题字段中本项目不做的部分(无 topics 专题分类法)。
|
||||
const EXCLUDED_KEYS = ['topics_s', 'topics_lists'];
|
||||
|
||||
test('JS schema 字段键与顺序对齐参考主题', () => {
|
||||
const found = [...js.matchAll(/key:\s*'([a-z0-9_]+)',\s*(?:label|type):/g)].map((m) => m[1]);
|
||||
// 顶层字段顺序(content 提示行与嵌套子字段除外)。
|
||||
for (const key of REFERENCE_KEYS) {
|
||||
assert.ok(found.includes(key), `JS schema 缺少字段 ${key}`);
|
||||
}
|
||||
const topIdx = REFERENCE_KEYS.map((k) => js.indexOf(`key: '${k}'`));
|
||||
assert.ok(topIdx.every((v, i) => i === 0 || v > topIdx[i - 1]), '顶层字段顺序应符合参考主题');
|
||||
// 嵌套子字段。
|
||||
for (const sub of ['name', 'key', 'vals']) {
|
||||
assert.ok(new RegExp(`key:\\s*'${sub}',\\s*label:`).test(js), `custom_filter 缺少子字段 ${sub}`);
|
||||
}
|
||||
const catSub = js.slice(js.indexOf("key: 'cat_custom_filter'"));
|
||||
const catOrder = ['cats', 'cat_s', 'cat_lists', 'tag_s', 'tag_lists', 'orderby_s', 'orderby_lists', 'custom_filters'];
|
||||
const catIdx = catOrder.map((k) => catSub.indexOf(`key: '${k}'`));
|
||||
assert.ok(catIdx.every((v) => v !== -1), 'cat_custom_filter 缺少子字段');
|
||||
assert.ok(catIdx.every((v, i) => i === 0 || v > catIdx[i - 1]), 'cat_custom_filter 子字段顺序应符合参考主题');
|
||||
});
|
||||
|
||||
test('PHP schema 字段键与顺序对齐参考主题', () => {
|
||||
const filterSection = php.slice(php.indexOf("'filter' => ["));
|
||||
assert.ok(filterSection.includes("'title' => '高级筛选'"), 'PHP filter 区块缺失');
|
||||
assert.ok(filterSection.includes("'version' => '2.0.0'"), 'PHP filter 区块版本应为 2.0.0');
|
||||
const order = REFERENCE_KEYS.map((k) => filterSection.indexOf(`'${k}'`));
|
||||
assert.ok(order.every((v) => v !== -1), `PHP schema 缺少字段: ${REFERENCE_KEYS.filter((k) => order.indexOf(-1) === -1)}`);
|
||||
assert.ok(order.every((v, i) => i === 0 || v > order[i - 1]), 'PHP 字段顺序应符合参考主题');
|
||||
const catOrder = ['cats', 'cat_s', 'cat_lists', 'tag_s', 'tag_lists', 'orderby_s', 'orderby_lists', 'custom_filters'];
|
||||
const catSub = filterSection.slice(filterSection.indexOf("'cat_custom_filter'"));
|
||||
const catIdx = catOrder.map((k) => catSub.indexOf(`'${k}'`));
|
||||
assert.ok(catIdx.every((v) => v !== -1), 'PHP cat_custom_filter 缺少子字段');
|
||||
assert.ok(catIdx.every((v, i) => i === 0 || v > catIdx[i - 1]), 'PHP cat_custom_filter 子字段顺序应符合参考主题');
|
||||
});
|
||||
|
||||
test('JS schema 默认值、选项与依赖对齐参考主题', () => {
|
||||
assert.match(js, /custom_filter_show:\s*\[\]/);
|
||||
assert.match(js, /custom_filter:\s*\[\]/);
|
||||
assert.match(js, /cat_custom_filter:\s*\[\]/);
|
||||
for (const option of ['分类页面显示', '标签页面显示']) {
|
||||
assert.ok(js.includes(option), `custom_filter_show 缺少选项 ${option}`);
|
||||
}
|
||||
// 排序方式默认值与参考主题一致(orderby() default)。
|
||||
assert.match(js, /default:\s*\[\s*'modified',\s*'views',\s*'like',\s*'comment_count'\s*\]/);
|
||||
for (const option of ['更新', '发布', '浏览', '点赞', '评论', '收藏', '随机']) {
|
||||
assert.ok(js.includes(`label: '${option}'`), `排序方式缺少选项 ${option}`);
|
||||
}
|
||||
// 依赖链(参考主题 dependency)。
|
||||
assert.match(js, /key: 'cat_lists',[\s\S]{0,200}?dependsOn:\s*\{\s*key:\s*'cat_s',\s*value:\s*true\s*\}/);
|
||||
assert.match(js, /key: 'tag_lists',[\s\S]{0,200}?dependsOn:\s*\{\s*key:\s*'tag_s',\s*value:\s*true\s*\}/);
|
||||
assert.match(js, /key: 'orderby_lists',[\s\S]{0,220}?dependsOn:\s*\{\s*key:\s*'orderby_s',\s*value:\s*true\s*\}/);
|
||||
// custom_filters 为动态选项 checkbox(引用 custom_filter 维度定义)。
|
||||
assert.match(js, /key: 'custom_filters',[\s\S]{0,400}?options:\s*'custom_filter'/);
|
||||
// 提示行承载参考主题 submessage 与官网教程链接。
|
||||
assert.match(js, /type:\s*'content'/);
|
||||
assert.match(js, /ui_only:\s*true/);
|
||||
assert.match(js, /zibll\.com\/15886\.html/);
|
||||
assert.match(js, /避免与其他mate_key重复/);
|
||||
});
|
||||
|
||||
test('旧字段与 topics 在 schema 中均已移除(无双路)', () => {
|
||||
for (const legacy of LEGACY_KEYS) {
|
||||
assert.ok(!js.includes(legacy), `JS schema 残留旧字段: ${legacy}`);
|
||||
assert.ok(!php.includes(legacy), `PHP schema 残留旧字段: ${legacy}`);
|
||||
assert.ok(!postDisplay.includes(`$settings['${legacy}']`), `PostDisplay 残留旧字段读取: ${legacy}`);
|
||||
}
|
||||
for (const excluded of EXCLUDED_KEYS) {
|
||||
assert.ok(!js.includes(excluded), `JS schema 不应包含 ${excluded}(本项目无专题分类法)`);
|
||||
assert.ok(!php.includes(excluded), `PHP schema 不应包含 ${excluded}`);
|
||||
}
|
||||
// 运行时只读新键。
|
||||
assert.match(postDisplay, /\$settings\['custom_filter_show'\]/);
|
||||
assert.match(postDisplay, /\$settings\['custom_filter'\]/);
|
||||
assert.match(postDisplay, /\$settings\['cat_custom_filter'\]/);
|
||||
});
|
||||
|
||||
test('JS schema 与 PHP schema 一致性', () => {
|
||||
// JS 持久化字段 = 参考字段集;PHP 同样包含全部参考字段。
|
||||
for (const key of REFERENCE_KEYS) {
|
||||
assert.ok(php.includes(`'${key}'`), `PHP schema 缺少 ${key}`);
|
||||
}
|
||||
// addText 与参考主题 button_title 一致。
|
||||
assert.match(js, /addText:\s*'添加筛选类别'/);
|
||||
assert.match(js, /addText:\s*'添加值'/);
|
||||
assert.match(js, /addText:\s*'添加自定义分类筛选'/);
|
||||
assert.match(php, /'addText'\s*=>\s*'添加筛选类别'/);
|
||||
assert.match(php, /'addText'\s*=>\s*'添加值'/);
|
||||
assert.match(php, /'addText'\s*=>\s*'添加自定义分类筛选'/);
|
||||
// 排序选项不含 zibpay 系列与销量(本项目无对应运行时)。
|
||||
assert.ok(!js.includes('zibpay_price') && !js.includes('sales_volume'));
|
||||
});
|
||||
|
||||
test('前台链路:覆盖与门禁(参考主题 zib-category.php 语义)', () => {
|
||||
// cat 页按 cat_custom_filter 覆盖,回退 custom_filter_show 门禁。
|
||||
assert.match(postDisplay, /function get_archive_custom_filter_config\(int \$term_id, string \$page\)/);
|
||||
assert.match(postDisplay, /if \(\$page === 'cat'\)/);
|
||||
assert.match(postDisplay, /in_array\(\$term_id, \$rule\['cats'\], true\)/);
|
||||
assert.match(postDisplay, /in_array\(\$page, \$cfg\['custom_filter_show'\], true\)/);
|
||||
assert.match(postDisplay, /'custom_filters'\s*=>\s*'all'/);
|
||||
// Settings 委托。
|
||||
assert.match(settings, /function get_archive_custom_filter_config\(int \$term_id, string \$page\)/);
|
||||
// 归档设置接入覆盖与自定义筛选配置。
|
||||
assert.match(termMeta, /get_archive_custom_filter_config\(/);
|
||||
assert.match(termMeta, /'custom_filter_enabled'/);
|
||||
assert.match(termMeta, /'custom_filter_keys'/);
|
||||
assert.match(termMeta, /'custom_dimensions'/);
|
||||
});
|
||||
|
||||
test('前台链路:维度筛选渲染与查询过滤', () => {
|
||||
// 渲染:维度 chip 组(全部 chip 参考主题 focus 语义)。
|
||||
assert.match(filtersHelper, /function aksrt_archive_build_custom_filter_groups\(/);
|
||||
assert.match(filtersHelper, /data-filter-dim/);
|
||||
assert.match(filtersHelper, /\$keys !== 'all' && !in_array\(\$dim_key, \(array\) \$keys, true\)/);
|
||||
assert.match(categoryTpl, /aksrt_archive_build_custom_filter_groups\(/);
|
||||
assert.match(tagTpl, /aksrt_archive_build_custom_filter_groups\(/);
|
||||
// 查询:_aksrt_cf_{key} meta_query LIKE(主查询 + AJAX 端点)。
|
||||
assert.match(termMeta, /'_aksrt_cf_' \. \$dim_key/);
|
||||
assert.match(termMeta, /'compare'\s*=>\s*'LIKE'/);
|
||||
assert.match(globalController, /'custom_filter'\s*=>\s*\['type' => 'string'/);
|
||||
assert.match(globalController, /json_decode\(\$custom_raw, true\)/);
|
||||
assert.match(globalController, /'_aksrt_cf_' \. \$dim_key/);
|
||||
// JS:维度 chip 状态、请求参数与 URL 同步。
|
||||
assert.match(archiveJs, /data-filter-dim/);
|
||||
assert.match(archiveJs, /JSON\.stringify\(state\.custom\)/);
|
||||
assert.match(archiveJs, /state\.custom\[dimKey\]/);
|
||||
});
|
||||
Reference in New Issue
Block a user