Files
wordpress-theme-aksrt/tests/admin/feature-settings-reference-contract.test.cjs
T
kate522 4d6b2c6045 fix(admin): replicate reference post-feature settings page 1:1
- Rewrite post_list/feature schema (PHP + JS) with the reference theme's
  field set, order, defaults and dependencies; print/PDF toggles dropped
- Move the migrated reference fields off the post-page schema (lightbox
  six switches, post like, default view/like ranges) to kill the dual path
- Map PostDisplay::get_post_page_settings() to the new keys without legacy
  fallback: imagelightbox* (pc_s/m_s checkboxes merged), post_like_s,
  post_default_mate ranges, article_nav + article_nav_mobile_nav_s
- Wire article_nav runtime: content data-nav/no-nav gate with per-post
  override, mobile drawer TOC box, imgbox_type config
- Seed random views/likes on new posts from post_default_mate ranges
- Keep the print stylesheet enqueued unconditionally (no switch); drop the
  dead print/PDF settings and needs_print requirement
- Add post-feature settings reference contract test
2026-09-24 14:29:04 +08:00

233 lines
12 KiB
JavaScript
Raw 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.
'use strict';
/**
* 「文章功能」后台设置页契约(对齐参考主题 1:1):
* 1) PHP schema(SchemaPostList.php feature section)字段键、顺序、默认值与
* JS schema(assets/admin/src/schema/sections/post-list/feature.js)一致;
* 2) 参考主题字段全集存在:article_nav / article_nav_mobile_nav_s /
* imagelightbox / imagelightbox_type / imagelightbox_thumbs_s /
* imagelightbox_play_s / imagelightbox_zoom_s / imagelightbox_full_s /
* imagelightbox_down_s / post_like_s / post_default_mate(views/like);
* 3) 旧 AKSRT 私有/异名字段(feature_print / feature_pdf_export /
* post_lightbox* / post_like_enabled / post_default_*_min/max)已彻底移除,
* 文章页面(page)不再承载这些字段,无双路兼容;
* 4) 运行时(PostDisplay::get_post_page_settings)只读取新键,灯箱复选框
* pc_s/m_s 归并为开关,目录树与预置参数均接入运行时。
*/
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, '..', '..');
const php = fs.readFileSync(path.join(root, 'src/Admin/Schema/SchemaPostList.php'), 'utf8');
const js = fs.readFileSync(path.join(root, 'assets/admin/src/schema/sections/post-list/feature.js'), 'utf8');
const pageJs = fs.readFileSync(path.join(root, 'assets/admin/src/schema/sections/post-list/page.js'), 'utf8');
const runtime = fs.readFileSync(path.join(root, 'src/Settings/PostDisplay.php'), 'utf8');
const scripts1c = fs.readFileSync(path.join(root, 'src/Core/ScriptsPart1c.php'), 'utf8');
const scripts1b = fs.readFileSync(path.join(root, 'src/Core/ScriptsPart1b.php'), 'utf8');
const frontendTags = fs.readFileSync(path.join(root, 'inc/helpers/aksrt-frontend-tags.php'), 'utf8');
const coreHooks = fs.readFileSync(path.join(root, 'inc/core-hooks.php'), 'utf8');
const singleRender = fs.readFileSync(path.join(root, 'inc/helpers/aksrt-single-render.php'), 'utf8');
const mobileHeader = fs.readFileSync(path.join(root, 'inc/helpers/header/aksrt-header-mobile.php'), 'utf8');
const REFERENCE_KEYS = [
'article_nav',
'article_nav_mobile_nav_s',
'imagelightbox',
'imagelightbox_type',
'imagelightbox_thumbs_s',
'imagelightbox_play_s',
'imagelightbox_zoom_s',
'imagelightbox_full_s',
'imagelightbox_down_s',
'post_like_s',
'post_default_mate',
];
// 旧 AKSRT 私有 / 异名字段(打印与 PDF 导出、旧灯箱六开关、旧点赞与默认数值四键)。
const LEGACY_KEYS = [
'feature_print',
'feature_pdf_export',
'post_lightbox',
'post_lightbox_thumbs',
'post_lightbox_zoom',
'post_lightbox_full',
'post_lightbox_down',
'post_lightbox_play',
'post_like_enabled',
'post_default_views_min',
'post_default_views_max',
'post_default_likes_min',
'post_default_likes_max',
];
/** Extract a PHP section body by its section key (balanced brackets). */
function phpSection(src, sectionKey) {
const start = src.indexOf(`'${sectionKey}' => [`);
assert.notEqual(start, -1, `PHP schema has a ${sectionKey} section`);
const open = src.indexOf('[', start);
let depth = 0;
for (let i = open; i < src.length; i++) {
const ch = src[i];
if (ch === "'" || ch === '"') {
const quote = ch;
i++;
while (i < src.length) {
if (src[i] === '\\') { i += 2; continue; }
if (src[i] === quote) break;
i++;
}
continue;
}
if (ch === '[') depth++;
else if (ch === ']') {
depth--;
if (depth === 0) return src.slice(open, i + 1);
}
}
throw new Error(`unbalanced ${sectionKey} section`);
}
const featurePhp = phpSection(php, 'feature');
const pagePhp = phpSection(php, 'page');
test('PHP feature section 字段键与参考主题一致且顺序相同', () => {
// 字段定义均为 ['key' => '...', 'label' => ...] 开头;dependsOn 内的
// ['key' => '...', 'value' => ...] 不是字段定义,须排除。
const found = [];
const re = /'key'\s*=>\s*'([a-z0-9_]+)',\s*'label'/g;
let m;
while ((m = re.exec(featurePhp)) !== null) found.push(m[1]);
// post_default_mate 的 fieldset 父字段先于子字段 views / like 出现。
const expected = [...REFERENCE_KEYS.slice(0, REFERENCE_KEYS.indexOf('post_default_mate')), 'post_default_mate', 'views', 'like'];
assert.deepEqual(found, expected);
});
test('PHP feature section 不含旧私有/异名字段', () => {
for (const legacy of LEGACY_KEYS) {
assert.ok(!featurePhp.includes(`'${legacy}'`), `feature section 残留旧字段: ${legacy}`);
}
});
test('PHP feature section 默认值对齐参考主题', () => {
assert.match(featurePhp, /'article_nav',\s*'label'\s*=>\s*'文章目录树',\s*'type'\s*=>\s*'switcher',\s*'default'\s*=>\s*true/);
assert.match(featurePhp, /'article_nav_mobile_nav_s',\s*'label'\s*=>\s*'在移动端弹出菜单内显示',\s*'type'\s*=>\s*'switcher',\s*'default'\s*=>\s*true/);
assert.match(featurePhp, /'imagelightbox',\s*'label'\s*=>\s*'图片灯箱',\s*'type'\s*=>\s*'switcher',\s*'default'\s*=>\s*true/);
assert.match(featurePhp, /'imagelightbox_type',\s*'label'\s*=>\s*'图片灯箱模式',\s*'type'\s*=>\s*'radio',\s*'default'\s*=>\s*'group'/);
assert.match(featurePhp, /'imagelightbox_thumbs_s',\s*'label'\s*=>\s*'缩略图导航',\s*'type'\s*=>\s*'checkbox',\s*'default'\s*=>\s*\['pc_s',\s*'m_s'\]/);
assert.match(featurePhp, /'imagelightbox_play_s',\s*'label'\s*=>\s*'播放按钮',\s*'type'\s*=>\s*'checkbox',\s*'default'\s*=>\s*\['pc_s',\s*'m_s'\]/);
assert.match(featurePhp, /'imagelightbox_zoom_s',\s*'label'\s*=>\s*'缩放按钮',\s*'type'\s*=>\s*'checkbox',\s*'default'\s*=>\s*\['pc_s'\]/);
assert.match(featurePhp, /'imagelightbox_full_s',\s*'label'\s*=>\s*'全屏按钮',\s*'type'\s*=>\s*'checkbox',\s*'default'\s*=>\s*\['pc_s'\]/);
assert.match(featurePhp, /'imagelightbox_down_s',\s*'label'\s*=>\s*'下载按钮',\s*'type'\s*=>\s*'checkbox',\s*'default'\s*=>\s*\['pc_s'\]/);
assert.match(featurePhp, /'post_like_s',\s*'label'\s*=>\s*'文章点赞',\s*'type'\s*=>\s*'switcher',\s*'default'\s*=>\s*true/);
assert.match(featurePhp, /'post_default_mate',\s*'label'\s*=>\s*'文章预置参数',\s*'type'\s*=>\s*'fieldset'/);
assert.match(featurePhp, /'views',\s*'label'\s*=>\s*'阅读量',\s*'type'\s*=>\s*'between_number',\s*'default'\s*=>\s*\['min'\s*=>\s*20,\s*'max'\s*=>\s*50\]/);
assert.match(featurePhp, /'like',\s*'label'\s*=>\s*'点赞数',\s*'type'\s*=>\s*'between_number',\s*'default'\s*=>\s*\['min'\s*=>\s*5,\s*'max'\s*=>\s*15\]/);
});
test('PHP feature section 选项与依赖对齐参考主题', () => {
// 灯箱模式两种选项。
assert.ok(featurePhp.includes("'组合模式(将文章的全部图片组合成一组灯箱)'"), 'imagelightbox_type 缺少组合模式');
assert.ok(featurePhp.includes("'单张模式(点击某张图片仅显示该图片)'"), 'imagelightbox_type 缺少单张模式');
// 每个按钮复选框均带 PC/移动端两项。
assert.equal((featurePhp.match(/'PC端开启'/g) || []).length, 5, '五个灯箱按钮复选框均应含 PC端开启');
assert.equal((featurePhp.match(/'移动端开启'/g) || []).length, 5, '五个灯箱按钮复选框均应含 移动端开启');
// 依赖:移动端菜单 / 灯箱模式 / 缩略图+播放(组合模式)/ 缩放+全屏+下载。
assert.match(featurePhp, /'article_nav_mobile_nav_s'[\s\S]*?'dependsOn'\s*=>\s*\['key'\s*=>\s*'article_nav',\s*'value'\s*=>\s*true\]/);
assert.match(featurePhp, /'imagelightbox_type'[\s\S]*?'dependsOn'\s*=>\s*\['key'\s*=>\s*'imagelightbox',\s*'value'\s*=>\s*true\]/);
assert.match(featurePhp, /'imagelightbox_thumbs_s'[\s\S]*?'dependsOn'\s*=>\s*\['all'/);
assert.match(featurePhp, /'imagelightbox_play_s'[\s\S]*?'dependsOn'\s*=>\s*\['all'/);
assert.match(featurePhp, /'imagelightbox_zoom_s'[\s\S]*?'dependsOn'\s*=>\s*\['key'\s*=>\s*'imagelightbox',\s*'value'\s*=>\s*true\]/);
assert.match(featurePhp, /'imagelightbox_full_s'[\s\S]*?'dependsOn'\s*=>\s*\['key'\s*=>\s*'imagelightbox',\s*'value'\s*=>\s*true\]/);
assert.match(featurePhp, /'imagelightbox_down_s'[\s\S]*?'dependsOn'\s*=>\s*\['key'\s*=>\s*'imagelightbox',\s*'value'\s*=>\s*true\]/);
});
test('PHP page section 不再承载迁移字段(无双路)', () => {
for (const legacy of LEGACY_KEYS) {
assert.ok(!pagePhp.includes(`'${legacy}'`), `page section 残留迁移字段: ${legacy}`);
}
});
test('JS schema 与 PHP 字段键一致(ui_only 教程行除外)', () => {
const keys = [];
const re = /key:\s*'([a-z0-9_]+)',\s*label:/g;
let m;
while ((m = re.exec(js)) !== null) keys.push(m[1]);
const persisted = keys.filter((k) => REFERENCE_KEYS.includes(k));
assert.deepEqual(persisted, REFERENCE_KEYS);
for (const legacy of LEGACY_KEYS) {
assert.ok(!keys.includes(legacy), `JS schema 残留旧字段: ${legacy}`);
}
// 参考主题 desc 中的官网教程链接以 ui_only content 行承载。
assert.match(js, /ui_only:\s*true/);
assert.match(js, /https:\/\/www\.zibll\.com\/1717\.html/);
assert.match(js, /https:\/\/www\.zibll\.com\/683\.html/);
});
test('JS schema 默认值与选项对齐参考主题', () => {
assert.match(js, /article_nav:\s*true/);
assert.match(js, /article_nav_mobile_nav_s:\s*true/);
assert.match(js, /imagelightbox:\s*true/);
assert.match(js, /imagelightbox_type:\s*'group'/);
assert.match(js, /imagelightbox_thumbs_s:\s*\['pc_s',\s*'m_s'\]/);
assert.match(js, /imagelightbox_play_s:\s*\['pc_s',\s*'m_s'\]/);
assert.match(js, /imagelightbox_zoom_s:\s*\['pc_s'\]/);
assert.match(js, /imagelightbox_full_s:\s*\['pc_s'\]/);
assert.match(js, /imagelightbox_down_s:\s*\['pc_s'\]/);
assert.match(js, /post_like_s:\s*true/);
assert.match(js, /post_default_mate:\s*\{\s*views:\s*\{\s*min:\s*20,\s*max:\s*50\s*\},\s*like:\s*\{\s*min:\s*5,\s*max:\s*15\s*\}\s*\}/);
assert.match(js, /dependsOn:\s*\{\s*key:\s*'article_nav',\s*value:\s*true\s*\}/);
assert.match(js, /dependsOn:\s*\{\s*all:\s*\[\{ key: 'imagelightbox', value: true \},\s*\{ key: 'imagelightbox_type', value: 'group' \}\]\s*\}/);
});
test('JS page schema 不再承载迁移字段(无双路)', () => {
for (const legacy of LEGACY_KEYS) {
assert.ok(!pageJs.includes(`'${legacy}'`), `page.js 残留迁移字段: ${legacy}`);
}
});
test('旧字段在 schema 与运行时中均已移除(无双路)', () => {
for (const legacy of LEGACY_KEYS) {
assert.ok(!php.includes(legacy), `PHP schema 残留旧字段: ${legacy}`);
assert.ok(!runtime.includes(legacy), `PostDisplay 残留旧字段: ${legacy}`);
assert.ok(!js.includes(legacy), `feature.js 残留旧字段: ${legacy}`);
assert.ok(!pageJs.includes(legacy), `page.js 残留旧字段: ${legacy}`);
assert.ok(!frontendTags.includes(legacy), `aksrt-frontend-tags 残留旧字段: ${legacy}`);
}
assert.ok(!scripts1b.includes('needs_print'), 'ScriptsPart1b 残留 needs_print 分支');
assert.ok(!scripts1c.includes('needs_print'), 'ScriptsPart1c 残留 needs_print 需求');
assert.ok(!scripts1c.includes("'imgbox_type' => 'group'"), 'imgbox_type 仍为硬编码');
});
test('运行时只读取新键并接入灯箱/目录树/预置参数', () => {
// 灯箱六项 + 模式。
assert.match(runtime, /\$settings\['imagelightbox'\]/);
assert.match(runtime, /\$settings\['imagelightbox_type'\]/);
assert.match(runtime, /\$settings\['imagelightbox_thumbs_s'\]/);
assert.match(runtime, /\$settings\['imagelightbox_play_s'\]/);
assert.match(runtime, /\$settings\['imagelightbox_zoom_s'\]/);
assert.match(runtime, /\$settings\['imagelightbox_full_s'\]/);
assert.match(runtime, /\$settings\['imagelightbox_down_s'\]/);
assert.match(runtime, /'lightbox_type'\s*=>\s*\$lightbox_type/);
// 复选框 pc_s/m_s 归并。
assert.match(runtime, /!empty\(\$value\['pc_s'\]\) \|\| !empty\(\$value\['m_s'\]\)/);
// 点赞 / 预置参数 / 目录树。
assert.match(runtime, /\$settings\['post_like_s'\]/);
assert.match(runtime, /\$settings\['post_default_mate'\]/);
assert.match(runtime, /\$settings\['article_nav'\]/);
assert.match(runtime, /\$settings\['article_nav_mobile_nav_s'\]/);
// JS 配置透传灯箱模式。
assert.match(scripts1c, /'imgbox_type'\s*=>\s*\$lightbox_on \? \$lightbox_type : 'group'/);
// 目录树门禁:内容壳 data-nav / no-nav,移动端菜单盒。
assert.match(singleRender, /data-nav="posts"/);
assert.match(singleRender, /no-nav/);
assert.match(singleRender, /no_article-navs/);
assert.match(mobileHeader, /article_nav_mobile/);
// 预置参数:新建文章随机填入。
assert.match(coreHooks, /save_post/);
assert.match(coreHooks, /POST_VIEW_COUNT_KEY/);
assert.match(coreHooks, /_aksrt_like_count/);
});