'use strict'; /** * 「文章页面」后台设置页契约(对齐参考主题 zibll 1:1): * 1) PHP schema(SchemaPostList.php page section 2.0.0)字段键、顺序、默认值与 * JS schema(assets/admin/src/schema/sections/post-list/page.js)一致; * 2) 参考主题字段全集存在:三类封面(article_image_cover / article_slide_cover / * article_video_cover + option fieldset)、post_single_hide_time_s、 * post_p_indent_s、breadcrumbs_single_s、breadcrumbs_home_text(switcher)、 * post_prevnext_s、post_authordesc_s、article_maxheight_kg/maxheight、 * 一言三项、post_copyright_s/post_copyright、post_button_toptext、 * post_front_content/after_content、post_related_*; * 3) 幻灯片 fieldset 全字段及嵌套依赖(direction/scale_height/auto_height 组合) * 对齐参考主题 CFS_Module::slide; * 4) 旧 AKSRT 私有/异名字段(阅读进度条、面包屑末项文本、作者卡片配置、收藏开关、 * share_s 与 post_rewards_*、旧封面键)已彻底移除,无双路兼容; * 5) 运行时(PostDisplay::get_post_page_settings)只读取新键:幻灯片配置归一化并 * 接线 aksrt_render_slider,p_indent 接线容器类,breadcrumbs_home_text 为 * switcher 布尔语义(true = 用「首页」替代网站名称)。 */ 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/page.js'), 'utf8'); const runtime = fs.readFileSync(path.join(root, 'src/Settings/PostDisplay.php'), 'utf8'); const facadeBulk = fs.readFileSync(path.join(root, 'src/Settings/FacadeBulk.php'), 'utf8'); const metaBox = fs.readFileSync(path.join(root, 'src/Core/PostMetaBox.php'), 'utf8'); const frontendTags = fs.readFileSync(path.join(root, 'inc/helpers/aksrt-frontend-tags.php'), 'utf8'); const singleRender = fs.readFileSync(path.join(root, 'inc/helpers/aksrt-single-render.php'), 'utf8'); const contentCss = fs.readFileSync(path.join(root, 'assets/css/frontend/single-post/content.css'), 'utf8'); const shareJs = fs.readFileSync(path.join(root, 'assets/admin/src/schema/sections/post-list/share.js'), 'utf8'); const sharePhp = php; const featuresJs = fs.readFileSync(path.join(root, 'assets/admin/src/schema/sections/users/features.js'), 'utf8'); const usersPhp = fs.readFileSync(path.join(root, 'src/Admin/Schema/SchemaUsers.php'), 'utf8'); /** 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 pagePhp = phpSection(php, 'page'); // 参考主题 admin-options.php 4934–5249 的字段顺序(含 fieldset 父字段与子字段)。 const PAGE_FIELD_SEQUENCE = [ 'article_image_cover', 'article_image_cover_option', 'scale', // article_image_cover_option.scale 'article_slide_cover', 'article_slide_cover_option', // article_slide_cover_option(CFS_Module::slide 全字段) 'direction', 'loop', 'button', 'pagination', 'effect', 'scale_height', 'scale', 'auto_height', 'max_height', 'min_height', 'pc_height', 'm_height', 'spacebetween', 'speed', 'autoplay', 'interval', 'article_video_cover', 'article_video_cover_option', 'scale', // article_video_cover_option.scale 'post_single_hide_time_s', 'post_p_indent_s', 'breadcrumbs_single_s', 'breadcrumbs_home_text', 'post_prevnext_s', 'post_authordesc_s', 'article_maxheight_kg', 'article_maxheight', 'yiyan_single_content_header', 'yiyan_single_content_footer', 'yiyan_single_box', 'post_copyright_s', 'post_copyright', 'post_button_toptext', 'post_front_content', 'post_after_content', 'post_related_s', 'post_related_type', 'related_title', 'post_related_n', 'post_related_orderby', ]; // 旧 AKSRT 私有/异名字段:已迁移(share_s / post_rewards_*)或无参考对应项被移除。 const LEGACY_KEYS = [ 'post_reading_progress', 'post_author_card_limit', 'post_author_card_orderby', 'post_author_card_style', 'post_favorite_enabled', 'post_wechat_share', 'post_article_image_cover', 'post_article_slide_cover', 'post_article_video_cover', ]; test('PHP page section 字段键与参考主题一致且顺序相同', () => { // 字段定义均为 ['key' => '...', 'label' => ...] 开头;dependsOn 内的 // ['key' => '...', 'value'/'not' => ...] 不是字段定义,须排除。 const found = []; const re = /'key'\s*=>\s*'([a-z0-9_]+)',\s*'label'/g; let m; while ((m = re.exec(pagePhp)) !== null) found.push(m[1]); assert.deepEqual(found, PAGE_FIELD_SEQUENCE); }); test('PHP page section 版本升级至 2.0.0 且为单一 post_page 组', () => { assert.match(pagePhp, /'version'\s*=>\s*'2\.0\.0'/); assert.match(pagePhp, /'groups'\s*=>\s*\[\s*\['key'\s*=>\s*'post_page',\s*'title'\s*=>\s*'文章页面'/); }); test('PHP page section 默认值对齐参考主题', () => { // 三类封面开关。 assert.match(pagePhp, /'article_image_cover',\s*'label'\s*=>\s*'图片封面',\s*'type'\s*=>\s*'switcher',\s*'default'\s*=>\s*true/); assert.match(pagePhp, /'article_slide_cover',\s*'label'\s*=>\s*'幻灯片封面',\s*'type'\s*=>\s*'switcher',\s*'default'\s*=>\s*true/); assert.match(pagePhp, /'article_video_cover',\s*'label'\s*=>\s*'视频封面',\s*'type'\s*=>\s*'switcher',\s*'default'\s*=>\s*true/); // 图片封面比例:20-200 step5,默认 35。 assert.match(pagePhp, /'scale',\s*'label'\s*=>\s*'图片长宽比例',\s*'type'\s*=>\s*'number',\s*'default'\s*=>\s*35,\s*'min'\s*=>\s*20,\s*'max'\s*=>\s*200,\s*'step'\s*=>\s*5/); // 视频封面比例:0-200,默认 0(为 0 则不固定长宽比例)。 assert.match(pagePhp, /'scale',\s*'label'\s*=>\s*'固定长宽比例',\s*'type'\s*=>\s*'number',\s*'default'\s*=>\s*0,\s*'min'\s*=>\s*0,\s*'max'\s*=>\s*200/); // 面包屑「首页」替代开关(switcher,默认 true)。 assert.match(pagePhp, /'breadcrumbs_home_text',\s*'label'\s*=>\s*'面包屑导航用“首页”替代网站名称',\s*'type'\s*=>\s*'switcher',\s*'default'\s*=>\s*true/); // 段落缩进 / 隐藏时间。 assert.match(pagePhp, /'post_p_indent_s',\s*'label'\s*=>\s*'内容段落缩进',\s*'type'\s*=>\s*'switcher',\s*'default'\s*=>\s*false/); assert.match(pagePhp, /'post_single_hide_time_s',\s*'label'\s*=>\s*'隐藏时间',\s*'type'\s*=>\s*'switcher',\s*'default'\s*=>\s*false/); // 内容高度限制:slider 600-3000 step100。 assert.match(pagePhp, /'article_maxheight_kg',\s*'label'\s*=>\s*'内容高度限制',\s*'type'\s*=>\s*'switcher',\s*'default'\s*=>\s*false/); assert.match(pagePhp, /'article_maxheight',\s*'label'\s*=>\s*'限制的最大高度',\s*'type'\s*=>\s*'slider',\s*'default'\s*=>\s*1000,\s*'min'\s*=>\s*600,\s*'max'\s*=>\s*3000,\s*'step'\s*=>\s*100/); // 版权声明。 assert.match(pagePhp, /'post_copyright_s',\s*'label'\s*=>\s*'版权声明',\s*'type'\s*=>\s*'switcher',\s*'default'\s*=>\s*true/); assert.match(pagePhp, /'post_copyright',\s*'label'\s*=>\s*'版权提示内容',\s*'type'\s*=>\s*'textarea',\s*'default'\s*=>\s*'文章版权归作者所有,未经允许请勿转载。'/); assert.match(pagePhp, /'post_button_toptext',\s*'label'\s*=>\s*'文章页脚文案',\s*'type'\s*=>\s*'input',\s*'default'\s*=>\s*'喜欢就支持一下吧'/); // 相关文章:image_select 三样式 + 数量 4-12 step2 + 排序默认 views。 assert.match(pagePhp, /'post_related_type',\s*'label'\s*=>\s*'显示样式',\s*'type'\s*=>\s*'image_select',\s*'default'\s*=>\s*'img'/); assert.match(pagePhp, /'post_related_n',\s*'label'\s*=>\s*'显示数量',\s*'type'\s*=>\s*'number',\s*'default'\s*=>\s*6,\s*'min'\s*=>\s*4,\s*'max'\s*=>\s*12,\s*'step'\s*=>\s*2/); assert.match(pagePhp, /'post_related_orderby',\s*'label'\s*=>\s*'排序方式',\s*'type'\s*=>\s*'select',\s*'default'\s*=>\s*'views'/); }); test('PHP slide fieldset 全字段及嵌套依赖对齐 CFS_Module::slide', () => { // 幻灯片默认值(pagination: true, pc_height: 380, m_height: 180 为参考主题默认)。 assert.match(pagePhp, /'direction'\s*=>\s*'horizontal',\s*'loop'\s*=>\s*true,\s*'button'\s*=>\s*false,\s*'pagination'\s*=>\s*true,\s*'effect'\s*=>\s*'slide'/); assert.match(pagePhp, /'pc_height'\s*=>\s*380,\s*'m_height'\s*=>\s*180,\s*'spacebetween'\s*=>\s*15,\s*'speed'\s*=>\s*0,\s*'autoplay'\s*=>\s*true,\s*'interval'\s*=>\s*4/); // 嵌套依赖:scale_height 需要 direction != vertical。 assert.match(pagePhp, /'scale_height',\s*'label'\s*=>\s*'按比例自动高度',\s*'type'\s*=>\s*'switch',\s*'default'\s*=>\s*true,\s*'dependsOn'\s*=>\s*\['key'\s*=>\s*'direction',\s*'not'\s*=>\s*'vertical'\]/); // scale 需要 direction != vertical 且 scale_height 开启。 assert.match(pagePhp, /'scale',\s*'label'\s*=>\s*'长宽比例'[\s\S]*?'dependsOn'\s*=>\s*\['all'\s*=>\s*\[\['key'\s*=>\s*'direction',\s*'not'\s*=>\s*'vertical'\],\s*\['key'\s*=>\s*'scale_height',\s*'value'\s*=>\s*true\]\]\]/); // auto_height 需要 direction != vertical 且 scale_height 关闭。 assert.match(pagePhp, /'auto_height',\s*'label'\s*=>\s*'自动高度'[\s\S]*?'dependsOn'\s*=>\s*\['all'\s*=>\s*\[\['key'\s*=>\s*'direction',\s*'not'\s*=>\s*'vertical'\],\s*\['key'\s*=>\s*'scale_height',\s*'value'\s*=>\s*false\]\]\]/); // max/min_height 需要 auto_height 开启 + direction != vertical + scale_height 关闭。 assert.match(pagePhp, /'max_height',\s*'label'\s*=>\s*'最大高度'[\s\S]*?'dependsOn'\s*=>\s*\['all'\s*=>\s*\[\['key'\s*=>\s*'auto_height',\s*'value'\s*=>\s*true\],\s*\['key'\s*=>\s*'direction',\s*'not'\s*=>\s*'vertical'\],\s*\['key'\s*=>\s*'scale_height',\s*'value'\s*=>\s*false\]\]\]/); // interval 依赖 autoplay。 assert.match(pagePhp, /'interval',\s*'label'\s*=>\s*'停顿时间'[\s\S]*?'dependsOn'\s*=>\s*\['key'\s*=>\s*'autoplay',\s*'value'\s*=>\s*true\]/); // 五种切换动画。 for (const effect of ['slide', 'fade', 'cube', 'coverflow', 'flip']) { assert.ok(pagePhp.includes(`'value' => '${effect}'`), `缺少切换动画 ${effect}`); } }); test('PHP page section 不含旧私有/异名字段(无双路)', () => { for (const legacy of LEGACY_KEYS) { assert.ok(!pagePhp.includes(`'${legacy}'`), `page section 残留旧字段: ${legacy}`); } // share_s / post_rewards_* 已迁移至分享设置页与用户功能页。 assert.ok(!pagePhp.includes(`'share_s'`), 'page section 残留 share_s'); assert.ok(!pagePhp.includes(`'post_rewards_`), 'page section 残留 post_rewards_*'); }); 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]); assert.deepEqual(keys, PAGE_FIELD_SEQUENCE); for (const legacy of LEGACY_KEYS) { assert.ok(!keys.includes(legacy), `JS schema 残留旧字段: ${legacy}`); } assert.ok(!js.includes(`key: 'share_s'`), 'JS page schema 残留 share_s'); assert.ok(!/key:\s*'post_rewards_/.test(js), 'JS page schema 残留 post_rewards_*'); // 参考主题视频封面配置教程链接以 ui_only description 承载。 assert.match(js, /ui_only:\s*true/); assert.match(js, /https:\/\/www\.zibll\.com\/1900\.html/); }); test('JS schema 默认值与依赖对齐参考主题', () => { assert.match(js, /article_image_cover:\s*true/); assert.match(js, /article_image_cover_option:\s*\{\s*scale:\s*35\s*\}/); assert.match(js, /article_slide_cover:\s*true/); assert.match(js, /article_video_cover:\s*true/); assert.match(js, /article_video_cover_option:\s*\{\s*scale:\s*0\s*\}/); assert.match(js, /post_single_hide_time_s:\s*false/); assert.match(js, /post_p_indent_s:\s*false/); assert.match(js, /breadcrumbs_single_s:\s*true/); assert.match(js, /breadcrumbs_home_text:\s*true/); assert.match(js, /post_prevnext_s:\s*true/); assert.match(js, /post_authordesc_s:\s*true/); assert.match(js, /article_maxheight_kg:\s*false/); assert.match(js, /article_maxheight:\s*1000/); assert.match(js, /post_copyright_s:\s*true/); assert.match(js, /post_copyright:\s*'文章版权归作者所有,未经允许请勿转载。'/); assert.match(js, /post_button_toptext:\s*'喜欢就支持一下吧'/); assert.match(js, /post_related_s:\s*true/); assert.match(js, /post_related_type:\s*'img'/); assert.match(js, /related_title:\s*'相关推荐'/); assert.match(js, /post_related_n:\s*6/); assert.match(js, /post_related_orderby:\s*'views'/); // 幻灯片嵌套依赖(dependsOn {key, not} / {all: [...]} 形式)。 assert.match(js, /key:\s*'scale_height',[\s\S]*?dependsOn:\s*\{\s*key:\s*'direction',\s*not:\s*'vertical'\s*\}/); assert.match(js, /key:\s*'scale',[\s\S]*?dependsOn:\s*\{\s*all:\s*\[\{\s*key:\s*'direction',\s*not:\s*'vertical'\s*\},\s*\{\s*key:\s*'scale_height',\s*value:\s*true\s*\}\]\s*\}/); assert.match(js, /key:\s*'auto_height',[\s\S]*?dependsOn:\s*\{\s*all:\s*\[\{\s*key:\s*'direction',\s*not:\s*'vertical'\s*\},\s*\{\s*key:\s*'scale_height',\s*value:\s*false\s*\}\]\s*\}/); assert.match(js, /key:\s*'interval',[\s\S]*?dependsOn:\s*\{\s*key:\s*'autoplay',\s*value:\s*true\s*\}/); }); test('share_s 迁移至分享设置页 basic 组(键不变,无双路)', () => { assert.match(sharePhp, /\['key' => 'basic', 'title' => '分享按钮', 'fields' => \[\s*\['key' => 'share_s', 'label' => '分享按钮', 'type' => 'switcher', 'default' => true, 'help' => '关闭后,在文章页面将不显示分享按钮,论坛及商城不受影响'\]/); assert.match(shareJs, /key: 'basic',/); assert.match(shareJs, /share_s: true/); assert.match(shareJs, /key: 'share_s', label: '分享按钮', type: 'switch', default: true, help: '关闭后,在文章页面将不显示分享按钮,论坛及商城不受影响'/); }); test('post_rewards_* 迁移至用户功能页 rewards 组(对齐参考主题 8977 行)', () => { assert.match(usersPhp, /\['key' => 'rewards', 'title' => '打赏', 'fields' => \[/); assert.match(usersPhp, /'post_rewards_s', 'label' => '用户打赏功能', 'type' => 'switcher', 'default' => true/); assert.match(usersPhp, /'post_rewards_text', 'label' => '打赏按钮文案', 'type' => 'input', 'default' => '赞赏'/); assert.match(featuresJs, /post_rewards_s:\s*true/); assert.match(featuresJs, /post_rewards_text:\s*'赞赏'/); assert.match(featuresJs, /key: 'post_rewards_s', label: '用户打赏功能', type: 'switch', default: true/); }); 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), `page.js 残留旧字段: ${legacy}`); assert.ok(!metaBox.includes(legacy), `PostMetaBox 残留旧字段: ${legacy}`); } // 面包屑末项正文无参考配置:schema 字段移除,运行时硬编码为常开。 assert.ok(!pagePhp.includes(`'breadcrumbs_single_text'`), 'page section 残留 breadcrumbs_single_text'); assert.ok(!js.includes(`key: 'breadcrumbs_single_text'`), 'page.js 残留 breadcrumbs_single_text'); assert.match(runtime, /'breadcrumbs_single_text'\s*=>\s*true/); // aksrt-frontend-tags 不再有 breadcrumbs_home_text 文本兼容(switcher 布尔语义)。 assert.ok(!frontendTags.includes("breadcrumbs_home'] = (string) $settings['breadcrumbs_home_text'"), 'frontend-tags 残留 breadcrumbs_home_text 文本兼容'); }); test('运行时只读取新键并接线封面/缩进/版权/一言', () => { // 三类封面 + option fieldset。 assert.match(runtime, /\$settings\['article_image_cover'\]/); assert.match(runtime, /\$settings\['article_image_cover_option'\]/); assert.match(runtime, /\$settings\['article_slide_cover'\]/); assert.match(runtime, /\$settings\['article_slide_cover_option'\]/); assert.match(runtime, /\$settings\['article_video_cover'\]/); assert.match(runtime, /\$settings\['article_video_cover_option'\]/); // 图片封面比例 clamp 20-200(参考主题默认 35)。 assert.match(runtime, /max\(20, min\(200, \(int\) \(\$image_cover_option\['scale'\] \?\? 35\)\)\)/); // 视频封面比例 clamp 0-200(0 = 不固定长宽比例)。 assert.match(runtime, /max\(0, min\(200, \(int\) \(\$video_cover_option\['scale'\] \?\? 0\)\)\)/); // 幻灯片配置归一化(CFS_Module::slide 全字段)。 assert.match(runtime, /normalize_slide_cover_option\(\$slide_option_raw\)/); for (const key of ['direction', 'loop', 'button', 'pagination', 'effect', 'scale_height', 'scale', 'auto_height', 'max_height', 'min_height', 'pc_height', 'm_height', 'spacebetween', 'speed', 'autoplay', 'interval']) { assert.ok(runtime.includes(`'${key}'`), `normalize_slide_cover_option 缺少 ${key}`); } // 面包屑「首页」替代语义:true → 「首页」,false → 网站名称。 assert.match(runtime, /!isset\(\$settings\['breadcrumbs_home_text'\]\) \|\| !empty\(\$settings\['breadcrumbs_home_text'\]\)/); assert.match(runtime, /get_bloginfo\('name'\)/); // 段落缩进 / 高度限制 / 版权 / 页脚文案 / 插入内容 / 一言 / 相关文章。 assert.match(runtime, /\$settings\['post_p_indent_s'\]/); assert.match(runtime, /\$settings\['article_maxheight_kg'\]/); assert.match(runtime, /\$settings\['article_maxheight'\]/); assert.match(runtime, /\$settings\['post_copyright_s'\]/); assert.match(runtime, /\$settings\['post_copyright'\]/); assert.match(runtime, /\$settings\['post_button_toptext'\]/); assert.match(runtime, /\$settings\['post_front_content'\]/); assert.match(runtime, /\$settings\['post_after_content'\]/); assert.match(runtime, /\$settings\['yiyan_single_content_header'\]/); assert.match(runtime, /\$settings\['yiyan_single_content_footer'\]/); assert.match(runtime, /\$settings\['yiyan_single_box'\]/); assert.match(runtime, /\$settings\['post_related_s'\]/); assert.match(runtime, /\$settings\['post_related_type'\]/); assert.match(runtime, /\$settings\['post_related_n'\]/); assert.match(runtime, /\$settings\['post_related_orderby'\]/); // 阅读进度/收藏/作者卡片无参考配置:常量默认(无双路)。 assert.match(runtime, /'reading_progress'\s*=>\s*true/); assert.match(runtime, /'favorite_enabled'\s*=>\s*true/); assert.match(runtime, /'author_card_limit'\s*=>\s*6/); }); test('前台链路:幻灯片配置接线渲染器,p_indent 接线容器与 CSS', () => { // 幻灯片配置接入 aksrt_render_slider(effect/direction/spaceBetween/speed/interval)。 assert.match(singleRender, /article_slide_cover_option/); assert.match(singleRender, /'effect'\s*=>\s*\(string\) \(\$slide_option\['effect'\] \?\? 'slide'\)/); assert.match(singleRender, /'direction'\s*=>\s*\(string\) \(\$slide_option\['direction'\] \?\? 'horizontal'\)/); assert.match(singleRender, /'spaceBetween'\s*=>\s*\(int\) \(\$slide_option\['spacebetween'\] \?\? 15\)/); assert.match(singleRender, /'interval'\s*=>\s*max\(1, \(int\) \(\$slide_option\['interval'\] \?\? 4\)\) \* 1000/); assert.match(singleRender, /'show_nav'\s*=>\s*!empty\(\$slide_option\['button'\]\)/); assert.match(singleRender, /'show_pagination'\s*=>\s*!empty\(\$slide_option\['pagination'\]\)/); // 固定高度模式(scale_height)与比例高度模式(scale + 50px 标题预留)双分支。 assert.match(singleRender, /!empty\(\$slide_option\['scale_height'\]\)/); assert.match(singleRender, /\$slider_args\['pc_height'\] = \(\(int\) \(\$slide_option\['pc_height'\] \?\? 380\)\) \. 'px'/); assert.match(singleRender, /\$slider_args\['scale_ratio'\] = max\(10, \(int\) \(\$slide_option\['scale'\] \?\? 35\)\) \/ 100/); // 图片封面比例 clamp 20-200;视频封面 0 → 16:9(56.25)回退。 assert.match(singleRender, /max\(20, min\(200, \(int\) \$ctx\['img_scale'\]\)\)/); assert.match(singleRender, /max\(0, min\(200, \(int\) \$ctx\['video_scale'\]\)\)/); assert.match(singleRender, /\$video_scale > 0 \? \(string\) \$video_scale : '56\.25'/); // p_indent 容器类 + CSS 规则(首行缩进 2em)。 assert.match(singleRender, /p-indent/); assert.match(contentCss, /\.wp-posts-content\.p-indent p\s*\{\s*text-indent:\s*2em;/); // 前端回退数组包含新键。 assert.match(frontendTags, /'p_indent'\s*=>\s*false/); assert.match(frontendTags, /'article_image_cover_scale'\s*=>\s*35/); assert.match(frontendTags, /'article_slide_cover_option'\s*=>\s*\[\]/); assert.match(frontendTags, /'article_video_cover_scale'\s*=>\s*0/); // PostMetaBox 读取新封面键(文章级封面字段可见性)。 assert.match(metaBox, /!empty\(\$settings\['article_image_cover'\]\)/); assert.match(metaBox, /!empty\(\$settings\['article_slide_cover'\]\)/); assert.match(metaBox, /!empty\(\$settings\['article_video_cover'\]\)/); // FacadeBulk 独立回退与新键同步。 assert.match(facadeBulk, /'breadcrumbs_home_replace'/); assert.match(facadeBulk, /'article_slide_cover_option'/); });