Files
wordpress-theme-aksrt/tests/widget-system/comment-image-upload-contract.test.cjs
kate522andClaude Opus 5 451b04160b fix(comments): restore syntax highlighting for comment code blocks
Comment [code] blocks rendered as bare <pre><code> with only Bootstrap's
grey box — no syntax colours, unlike the reference.

The runtime gates EnlighterJS on `_win.highlight_kg`, which was hardcoded to
false. With that false the selector degrades to `pre code.gl, pre code.special`,
and comment code carries neither class, so it never highlighted. The admin
already exposes 「全局功能 → 代码高亮」 (default on), and jioData.codeHighlight
reads it — only the runtime payload disagreed.

- _win.highlight_kg / highlight_hh / highlight_zt (plus white/dark theme
  names) now come from Settings::get_code_highlight_config(); the initial
  theme follows cookie → mode → time-auto via initial_theme_mode()
- define those values before the _win localize (it sits above jioData in the
  same method)
- inject the reference's two head rules the runtime path was missing:
  max-height from `highlight_maxheight`, and
  `.enlighter-toolbar{display:none}` when `highlight_btn` is off

Tests: comment-image-upload contract gains cases pinning the settings-backed
booleans (and their definition order) and the two inline CSS rules; suite
837/838 with the pre-existing frontend-lifecycle failure.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-11-03 17:31:42 +08:00

158 lines
7.6 KiB
JavaScript
Raw Permalink 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';
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 read = (file) => fs.readFileSync(path.join(root, file), 'utf8');
const formParts = read('inc/helpers/comments/form-parts.php');
const tools = read('assets/js/comment-tools.js');
const controller = read('src/Admin/UserController.php');
const display = read('src/Settings/PostDisplay.php');
const phpSchema = read('src/Admin/Schema/SchemaPostList.php');
const adminSchema = read('assets/admin/src/schema/sections/post-list/comment.js');
const runtime = read('src/Core/ScriptsPart1c.php');
const commentsCss = read('assets/css/build/comments.css');
const runtimeBundle = read('assets/js/runtime/main.min.js');
test('comment image settings use one two-switch contract', () => {
for (const source of [phpSchema, adminSchema]) {
assert.match(source, /comment_img/);
assert.match(source, /comment_upload_img/);
}
assert.match(display, /!empty\(\$settings\['comment_upload_img'\]\)/);
assert.match(display, /is_user_logged_in\(\)/);
assert.match(display, /jio_user_can\('upload_comment_image'/);
assert.doesNotMatch(runtime, /commentUploadEnabled/);
assert.doesNotMatch(runtime, /'comment_upload_img'\s*=>/);
});
test('comment image form matches the single-file reference interaction', () => {
assert.match(formParts, /comment-image-tabs/);
assert.match(formParts, /input-image-upload-tab/);
assert.match(formParts, /input-image-address-tab/);
assert.match(formParts, /class="form-upload comment-image-upload"/);
assert.match(formParts, /class="preview text-center mb6"/);
assert.match(formParts, /class="hide comment-image-file"/);
assert.match(formParts, /accept="image\/gif,image\/jpeg,image\/jpg,image\/png"/);
assert.match(formParts, /name="image"/);
assert.match(formParts, /data-comment-upload-submit/);
assert.equal((formParts.match(/name="image"/g) || []).length, 1);
});
test('comment uploader validates, previews, tracks progress, and inserts only a valid URL', () => {
assert.match(tools, /FileReader/);
assert.match(tools, /commentUploadMaxSize/);
assert.match(tools, /validateImage/);
assert.match(tools, /XMLHttpRequest/);
assert.match(tools, /xhr\.upload\.onprogress/);
assert.match(tools, /formData\.append\('image'/);
assert.match(tools, /response\.success === true/);
assert.match(tools, /response\.data\.url/);
assert.match(tools, /'\[img=' \+ url \+ '\]\\n'/);
assert.doesNotMatch(tools, /formData\.append\(['"]file['"]/);
assert.doesNotMatch(tools, /source_url/);
assert.doesNotMatch(tools, /attachment_id/);
assert.doesNotMatch(tools, /input-image-upload(?!-tab)/);
});
test('comment upload REST endpoint has explicit nonce, switch, capability, and image-only guards', () => {
assert.match(controller, /check_comment_image_upload_permission/);
assert.match(controller, /wp_verify_nonce\(\$nonce, 'wp_rest'\)/);
assert.match(controller, /\$settings\['comment_img'\]/);
assert.match(controller, /\$settings\['comment_upload_img'\]/);
assert.match(controller, /jio_user_can\('upload_comment_image'/);
assert.match(controller, /empty\(\$_FILES\['image'\]\)/);
assert.match(controller, /\$file_key = 'image'/);
assert.match(controller, /wp_delete_attachment\(\(int\) \$attachment_id, true\)/);
assert.match(controller, /'url' => \$url/);
assert.doesNotMatch(controller, /\$_FILES\['file'\]/);
assert.doesNotMatch(controller, /'attachment_id'\s*=>/);
});
test('built comment assets carry the upload contract and no removed submit handler', () => {
assert.match(commentsCss, /comment-image-upload/);
assert.match(commentsCss, /progress-bar/);
assert.doesNotMatch(runtimeBundle, /handleResponse/);
});
test('上传中骨架与参考主题一致:Bootstrap 条纹进度层铺满按钮', () => {
// 参考结构:<span class="miniupload-ing"> + <div class="progress progress-striped active">
// 内层 <div class="progress-bar progress-bar-success">,用 div 而非 span。
assert.match(
tools,
/'<span class="miniupload-ing">'[\s\S]{0,400}'<div class="progress progress-striped active"><div class="progress-bar progress-bar-success" role="progressbar" style="width:0"><\/div><\/div>'/,
'上传中骨架必须复用参考主题的 miniupload-ing + Bootstrap 条纹进度层'
);
assert.doesNotMatch(tools, /<span class="progress progress-striped/, '进度层必须是 div,span 会破坏 Bootstrap 的块级布局');
assert.match(tools, /window\.notyf\('正在处理请稍等\.\.\.', 'load', '', 'miniupload_ajax'\)/, '上传开始应弹出参考主题的「正在处理」提示');
assert.match(tools, /loadingText\.innerHTML = '处理中 \.\.\.'/, '到达 100% 后文案切换为「处理中 ...」');
});
test('上传按钮提供定位上下文,并去掉自造的静态细条覆盖', () => {
const shell = read('assets/css/frontend/comments/shell.css');
assert.match(
shell,
/#comments \.comment-image-upload \.input-expand-upload \{[\s\S]*?position: relative;/,
'.progress 是绝对铺满层,按钮必须是定位祖先'
);
assert.doesNotMatch(
shell,
/#comments \.comment-image-upload \.progress \{[\s\S]{0,200}position: static/,
'不得再把 .progress 改成静态细条(偏离参考主题)'
);
assert.doesNotMatch(shell, /height: 3px;[\s\S]{0,80}#comments \.comment-image-upload \.progress/, '自造的 3px 细条已删除');
});
test('评论区代码块随全局代码高亮设置一起高亮(与参考主题一致)', () => {
const scripts = read('src/Core/ScriptsPart1c.php');
// 旧实现把 highlight_* 硬编码为 false,导致 [code] 输出的 <pre><code>
// 不匹配 'pre code:not(.enlighter-origin)',评论区代码块永远无高亮。
assert.doesNotMatch(
scripts,
/'highlight_kg'\s*=>\s*false/,
'highlight_kg 不得再硬编码为 false'
);
assert.match(scripts, /get_code_highlight_config\(\)/, '代码高亮必须读取后台设置');
assert.match(
scripts,
/'highlight_kg'\s*=>\s*\$code_highlight\['enabled'\]/,
'highlight_kg 应来自设置'
);
assert.match(
scripts,
/'highlight_hh'\s*=>\s*\$code_highlight\['lineNumbers'\]/,
'行号开关应来自设置'
);
assert.match(
scripts,
/'highlight_zt'\s*=>\s*\$highlight_theme\['current'\]/,
'初始主题应按当前明暗模式选择'
);
// 变量必须先于 _win 的 wp_localize_script 定义。
const winLocalize = scripts.indexOf("wp_localize_script(");
const codeHighlightAssign = scripts.indexOf('$code_highlight = class_exists');
assert.ok(codeHighlightAssign > 0 && codeHighlightAssign < winLocalize, '$code_highlight 必须先于 _win 定义');
// 注释里的 [code] 输出 <pre><code>,需要匹配运行时的高亮选择器。
const runtime = read('assets/js/runtime/main.js');
assert.match(runtime, /pre code:not\(\.enlighter-origin\)/, '运行时高亮选择器需覆盖裸 pre>code');
});
test('代码高亮的最大高度与按钮开关由后台设置驱动(含评论区 runtime 路径)', () => {
const part1b = read('src/Core/ScriptsPart1b.php');
assert.match(part1b, /get_code_highlight_config\(\)/, '应读取统一的代码高亮配置');
assert.match(
part1b,
/max-height:' \. \$max_height_css \. 'px;overflow-y:auto !important;/,
'最大高度应按设置注入(参考主题同款规则)'
);
assert.match(part1b, /\.enlighter-toolbar\{display:none !important;\}/, '关闭扩展按钮时隐藏工具栏');
assert.match(part1b, /if \(empty\(\$code_highlight_css\['showButtons'\]\)\)/, '仅在按钮开关关闭时隐藏工具栏');
});