(object) ['singular_name' => '分类']]; } function get_post_type_object($type) { return (object) ['labels' => (object) ['singular_name' => $type === 'page' ? '页面' : '文章']]; } function get_term($id, $tax = '') { return $GLOBALS['aksrt_set_term']; } function wp_update_term($id, $tax, $args) { $GLOBALS['aksrt_set_meta']['__term'] = $args; return 1; } function wp_update_post($args, $wp_error = false) { $GLOBALS['aksrt_set_meta']['__post'] = $args; return 1; } function is_wp_error($v): bool { return false; } function wp_is_mobile(): bool { return false; } function is_page_template($tpl): bool { return in_array($tpl, $GLOBALS['aksrt_set_page_templates'], true); } function home_url($path = ''): string { return 'https://example.test' . $path; } function add_query_arg($args, $url = null) { if ($args === null) { return 'https://example.test/current'; } if ($url === null) { $url = 'https://example.test/current'; } $query = is_array($args) ? http_build_query($args) : (string) $args; return $url . (strpos($url, '?') === false ? '?' : '&') . $query; } function admin_url($path = ''): string { return 'https://example.test/wp-admin/' . ltrim((string) $path, '/'); } function wp_nonce_field($action = -1, $name = '_wpnonce', $referer = true, $echo = true) { $html = ''; if ($echo) { echo $html; return null; } return $html; } function check_ajax_referer($action = -1, $name = '_wpnonce'): bool { return true; } function wp_send_json($data, $code = null): void { $GLOBALS['aksrt_set_json'] = $data; throw new \RuntimeException('__sent__'); } function wp_kses($t, $allowed = null): string { return (string) $t; } function checked($a, $b = true, $echo = true): string { return (string) $a === (string) $b ? ' checked="checked"' : ''; } function selected($a, $b = true, $echo = true): string { return (string) $a === (string) $b ? ' selected="selected"' : ''; } function wp_enqueue_script($handle, $src = '', $deps = [], $ver = false, $in_footer = false): void { $GLOBALS['aksrt_set_scripts'][$handle] = ['src' => $src, 'deps' => $deps, 'in_footer' => $in_footer]; } function get_template_directory(): string { return dirname(__DIR__, 2); } function get_template_directory_uri(): string { return 'https://example.test/wp-content/themes/aksrt-theme'; } function set_check(bool $condition, string $message): void { if (!$condition) throw new \RuntimeException($message); } } namespace AKSRT\Settings { final class ExtensionRuntime { public static array $settings = []; public static function get_system_tools_settings(): array { return self::$settings; } } } namespace AKSRT\Core { final class PostMetaBox { public const META_KEY = 'aksrt_post_extension'; public static function get_meta(int $post_id): array { return $GLOBALS['aksrt_set_meta']; } } final class DomainModel { public const POST_VIEW_COUNT_KEY = 'aksrt_post_views'; } } namespace { use AKSRT\Frontend\PageAdminSet; require dirname(__DIR__, 2) . '/src/Frontend/PageAdminSet.php'; \AKSRT\Settings\ExtensionRuntime::$settings = ['admin_frontend_set' => true]; $GLOBALS['aksrt_set_queried'] = 0; $GLOBALS['aksrt_set_is_single'] = true; // --- Gate: a non-admin-visible context must not render anything. --- set_check(PageAdminSet::enabled() === false, 'enabled() must be false without a queried object'); $GLOBALS['aksrt_set_queried'] = 12; set_check(PageAdminSet::enabled() === true, 'enabled() must be true on a single post for an admin'); $GLOBALS['aksrt_set_can'] = false; set_check(PageAdminSet::enabled() === false, 'enabled() must respect manage_options'); $GLOBALS['aksrt_set_can'] = true; \AKSRT\Settings\ExtensionRuntime::$settings = ['admin_frontend_set' => false]; set_check(PageAdminSet::enabled() === false, 'enabled() must respect the admin_frontend_set switch'); \AKSRT\Settings\ExtensionRuntime::$settings = ['admin_frontend_set' => true]; $GLOBALS['aksrt_set_page_templates'] = ['templates/pages/page-submit.php']; set_check(PageAdminSet::enabled() === false, 'enabled() must exclude the submit page template'); $GLOBALS['aksrt_set_page_templates'] = []; $GLOBALS['aksrt_set_post'] = (object) ['ID' => 12, 'post_type' => 'post']; // --- Modal: reference markup parity. --- ob_start(); (new PageAdminSet())->render_modal(); $modal = (string) ob_get_clean(); set_check(strpos($modal, 'id="modal_admin_set"') !== false, 'modal must render #modal_admin_set'); set_check(strpos($modal, 'win-ajax-replace="modal_admin_set"') !== false, 'modal must carry the reference win-ajax-replace key'); set_check(strpos($modal, 'aria-hidden="false"') !== false, 'modal must expose aria-hidden="false"'); set_check(strpos($modal, 'class="modal-content page-set-modal"') !== false, 'modal must keep the page-set-modal class'); set_check(strpos($modal, 'class="modal-dialog" role="document"') !== false, 'modal must keep the reference dialog/document roles'); set_check(strpos($modal, 'data-dismiss="modal"') !== false, 'modal header must expose the dismiss button'); set_check(strpos($modal, 'name="action" value="aksrt_frontend_set_save"') !== false, 'modal must post aksrt_frontend_set_save'); set_check(strpos($modal, 'name="taxonomy" value=""') !== false, 'modal must post the taxonomy field'); set_check(strpos($modal, 'name="post_type" value="post"') !== false, 'modal must post the post_type field'); set_check(strpos($modal, 'name="id" value="12"') !== false, 'modal must post the object id'); set_check(strpos($modal, 'customize.php') !== false, '模块布局 must target the customizer'); set_check(strpos($modal, 'wp-ajax-submit') !== false, 'modal footer must submit via wp-ajax-submit'); set_check(strpos($modal, 'but jb-blue padding-lg') !== false, 'submit button must keep reference classes'); set_check(strpos($modal, 'but c-yellow padding-lg') !== false, '后台编辑 must keep reference classes'); set_check(strpos($modal, 'but c-yellow-2 padding-lg') !== false, '模块布局 must keep reference classes'); set_check(strpos($modal, 'col-xs-3 text-right') !== false, 'inputs must use the reference heading column'); set_check(strpos($modal, 'col-xs-8') !== false, 'inputs must use the reference option column'); set_check(strpos($modal, 'name="show_layout"') !== false, 'post inputs must include show_layout'); set_check(strpos($modal, 'name="no_article-navs"') !== false, 'post inputs must include the 目录树 checkbox'); set_check(strpos($modal, 'value="checkbox"') === false, 'checkbox inputs must render type="checkbox" not value="checkbox"'); // --- Save: the reference contract returns {msg, reload} outside any query context. --- $_POST = [ 'id' => '12', 'type' => 'post', 'post_title' => '新标题', 'comments_open' => '', 'show_layout' => 'no_sidebar', 'subtitle' => '副标题', 'views' => '42', 'like' => '7', 'no_article-navs' => '1', ]; try { (new PageAdminSet())->ajax_save(); set_check(false, 'ajax_save must terminate the request'); } catch (\RuntimeException $e) { set_check($e->getMessage() === '__sent__', 'ajax_save must terminate via wp_send_json'); } set_check(is_array($GLOBALS['aksrt_set_json']), 'save must send a JSON payload'); set_check(($GLOBALS['aksrt_set_json']['msg'] ?? '') === '保存成功,正在刷新页面', 'save must reuse the reference success message'); set_check(!empty($GLOBALS['aksrt_set_json']['reload']), 'save must ask the client to reload'); set_check(($GLOBALS['aksrt_set_meta']['__post']['post_title'] ?? '') === '新标题', 'save must update the post title'); set_check(($GLOBALS['aksrt_set_meta']['__post']['comment_status'] ?? '') === 'closed', 'save must close comments when empty'); $saved_meta = $GLOBALS['aksrt_set_meta']['aksrt_post_extension'] ?? []; set_check(($saved_meta['show_layout'] ?? '') === 'no_sidebar', 'save must persist show_layout'); set_check(($saved_meta['subtitle'] ?? '') === '副标题', 'save must persist the subtitle'); set_check(!empty($saved_meta['no_article-navs']), 'checked checkbox must persist as on'); set_check(empty($saved_meta['article_maxheight_xz']), 'unchecked checkbox must persist as off'); set_check((int) $GLOBALS['aksrt_set_meta']['aksrt_post_views'] === 42, 'save must persist the view count'); set_check((int) $GLOBALS['aksrt_set_meta']['_aksrt_like_count'] === 7, 'save must persist the like count'); // --- Permission: the switch off must reject the save even with a valid object. --- \AKSRT\Settings\ExtensionRuntime::$settings = ['admin_frontend_set' => false]; $GLOBALS['aksrt_set_json'] = null; try { (new PageAdminSet())->ajax_save(); } catch (\RuntimeException $e) { // expected } set_check(($GLOBALS['aksrt_set_json']['error'] ?? 0) === 1, 'disabled switch must reject the save'); set_check(!empty($GLOBALS['aksrt_set_json']['ys']), 'rejection must carry the danger type for the legacy client'); // --- Term save path. --- \AKSRT\Settings\ExtensionRuntime::$settings = ['admin_frontend_set' => true]; $GLOBALS['aksrt_set_json'] = null; $GLOBALS['aksrt_set_meta'] = []; $GLOBALS['aksrt_set_term'] = (object) ['term_id' => 5, 'taxonomy' => 'category', 'name' => '旧名称', 'description' => '']; $_POST = ['id' => '5', 'type' => 'tax', 'taxonomy' => 'category', 'term_name' => '新分类', 'term_description' => '描述']; try { (new PageAdminSet())->ajax_save(); } catch (\RuntimeException $e) { // expected } set_check(($GLOBALS['aksrt_set_meta']['__term']['name'] ?? '') === '新分类', 'term save must update the name'); set_check(($GLOBALS['aksrt_set_meta']['__term']['description'] ?? '') === '描述', 'term save must update the description'); // --- Page branch: 标题样式 + 模块布局 + 容器位置. --- $GLOBALS['aksrt_set_queried'] = 21; $GLOBALS['aksrt_set_is_single'] = false; $GLOBALS['aksrt_set_is_page'] = true; $GLOBALS['aksrt_set_post_type'] = 'page'; $GLOBALS['aksrt_set_post'] = (object) ['ID' => 21, 'post_type' => 'page']; $GLOBALS['aksrt_set_json'] = null; $GLOBALS['aksrt_set_meta'] = []; $_POST = [ 'id' => '21', 'type' => 'post', 'post_title' => '页面标题', 'page_header_style' => '2', 'widgets_register' => '1', 'widgets_register_container' => ['sidebar', 'top_fluid'], ]; try { (new PageAdminSet())->ajax_save(); } catch (\RuntimeException $e) { // expected } set_check(($GLOBALS['aksrt_set_meta']['page_header_style'] ?? '') === '2', 'page save must persist the header style'); set_check(($GLOBALS['aksrt_set_meta']['widgets_register'] ?? '') === '1', 'page save must enable the widget container'); set_check(($GLOBALS['aksrt_set_meta']['widgets_register_container'] ?? []) === ['sidebar', 'top_fluid'], 'page save must persist container positions'); $GLOBALS['aksrt_set_json'] = null; $GLOBALS['aksrt_set_meta'] = []; $_POST = ['id' => '21', 'type' => 'post', 'post_title' => '页面标题', 'widgets_register' => '0']; try { (new PageAdminSet())->ajax_save(); } catch (\RuntimeException $e) { // expected } set_check(($GLOBALS['aksrt_set_meta']['widgets_register'] ?? '') === '', 'closing the widget container must clear the flag'); set_check(($GLOBALS['aksrt_set_meta']['widgets_register_container'] ?? ['x']) === [], 'closing the widget container must clear positions'); // --- Hint script: the transient label must fade like the reference theme. --- PageAdminSet::enqueue_hint_script(); $hint = $GLOBALS['aksrt_set_scripts']['aksrt-front-page-admin-set'] ?? null; set_check(is_array($hint), 'the hint fade script must be enqueued when the feature is on'); set_check(strpos((string) ($hint['src'] ?? ''), '/assets/js/front-page-admin-set.js') !== false, 'the hint fade script must point at front-page-admin-set.js'); set_check(($hint['deps'] ?? []) === ['jquery'], 'the hint fade script must depend on jQuery'); set_check(!empty($hint['in_footer']), 'the hint fade script must load in the footer'); \AKSRT\Settings\ExtensionRuntime::$settings = ['admin_frontend_set' => false]; $GLOBALS['aksrt_set_scripts'] = []; PageAdminSet::enqueue_hint_script(); set_check($GLOBALS['aksrt_set_scripts'] === [], 'the disabled switch must not enqueue the hint fade script'); \AKSRT\Settings\ExtensionRuntime::$settings = ['admin_frontend_set' => true]; // --- Icons: the site-wide convention is the normalized `fa fas fa-*` form. --- $GLOBALS['aksrt_set_queried'] = 12; $GLOBALS['aksrt_set_is_single'] = true; $GLOBALS['aksrt_set_is_page'] = false; $GLOBALS['aksrt_set_post_type'] = 'post'; $GLOBALS['aksrt_set_post'] = (object) ['ID' => 12, 'post_type' => 'post']; ob_start(); (new PageAdminSet())->render_modal(); $modal_icons = (string) ob_get_clean(); foreach (['fa fas fa-sliders-h', 'fa fas fa-times', 'fa fas fa-chart-pie', 'fa fas fa-wordpress', 'fa fas fa-check'] as $icon) { set_check(strpos($modal_icons, $icon) !== false, "modal must use the FA5 icon {$icon}"); } set_check(strpos($modal_icons, 'class="fa fa-') === false, 'modal must not use bare FA4 icon classes'); $gear = (new PageAdminSet())->float_button('', false); set_check(strpos($gear, 'fa fas fa-cog fa-spin') !== false, 'the gear must use the FA5 cog class'); echo "Frontend page edit harness passed\n"; }