'use strict'; /** * 「评论设置」后台设置页契约(对齐参考主题 1:1): * 1) PHP schema(SchemaPostList.php comment section)字段键、顺序、默认值与 * JS schema(assets/admin/src/schema/sections/post-list/comment.js)一致; * 2) 参考主题字段全集存在:close_comments / comment_box_hide / * comment_paginate_type / comment_paging_ajax_ias_s / * comment_paging_ajax_ias_max / comment_code / comment_img / * comment_upload_img / comment_link / comment_author_tag / * user_edit_comment / comment_like_s / comment_user_url_s / * comment_corderby / comment_title / comment_submit_text / comment_text; * 3) 插入表情(comment_smilie)、评论地理位置(comment_city_s/comment_city_type, * 由扩展&增强/IP归属地实现)不在本页; * 4) 快捷回复按评论区硬切换契约保持移除(comment_quick_* 不存在); * 5) 旧 AKSRT 私有字段(comment_order / comment_per_page / comment_pagination_type / * comment_nested_depth / comment_avatar_size / comment_close_comments / * comment_edit_window / comment_hot_threshold / comment_empty_text / * post_close_comments)已彻底移除,无双路兼容; * 6) 运行时(PostDisplay::get_comment_display_settings)只读取新键。 */ 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/comment.js'), 'utf8'); const runtime = fs.readFileSync(path.join(root, 'src/Settings/PostDisplay.php'), 'utf8'); const commentsHelper = fs.readFileSync(path.join(root, 'inc/helpers/aksrt-comments.php'), 'utf8'); const geoHelper = fs.readFileSync(path.join(root, 'inc/helpers/comments/geo.php'), 'utf8'); const actionsHelper = fs.readFileSync(path.join(root, 'inc/helpers/comments/actions.php'), 'utf8'); const REFERENCE_KEYS = [ 'close_comments', 'comment_box_hide', 'comment_paginate_type', 'comment_paging_ajax_ias_s', 'comment_paging_ajax_ias_max', 'comment_code', 'comment_img', 'comment_upload_img', 'comment_link', 'comment_author_tag', 'user_edit_comment', 'comment_like_s', 'comment_user_url_s', 'comment_corderby', 'comment_title', 'comment_submit_text', 'comment_text', ]; // 参考主题明确不做 / 本项目不做 / 其他地方实现的字段。 const EXCLUDED_KEYS = [ 'comment_smilie', 'comment_city_s', 'comment_city_type', 'comment_quick_s', 'comment_quick_often', ]; // 旧 AKSRT 私有字段(含已并入 WP 讨论设置与死字段 post_close_comments)。 const LEGACY_KEYS = [ 'comment_order', 'comment_per_page', 'comment_pagination_type', 'comment_nested_depth', 'comment_avatar_size', 'comment_close_comments', 'comment_edit_window', 'comment_hot_threshold', 'comment_empty_text', 'post_close_comments', ]; /** Extract the PHP 'comment' => [ ... ] section body (balanced brackets). */ function phpCommentSection(src) { const start = src.indexOf("'comment' => ["); assert.notEqual(start, -1, 'PHP schema has a comment 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 comment section'); } const commentPhp = phpCommentSection(php); test('PHP comment 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(commentPhp)) !== null) found.push(m[1]); assert.deepEqual(found, REFERENCE_KEYS); }); test('PHP comment section 不含表情/地理位置/快捷回复字段', () => { for (const key of EXCLUDED_KEYS) { assert.doesNotMatch(commentPhp, new RegExp(`'${key}'`), `comment section 不应包含 ${key}`); } }); test('PHP comment section 默认值对齐参考主题', () => { assert.match(commentPhp, /'close_comments',\s*'label'\s*=>\s*'关闭文章评论功能',\s*'type'\s*=>\s*'switcher',\s*'default'\s*=>\s*false/); assert.match(commentPhp, /'comment_box_hide',\s*'label'\s*=>\s*'隐藏评论模块',\s*'type'\s*=>\s*'radio',\s*'default'\s*=>\s*''/); assert.match(commentPhp, /'comment_paginate_type',\s*'label'\s*=>\s*'评论列表翻页模式',\s*'type'\s*=>\s*'radio',\s*'default'\s*=>\s*'default'/); assert.match(commentPhp, /'comment_paging_ajax_ias_s',\s*'label'\s*=>\s*'AJAX翻页自动加载',\s*'type'\s*=>\s*'switcher',\s*'default'\s*=>\s*true/); assert.match(commentPhp, /'comment_paging_ajax_ias_max',\s*'label'\s*=>\s*'自动加载页数',\s*'type'\s*=>\s*'number',\s*'default'\s*=>\s*3/); assert.match(commentPhp, /'comment_code',\s*'label'\s*=>\s*'允许插入代码',\s*'type'\s*=>\s*'switcher',\s*'default'\s*=>\s*true/); assert.match(commentPhp, /'comment_img',\s*'label'\s*=>\s*'允许插入图片',\s*'type'\s*=>\s*'switcher',\s*'default'\s*=>\s*true/); assert.match(commentPhp, /'comment_upload_img',\s*'label'\s*=>\s*'允许上传图片',\s*'type'\s*=>\s*'switcher',\s*'default'\s*=>\s*false/); assert.match(commentPhp, /'comment_link',\s*'label'\s*=>\s*'允许插入链接',\s*'type'\s*=>\s*'switcher',\s*'default'\s*=>\s*false/); assert.match(commentPhp, /'comment_author_tag',\s*'label'\s*=>\s*'显示“作者”标签',\s*'type'\s*=>\s*'switcher',\s*'default'\s*=>\s*true/); assert.match(commentPhp, /'user_edit_comment',\s*'label'\s*=>\s*'允许用户编辑评论',\s*'type'\s*=>\s*'switcher',\s*'default'\s*=>\s*true/); assert.match(commentPhp, /'comment_like_s',\s*'label'\s*=>\s*'评论点赞功能',\s*'type'\s*=>\s*'switcher',\s*'default'\s*=>\s*true/); assert.match(commentPhp, /'comment_user_url_s',\s*'label'\s*=>\s*'匿名评论允许输入链接',\s*'type'\s*=>\s*'switcher',\s*'default'\s*=>\s*false/); assert.match(commentPhp, /'comment_corderby',\s*'label'\s*=>\s*'评论排序功能',\s*'type'\s*=>\s*'switcher',\s*'default'\s*=>\s*true/); assert.match(commentPhp, /'comment_title',\s*'label'\s*=>\s*'自定义评论标题',\s*'type'\s*=>\s*'textarea',\s*'default'\s*=>\s*'评论'/); assert.match(commentPhp, /'comment_submit_text',\s*'label'\s*=>\s*'自定义评论提交按钮文案',\s*'type'\s*=>\s*'textarea',\s*'default'\s*=>\s*'提交评论'/); assert.match(commentPhp, /'comment_text',\s*'label'\s*=>\s*'自定义评论框占位符文案',\s*'type'\s*=>\s*'input',\s*'default'\s*=>\s*'欢迎您留下宝贵的见解!'/); }); test('PHP comment section 选项与依赖对齐参考主题', () => { // 隐藏评论模块选项。 for (const option of ['不隐藏', '登录后显示', '所有会员显示', '二级会员显示', '认证用户显示']) { assert.ok(commentPhp.includes(`'${option}'`), `comment_box_hide 缺少选项 ${option}`); } // 翻页模式选项。 for (const option of ['AJAX追加列表翻页', '数字翻页按钮']) { assert.ok(commentPhp.includes(`'${option}'`), `comment_paginate_type 缺少选项 ${option}`); } assert.match(commentPhp, /'comment_box_hide'[\s\S]*?'dependsOn'\s*=>\s*\['key'\s*=>\s*'close_comments',\s*'value'\s*=>\s*false\]/); assert.match(commentPhp, /'comment_paginate_type'[\s\S]*?'dependsOn'\s*=>\s*\['key'\s*=>\s*'close_comments',\s*'value'\s*=>\s*false\]/); assert.match(commentPhp, /'comment_paging_ajax_ias_s'[\s\S]*?'dependsOn'\s*=>\s*\['key'\s*=>\s*'comment_paginate_type',\s*'value'\s*=>\s*'ajax_lists'\]/); assert.match(commentPhp, /'comment_paging_ajax_ias_max'[\s\S]*?'dependsOn'\s*=>\s*\['all'/); assert.match(commentPhp, /'comment_upload_img'[\s\S]*?'dependsOn'\s*=>\s*\['all'/); assert.match(commentPhp, /'comment_corderby'[\s\S]*?'dependsOn'\s*=>\s*\['all'/); // 允许插入链接在参考主题中没有依赖(其后紧跟 comment_author_tag)。 const linkIdx = commentPhp.indexOf("'comment_link'"); const authorTagIdx = commentPhp.indexOf("'comment_author_tag'"); assert.ok(linkIdx !== -1 && authorTagIdx > linkIdx, 'comment_link / comment_author_tag 顺序应符合参考主题'); const linkSlice = commentPhp.slice(linkIdx, authorTagIdx); assert.ok(!linkSlice.includes('dependsOn'), 'comment_link 不应有 dependsOn'); }); test('JS schema 与 PHP 字段键一致(ui_only 提示行除外)', () => { // 字段定义为 { key: '...', label: ... };dependsOn 内的 { key, value } 须排除。 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}`); } for (const excluded of EXCLUDED_KEYS) { assert.ok(!keys.includes(excluded), `JS schema 不应包含 ${excluded}`); } // WP 讨论设置指引行承载参考主题 submessage 与【去设置】链接。 assert.match(js, /ui_only:\s*true/); assert.match(js, /options-discussion\.php/); assert.match(js, /分页显示评论/); }); test('JS schema 默认值与选项对齐参考主题', () => { assert.match(js, /close_comments:\s*false/); assert.match(js, /comment_box_hide:\s*''/); assert.match(js, /comment_paginate_type:\s*'default'/); assert.match(js, /comment_paging_ajax_ias_s:\s*true/); assert.match(js, /comment_paging_ajax_ias_max:\s*3/); assert.match(js, /user_edit_comment:\s*true/); assert.match(js, /comment_text:\s*'欢迎您留下宝贵的见解!'/); for (const option of ['AJAX追加列表翻页', '数字翻页按钮', '登录后显示', '所有会员显示', '二级会员显示', '认证用户显示']) { assert.ok(js.includes(option), `JS schema 缺少选项 ${option}`); } assert.match(js, /dependsOn:\s*\{\s*key:\s*'close_comments',\s*value:\s*false\s*\}/); assert.match(js, /dependsOn:\s*\{\s*key:\s*'comment_paginate_type',\s*value:\s*'ajax_lists'\s*\}/); }); test('旧字段在 schema 与运行时中均已移除(无双路)', () => { for (const legacy of LEGACY_KEYS) { assert.ok(!php.includes(legacy), `PHP schema 残留旧字段: ${legacy}`); // 运行时按主题设置读取的形态 $settings[''] 必须消失 // (comment_order 允许以 WP 讨论选项 get_option('comment_order') 形态存在)。 for (const source of [['PostDisplay', runtime], ['aksrt-comments.php', commentsHelper], ['comments/geo.php', geoHelper], ['comments/actions.php', actionsHelper]]) { assert.ok(!source[1].includes(`$settings['${legacy}']`), `${source[0]} 残留旧字段读取: ${legacy}`); } } // 运行时只读取新键。 assert.match(runtime, /\$settings\['close_comments'\]/); assert.match(runtime, /\$settings\['user_edit_comment'\]/); assert.match(runtime, /\$settings\['comment_box_hide'\]/); assert.match(runtime, /Settings::get_value\('comment_paginate_type'/); assert.match(runtime, /return \$mode === 'ajax_lists' \? 'ajax_lists' : 'numbered';/); // 排序回退到 WP 讨论设置(参考主题行为)。 assert.match(runtime, /get_option\('comment_order', 'desc'\)/); // 评论区位置显示交由扩展 IP 归属地控制。 assert.match(geoHelper, /aksrt_ip_geo_display_enabled\('comment'\)/); assert.match(geoHelper, /aksrt_ip_geo_format_key/); // 编辑权限使用 user_edit 总开关。 assert.match(actionsHelper, /\$display\['user_edit'\]/); });