345 lines
22 KiB
JavaScript
345 lines
22 KiB
JavaScript
'use strict';
|
|
|
|
const assert = require('node:assert/strict');
|
|
const { execFileSync } = require('node:child_process');
|
|
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 SYSTEM_FIELDS = [
|
|
'hide_admin_bar', 'remove_emoji', 'remove_open_sans', 'remove_more_wp_head',
|
|
'remove_dashboard_widgets', 'display_wp_update',
|
|
'split_upload_s', 'split_minimum_size', 'newfilename', 'newfilename_type',
|
|
'disabled_large_thumbnail',
|
|
'admin_user_del_fields', 'admin_frontend_set', 'close_gutenberg', 'disabled_autoembed',
|
|
'mce_float_toolbar', 'time_ago_s', 'time_format',
|
|
];
|
|
|
|
test('system tools schema registers the 系统工具 page under 扩展&增强', () => {
|
|
const schema = read('src/Admin/Schema/SchemaExtensionsSystem.php');
|
|
const registry = read('src/Admin/Schema/SchemaExtensions.php');
|
|
const clientMirror = read('assets/admin/src/schema/sections/extensions/system.js');
|
|
const clientIndex = read('assets/admin/src/schema/sections/extensions/index.js');
|
|
const nav = read('assets/admin/src/constants/sections/extensions.js');
|
|
const sectionDefaults = read('assets/admin/src/constants/sections/index.js');
|
|
|
|
assert.match(registry, /'system'\s+=> SchemaExtensionsSystem::build\(\)/);
|
|
assert.match(schema, /'key'\s+=> 'system'/);
|
|
assert.match(schema, /'title'\s+=> '系统工具'/);
|
|
for (const key of SYSTEM_FIELDS) {
|
|
assert.match(schema, new RegExp(`'${key}'`), `missing schema field ${key}`);
|
|
assert.match(clientMirror, new RegExp(`${key}`), `missing client mirror field ${key}`);
|
|
}
|
|
// Conditional fields mirror the reference theme's dependency rules.
|
|
assert.match(schema, /'newfilename_type'[^[]*'dependsOn'\s+=> \['key' => 'newfilename', 'value' => true\]/s);
|
|
assert.match(schema, /'split_minimum_size'[^[]*'dependsOn'\s+=> \['key' => 'split_upload_s', 'value' => true\]/s);
|
|
assert.match(schema, /'time_format'[^[]*'dependsOn'\s+=> \['key' => 'time_ago_s', 'operator' => 'falsy'\]/s);
|
|
assert.match(clientMirror, /split_minimum_size[\s\S]*dependsOn:\s*\{\s*key:\s*'split_upload_s',\s*value:\s*true\s*\}/);
|
|
assert.match(clientMirror, /time_format[\s\S]*dependsOn:\s*\{\s*key:\s*'time_ago_s',\s*operator:\s*'falsy'\s*\}/);
|
|
assert.match(clientIndex, /import \{ systemSchema \} from '\.\/system\.js'/);
|
|
assert.match(clientIndex, /system: systemSchema/);
|
|
assert.match(nav, /key: 'extensions-system'/);
|
|
assert.match(nav, /route: '\/extensions\/system'/);
|
|
assert.match(nav, /section: 'system'/);
|
|
// 系统工具 is the first child of 扩展&增强 (reference theme order) and the default landing page.
|
|
assert.ok(nav.indexOf('extensions-system') < nav.indexOf('extensions-ip'), '系统工具 should precede IP归属地');
|
|
assert.match(sectionDefaults, /extensions:\s*'extensions-system'/);
|
|
});
|
|
|
|
test('system tools settings are consumed at runtime and initialized on boot', () => {
|
|
const runtime = read('src/Settings/ExtensionRuntime.php');
|
|
const bootstrap = read('functions.php');
|
|
|
|
assert.match(runtime, /public static function get_system_tools_settings\(\): array/);
|
|
assert.match(runtime, /'newfilename_type'\s+=> \$rename_type/);
|
|
assert.match(runtime, /'split_minimum_size'\s+=> max\(4, min\(100, \(int\) \(\$settings\['split_minimum_size'\] \?\? 20\)\)\)/);
|
|
assert.match(bootstrap, /\\AKSRT\\Extensions\\SystemTools::get_instance\(\)->init\(\)/);
|
|
assert.match(bootstrap, /\\AKSRT\\Frontend\\PageAdminSet::get_instance\(\)->init\(\)/);
|
|
});
|
|
|
|
test('system tools switches wire the reference theme hook suite', () => {
|
|
const tools = read('src/Extensions/SystemTools.php');
|
|
|
|
assert.match(tools, /!empty\(\$settings\['hide_admin_bar'\]\) \|\| !is_super_admin\(\)/);
|
|
assert.match(tools, /remove_action\('wp_head', 'print_emoji_detection_script', 7\)/);
|
|
assert.match(tools, /remove_filter\('wp_mail', 'wp_staticize_emoji_for_email'\)/);
|
|
assert.match(tools, /add_action\('init', \[\$this, 'remove_open_sans'\]\)/);
|
|
assert.match(tools, /wp_deregister_style\('open-sans'\)/);
|
|
assert.match(tools, /add_action\('after_setup_theme', \[\$this, 'remove_more_wp_head'\]\)/);
|
|
assert.match(tools, /remove_action\('wp_head', 'wp_generator'\)/);
|
|
assert.match(tools, /add_action\('do_meta_boxes', \[\$this, 'remove_dashboard_widgets'\], 10, 3\)/);
|
|
assert.match(tools, /unset\(\$wp_meta_boxes\['dashboard'\]\['normal'\]\['high'\]\['dashboard_php_nag'\]\)/);
|
|
assert.match(tools, /add_filter\('site_status_tests', \[\$this, 'filter_site_status_tests'\]\)/);
|
|
assert.match(tools, /add_filter\('pre_http_request', \[\$this, 'block_site_health_requests'\], 10, 3\)/);
|
|
assert.match(tools, /'api\.wordpress\.org\/core\/serve-happy\/'/);
|
|
assert.match(tools, /remove_action\('admin_init', '_maybe_update_core'\)/);
|
|
assert.match(tools, /add_filter\('automatic_updater_disabled', '__return_true'\)/);
|
|
assert.match(tools, /remove_action\('wp_version_check', 'wp_version_check'\)/);
|
|
assert.match(tools, /add_action\('admin_menu', \[\$this, 'hide_update_notices'\]\)/);
|
|
assert.match(tools, /remove_action\('admin_notices', 'update_nag', 3\)/);
|
|
assert.match(tools, /remove_image_size\('1536x1536'\)/);
|
|
assert.match(tools, /add_filter\('intermediate_image_sizes', static function \(\$sizes\)/);
|
|
assert.match(tools, /add_filter\('use_block_editor_for_post', '__return_false'\)/);
|
|
assert.match(tools, /remove_filter\('the_content', \[\$GLOBALS\['wp_embed'\], 'autoembed'\], 8\)/);
|
|
assert.match(tools, /add_action\('show_user_profile', \[\$this, 'hide_user_profile_fields'\]\)/);
|
|
assert.match(tools, /add_action\('edit_user_profile', \[\$this, 'hide_user_profile_fields'\]\)/);
|
|
});
|
|
|
|
test('upload rename settings are canonical to 系统工具 and consumed by the upload pipeline', () => {
|
|
const permissionsSchema = read('src/Admin/Schema/SchemaPermissions.php');
|
|
const permissionsMirror = read('assets/admin/src/schema/sections/permissions/index.js');
|
|
const systemSchema = read('src/Admin/Schema/SchemaExtensionsSystem.php');
|
|
const uploadLimits = read('src/Attachment/UploadLimits.php');
|
|
|
|
assert.doesNotMatch(permissionsSchema, /newfilename/);
|
|
assert.doesNotMatch(permissionsMirror, /newfilename/);
|
|
assert.match(systemSchema, /'newfilename'/);
|
|
assert.match(uploadLimits, /add_filter\('wp_handle_upload_prefilter', \[\$this, 'rename_upload_file'\]/);
|
|
assert.match(uploadLimits, /\$settings\['newfilename_type'\] \?\? 'time'\) === 'random'/);
|
|
});
|
|
|
|
test('split upload ships a full chunk protocol: routes, merge pipeline, and client uploader', () => {
|
|
const chunk = read('src/Core/FrontSubmitChunk.php');
|
|
const frontSubmit = read('src/Core/FrontSubmit.php');
|
|
const editorJs = read('assets/js/front-submit-editor.js');
|
|
const submitPage = read('templates/pages/page-submit.php');
|
|
const schema = read('src/Admin/Schema/SchemaExtensionsSystem.php');
|
|
|
|
// The submit class consumes the chunk trait and registers its routes.
|
|
assert.match(frontSubmit, /use FrontSubmitChunk;/);
|
|
assert.match(frontSubmit, /\$this->register_chunk_routes\(\)/);
|
|
assert.match(frontSubmit, /public function persist_uploaded_file\(string \$kind, array \$file, bool \$sideload = false\)/);
|
|
|
|
// Backend mirrors the reference theme's user_split_upload protocol.
|
|
assert.match(chunk, /\/front-submit\/upload-chunk\/\(\?P<kind>image\|video\|resource\)/);
|
|
assert.match(chunk, /\/front-submit\/upload-merge\(\?P<kind>image\|video\|resource\)|\/front-submit\/upload-merge\/\(\?P<kind>image\|video\|resource\)/);
|
|
assert.match(chunk, /\/front-submit\/upload-status\/\(\?P<kind>image\|video\|resource\)/);
|
|
assert.match(chunk, /permission_callback' => \[\$this, 'can_upload_attachment'\]/);
|
|
assert.match(chunk, /const CHUNK_SIZE_BYTES = 2 \* 1048576;/);
|
|
assert.match(chunk, /md5\(get_current_blog_id\(\) \. '\|' \. get_current_user_id\(\) \. '\|' \. \$name\)/);
|
|
assert.match(chunk, /\$this->persist_uploaded_file\(\$params\['kind'\], \[/);
|
|
assert.match(chunk, /apply_filters\('aksrt_temp_dir', WP_CONTENT_DIR \. '\/aksrt-temp'\)/);
|
|
assert.match(chunk, /\$this->chunk_temp_cleanup\(\$temp_dir\)/);
|
|
|
|
// Whole-file upload and chunk merge share one persistence pipeline; merged
|
|
// files are not HTTP uploads, so the pipeline must use the sideload action.
|
|
assert.match(frontSubmit, /return \$this->persist_uploaded_file\(\$kind, \$file, false\);/);
|
|
assert.match(frontSubmit, /'action' => 'wp_handle_sideload'/);
|
|
assert.match(frontSubmit, /media_handle_upload\('aksrt_front_submit_file', 0, \[\], \$overrides\)/);
|
|
|
|
// Client uploader picks the chunked path past the threshold and resumes from status.
|
|
assert.match(editorJs, /function uploadInChunks\(file, type\)/);
|
|
assert.match(editorJs, /splitUploadConfig\(\)/);
|
|
assert.match(editorJs, /file\.size > split\.minSize \* 1048576/);
|
|
assert.match(editorJs, /chunkUrl\('upload-status'\)/);
|
|
assert.match(editorJs, /chunkUrl\('upload-chunk'\)/);
|
|
assert.match(editorJs, /chunkUrl\('upload-merge'\)/);
|
|
assert.match(editorJs, /'\/front-submit\/' \+ action \+ '\/' \+ uploadKey/);
|
|
|
|
// Config flows from the settings page into aksrtSubmitData.
|
|
assert.match(submitPage, /'splitUpload' => \[/);
|
|
assert.match(submitPage, /'enabled' => !empty\(\$system_tools\['split_upload_s'\]\)/);
|
|
assert.match(submitPage, /'minSize' => \(int\) \(\$system_tools\['split_minimum_size'\] \?\? 20\)/);
|
|
assert.match(schema, /'split_upload_s'/);
|
|
assert.match(schema, /'split_minimum_size'/);
|
|
|
|
// The legacy dead config keys stay out of the shared script payload.
|
|
assert.doesNotMatch(read('src/Core/ScriptsPart1c.php'), /is_split_upload|split_minimum_size/);
|
|
});
|
|
|
|
test('frontend page edit ships gate, footer modal, and legacy ajax-save contract', () => {
|
|
const pageAdminSet = read('src/Frontend/PageAdminSet.php');
|
|
const floatButtons = read('inc/helpers/aksrt-float-buttons.php');
|
|
const bootstrap = read('functions.php');
|
|
const mainJs = read('assets/js/runtime/main.js');
|
|
|
|
assert.match(bootstrap, /\\AKSRT\\Frontend\\PageAdminSet::get_instance\(\)->init\(\)/);
|
|
assert.match(pageAdminSet, /public static function enabled\(\): bool/);
|
|
assert.match(pageAdminSet, /empty\(\$settings\['admin_frontend_set'\]/);
|
|
assert.match(pageAdminSet, /current_user_can\('manage_options'\)/);
|
|
assert.match(pageAdminSet, /is_page_template\('templates\/pages\/page-submit\.php'\)/);
|
|
assert.match(pageAdminSet, /is_page_template\('templates\/pages\/page-posts-navs\.php'\)/);
|
|
|
|
// Reference parity: the gear float button opens the footer-rendered bootstrap modal.
|
|
assert.match(floatButtons, /apply_filters\('aksrt_float_right_buttons', \$btn, \$is_mobile\)/);
|
|
assert.match(pageAdminSet, /add_filter\('aksrt_float_right_buttons', \[\$this, 'float_button'\], 10, 2\)/);
|
|
assert.match(pageAdminSet, /add_action\('wp_footer', \[\$this, 'render_modal'\], 10\)/);
|
|
assert.match(pageAdminSet, /data-toggle="modal" data-target="#modal_admin_set"/);
|
|
assert.match(pageAdminSet, /class="modal fade" id="modal_admin_set" tabindex="-1" role="dialog" aria-hidden="false" win-ajax-replace="modal_admin_set"/);
|
|
assert.match(pageAdminSet, /class="modal-content page-set-modal"/);
|
|
assert.match(pageAdminSet, /wp_nonce_field\('aksrt_frontend_set', '_wpnonce', true, false\)/);
|
|
// Reference hidden inputs: type + taxonomy + post_type + id travel with the submit.
|
|
assert.match(pageAdminSet, /name="action" value="aksrt_frontend_set_save"/);
|
|
assert.match(pageAdminSet, /name="type" value="' . esc_attr\(\$type\)/);
|
|
assert.match(pageAdminSet, /name="taxonomy" value="' . esc_attr\(\$taxonomy\)/);
|
|
assert.match(pageAdminSet, /name="post_type" value="' . esc_attr\(\$post_type\)/);
|
|
// 模块布局 opens the customizer widgets panel and returns to the current URL.
|
|
assert.match(pageAdminSet, /admin_url\('customize\.php'\)/);
|
|
assert.match(pageAdminSet, /'autofocus' => \['panel' => 'widgets'\]/);
|
|
|
|
// Modal footer mirrors the reference: 模块布局 / 后台编辑 / 确认修改 (wp-ajax-submit).
|
|
assert.match(pageAdminSet, /esc_html__\('模块布局', 'aksrt'\)/);
|
|
assert.match(pageAdminSet, /esc_html__\('后台编辑', 'aksrt'\)/);
|
|
assert.match(pageAdminSet, /esc_html__\('确认修改', 'aksrt'\)/);
|
|
assert.match(pageAdminSet, /class="but c-yellow-2 padding-lg"/);
|
|
assert.match(pageAdminSet, /class="but c-yellow padding-lg"/);
|
|
assert.match(pageAdminSet, /class="but jb-blue padding-lg wp-ajax-submit"/);
|
|
|
|
// Fields replicate the reference input construct and map to AKSRT's post meta.
|
|
assert.match(pageAdminSet, /private function render_inputs\(array \$inputs\): string/);
|
|
assert.match(pageAdminSet, /class="heading col-xs-3 text-right"/);
|
|
assert.match(pageAdminSet, /class="option col-xs-8"/);
|
|
assert.match(pageAdminSet, /\\AKSRT\\Core\\PostMetaBox::META_KEY/);
|
|
assert.match(pageAdminSet, /\\AKSRT\\Core\\DomainModel::POST_VIEW_COUNT_KEY/);
|
|
assert.match(pageAdminSet, /update_post_meta\(\$post_id, '_aksrt_like_count', max\(0, \(int\) \$_POST\['like'\]\)\)/);
|
|
assert.match(pageAdminSet, /wp_update_term\(\$term_id, \(string\) \$term->taxonomy, \$data\)/);
|
|
assert.match(pageAdminSet, /set_post_format\(\$post_id, \$format === 'standard' \? '' : \$format\)/);
|
|
// 文章格式 mirrors the reference's fixed four-option list, not the theme's
|
|
// wider add_theme_support('post-formats') registration.
|
|
assert.match(pageAdminSet, /return \[\s*'standard' => __\('标准', 'aksrt'\),\s*'image' => __\('图像', 'aksrt'\),\s*'gallery' => __\('画廊', 'aksrt'\),\s*'video' => __\('视频', 'aksrt'\),\s*\];/s);
|
|
assert.doesNotMatch(pageAdminSet, /get_theme_support\('post-formats'\)\s*\?/);
|
|
assert.doesNotMatch(pageAdminSet, /get_post_format_string\(/);
|
|
// Page branch: 标题样式 + 模块布局(widgets_register) + 容器位置.
|
|
assert.match(pageAdminSet, /esc_attr\(\(string\) \(\$meta\['id'\] \?\? ''\)\) \. '\[\]" id="'/);
|
|
assert.match(pageAdminSet, /'page_header_style'/);
|
|
assert.match(pageAdminSet, /update_post_meta\(\$post_id, 'widgets_register', \$register \? '1' : ''\)/);
|
|
assert.match(pageAdminSet, /update_post_meta\(\$post_id, 'widgets_register_container', \$containers\)/);
|
|
// Reference checkbox semantics: unchecked boxes never submit, so both are written unconditionally.
|
|
assert.match(pageAdminSet, /\$meta\['no_article-navs'\] = !empty\(\$_POST\['no_article-navs'\]\)/);
|
|
assert.match(pageAdminSet, /\$meta\['article_maxheight_xz'\] = !empty\(\$_POST\['article_maxheight_xz'\]\)/);
|
|
// The geometry mirrors the reference construct (row + heading + option, \n separators).
|
|
assert.match(pageAdminSet, /'<div class="mb10 row' \. esc_attr\(\$class\) \. '">' \. "\\n"/);
|
|
|
|
// Save runs under the vendored main.js aksrt_ajax contract ({msg,reload}).
|
|
assert.match(pageAdminSet, /add_action\('wp_ajax_aksrt_frontend_set_save', \[\$this, 'ajax_save'\]\)/);
|
|
assert.match(pageAdminSet, /check_ajax_referer\('aksrt_frontend_set', '_wpnonce'\)/);
|
|
assert.match(pageAdminSet, /wp_send_json\(\['msg' => __\('保存成功,正在刷新页面', 'aksrt'\), 'reload' => true\]\)/);
|
|
assert.match(mainJs, /jQuery\.fn\.serializeObject = function/);
|
|
assert.match(mainJs, /if \(n\.reload\) \{/);
|
|
// The dedicated fetch-submit client is gone: vendored main.js owns the flow.
|
|
assert.equal(fs.existsSync(path.join(root, 'assets/js/page-admin-set.js')), false);
|
|
});
|
|
|
|
test('frontend page edit icons follow the FA5 convention and the hint bar fades like the reference', () => {
|
|
const pageAdminSet = read('src/Frontend/PageAdminSet.php');
|
|
const hintJs = read('assets/js/front-page-admin-set.js');
|
|
const floatingCss = read('assets/css/frontend/floating-buttons.css');
|
|
const publicCss = read('assets/css/build/public.css');
|
|
const publicMinCss = read('assets/css/build/public.min.css');
|
|
|
|
// Site-wide icon convention is aksrt_icon_markup()'s normalized `fa fas fa-*`:
|
|
// the gear, the modal header, both footer links and the submit button.
|
|
// FA5 renames the reference's FA4 glyphs (sliders→sliders-h, pie-chart→chart-pie).
|
|
assert.match(pageAdminSet, /<i class="fa fas fa-cog fa-spin" aria-hidden="true"><\/i>/);
|
|
assert.match(pageAdminSet, /<i class="fa fas fa-sliders-h mr10" aria-hidden="true"><\/i>/);
|
|
assert.match(pageAdminSet, /<i class="fa fas fa-chart-pie" aria-hidden="true"><\/i>/);
|
|
// fa-wordpress lives in FA5's Brands family, so it needs `fa fab`, not `fa fas`.
|
|
assert.match(pageAdminSet, /<i class="fa fab fa-wordpress" aria-hidden="true"><\/i>/);
|
|
assert.match(pageAdminSet, /<i class="fa fas fa-check" aria-hidden="true"><\/i>/);
|
|
assert.match(pageAdminSet, /<i class="fa fas fa-times" aria-hidden="true"><\/i>/);
|
|
// No bare FA4 prefixes survive inside the feature markup.
|
|
assert.doesNotMatch(pageAdminSet, /class="fa fa-(cog|sliders|sliders-h|chart-pie|pie-chart|wordpress|check|times)\b/);
|
|
// Every glyph must exist in the shipped FA5 build (fa-spin is a utility, not a glyph).
|
|
const fa5 = read('assets/fontawesome5/css/all.min.css');
|
|
for (const token of ['fa-cog', 'fa-sliders-h', 'fa-chart-pie', 'fa-wordpress', 'fa-check', 'fa-times']) {
|
|
assert.ok(fa5.includes(`.${token}:before`), `${token} is missing from Font Awesome 5.15.4`);
|
|
}
|
|
// Brand icons resolve through the Brands family: .fab must set that font-family,
|
|
// and fa-wordpress must sit in it, otherwise the glyph box renders empty.
|
|
const fabRule = fa5.match(/\.fab\s*\{[^}]*\}/);
|
|
assert.ok(fabRule, '.fab is missing from Font Awesome 5.15.4');
|
|
assert.match(fabRule[0], /Font Awesome 5 Brands/);
|
|
assert.match(fa5, /\.fa-wordpress::?before\s*\{[^}]*content:\s*"\\f[0-9a-f]+"/i, 'fa-wordpress has no glyph content in FA5');
|
|
|
|
// The transient label fades 2s after load over 1s (reference js/page-edit.js).
|
|
assert.match(pageAdminSet, /add_action\('wp_footer', \[self::class, 'enqueue_hint_script'\], 9\)/);
|
|
assert.match(pageAdminSet, /wp_enqueue_script\(\s*'aksrt-front-page-admin-set',\s*get_template_directory_uri\(\) \. '\/assets\/js\/front-page-admin-set\.js',\s*\['jquery'\],\s*\(string\) filemtime\(\$path\),\s*true\s*\)/s);
|
|
assert.match(hintJs, /setTimeout\(function \(\) \{/, 'the fade is delayed like the reference');
|
|
assert.match(hintJs, /hint\.style\.transition = 'opacity 1s'/);
|
|
assert.match(hintJs, /hint\.style\.opacity = '0'/);
|
|
assert.match(hintJs, /querySelector\('\.admin-set-page \.admin-set-info'\)/);
|
|
|
|
// Label geometry: reference .abs-right is top:50% + right:.7em + translateY(-50%).
|
|
// AKSRT's legacy .abs-right is top:0/right:0, so the float bar restores it.
|
|
// The minified bundle has no space before the brace, so keep it optional.
|
|
const labelRule = /\.float-right \.admin-set-page \.admin-set-info\s*\{[^}]*\}/s;
|
|
assert.match(floatingCss, labelRule);
|
|
assert.match(publicCss, labelRule);
|
|
assert.match(publicMinCss, labelRule);
|
|
for (const css of [floatingCss, publicCss, publicMinCss]) {
|
|
const rule = css.match(labelRule)[0];
|
|
assert.match(rule, /position:\s*absolute/);
|
|
assert.match(rule, /top:\s*50%/);
|
|
assert.match(rule, /right:\s*0?\.7em/);
|
|
assert.match(rule, /transform:\s*translateY\(-50%\)/);
|
|
}
|
|
|
|
// Select arrow: .form-select::before already paints the FA5 chevron, so the
|
|
// wrapper's <select> must reset appearance or Chrome draws the native arrow
|
|
// beside it. main.min.css is the runtime file PHP enqueues.
|
|
const mainCss = read('assets/css/main.css');
|
|
const mainMinCss = read('assets/css/main.min.css');
|
|
const arrowRule = /\.form-select select\s*\{[^}]*\}/s;
|
|
assert.match(mainCss, arrowRule);
|
|
assert.match(mainMinCss, arrowRule);
|
|
for (const css of [mainCss, mainMinCss]) {
|
|
const rule = css.match(arrowRule)[0];
|
|
assert.match(rule, /-webkit-appearance:\s*none/);
|
|
assert.match(rule, /-moz-appearance:\s*none/);
|
|
assert.match(rule, /(^|[^-])appearance:\s*none/);
|
|
}
|
|
// The chevron itself stays co-located so the pair is reviewed together.
|
|
assert.match(mainCss, /\.form-select::before\s*\{[^}]*content:\s*'\\f107'/s);
|
|
});
|
|
|
|
test('float toolbar config reaches the tinymce plugin with reference-context buttons', () => {
|
|
const editorJs = read('assets/js/front-submit-editor.js');
|
|
const submitPage = read('templates/pages/page-submit.php');
|
|
const editorTemplate = read('templates/parts/submit/editor-main.php');
|
|
const schema = read('src/Admin/Schema/SchemaExtensionsSystem.php');
|
|
|
|
assert.match(submitPage, /\$float_toolbar = wp_is_mobile\(\) \? in_array\('m_s', \$float_scenes, true\) : in_array\('pc_s', \$float_scenes, true\)/);
|
|
assert.match(submitPage, /'floatToolbar'\s+=> \$float_toolbar/);
|
|
assert.match(editorJs, /if \(editorCfg\.floatToolbar\) \{\s*\n\s*registerFloatToolbar\(editor\);/);
|
|
assert.match(editorJs, /editor\.on\('wptoolbar', function \(event\)/);
|
|
assert.match(editorJs, /editor\.wp\._createToolbar\(bars\)/);
|
|
assert.match(editorJs, /editor\.addButton\('aksrt_h2'/);
|
|
assert.match(editorJs, /editor\.addButton\('aksrt_h3'/);
|
|
assert.match(editorJs, /editor\.addButton\('aksrt_remove'/);
|
|
assert.match(editorJs, /function floatToolbarButtons\(editor, event\)/);
|
|
assert.match(schema, /'mce_float_toolbar', 'label' => '经典编辑器增强浮动工具栏', 'type' => 'checkbox'/);
|
|
// The editor template still wires the plugin into wp_editor.
|
|
assert.match(editorTemplate, /aksrt_editor_tools/);
|
|
});
|
|
|
|
test('countdown display switch gates aksrt_time_ago with the custom format fallback', () => {
|
|
const helper = read('inc/helpers/template-tags/search-meta.php');
|
|
const coreHooks = read('inc/core-hooks.php');
|
|
|
|
assert.match(helper, /empty\(\$system\['time_ago_s'\]\) && \(\$system\['time_format'\] \?\? ''\) !== ''/);
|
|
assert.match(helper, /return date\(\$system\['time_format'\], \$time\)/);
|
|
// Admin bar gating moved out of the unconditional core hook into SystemTools.
|
|
assert.doesNotMatch(coreHooks, /add_filter\('show_admin_bar', '__return_false'\)/);
|
|
});
|
|
|
|
test('system tools runtime contract harness passes', () => {
|
|
const harness = path.join(root, 'tests', 'widget-system', 'system-tools-harness.php');
|
|
const output = execFileSync('php', [harness], { encoding: 'utf8' });
|
|
assert.match(output, /System tools harness passed/);
|
|
});
|
|
|
|
test('split upload runtime contract harness passes', () => {
|
|
const harness = path.join(root, 'tests', 'widget-system', 'split-upload-harness.php');
|
|
const output = execFileSync('php', [harness], { encoding: 'utf8' });
|
|
assert.match(output, /Split upload harness passed/);
|
|
});
|
|
|
|
test('frontend page edit runtime contract harness passes', () => {
|
|
const harness = path.join(root, 'tests', 'widget-system', 'frontend-page-edit-harness.php');
|
|
const output = execFileSync('php', [harness], { encoding: 'utf8' });
|
|
assert.match(output, /Frontend page edit harness passed/);
|
|
});
|