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
This commit is contained in:
@@ -0,0 +1,232 @@
|
||||
'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/);
|
||||
});
|
||||
Reference in New Issue
Block a user