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'); test('front submit enqueues the WordPress editor before wp_head is rendered', () => { const source = read('templates/pages/page-submit.php'); const enqueueAt = source.indexOf('wp_enqueue_editor();'); const headerAt = source.indexOf('\nget_header();'); assert.notEqual(enqueueAt, -1, 'front submit must explicitly enqueue the editor runtime'); assert.notEqual(headerAt, -1, 'front submit must render the theme header'); assert.ok(enqueueAt < headerAt, 'editor assets must be queued before get_header()/wp_head'); }); test('front submit uploads use the effective WordPress server limit', () => { const controller = read('src/Core/FrontSubmit.php'); const page = read('templates/pages/page-submit.php'); assert.match(controller, /function upload_limit_bytes\(string \$kind\): int/); assert.match(controller, /min\(\$configured_mb \* MB_IN_BYTES, \(int\) wp_max_upload_size\(\)\)/); assert.match(controller, /UPLOAD_ERR_INI_SIZE/); assert.match(controller, /UPLOAD_ERR_FORM_SIZE/); assert.match(controller, /UPLOAD_ERR_PARTIAL/); assert.match(controller, /\['status' => 413\]/); assert.match(page, /FrontSubmit::upload_limit_bytes\('image'\)/); assert.match(page, /FrontSubmit::upload_limit_bytes\('video'\)/); assert.match(page, /FrontSubmit::upload_limit_bytes\('resource'\)/); assert.doesNotMatch(page, /\$attachment_settings\s*=/); }); test('published post content reaches the front submit editor unchanged', () => { const context = read('templates/parts/submit/context.php'); const editor = read('templates/parts/submit/editor-main.php'); assert.match(context, /\$current_content\s*=\s*\$edit_post\s*\?\s*\$edit_post->post_content\s*:\s*'';/); assert.match(editor, /wp_editor\(\(string\) \$ctx\['current_content'\], 'post_content', \$editor_settings\);/); }); test('front submit uses the WordPress registered jQuery runtime', () => { const source = read('src/Core/ScriptsPart1b.php'); assert.match(source, /wp_enqueue_script\('jquery'\);/); assert.match(source, /registered\['jquery'\]->deps\s*=\s*\['jquery-core'\]/); assert.doesNotMatch(source, /wp_deregister_script\('jquery'\)/); assert.doesNotMatch(source, /wp_register_script\(\s*'jquery'/); assert.doesNotMatch(source, /jquery-migrate/); assert.doesNotMatch(source, /runtime\/libs\/jquery\.min\.js|['"]1\.9\.1['"]/); }); test('legacy runtime modules use jQuery explicitly without a dollar alias', () => { const modules = [ 'main', 'captcha', 'comment', 'input-expand', 'mini-touch', 'mini-upload', 'page-edit', 'page-navs', 'page-template', 'poster-share', 'section-navs', 'svg-icon', 'waterfall', 'weixin-share', ]; for (const module of modules) { for (const suffix of ['.js', '.min.js']) { const relative = `assets/js/runtime/${module}${suffix}`; const absolute = path.join(root, relative); if (!fs.existsSync(absolute) || fs.statSync(absolute).size === 0) continue; const source = fs.readFileSync(absolute, 'utf8'); assert.doesNotMatch(source, /(^|[^\w$])\$(?:\(|\.)/, `${relative} must use jQuery explicitly`); assert.doesNotMatch(source, /(?:window\.)?\$\s*=/, `${relative} must not create a dollar alias`); } } }); test('theme runtime never changes native listener passivity for third-party editors', () => { const scripts = read('src/Core/Scripts.php'); const scriptsPart2 = read('src/Core/ScriptsPart2.php'); const buildTool = read('tools/build-public-assets.mjs'); const publicRuntime = read('assets/js/build/public.js'); assert.doesNotMatch(scripts, /enqueue_passive_polyfill|aksrt-passive-events/); assert.doesNotMatch(scriptsPart2, /enqueue_passive_polyfill|aksrt-passive-events/); assert.doesNotMatch(buildTool, /passive-events\.js/); assert.doesNotMatch(publicRuntime, /EventTarget\.prototype\.addEventListener|__passiveSupported/); assert.equal(fs.existsSync(path.join(root, 'assets/js/passive-events.js')), false); }); test('public theme does not inject a global focus-visible ring', () => { const globalFocusRule = /(^|})\s*:focus-visible\s*\{/m; const reset = read('assets/css/core/reset.css'); const publicCss = read('assets/css/build/public.css'); const publicMinCss = read('assets/css/build/public.min.css'); assert.doesNotMatch(reset, globalFocusRule); assert.doesNotMatch(publicCss, globalFocusRule); assert.doesNotMatch(publicMinCss, globalFocusRule); }); test('non-draft front-submit updates require one server captcha admission', () => { const controller = read('src/Core/FrontSubmit.php'); const start = controller.indexOf('public function ajax_update_post(): void'); const end = controller.indexOf('\n public function ajax_delete_draft', start); const body = controller.slice(start, end); assert.match(body, /\$action = \(\(\$data\['status'\] \?\? ''\) === 'draft'\) \? 'posts_draft' : 'posts_save';/); assert.match(body, /if \(\$action !== 'posts_draft'\) \{\s*\$this->verify_captcha\(\);\s*\}/); assert.match(controller, /get_error_code\(\)/); assert.match(controller, /get_error_message\(\)/); }); test('front submit TinyMCE status bar shows one live character counter', () => { const editor = read('assets/js/front-submit-editor.js'); const submitCss = read('assets/css/frontend/pages/submit.css'); const legacyPageEdit = read('assets/js/page-edit.js'); assert.match(editor, /querySelector\('\.mce-statusbar \.mce-container-body'\)/); assert.match(editor, /data-submit-word-count/); assert.match(editor, /textContent\s*=\s*'字数:'\s*\+\s*count/); assert.match(editor, /editor\.on\('init SetContent input keyup change Undo Redo'/); assert.match(editor, /replace\(\/\\s\+\/g, ''\)\.length/); assert.match(submitCss, /\.newposts-editor \.aksrt-submit-word-count\s*{[^}]*float:\s*right;/); assert.doesNotMatch(legacyPageEdit, /initWordCount|updateWordCount|aksrt-page-edit__word-count|字数:/); });