refactor: rename the theme brand and every identifier from AKSRT to Jio
Full-project rename. `git grep -i aksrt` is now empty apart from the
DBMigrator 3.3.0 changelog note that documents the rename itself.
Code identifiers
- PHP namespace AKSRT\ -> Jio\ (composer PSR-4 + 378 declarations)
- global constants AKSRT_* -> JIO_* (VERSION/DIR/URI, PAY_TYPE_*, CAPTCHA_*)
- procedural functions, hooks, options, meta keys, table names,
cookies, query vars, ajax actions and nonces: aksrt_ -> jio_
- text domain aksrt -> jio; Theme Name -> Jio
- JS globals: aksrtData/aksrtAdminData/... -> jioData/jioAdminData/...,
window.AKSRTCaptcha & friends -> window.JioCaptcha, Aksrt* -> Jio*
- Vue components Aks*.vue -> Jio*.vue (15 files renamed + imports)
- CSS classes/data-attrs/custom properties: aksrt- -> jio-
- REST namespace aksrt/v1 -> jio/v1 (incl. payment gateway callbacks)
Storage layer
- DB tables {prefix}aksrt_* -> {prefix}jio_*; SCHEMA_VERSION 3.2.0 -> 3.3.0
so dbDelta creates the new names on install
- widget id_base aksrt_widget_* -> jio_widget_*
- uninstall.php drop list rebuilt from the actual migrator tables
(dropped 3 stale entries, added the 17 missing ones)
Removed
- hardcoded https://api.aksrt.com/version.json default update endpoint;
get_endpoint() now reads the 「更新端点」 setting and do_check() no-ops
when it is empty
Files renamed: inc/helpers/aksrt-*.php -> jio-*.php (54),
assets/admin/src/components/form/Aks*.vue -> Jio*.vue (15).
Build artifacts regenerated: assets/css/build, assets/js/runtime,
assets/admin/build.
Verified: audit-psr4-paths errors=0, audit-admin-contracts errors=0
(warnings=56, unchanged baseline), 813/813 widget-system tests,
44/44 phpunit, php -l clean across all PHP.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
5b16bc6e15
commit
35bb96e82f
997 files changed
+28216
-28188
No files matched your search
@@ -17,44 +17,44 @@ test('account dialogs expose configured email steps, phone steps, and reference
|
||||
assert.match(template, /\$email_verify_required = \$account_email !== '' && !empty\(\$user_settings\['email_set_captch'\]\)/);
|
||||
assert.match(template, /data-account-step="email-old"[\s\S]*?data-account-step="email-new"[\s\S]*?data-account-step="email-success"/);
|
||||
assert.match(template, /if \(\$email_verify_required\)/);
|
||||
assert.match(template, /if \(\$email_verify_required\)[\s\S]*?else : \?>[\s\S]*?aksrt-settings-modal__pane/);
|
||||
assert.match(template, /if \(\$email_verify_required\)[\s\S]*?else : \?>[\s\S]*?jio-settings-modal__pane/);
|
||||
assert.match(template, /if \(!empty\(\$user_settings\['email_set_captch'\]\)\)/);
|
||||
assert.match(template, /data-account-step="phone-old"[\s\S]*?data-account-step="phone-new"[\s\S]*?data-account-step="phone-success"/);
|
||||
assert.match(template, /if \(\$account_phone !== ''\)[\s\S]*?else : \?>[\s\S]*?aksrt-settings-modal__pane/);
|
||||
assert.match(template, /data-aksrt-sms-scene="verify_old_phone"/);
|
||||
assert.match(template, /data-aksrt-sms-scene="bind_new_phone"/);
|
||||
assert.match(template, /\$account_close_icon = function_exists\('aksrt_icon_markup'\)/);
|
||||
assert.match(template, /if \(\$account_phone !== ''\)[\s\S]*?else : \?>[\s\S]*?jio-settings-modal__pane/);
|
||||
assert.match(template, /data-jio-sms-scene="verify_old_phone"/);
|
||||
assert.match(template, /data-jio-sms-scene="bind_new_phone"/);
|
||||
assert.match(template, /\$account_close_icon = function_exists\('jio_icon_markup'\)/);
|
||||
assert.match(template, /<i class="fa far fa-envelope"/);
|
||||
assert.match(template, /\$account_phone_display = function_exists\('aksrt_phone_mask_display'\) \? aksrt_phone_mask_display\(\$account_phone\) : \$account_phone;/);
|
||||
assert.match(template, /\$account_phone_display = function_exists\('jio_phone_mask_display'\) \? jio_phone_mask_display\(\$account_phone\) : \$account_phone;/);
|
||||
assert.match(template, /\$account_phone !== '' \? \$account_phone_display : __\('暂未绑定'/);
|
||||
assert.doesNotMatch(template, /为确认是您本人操作,请补全绑定手机号 %s/);
|
||||
assert.match(template, /为确认是您本人操作,请补全绑定手机号/);
|
||||
assert.match(template, /class="aksrt-phone-digit__edge"><\?php echo esc_html\(\(\$phone_mask\['leading_plus'\] \? '\+' : ''\) \. \(string\) \$phone_mask\['prefix'\]\); \?><\/span>/);
|
||||
assert.match(template, /<span class="aksrt-phone-digit__edge"><\?php echo esc_html\(\(string\) \$phone_mask\['suffix'\]\); \?><\/span>/);
|
||||
assert.match(template, /class="jio-phone-digit__edge"><\?php echo esc_html\(\(\$phone_mask\['leading_plus'\] \? '\+' : ''\) \. \(string\) \$phone_mask\['prefix'\]\); \?><\/span>/);
|
||||
assert.match(template, /<span class="jio-phone-digit__edge"><\?php echo esc_html\(\(string\) \$phone_mask\['suffix'\]\); \?><\/span>/);
|
||||
assert.doesNotMatch(template, /name="phone" value="/);
|
||||
assert.match(template, /data-aksrt-phone-digits data-hidden-length/);
|
||||
assert.match(template, /class="aksrt-phone-digit" inputmode="numeric" maxlength="1"/);
|
||||
assert.match(template, /aria-label="<\?php echo esc_attr\(sprintf\(__\('手机号第 %d 位数字', 'aksrt'\), 4 \+ \$phone_digit_index\)\); \?>"/);
|
||||
assert.match(template, /data-aksrt-phone-code-area<\?php echo \$phone_mask !== null \? ' hidden' : ''; \?>/);
|
||||
assert.match(template, /data-aksrt-phone-digits-error/);
|
||||
assert.match(template, /data-jio-phone-digits data-hidden-length/);
|
||||
assert.match(template, /class="jio-phone-digit" inputmode="numeric" maxlength="1"/);
|
||||
assert.match(template, /aria-label="<\?php echo esc_attr\(sprintf\(__\('手机号第 %d 位数字', 'jio'\), 4 \+ \$phone_digit_index\)\); \?>"/);
|
||||
assert.match(template, /data-jio-phone-code-area<\?php echo \$phone_mask !== null \? ' hidden' : ''; \?>/);
|
||||
assert.match(template, /data-jio-phone-digits-error/);
|
||||
assert.match(javascript, /\/user\/phone-match/);
|
||||
assert.match(javascript, /middle: middle/);
|
||||
assert.match(javascript, /stage: 'reset_old_phone'/);
|
||||
assert.match(javascript, /function initPhoneDigits\(/);
|
||||
assert.match(css, /\.aksrt-settings-modal \.aksrt-phone-digit \{/);
|
||||
assert.match(css, /\.aksrt-settings-modal \.aksrt-phone-digit__edge \{/);
|
||||
assert.match(css, /\.jio-settings-modal \.jio-phone-digit \{/);
|
||||
assert.match(css, /\.jio-settings-modal \.jio-phone-digit__edge \{/);
|
||||
assert.match(css, /background-color: var\(--main-bg-color, #f7f8fa\)/);
|
||||
assert.match(css, /\.aksrt-settings-modal \.aksrt-phone-digits\.has-error \.aksrt-phone-digit \{/);
|
||||
assert.match(template, /aksrt_render_captcha\('change_email'\)[\s\S]*?id="email-code"/);
|
||||
assert.match(template, /aksrt_render_captcha\('bind_phone'\)[\s\S]*?id="bind-phone-code"/);
|
||||
assert.match(css, /\.jio-settings-modal \.jio-phone-digits\.has-error \.jio-phone-digit \{/);
|
||||
assert.match(template, /jio_render_captcha\('change_email'\)[\s\S]*?id="email-code"/);
|
||||
assert.match(template, /jio_render_captcha\('bind_phone'\)[\s\S]*?id="bind-phone-code"/);
|
||||
assert.match(template, /if \(!\$account_oauth_new\)[\s\S]*?name="passwordold"/);
|
||||
assert.match(template, /if \(!\$account_oauth_new && \$can_recover_password\)/);
|
||||
assert.match(template, /data-account-password-pane="recovery"/);
|
||||
assert.match(javascript, /\/auth\/code/);
|
||||
assert.match(javascript, /\/auth\/reset-password/);
|
||||
assert.match(javascript, /className: 'aksrt-user-settings-modal modal-mini full-sm'/);
|
||||
assert.match(javascript, /className: 'jio-user-settings-modal modal-mini full-sm'/);
|
||||
assert.doesNotMatch(template + javascript + css, /pwd-strength|pwd-send-code|email-send-code/);
|
||||
assert.match(css, /\.aksrt-modal-runtime\.aksrt-user-settings-modal\.aksrt-modal-runtime--bottom \.aksrt-modal-runtime__dialog \{[\s\S]*?width: 400px/);
|
||||
assert.match(css, /\.jio-modal-runtime\.jio-user-settings-modal\.jio-modal-runtime--bottom \.jio-modal-runtime__dialog \{[\s\S]*?width: 400px/);
|
||||
assert.match(css, /@media \(max-width: 640px\)[\s\S]*?width: 100%/);
|
||||
assert.match(css, /modal-line-field--code \.yztx\.abs-right[\s\S]*?top: 50%[\s\S]*?right: \.7em/);
|
||||
assert.doesNotMatch(css, /\.code-send-btn \{ min-width:/);
|
||||
@@ -83,25 +83,25 @@ test('account PHP harness exercises email and password state transitions', () =>
|
||||
|
||||
test('email update keeps both proofs through validation failures and consumes them only after the write', () => {
|
||||
const ajax = read('inc/modules/user-parts/account-ajax.php');
|
||||
const send = ajax.slice(ajax.indexOf("add_action('wp_ajax_aksrt_send_account_email_code'"), ajax.indexOf("add_action('wp_ajax_aksrt_verify_account_email'"));
|
||||
const verify = ajax.slice(ajax.indexOf("add_action('wp_ajax_aksrt_verify_account_email'"), ajax.indexOf("add_action('wp_ajax_aksrt_change_password'"));
|
||||
const change = ajax.slice(ajax.indexOf("add_action('wp_ajax_aksrt_change_email'"));
|
||||
const send = ajax.slice(ajax.indexOf("add_action('wp_ajax_jio_send_account_email_code'"), ajax.indexOf("add_action('wp_ajax_jio_verify_account_email'"));
|
||||
const verify = ajax.slice(ajax.indexOf("add_action('wp_ajax_jio_verify_account_email'"), ajax.indexOf("add_action('wp_ajax_jio_change_password'"));
|
||||
const change = ajax.slice(ajax.indexOf("add_action('wp_ajax_jio_change_email'"));
|
||||
|
||||
assert.match(send, /\['verify_email', 'change_email'\]/);
|
||||
assert.match(send, /email_exists\(\$target\)/);
|
||||
assert.match(send, /SendLimit::check\(\$target\)/);
|
||||
assert.match(send, /SendLimit::record\(\$target\)/);
|
||||
assert.doesNotMatch(send, /aksrt_account_email_rate_/);
|
||||
assert.doesNotMatch(ajax, /aksrt_account_email_rate_/);
|
||||
assert.match(verify, /aksrt_require_captcha\(\s*'change_email'/);
|
||||
assert.match(verify, /aksrt_account_set_proof\(\$user_id, 'email_old'/);
|
||||
assert.match(change, /aksrt_account_email_verification_required\(\)/);
|
||||
assert.match(change, /aksrt_account_consume_proof\([^;]+false\)/);
|
||||
assert.match(change, /aksrt_account_consume_code\([\s\S]*?false\s*\)/);
|
||||
assert.ok(change.indexOf('wp_update_user(') < change.indexOf("delete_transient(aksrt_account_proof_key"));
|
||||
assert.ok(change.indexOf('wp_update_user(') < change.indexOf("delete_transient(aksrt_account_code_key"));
|
||||
assert.doesNotMatch(send, /jio_account_email_rate_/);
|
||||
assert.doesNotMatch(ajax, /jio_account_email_rate_/);
|
||||
assert.match(verify, /jio_require_captcha\(\s*'change_email'/);
|
||||
assert.match(verify, /jio_account_set_proof\(\$user_id, 'email_old'/);
|
||||
assert.match(change, /jio_account_email_verification_required\(\)/);
|
||||
assert.match(change, /jio_account_consume_proof\([^;]+false\)/);
|
||||
assert.match(change, /jio_account_consume_code\([\s\S]*?false\s*\)/);
|
||||
assert.ok(change.indexOf('wp_update_user(') < change.indexOf("delete_transient(jio_account_proof_key"));
|
||||
assert.ok(change.indexOf('wp_update_user(') < change.indexOf("delete_transient(jio_account_code_key"));
|
||||
assert.match(change, /\$agreement\['required'\]/);
|
||||
assert.doesNotMatch(ajax, /wp_ajax_aksrt_send_email_code/);
|
||||
assert.doesNotMatch(ajax, /wp_ajax_jio_send_email_code/);
|
||||
});
|
||||
|
||||
test('phone replacement requires old-number proof and a new-number code while reusing bind side effects', () => {
|
||||
@@ -123,29 +123,29 @@ test('phone replacement requires old-number proof and a new-number code while re
|
||||
assert.match(code, /\$phone = \$current_phone;/);
|
||||
assert.doesNotMatch(code, /auth_rate_limited\('account-phone-code'/);
|
||||
assert.match(verify, /auth_consume_code\('account_verify_old_phone_' \./);
|
||||
assert.match(verify, /aksrt_account_phone_old_/);
|
||||
assert.match(bind, /aksrt_account_phone_old_/);
|
||||
assert.match(verify, /jio_account_phone_old_/);
|
||||
assert.match(bind, /jio_account_phone_old_/);
|
||||
assert.match(bind, /auth_consume_code\(\$code_scene, \$phone, \$code, false\)/);
|
||||
assert.ok(bind.indexOf("Auth\\Service::bind_phone") < bind.indexOf("delete_transient($this->auth_code_key($code_scene, $phone))"));
|
||||
assert.match(bind, /aksrt_get_auth_agreement_view/);
|
||||
assert.match(bind, /jio_get_auth_agreement_view/);
|
||||
assert.match(bind, /Auth\\Service::bind_phone\(\$payload, true\)/);
|
||||
assert.match(binding, /aksrt_user_phone_changed/);
|
||||
assert.match(binding, /jio_user_phone_changed/);
|
||||
assert.doesNotMatch(controller, /auth_consume_code\('bind_phone'/);
|
||||
});
|
||||
|
||||
test('password change retains old-password limits and captcha; recovery uses shared auth endpoints', () => {
|
||||
const ajax = read('inc/modules/user-parts/account-ajax.php');
|
||||
const javascript = read('assets/js/routes/user-center.js');
|
||||
const password = ajax.slice(ajax.indexOf("add_action('wp_ajax_aksrt_change_password'"), ajax.indexOf("add_action('wp_ajax_aksrt_change_email'"));
|
||||
const password = ajax.slice(ajax.indexOf("add_action('wp_ajax_jio_change_password'"), ajax.indexOf("add_action('wp_ajax_jio_change_email'"));
|
||||
|
||||
assert.match(password, /get_user_meta\(\$user_id, 'oauth_new', true\)/);
|
||||
assert.match(password, /wp_check_password\(\$old/);
|
||||
assert.match(password, /SecurityHardening::error_frequency_wait\(\)/);
|
||||
assert.match(password, /SecurityHardening::error_frequency_record\(\)/);
|
||||
assert.doesNotMatch(password, /aksrt_account_rate_limited/);
|
||||
assert.doesNotMatch(ajax, /function aksrt_account_rate_limited/);
|
||||
assert.doesNotMatch(ajax, /function aksrt_account_reset_rate/);
|
||||
assert.match(password, /aksrt_require_captcha\('change_password'/);
|
||||
assert.doesNotMatch(password, /jio_account_rate_limited/);
|
||||
assert.doesNotMatch(ajax, /function jio_account_rate_limited/);
|
||||
assert.doesNotMatch(ajax, /function jio_account_reset_rate/);
|
||||
assert.match(password, /jio_require_captcha\('change_password'/);
|
||||
assert.match(password, /Auth\\Service::validate_password\(\$password, \$password2\)/);
|
||||
assert.doesNotMatch(password, /email.*code|password.*strength/i);
|
||||
assert.match(javascript, /scene: 'resetpassword', target: target, channel:/);
|
||||
|
||||
@@ -6,17 +6,17 @@ declare(strict_types=1);
|
||||
namespace {
|
||||
define('ABSPATH', __DIR__ . '/');
|
||||
define('MINUTE_IN_SECONDS', 60);
|
||||
$GLOBALS['aksrt_account_settings'] = ['email_set_captch' => true];
|
||||
$GLOBALS['aksrt_account_user_id'] = 7;
|
||||
$GLOBALS['aksrt_account_user'] = null;
|
||||
$GLOBALS['aksrt_account_meta'] = [];
|
||||
$GLOBALS['aksrt_account_transients'] = [];
|
||||
$GLOBALS['aksrt_account_actions'] = [];
|
||||
$GLOBALS['aksrt_account_mail'] = [];
|
||||
$GLOBALS['aksrt_account_existing_emails'] = [];
|
||||
$GLOBALS['aksrt_account_captcha_calls'] = [];
|
||||
$GLOBALS['aksrt_account_captcha_fail'] = false;
|
||||
$GLOBALS['aksrt_account_update_fail'] = false;
|
||||
$GLOBALS['jio_account_settings'] = ['email_set_captch' => true];
|
||||
$GLOBALS['jio_account_user_id'] = 7;
|
||||
$GLOBALS['jio_account_user'] = null;
|
||||
$GLOBALS['jio_account_meta'] = [];
|
||||
$GLOBALS['jio_account_transients'] = [];
|
||||
$GLOBALS['jio_account_actions'] = [];
|
||||
$GLOBALS['jio_account_mail'] = [];
|
||||
$GLOBALS['jio_account_existing_emails'] = [];
|
||||
$GLOBALS['jio_account_captcha_calls'] = [];
|
||||
$GLOBALS['jio_account_captcha_fail'] = false;
|
||||
$GLOBALS['jio_account_update_fail'] = false;
|
||||
|
||||
final class WP_User {
|
||||
public function __construct(
|
||||
@@ -43,7 +43,7 @@ namespace {
|
||||
}
|
||||
|
||||
function add_action(string $hook, callable $callback): void {
|
||||
$GLOBALS['aksrt_account_actions'][$hook] = $callback;
|
||||
$GLOBALS['jio_account_actions'][$hook] = $callback;
|
||||
}
|
||||
function __(string $text, string $domain = ''): string { return $text; }
|
||||
function is_wp_error(mixed $value): bool { return $value instanceof WP_Error; }
|
||||
@@ -52,64 +52,64 @@ namespace {
|
||||
function sanitize_key(string $key): string { return preg_replace('/[^a-z0-9_\-]/', '', strtolower($key)) ?? ''; }
|
||||
function sanitize_text_field(string $value): string { return trim(strip_tags($value)); }
|
||||
function wp_unslash(mixed $value): mixed { return $value; }
|
||||
function wp_verify_nonce(string $nonce, string $action): bool { return $nonce === 'valid' && $action === 'aksrt_account_settings'; }
|
||||
function get_current_user_id(): int { return (int) $GLOBALS['aksrt_account_user_id']; }
|
||||
function wp_get_current_user(): mixed { return $GLOBALS['aksrt_account_user']; }
|
||||
function get_userdata(int $user_id): ?WP_User { return $user_id === 7 ? $GLOBALS['aksrt_account_user'] : null; }
|
||||
function get_bloginfo(string $show = ''): string { return 'AKSRT Test'; }
|
||||
function wp_verify_nonce(string $nonce, string $action): bool { return $nonce === 'valid' && $action === 'jio_account_settings'; }
|
||||
function get_current_user_id(): int { return (int) $GLOBALS['jio_account_user_id']; }
|
||||
function wp_get_current_user(): mixed { return $GLOBALS['jio_account_user']; }
|
||||
function get_userdata(int $user_id): ?WP_User { return $user_id === 7 ? $GLOBALS['jio_account_user'] : null; }
|
||||
function get_bloginfo(string $show = ''): string { return 'Jio Test'; }
|
||||
function wp_hash_password(string $password): string { return 'hash:' . $password; }
|
||||
function wp_check_password(string $password, string $hash, int $user_id = 0): bool { return hash_equals('hash:' . $password, $hash); }
|
||||
function email_exists(string $email): int|false { return $GLOBALS['aksrt_account_existing_emails'][strtolower($email)] ?? false; }
|
||||
function email_exists(string $email): int|false { return $GLOBALS['jio_account_existing_emails'][strtolower($email)] ?? false; }
|
||||
function set_transient(string $key, mixed $value, int $expiration = 0): bool {
|
||||
$GLOBALS['aksrt_account_transients'][$key] = [
|
||||
$GLOBALS['jio_account_transients'][$key] = [
|
||||
'value' => $value,
|
||||
'expires' => $expiration > 0 ? time() + $expiration : 0,
|
||||
];
|
||||
return true;
|
||||
}
|
||||
function get_transient(string $key): mixed {
|
||||
$row = $GLOBALS['aksrt_account_transients'][$key] ?? null;
|
||||
$row = $GLOBALS['jio_account_transients'][$key] ?? null;
|
||||
if (!is_array($row)) return false;
|
||||
if (!empty($row['expires']) && $row['expires'] <= time()) {
|
||||
unset($GLOBALS['aksrt_account_transients'][$key]);
|
||||
unset($GLOBALS['jio_account_transients'][$key]);
|
||||
return false;
|
||||
}
|
||||
return $row['value'] ?? false;
|
||||
}
|
||||
function delete_transient(string $key): bool {
|
||||
unset($GLOBALS['aksrt_account_transients'][$key]);
|
||||
unset($GLOBALS['jio_account_transients'][$key]);
|
||||
return true;
|
||||
}
|
||||
function get_user_meta(int $user_id, string $key, bool $single = false): mixed {
|
||||
return $GLOBALS['aksrt_account_meta'][$user_id][$key] ?? '';
|
||||
return $GLOBALS['jio_account_meta'][$user_id][$key] ?? '';
|
||||
}
|
||||
function update_user_meta(int $user_id, string $key, mixed $value): bool {
|
||||
$GLOBALS['aksrt_account_meta'][$user_id][$key] = $value;
|
||||
$GLOBALS['jio_account_meta'][$user_id][$key] = $value;
|
||||
return true;
|
||||
}
|
||||
function delete_user_meta(int $user_id, string $key): bool {
|
||||
unset($GLOBALS['aksrt_account_meta'][$user_id][$key]);
|
||||
unset($GLOBALS['jio_account_meta'][$user_id][$key]);
|
||||
return true;
|
||||
}
|
||||
function wp_update_user(array $data): int|WP_Error {
|
||||
if ($GLOBALS['aksrt_account_update_fail']) {
|
||||
if ($GLOBALS['jio_account_update_fail']) {
|
||||
return new WP_Error('update_failed', 'update failed');
|
||||
}
|
||||
$user = $GLOBALS['aksrt_account_user'];
|
||||
$user = $GLOBALS['jio_account_user'];
|
||||
if (isset($data['user_email'])) $user->user_email = (string) $data['user_email'];
|
||||
if (isset($data['user_pass'])) $user->user_pass = wp_hash_password((string) $data['user_pass']);
|
||||
return (int) $user->ID;
|
||||
}
|
||||
function aksrt_require_captcha(string $scene, array $payload): true|WP_Error {
|
||||
$GLOBALS['aksrt_account_captcha_calls'][] = $scene;
|
||||
return $GLOBALS['aksrt_account_captcha_fail'] ? new WP_Error('captcha_failed', 'captcha failed') : true;
|
||||
function jio_require_captcha(string $scene, array $payload): true|WP_Error {
|
||||
$GLOBALS['jio_account_captcha_calls'][] = $scene;
|
||||
return $GLOBALS['jio_account_captcha_fail'] ? new WP_Error('captcha_failed', 'captcha failed') : true;
|
||||
}
|
||||
function aksrt_get_auth_agreement_view(): array { return ['enabled' => true, 'required' => true]; }
|
||||
function jio_get_auth_agreement_view(): array { return ['enabled' => true, 'required' => true]; }
|
||||
function wp_send_json_error(array $data): never { throw new JsonResponseException(['success' => false, 'data' => $data]); }
|
||||
function wp_send_json_success(array $data): never { throw new JsonResponseException(['success' => true, 'data' => $data]); }
|
||||
|
||||
function account_settings_request(string $action, array $data = []): array {
|
||||
$callback = $GLOBALS['aksrt_account_actions'][$action] ?? null;
|
||||
$callback = $GLOBALS['jio_account_actions'][$action] ?? null;
|
||||
if (!is_callable($callback)) throw new \RuntimeException('Missing AJAX action: ' . $action);
|
||||
$_POST = array_merge(['_settings_nonce' => 'valid'], $data);
|
||||
try {
|
||||
@@ -125,15 +125,15 @@ namespace {
|
||||
}
|
||||
|
||||
function account_settings_code_key(string $scene, string $target): string {
|
||||
return 'aksrt_account_code_7_' . $scene . '_' . md5(strtolower(trim($target)));
|
||||
return 'jio_account_code_7_' . $scene . '_' . md5(strtolower(trim($target)));
|
||||
}
|
||||
|
||||
function account_settings_send_cooldown_key(string $target): string {
|
||||
return 'aksrt_send_cd_' . md5(strtolower(trim($target)));
|
||||
return 'jio_send_cd_' . md5(strtolower(trim($target)));
|
||||
}
|
||||
|
||||
function account_settings_error_frequency_key(): string {
|
||||
return 'aksrt_err_freq_test';
|
||||
return 'jio_err_freq_test';
|
||||
}
|
||||
|
||||
function account_settings_code(string $scene, string $target): string {
|
||||
@@ -143,23 +143,23 @@ namespace {
|
||||
}
|
||||
|
||||
function account_settings_proof_key(): string {
|
||||
return 'aksrt_account_proof_email_old_7';
|
||||
return 'jio_account_proof_email_old_7';
|
||||
}
|
||||
|
||||
require dirname(__DIR__, 2) . '/inc/modules/user-parts/account-ajax.php';
|
||||
}
|
||||
|
||||
namespace AKSRT\Settings {
|
||||
namespace Jio\Settings {
|
||||
final class Settings {
|
||||
public static function get_all(): array { return $GLOBALS['aksrt_account_settings']; }
|
||||
public static function get_all(): array { return $GLOBALS['jio_account_settings']; }
|
||||
public static function send_event_email(string $event, string $to, array $values): bool {
|
||||
$GLOBALS['aksrt_account_mail'][] = ['event' => $event, 'to' => $to, 'values' => $values];
|
||||
$GLOBALS['jio_account_mail'][] = ['event' => $event, 'to' => $to, 'values' => $values];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace AKSRT\Auth {
|
||||
namespace Jio\Auth {
|
||||
final class Service {
|
||||
public static function validate_password(string $password, string $confirm = ''): true|string {
|
||||
if (strlen($password) < 6) return 'password too short';
|
||||
@@ -169,52 +169,52 @@ namespace AKSRT\Auth {
|
||||
}
|
||||
}
|
||||
|
||||
namespace AKSRT\Extensions {
|
||||
namespace Jio\Extensions {
|
||||
final class SendLimit {
|
||||
public static function check(string $target): array {
|
||||
$sent_at = (int) get_transient('aksrt_send_cd_' . md5(strtolower(trim($target))));
|
||||
$sent_at = (int) get_transient('jio_send_cd_' . md5(strtolower(trim($target))));
|
||||
if ($sent_at > 0 && time() - $sent_at < 60) {
|
||||
return ['ok' => false, 'message' => 'cooldown', 'remaining' => 60 - (time() - $sent_at)];
|
||||
}
|
||||
return ['ok' => true, 'message' => '', 'remaining' => 0];
|
||||
}
|
||||
public static function record(string $target): int {
|
||||
set_transient('aksrt_send_cd_' . md5(strtolower(trim($target))), time(), 60);
|
||||
set_transient('jio_send_cd_' . md5(strtolower(trim($target))), time(), 60);
|
||||
return 60;
|
||||
}
|
||||
}
|
||||
|
||||
final class SecurityHardening {
|
||||
public static function error_frequency_wait(): int {
|
||||
$state = get_transient('aksrt_err_freq_test');
|
||||
$state = get_transient('jio_err_freq_test');
|
||||
$count = is_array($state) ? (int) ($state['count'] ?? 0) : 0;
|
||||
if ($count < 5) return 0;
|
||||
return max(0, ($count >= 10 ? 180 : 30) - (time() - (int) ($state['last'] ?? 0)));
|
||||
}
|
||||
public static function error_frequency_record(): void {
|
||||
$state = get_transient('aksrt_err_freq_test');
|
||||
$state = get_transient('jio_err_freq_test');
|
||||
$count = is_array($state) ? (int) ($state['count'] ?? 0) + 1 : 1;
|
||||
set_transient('aksrt_err_freq_test', ['count' => $count, 'last' => time()], 600);
|
||||
set_transient('jio_err_freq_test', ['count' => $count, 'last' => time()], 600);
|
||||
}
|
||||
public static function error_frequency_clear(): void {
|
||||
delete_transient('aksrt_err_freq_test');
|
||||
delete_transient('jio_err_freq_test');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
$GLOBALS['aksrt_account_user'] = new WP_User(7, 'old@example.test', 'Test User', 'hash:OldPass123');
|
||||
$send_action = 'wp_ajax_aksrt_send_account_email_code';
|
||||
$verify_action = 'wp_ajax_aksrt_verify_account_email';
|
||||
$change_email_action = 'wp_ajax_aksrt_change_email';
|
||||
$change_password_action = 'wp_ajax_aksrt_change_password';
|
||||
$saved_user = $GLOBALS['aksrt_account_user'];
|
||||
$GLOBALS['aksrt_account_user'] = null;
|
||||
$GLOBALS['jio_account_user'] = new WP_User(7, 'old@example.test', 'Test User', 'hash:OldPass123');
|
||||
$send_action = 'wp_ajax_jio_send_account_email_code';
|
||||
$verify_action = 'wp_ajax_jio_verify_account_email';
|
||||
$change_email_action = 'wp_ajax_jio_change_email';
|
||||
$change_password_action = 'wp_ajax_jio_change_password';
|
||||
$saved_user = $GLOBALS['jio_account_user'];
|
||||
$GLOBALS['jio_account_user'] = null;
|
||||
account_settings_check(empty(account_settings_request($send_action, ['scene' => 'verify_email'])['success']), 'email-code request did not handle a missing user object');
|
||||
account_settings_check(empty(account_settings_request($verify_action, ['code' => '123456'])['success']), 'old-email verification did not handle a missing user object');
|
||||
account_settings_check(empty(account_settings_request($change_email_action, ['new_email' => 'new@example.test'])['success']), 'email update did not handle a missing user object');
|
||||
account_settings_check(empty(account_settings_request($change_password_action, ['password' => 'NewPass123', 'password2' => 'NewPass123'])['success']), 'password update did not handle a missing user object');
|
||||
$GLOBALS['aksrt_account_user'] = $saved_user;
|
||||
$GLOBALS['jio_account_user'] = $saved_user;
|
||||
|
||||
$old_send = account_settings_request($send_action, ['scene' => 'verify_email']);
|
||||
account_settings_check(!empty($old_send['success']), 'old email verification code could not be sent');
|
||||
@@ -226,79 +226,79 @@ namespace {
|
||||
account_settings_check(empty($bad_old_code['success']), 'email verification accepted an incorrect old-email code');
|
||||
$old_code_row = get_transient(account_settings_code_key('verify_email', 'old@example.test'));
|
||||
account_settings_check(($old_code_row['attempts'] ?? 0) === 1, 'incorrect old-email attempt was not recorded');
|
||||
$GLOBALS['aksrt_account_transients'][account_settings_code_key('verify_email', 'old@example.test')]['expires'] = time() - 1;
|
||||
$GLOBALS['jio_account_transients'][account_settings_code_key('verify_email', 'old@example.test')]['expires'] = time() - 1;
|
||||
$expired_old_code = account_settings_request($verify_action, ['code' => $old_code]);
|
||||
account_settings_check(empty($expired_old_code['success']), 'email verification accepted an expired old-email code');
|
||||
account_settings_check(get_transient(account_settings_code_key('verify_email', 'old@example.test')) === false, 'expired old-email code was not discarded');
|
||||
account_settings_check(!aksrt_account_consume_proof(7, 'email_old', 'old@example.test', false), 'expired old-email code issued an old-email proof');
|
||||
account_settings_check(!jio_account_consume_proof(7, 'email_old', 'old@example.test', false), 'expired old-email code issued an old-email proof');
|
||||
delete_transient(account_settings_send_cooldown_key('old@example.test'));
|
||||
$old_send = account_settings_request($send_action, ['scene' => 'verify_email']);
|
||||
account_settings_check(!empty($old_send['success']), 'old email code could not be resent after expiry');
|
||||
$old_code = account_settings_code('verify_email', 'old@example.test');
|
||||
$verified_old = account_settings_request($verify_action, ['code' => $old_code]);
|
||||
account_settings_check(!empty($verified_old['success']), 'email verification rejected the correct old-email code');
|
||||
account_settings_check(aksrt_account_consume_proof(7, 'email_old', 'old@example.test', false), 'old-email proof was not issued');
|
||||
account_settings_check(jio_account_consume_proof(7, 'email_old', 'old@example.test', false), 'old-email proof was not issued');
|
||||
account_settings_check(get_transient(account_settings_code_key('verify_email', 'old@example.test')) === false, 'valid old-email code was not consumed');
|
||||
|
||||
$new_email = 'new@example.test';
|
||||
$new_send = account_settings_request($send_action, ['scene' => 'change_email', 'new_email' => $new_email]);
|
||||
account_settings_check(!empty($new_send['success']), 'new email verification code could not be sent');
|
||||
$new_code = account_settings_code('change_email', $new_email);
|
||||
$GLOBALS['aksrt_account_existing_emails']['taken@example.test'] = 19;
|
||||
$GLOBALS['jio_account_existing_emails']['taken@example.test'] = 19;
|
||||
$duplicate = account_settings_request($change_email_action, ['new_email' => 'taken@example.test', 'code' => $new_code, 'agree_terms' => '1']);
|
||||
account_settings_check(empty($duplicate['success']), 'email update accepted an address already owned by another account');
|
||||
account_settings_check(aksrt_account_consume_proof(7, 'email_old', 'old@example.test', false), 'duplicate-email validation consumed the old-email proof');
|
||||
account_settings_check(jio_account_consume_proof(7, 'email_old', 'old@example.test', false), 'duplicate-email validation consumed the old-email proof');
|
||||
account_settings_check(get_transient(account_settings_code_key('change_email', $new_email)) !== false, 'duplicate-email validation consumed the new-email code');
|
||||
unset($GLOBALS['aksrt_account_existing_emails']['taken@example.test']);
|
||||
unset($GLOBALS['jio_account_existing_emails']['taken@example.test']);
|
||||
|
||||
$bad_new_code = account_settings_request($change_email_action, ['new_email' => $new_email, 'code' => 'wrong', 'agree_terms' => '1']);
|
||||
account_settings_check(empty($bad_new_code['success']), 'email update accepted an incorrect new-email code');
|
||||
account_settings_check(aksrt_account_consume_proof(7, 'email_old', 'old@example.test', false), 'incorrect-code validation consumed the old-email proof');
|
||||
account_settings_check(jio_account_consume_proof(7, 'email_old', 'old@example.test', false), 'incorrect-code validation consumed the old-email proof');
|
||||
account_settings_check(get_transient(account_settings_code_key('change_email', $new_email)) !== false, 'incorrect-code validation consumed the new-email code');
|
||||
$GLOBALS['aksrt_account_transients'][account_settings_code_key('change_email', $new_email)]['expires'] = time() - 1;
|
||||
$GLOBALS['jio_account_transients'][account_settings_code_key('change_email', $new_email)]['expires'] = time() - 1;
|
||||
$expired_new_code = account_settings_request($change_email_action, ['new_email' => $new_email, 'code' => $new_code, 'agree_terms' => '1']);
|
||||
account_settings_check(empty($expired_new_code['success']), 'email update accepted an expired new-email code');
|
||||
account_settings_check(aksrt_account_consume_proof(7, 'email_old', 'old@example.test', false), 'expired new-email code validation consumed the old-email proof');
|
||||
account_settings_check(jio_account_consume_proof(7, 'email_old', 'old@example.test', false), 'expired new-email code validation consumed the old-email proof');
|
||||
account_settings_check(get_transient(account_settings_code_key('change_email', $new_email)) === false, 'expired new-email code was not discarded');
|
||||
delete_transient(account_settings_send_cooldown_key($new_email));
|
||||
$new_send = account_settings_request($send_action, ['scene' => 'change_email', 'new_email' => $new_email]);
|
||||
account_settings_check(!empty($new_send['success']), 'new email code could not be resent after expiry');
|
||||
$new_code = account_settings_code('change_email', $new_email);
|
||||
|
||||
$GLOBALS['aksrt_account_update_fail'] = true;
|
||||
$GLOBALS['jio_account_update_fail'] = true;
|
||||
$write_failure = account_settings_request($change_email_action, ['new_email' => $new_email, 'code' => $new_code, 'agree_terms' => '1']);
|
||||
account_settings_check(empty($write_failure['success']), 'email update reported success after the user write failed');
|
||||
account_settings_check(aksrt_account_consume_proof(7, 'email_old', 'old@example.test', false), 'failed database write consumed the old-email proof');
|
||||
account_settings_check(jio_account_consume_proof(7, 'email_old', 'old@example.test', false), 'failed database write consumed the old-email proof');
|
||||
account_settings_check(get_transient(account_settings_code_key('change_email', $new_email)) !== false, 'failed database write consumed the new-email code');
|
||||
$GLOBALS['aksrt_account_update_fail'] = false;
|
||||
$GLOBALS['jio_account_update_fail'] = false;
|
||||
$changed = account_settings_request($change_email_action, ['new_email' => $new_email, 'code' => $new_code, 'agree_terms' => '1']);
|
||||
account_settings_check(!empty($changed['success']), 'email update rejected valid old and new proofs');
|
||||
account_settings_check($GLOBALS['aksrt_account_user']->user_email === $new_email, 'email update did not persist the new address');
|
||||
account_settings_check(!aksrt_account_consume_proof(7, 'email_old', 'old@example.test', false), 'successful update retained the old-email proof');
|
||||
account_settings_check($GLOBALS['jio_account_user']->user_email === $new_email, 'email update did not persist the new address');
|
||||
account_settings_check(!jio_account_consume_proof(7, 'email_old', 'old@example.test', false), 'successful update retained the old-email proof');
|
||||
account_settings_check(get_transient(account_settings_code_key('change_email', $new_email)) === false, 'successful update retained the new-email code');
|
||||
account_settings_check((int) get_user_meta(7, 'aksrt_email_verified', true) === 1, 'successful update did not mark the email verified');
|
||||
account_settings_check(end($GLOBALS['aksrt_account_mail'])['event'] === 'change_notice', 'successful update did not notify the previous email');
|
||||
account_settings_check((int) get_user_meta(7, 'jio_email_verified', true) === 1, 'successful update did not mark the email verified');
|
||||
account_settings_check(end($GLOBALS['jio_account_mail'])['event'] === 'change_notice', 'successful update did not notify the previous email');
|
||||
|
||||
$GLOBALS['aksrt_account_settings']['email_set_captch'] = false;
|
||||
$GLOBALS['jio_account_settings']['email_set_captch'] = false;
|
||||
$direct_email = account_settings_request($change_email_action, ['new_email' => 'direct@example.test', 'agree_terms' => '1']);
|
||||
account_settings_check(!empty($direct_email['success']), 'email update without configured verification still required a code');
|
||||
account_settings_check($GLOBALS['aksrt_account_user']->user_email === 'direct@example.test', 'direct email update did not persist');
|
||||
account_settings_check($GLOBALS['jio_account_user']->user_email === 'direct@example.test', 'direct email update did not persist');
|
||||
|
||||
$GLOBALS['aksrt_account_settings']['email_set_captch'] = true;
|
||||
$GLOBALS['aksrt_account_user']->user_email = '';
|
||||
$GLOBALS['jio_account_settings']['email_set_captch'] = true;
|
||||
$GLOBALS['jio_account_user']->user_email = '';
|
||||
$first_email = 'first@example.test';
|
||||
$first_send = account_settings_request($send_action, ['scene' => 'change_email', 'new_email' => $first_email]);
|
||||
account_settings_check(!empty($first_send['success']), 'first email binding did not send a new-address code');
|
||||
$first_code = account_settings_code('change_email', $first_email);
|
||||
$first_bind = account_settings_request($change_email_action, ['new_email' => $first_email, 'code' => $first_code, 'agree_terms' => '1']);
|
||||
account_settings_check(!empty($first_bind['success']), 'first email binding incorrectly required old-email verification');
|
||||
account_settings_check($GLOBALS['aksrt_account_user']->user_email === $first_email, 'first email binding did not persist');
|
||||
account_settings_check($GLOBALS['jio_account_user']->user_email === $first_email, 'first email binding did not persist');
|
||||
|
||||
$GLOBALS['aksrt_account_captcha_fail'] = true;
|
||||
$GLOBALS['jio_account_captcha_fail'] = true;
|
||||
$captcha_rejected = account_settings_request($change_password_action, ['password' => 'NewPass123', 'password2' => 'NewPass123', 'passwordold' => 'OldPass123']);
|
||||
account_settings_check(empty($captcha_rejected['success']), 'password change bypassed human verification');
|
||||
account_settings_check($GLOBALS['aksrt_account_user']->user_pass === 'hash:OldPass123', 'captcha rejection changed the password');
|
||||
$GLOBALS['aksrt_account_captcha_fail'] = false;
|
||||
account_settings_check($GLOBALS['jio_account_user']->user_pass === 'hash:OldPass123', 'captcha rejection changed the password');
|
||||
$GLOBALS['jio_account_captcha_fail'] = false;
|
||||
|
||||
for ($attempt = 0; $attempt < 8; $attempt++) {
|
||||
$wrong_password = account_settings_request($change_password_action, ['password' => 'NewPass123', 'password2' => 'NewPass123', 'passwordold' => 'wrong']);
|
||||
@@ -309,7 +309,7 @@ namespace {
|
||||
delete_transient(account_settings_error_frequency_key());
|
||||
$changed_password = account_settings_request($change_password_action, ['password' => 'NewPass123', 'password2' => 'NewPass123', 'passwordold' => 'OldPass123']);
|
||||
account_settings_check(!empty($changed_password['success']), 'password change rejected the correct old password');
|
||||
account_settings_check($GLOBALS['aksrt_account_user']->user_pass === 'hash:NewPass123', 'password change did not persist the new password');
|
||||
account_settings_check($GLOBALS['jio_account_user']->user_pass === 'hash:NewPass123', 'password change did not persist the new password');
|
||||
account_settings_check(get_transient(account_settings_error_frequency_key()) === false, 'successful password change did not reset its error limit');
|
||||
|
||||
update_user_meta(7, 'oauth_new', 1);
|
||||
|
||||
@@ -12,8 +12,8 @@ assert.equal(core.dependencyVisible(['mode', '!=', 'cards'], 'cards'), false);
|
||||
assert.equal(core.dependencyVisible([], 'anything'), true);
|
||||
|
||||
assert.equal(
|
||||
core.replaceRepeaterIndexes('widget-aksrt_widget_slider[4][slides][__index_ab12cd34__][layers][9][title]', [2, 1]),
|
||||
'widget-aksrt_widget_slider[4][slides][2][layers][1][title]'
|
||||
core.replaceRepeaterIndexes('widget-jio_widget_slider[4][slides][__index_ab12cd34__][layers][9][title]', [2, 1]),
|
||||
'widget-jio_widget_slider[4][slides][2][layers][1][title]'
|
||||
);
|
||||
assert.equal(core.replaceRepeaterIndexes('plain[name]', [1]), 'plain[name]');
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@ const root = path.resolve(__dirname, '../..')
|
||||
const read = file => fs.readFileSync(path.join(root, file), 'utf8')
|
||||
|
||||
const list = read('src/Admin/Native/PostListEnhance.php')
|
||||
const tags = read('inc/helpers/aksrt-frontend-tags.php')
|
||||
const pay = read('inc/helpers/aksrt-pay.php')
|
||||
const tags = read('inc/helpers/jio-frontend-tags.php')
|
||||
const pay = read('inc/helpers/jio-pay.php')
|
||||
|
||||
test('admin post list has the interaction stats column', () => {
|
||||
assert.match(list, /manage_post_posts_columns/)
|
||||
@@ -20,7 +20,7 @@ test('admin post list has the interaction stats column', () => {
|
||||
test('admin post list has the sales data column with a sortable header', () => {
|
||||
// 表头「销售数据」必须是可点击排序链接,排序键为 sales_volume。
|
||||
assert.match(list, /销售数据/)
|
||||
assert.match(list, /'aksrt_sales'/)
|
||||
assert.match(list, /'jio_sales'/)
|
||||
assert.match(list, /orderby.*sales_volume/)
|
||||
|
||||
// 排序靠表头 label 里的链接 + pre_get_posts 落地,不注册 sortable_columns:
|
||||
@@ -38,25 +38,25 @@ test('sales column renders pay type, price and both sales counts', () => {
|
||||
assert.match(list, /pay_type_labels\(\)/)
|
||||
assert.match(list, /真实销量/)
|
||||
assert.match(list, /get_content_sales_map/)
|
||||
// 第一行取自 _aksrt_pay,第二行的销量取自 _aksrt_sales_volume。
|
||||
assert.match(list, /aksrt_get_pay_meta/)
|
||||
// 第一行取自 _jio_pay,第二行的销量取自 _jio_sales_volume。
|
||||
assert.match(list, /jio_get_pay_meta/)
|
||||
assert.match(list, /DomainModel::RESOURCE_SALES_VOLUME_KEY/)
|
||||
// 真实销量链接必须跳到订单页(aksrt_orders)并按商品 ID 过滤。
|
||||
assert.match(list, /'aksrt_orders'/)
|
||||
// 真实销量链接必须跳到订单页(jio_orders)并按商品 ID 过滤。
|
||||
assert.match(list, /'jio_orders'/)
|
||||
assert.match(list, /'product_id'/)
|
||||
})
|
||||
|
||||
test('interaction counts are read from the keys the theme actually writes', () => {
|
||||
// 计数一律落在这几个 meta key 上:aksrt_post_views / _aksrt_like_count /
|
||||
// _aksrt_favorite_count。列回调只能用同一套键,否则全列恒为 0。
|
||||
// 计数一律落在这几个 meta key 上:jio_post_views / _jio_like_count /
|
||||
// _jio_favorite_count。列回调只能用同一套键,否则全列恒为 0。
|
||||
// 互动数据不做下载量统计,因此不再读取任何 download 键。
|
||||
assert.match(tags, /'_aksrt_like_count'/)
|
||||
assert.match(tags, /'_aksrt_favorite_count'/)
|
||||
assert.match(tags, /'aksrt_post_views'/)
|
||||
assert.match(tags, /'_jio_like_count'/)
|
||||
assert.match(tags, /'_jio_favorite_count'/)
|
||||
assert.match(tags, /'jio_post_views'/)
|
||||
|
||||
assert.match(list, /aksrt_get_post_views\(\$post_id\)/)
|
||||
assert.match(list, /aksrt_get_post_likes\(\$post_id\)/)
|
||||
assert.match(list, /aksrt_get_post_favorites\(\$post_id\)/)
|
||||
assert.match(list, /jio_get_post_views\(\$post_id\)/)
|
||||
assert.match(list, /jio_get_post_likes\(\$post_id\)/)
|
||||
assert.match(list, /jio_get_post_favorites\(\$post_id\)/)
|
||||
|
||||
// 不得再直接读这些不存在的键。
|
||||
const column = list.slice(list.indexOf('private function render_stats_column'), list.indexOf('private function render_sales_column'))
|
||||
@@ -71,17 +71,17 @@ test('interaction counts are read from the keys the theme actually writes', () =
|
||||
assert.doesNotMatch(column, /下载/)
|
||||
})
|
||||
|
||||
test('admin stats column shares the aksrt_format_number abbreviation', () => {
|
||||
test('admin stats column shares the jio_format_number abbreviation', () => {
|
||||
// 后台列与前台必须同一套缩写口径(≥10000 → 1.2W+,≥1000 → 1.5K)。
|
||||
assert.match(list, /aksrt_format_number\(\$n\)/)
|
||||
assert.match(list, /jio_format_number\(\$n\)/)
|
||||
const column = list.slice(list.indexOf('private function render_stats_column'), list.indexOf('private function render_sales_column'))
|
||||
assert.doesNotMatch(column, /'万'/)
|
||||
})
|
||||
|
||||
test('sortable meta maps the public orderby keys to the stored keys', () => {
|
||||
assert.match(list, /'views'\s*=>\s*\\AKSRT\\Core\\DomainModel::POST_VIEW_COUNT_KEY/)
|
||||
assert.match(list, /'like'\s*=>\s*'_aksrt_like_count'/)
|
||||
assert.match(list, /'favorite'\s*=>\s*'_aksrt_favorite_count'/)
|
||||
assert.match(list, /'views'\s*=>\s*\\Jio\\Core\\DomainModel::POST_VIEW_COUNT_KEY/)
|
||||
assert.match(list, /'like'\s*=>\s*'_jio_like_count'/)
|
||||
assert.match(list, /'favorite'\s*=>\s*'_jio_favorite_count'/)
|
||||
// meta_key 必须是真实存储键,而不是用户可见的排序键。
|
||||
assert.match(list, /\$query->set\('meta_key', \$this->stats_meta_key\(\$orderby\)\)/)
|
||||
// 互动数据不统计下载量,排序映射里也不得再出现该维度。
|
||||
@@ -90,21 +90,21 @@ test('sortable meta maps the public orderby keys to the stored keys', () => {
|
||||
|
||||
test('every counter meta key the admin column renders has a writer', () => {
|
||||
// 逐键核对:列里展示的每项计数都必须在某个 helper 里真正被写入过。
|
||||
// _aksrt_sales_volume 的写入方是付费订单流水(PayOrders),不在这份清单里,
|
||||
// _jio_sales_volume 的写入方是付费订单流水(PayOrders),不在这份清单里,
|
||||
// 由下一个用例单独覆盖。
|
||||
const sources = [
|
||||
'src/Admin/Native/PostListEnhance.php',
|
||||
'inc/helpers/aksrt-frontend-tags.php',
|
||||
'inc/helpers/jio-frontend-tags.php',
|
||||
'src/Core/PostMetaBox.php',
|
||||
'src/Frontend/PageAdminSet.php',
|
||||
].map(read).join('\n')
|
||||
|
||||
for (const key of ['aksrt_post_views', '_aksrt_like_count', '_aksrt_favorite_count']) {
|
||||
for (const key of ['jio_post_views', '_jio_like_count', '_jio_favorite_count']) {
|
||||
assert.ok(sources.includes(`'${key}'`), `${key} is written somewhere`)
|
||||
}
|
||||
// 资源帖的浏览有独立键,aksrt_get_post_views() 取两者较大值。
|
||||
assert.match(tags, /_aksrt_res_view_count/)
|
||||
assert.match(read('inc/helpers/aksrt-resource.php'), /RESOURCE_VIEW_COUNT_KEY|_aksrt_res_view_count/)
|
||||
// 资源帖的浏览有独立键,jio_get_post_views() 取两者较大值。
|
||||
assert.match(tags, /_jio_res_view_count/)
|
||||
assert.match(read('inc/helpers/jio-resource.php'), /RESOURCE_VIEW_COUNT_KEY|_jio_res_view_count/)
|
||||
|
||||
// 互动数据不统计下载量:列里不得再出现下载键的读取或回退逻辑。
|
||||
assert.doesNotMatch(list, /RESOURCE_DOWNLOAD_COUNT_KEY/)
|
||||
@@ -112,14 +112,14 @@ test('every counter meta key the admin column renders has a writer', () => {
|
||||
})
|
||||
|
||||
test('sales volume is written by the order pipeline', () => {
|
||||
// _aksrt_sales_volume 的写入方在付费助手与订单处理器里,列本身只读。
|
||||
assert.match(pay, /aksrt_bump_content_sales_volume/)
|
||||
// _jio_sales_volume 的写入方在付费助手与订单处理器里,列本身只读。
|
||||
assert.match(pay, /jio_bump_content_sales_volume/)
|
||||
assert.match(pay, /RESOURCE_SALES_VOLUME_KEY/)
|
||||
assert.match(read('src/Content/PayOrders.php'), /aksrt_bump_content_sales_volume/)
|
||||
assert.match(read('src/Content/PayOrders.php'), /jio_bump_content_sales_volume/)
|
||||
})
|
||||
|
||||
test('admin column icons stay behind the Font Awesome enqueue on edit.php', () => {
|
||||
assert.match(list, /admin_enqueue_scripts[\s\S]*?edit\.php[\s\S]*?aksrt_enqueue_fontawesome\(\)/)
|
||||
assert.match(list, /admin_enqueue_scripts[\s\S]*?edit\.php[\s\S]*?jio_enqueue_fontawesome\(\)/)
|
||||
for (const klass of ['fa-eye', 'fa-thumbs-up', 'fa-star']) {
|
||||
assert.match(list, new RegExp(klass))
|
||||
}
|
||||
@@ -131,7 +131,7 @@ test('the old closure-based column implementation is gone from core-hooks', () =
|
||||
const hooks = read('inc/core-hooks.php')
|
||||
assert.doesNotMatch(hooks, /manage_post_posts_columns/)
|
||||
assert.doesNotMatch(hooks, /manage_post_posts_custom_column/)
|
||||
assert.doesNotMatch(hooks, /aksrt_stats/)
|
||||
assert.doesNotMatch(hooks, /jio_stats/)
|
||||
})
|
||||
|
||||
test('PostListEnhance is wired up from functions.php', () => {
|
||||
|
||||
@@ -8,21 +8,21 @@ const read = file => fs.readFileSync(path.join(root, file), 'utf8')
|
||||
|
||||
const app = read('assets/admin/src/App.vue')
|
||||
const styles = read('assets/admin/src/style.css')
|
||||
const field = read('assets/admin/src/components/form/AksField.vue')
|
||||
const fieldGroup = read('assets/admin/src/components/form/AksFieldGroup.vue')
|
||||
const formRenderer = read('assets/admin/src/components/form/AksFormRenderer.vue')
|
||||
const saveBar = read('assets/admin/src/components/form/AksSaveBar.vue')
|
||||
const field = read('assets/admin/src/components/form/JioField.vue')
|
||||
const fieldGroup = read('assets/admin/src/components/form/JioFieldGroup.vue')
|
||||
const formRenderer = read('assets/admin/src/components/form/JioFormRenderer.vue')
|
||||
const saveBar = read('assets/admin/src/components/form/JioSaveBar.vue')
|
||||
const schemaProvider = read('src/Admin/SchemaProvider.php')
|
||||
|
||||
test('admin theme exposes the approved white, grey, and green tokens', () => {
|
||||
const requiredTokens = {
|
||||
'--aksrt-color-primary': '#18A058',
|
||||
'--aksrt-color-primary-soft': '#EAF7EF',
|
||||
'--aksrt-color-canvas': '#F5F7F6',
|
||||
'--aksrt-color-surface': '#FFFFFF',
|
||||
'--aksrt-color-text': '#1F2923',
|
||||
'--aksrt-color-muted': '#6F7A72',
|
||||
'--aksrt-color-border': '#E2E8E3',
|
||||
'--jio-color-primary': '#18A058',
|
||||
'--jio-color-primary-soft': '#EAF7EF',
|
||||
'--jio-color-canvas': '#F5F7F6',
|
||||
'--jio-color-surface': '#FFFFFF',
|
||||
'--jio-color-text': '#1F2923',
|
||||
'--jio-color-muted': '#6F7A72',
|
||||
'--jio-color-border': '#E2E8E3',
|
||||
}
|
||||
|
||||
for (const [token, value] of Object.entries(requiredTokens)) {
|
||||
@@ -35,27 +35,27 @@ test('app uses Naive UI overrides and the compact responsive shell', () => {
|
||||
assert.match(app, /const adminThemeOverrides = \{/)
|
||||
assert.match(app, /:width="232"/)
|
||||
assert.match(app, /const MOBILE_BREAKPOINT = 960/)
|
||||
assert.match(app, /aksrt-logo__version/)
|
||||
assert.doesNotMatch(app, /aksrt-csf-(?:shell|header|version)/)
|
||||
assert.match(app, /jio-logo__version/)
|
||||
assert.doesNotMatch(app, /jio-csf-(?:shell|header|version)/)
|
||||
})
|
||||
|
||||
test('rejected gradient banner and split-row form overrides do not return', () => {
|
||||
const source = `${app}\n${styles}`
|
||||
assert.doesNotMatch(source, /#f97794|#623aa2|#0991f5|#0997f5/i)
|
||||
assert.doesNotMatch(source, /linear-gradient/i)
|
||||
assert.doesNotMatch(styles, /\.aksrt-field\s*>\s*:not/)
|
||||
assert.doesNotMatch(styles, /\.aksrt-field__label-row\s*\{[^}]*grid-column/s)
|
||||
assert.doesNotMatch(styles, /\.jio-field\s*>\s*:not/)
|
||||
assert.doesNotMatch(styles, /\.jio-field__label-row\s*\{[^}]*grid-column/s)
|
||||
})
|
||||
|
||||
test('shared forms use card surfaces and design tokens', () => {
|
||||
assert.match(fieldGroup, /border:\s*1px solid var\(--aksrt-color-border\)/)
|
||||
assert.match(fieldGroup, /border:\s*1px solid var\(--jio-color-border\)/)
|
||||
assert.match(fieldGroup, /border-radius:\s*12px/)
|
||||
assert.match(fieldGroup, /box-shadow:\s*var\(--aksrt-shadow-card\)/)
|
||||
assert.match(field, /color:\s*var\(--aksrt-color-primary\)/)
|
||||
assert.match(field, /background:\s*var\(--aksrt-color-primary-soft\)/)
|
||||
assert.match(fieldGroup, /box-shadow:\s*var\(--jio-shadow-card\)/)
|
||||
assert.match(field, /color:\s*var\(--jio-color-primary\)/)
|
||||
assert.match(field, /background:\s*var\(--jio-color-primary-soft\)/)
|
||||
// Sticky save bar is shared by schema pages and custom settings pages.
|
||||
assert.match(formRenderer, /<AksSaveBar /)
|
||||
assert.match(saveBar, /background:\s*var\(--aksrt-color-surface\)/)
|
||||
assert.match(formRenderer, /<JioSaveBar /)
|
||||
assert.match(saveBar, /background:\s*var\(--jio-color-surface\)/)
|
||||
})
|
||||
|
||||
test('navigation, focus, responsive layout, and reduced motion are contracted', () => {
|
||||
@@ -87,7 +87,7 @@ test('captcha scene whitelist matches both admin schemas and excludes dead email
|
||||
assert.doesNotMatch(phpSchema, /'verify_email'/)
|
||||
assert.doesNotMatch(jsSchema, /value:\s*'verify_email'/)
|
||||
|
||||
const sceneBlock = captcha.match(/function aksrt_captcha_scenes\(\): array \{[\s\S]*?return \[([\s\S]*?)\n\t\];/)
|
||||
const sceneBlock = captcha.match(/function jio_captcha_scenes\(\): array \{[\s\S]*?return \[([\s\S]*?)\n\t\];/)
|
||||
const phpField = phpSchema.match(/'key' => 'enabled_scenarios'[\s\S]*?'options' => \[([\s\S]*?)\]\],/)
|
||||
const jsField = jsSchema.match(/key: 'enabled_scenarios'[\s\S]*?options: \[([\s\S]*?)\n\s*\],/)
|
||||
assert.ok(sceneBlock && phpField && jsField, 'captcha scene contracts must be discoverable')
|
||||
|
||||
@@ -32,12 +32,12 @@ test('old asset implementations and compatibility aliases are absent', () => {
|
||||
...sourceFiles(path.join(root, 'templates')),
|
||||
]
|
||||
const forbidden = [
|
||||
/aksrt_credits(?:\b|_)/,
|
||||
/['"]aksrt_balance['"]/,
|
||||
/jio_credits(?:\b|_)/,
|
||||
/['"]jio_balance['"]/,
|
||||
/\bcredit_log\b/,
|
||||
/\bbalance_log\b/,
|
||||
/aksrt_credits_(?:added|deducted)/,
|
||||
/aksrt_balance_(?:added|deducted)/,
|
||||
/jio_credits_(?:added|deducted)/,
|
||||
/jio_balance_(?:added|deducted)/,
|
||||
/credits_(?:added|deducted)/,
|
||||
// points_pass_exchange_* 现为正式设置键(卡密兑换积分功能,对齐参考主题命名),
|
||||
// 由 card-pass-modal-redeem.test.cjs 覆盖,不再作为遗留别名禁用。
|
||||
@@ -49,7 +49,7 @@ test('old asset implementations and compatibility aliases are absent', () => {
|
||||
/\brebate_log\b/,
|
||||
/\/payment\/rebate(?:\b|\/)/,
|
||||
/\bpay_rebate\b/,
|
||||
/aksrtpay_rebate_page/,
|
||||
/jiopay_rebate_page/,
|
||||
]
|
||||
const violations = []
|
||||
for (const file of files) {
|
||||
@@ -84,11 +84,11 @@ test('skeleton loading ships one contract and no dead spinner stylesheets', () =
|
||||
for (const file of css) {
|
||||
const text = fs.readFileSync(file, 'utf8')
|
||||
assert.doesNotMatch(text, /\.load-more__(?:btn|spinner)/, `${path.relative(root, file)} must not reintroduce .load-more__*`)
|
||||
assert.doesNotMatch(text, /\.aksrt-loading__(?:spinner|overlay|inline|text|dot)/, `${path.relative(root, file)} must not reintroduce .aksrt-loading__*`)
|
||||
assert.doesNotMatch(text, /aksrt-progress-bar/, `${path.relative(root, file)} must not reintroduce .aksrt-progress-bar`)
|
||||
assert.doesNotMatch(text, /\.jio-loading__(?:spinner|overlay|inline|text|dot)/, `${path.relative(root, file)} must not reintroduce .jio-loading__*`)
|
||||
assert.doesNotMatch(text, /jio-progress-bar/, `${path.relative(root, file)} must not reintroduce .jio-progress-bar`)
|
||||
// A second parallel skeleton system (never wired to any producer).
|
||||
assert.doesNotMatch(text, /\.aksrt-skeleton/, `${path.relative(root, file)} must not reintroduce .aksrt-skeleton-*`)
|
||||
assert.doesNotMatch(text, /aksrt-skeleton-shimmer/, `${path.relative(root, file)} must not reintroduce the parallel shimmer keyframes`)
|
||||
assert.doesNotMatch(text, /\.jio-skeleton/, `${path.relative(root, file)} must not reintroduce .jio-skeleton-*`)
|
||||
assert.doesNotMatch(text, /jio-skeleton-shimmer/, `${path.relative(root, file)} must not reintroduce the parallel shimmer keyframes`)
|
||||
assert.doesNotMatch(text, /--skeleton-(?:base|shine)/, `${path.relative(root, file)} must not reintroduce the unused skeleton tokens`)
|
||||
}
|
||||
})
|
||||
@@ -162,18 +162,18 @@ test('skeleton blocks share the card gutter instead of overflowing it', () => {
|
||||
// column. It must span every column and re-run the same 2-column template
|
||||
// internally while loading, so the placeholders line up with the real cards.
|
||||
const userHome = fs.readFileSync(path.join(root, 'assets/css/routes/user-home.css'), 'utf8')
|
||||
assert.match(userHome, /\.aksrt-user-home__post-list\[data-view="list"\] > \.post_ajax_loader \.posts-item \{\s*margin-right: 0;\s*margin-left: 0;/)
|
||||
assert.match(userHome, /\.aksrt-user-home__post-list\[data-view="list"\] > \.post_ajax_loader \{\s*grid-column: 1 \/ -1;/)
|
||||
assert.match(userHome, /\.jio-user-home__post-list\[data-view="list"\] > \.post_ajax_loader \.posts-item \{\s*margin-right: 0;\s*margin-left: 0;/)
|
||||
assert.match(userHome, /\.jio-user-home__post-list\[data-view="list"\] > \.post_ajax_loader \{\s*grid-column: 1 \/ -1;/)
|
||||
assert.match(
|
||||
userHome,
|
||||
/\.dashboard-panel\.is-loading \.aksrt-user-home__post-list\[data-view="list"\] > \.post_ajax_loader \{[^}]*display: grid;[^}]*grid-template-columns: repeat\(2, minmax\(0, 1fr\)\);/s
|
||||
/\.dashboard-panel\.is-loading \.jio-user-home__post-list\[data-view="list"\] > \.post_ajax_loader \{[^}]*display: grid;[^}]*grid-template-columns: repeat\(2, minmax\(0, 1fr\)\);/s
|
||||
)
|
||||
})
|
||||
|
||||
test('asset tables are InnoDB and operation keys are unique', () => {
|
||||
const schema = fs.readFileSync(path.join(root, 'src', 'Core', 'DBMigratorPart2.php'), 'utf8')
|
||||
for (const table of ['asset_account', 'asset_transaction', 'asset_entry']) {
|
||||
assert.match(schema, new RegExp(`CREATE TABLE \\{\\$prefix\\}aksrt_${table} \\([\\s\\S]*?\\) ENGINE=InnoDB`))
|
||||
assert.match(schema, new RegExp(`CREATE TABLE \\{\\$prefix\\}jio_${table} \\([\\s\\S]*?\\) ENGINE=InnoDB`))
|
||||
}
|
||||
assert.match(schema, /UNIQUE KEY uniq_operation_key \(operation_key\)/)
|
||||
assert.match(schema, /UNIQUE KEY uniq_transaction_line \(transaction_id, line_no\)/)
|
||||
@@ -207,12 +207,12 @@ test('asset record tabs and pagination stay in the DOM without URL state', () =>
|
||||
const credits = fs.readFileSync(path.join(root, 'assets', 'js', 'credits.js'), 'utf8')
|
||||
const center = fs.readFileSync(path.join(root, 'src', 'User', 'Center.php'), 'utf8')
|
||||
|
||||
assert.match(profile, /data-aksrt-asset-tab="balance"/)
|
||||
assert.match(profile, /data-aksrt-asset-tab="points"/)
|
||||
assert.match(points, /aksrt_render_asset_pagination\([^;]+, 'points'\)/)
|
||||
assert.match(wallet, /aksrt_render_asset_pagination\([^;]+, 'balance'\)/)
|
||||
assert.match(profile, /data-jio-asset-tab="balance"/)
|
||||
assert.match(profile, /data-jio-asset-tab="points"/)
|
||||
assert.match(points, /jio_render_asset_pagination\([^;]+, 'points'\)/)
|
||||
assert.match(wallet, /jio_render_asset_pagination\([^;]+, 'balance'\)/)
|
||||
assert.doesNotMatch(profile + points + wallet, /\$_GET\[['"](?:wallet_page|credits_page)['"]\]/)
|
||||
assert.match(credits, /data-aksrt-asset-page/)
|
||||
assert.match(credits, /data-jio-asset-page/)
|
||||
assert.match(credits, /user-center\/panel\/credits\?asset=/)
|
||||
assert.match(center, /'asset'\s*=>\s*\[/)
|
||||
assert.match(center, /\$context\['asset_request'\]/)
|
||||
@@ -256,15 +256,15 @@ test('points orders bypass cash commissions and purchase dead paths stay removed
|
||||
assert.ok(pointsGuard >= 0 && affiliateGuard > pointsGuard)
|
||||
assert.match(paidMethod, /\$currency\s*=\s*sanitize_key/)
|
||||
|
||||
const pay = fs.readFileSync(path.join(root, 'inc', 'helpers', 'aksrt-pay.php'), 'utf8')
|
||||
const payTypesStart = pay.indexOf('function aksrt_get_pay_types')
|
||||
const cashMethodsStart = pay.indexOf('function aksrt_list_cash_payment_methods', payTypesStart)
|
||||
const pay = fs.readFileSync(path.join(root, 'inc', 'helpers', 'jio-pay.php'), 'utf8')
|
||||
const payTypesStart = pay.indexOf('function jio_get_pay_types')
|
||||
const cashMethodsStart = pay.indexOf('function jio_list_cash_payment_methods', payTypesStart)
|
||||
const payTypesMethod = pay.slice(payTypesStart, cashMethodsStart)
|
||||
assert.ok(payTypesStart >= 0 && cashMethodsStart > payTypesStart)
|
||||
assert.equal((payTypesMethod.match(/(?:^|\n)\s*return\b/g) || []).length, 1)
|
||||
|
||||
const cashier = fs.readFileSync(path.join(root, 'assets', 'js', 'cms-download.js'), 'utf8')
|
||||
assert.match(cashier, /\[data-action="aksrt-need-purchase"\], \[data-aksrt-pay-content\]/)
|
||||
assert.match(cashier, /\[data-action="jio-need-purchase"\], \[data-jio-pay-content\]/)
|
||||
assert.doesNotMatch(cashier, /const cashierLink\s*=\s*e\.target\.closest\('\.cashier-link'\)/)
|
||||
})
|
||||
|
||||
@@ -356,7 +356,7 @@ test('asset purchases share the order center and refresh every wallet surface',
|
||||
const chargeCard = fs.readFileSync(path.join(root, 'src', 'Payment', 'ChargeCard.php'), 'utf8')
|
||||
const creditsJs = fs.readFileSync(path.join(root, 'assets', 'js', 'credits.js'), 'utf8')
|
||||
const sidebar = fs.readFileSync(path.join(root, 'inc', 'helpers', 'user-center-renderers', 'sidebar-commerce.php'), 'utf8')
|
||||
const header = fs.readFileSync(path.join(root, 'inc', 'helpers', 'aksrt-header-userbox.php'), 'utf8')
|
||||
const header = fs.readFileSync(path.join(root, 'inc', 'helpers', 'jio-header-userbox.php'), 'utf8')
|
||||
|
||||
assert.match(domain, /ORDER_PRODUCT_POINTS\s*=\s*'points'/)
|
||||
const productTypes = ['READING', 'DOWNLOAD', 'IMAGE', 'VIDEO', 'VIP', 'RECHARGE', 'POINTS']
|
||||
@@ -370,12 +370,12 @@ test('asset purchases share the order center and refresh every wallet surface',
|
||||
statuses.map((status) => [status, status.toLowerCase()]),
|
||||
)
|
||||
assert.deepEqual(
|
||||
[...order.matchAll(/public const STATUS_([A-Z_]+)\s*=\s*\\AKSRT\\Core\\DomainModel::ORDER_STATUS_([A-Z_]+);/g)].map((match) => [match[1], match[2]]),
|
||||
[...order.matchAll(/public const STATUS_([A-Z_]+)\s*=\s*\\Jio\\Core\\DomainModel::ORDER_STATUS_([A-Z_]+);/g)].map((match) => [match[1], match[2]]),
|
||||
statuses.map((status) => [status, status]),
|
||||
)
|
||||
for (const status of statuses) {
|
||||
assert.match(domain, new RegExp(`ORDER_STATUS_${status}\\s*=\\s*'${status.toLowerCase()}'`))
|
||||
assert.match(order, new RegExp(`STATUS_${status}\\s*=\\s*\\\\AKSRT\\\\Core\\\\DomainModel::ORDER_STATUS_${status}`))
|
||||
assert.match(order, new RegExp(`STATUS_${status}\\s*=\\s*\\\\Jio\\\\Core\\\\DomainModel::ORDER_STATUS_${status}`))
|
||||
assert.match(query, new RegExp(`STATUS_${status}\\s*=\\s*'${status.toLowerCase()}'`))
|
||||
}
|
||||
for (const status of ['CREATED', 'DELIVERED', 'COMPLETED', 'PENDING_REVIEW', 'CANCELLED', 'EXPIRED']) {
|
||||
@@ -401,7 +401,7 @@ test('asset purchases share the order center and refresh every wallet surface',
|
||||
assert.match(repository, /SUM\(CASE WHEN status = 'refunding' THEN 1 ELSE 0 END\) AS paid_legacy/)
|
||||
assert.match(repository, /'paid' => \(int\) \(\$row\['paid'\] \?\? 0\) \+ \(int\) \(\$row\['paid_legacy'\] \?\? 0\)/)
|
||||
assert.doesNotMatch(repository, /\$completed_statuses|\$completed_placeholders/)
|
||||
assert.match(adminRepository, /\$valid_statuses = array_keys\(\\AKSRT\\Core\\DomainModel::order_status_labels\(\)\)/)
|
||||
assert.match(adminRepository, /\$valid_statuses = array_keys\(\\Jio\\Core\\DomainModel::order_status_labels\(\)\)/)
|
||||
assert.doesNotMatch(payOrders, /<select name="new_status">/)
|
||||
assert.doesNotMatch(`${order}\n${repository}\n${userOrders}\n${payOrders}`, /['"]force['"]\s*=>\s*true/)
|
||||
assert.match(paymentRuntime, /\$status_keys = \['pending', 'paid', 'refunded', 'closed'\]/)
|
||||
@@ -410,8 +410,8 @@ test('asset purchases share the order center and refresh every wallet surface',
|
||||
}
|
||||
assert.doesNotMatch(paymentSchema, /payment_order_status_(?:completed|cancelled|processing)/)
|
||||
|
||||
assert.match(balanceRest, /register_rest_route\('aksrt\/v1', '\/pay\/assets'/)
|
||||
assert.match(balanceRest, /'product_type'\s*=>\s*\\AKSRT\\Core\\DomainModel::ORDER_PRODUCT_POINTS/)
|
||||
assert.match(balanceRest, /register_rest_route\('jio\/v1', '\/pay\/assets'/)
|
||||
assert.match(balanceRest, /'product_type'\s*=>\s*\\Jio\\Core\\DomainModel::ORDER_PRODUCT_POINTS/)
|
||||
assert.match(balanceRest, /\$existing_order\s*=\s*\$repo->get_by_request_key\(\$operation_key\)/)
|
||||
assert.match(balanceRest, /\$order_number\s*=\s*\$repo->allocate_order_number\(\)/)
|
||||
assert.doesNotMatch(balanceRest, /['"]PX['"]\s*\.|hash\('sha256',\s*\$operation_key\)/)
|
||||
@@ -420,15 +420,15 @@ test('asset purchases share the order center and refresh every wallet surface',
|
||||
assert.match(chargeCard, /self::TYPE_CREDITS[\s\S]*?ORDER_PRODUCT_POINTS/)
|
||||
|
||||
assert.match(creditsJs, /function refreshAssetBalances\(/)
|
||||
assert.match(creditsJs, /querySelectorAll\('\[data-aksrt-wallet-value\]'/)
|
||||
assert.match(creditsJs, /querySelectorAll\('\[data-aksrt-points-value\]'/)
|
||||
assert.match(sidebar, /data-aksrt-wallet-value/)
|
||||
assert.match(sidebar, /data-aksrt-points-value/)
|
||||
assert.match(header, /data-aksrt-wallet-value/)
|
||||
assert.match(header, /data-aksrt-points-value/)
|
||||
assert.match(header, /data-aksrt-wallet-value data-aksrt-count-format="cut-count"/)
|
||||
assert.match(header, /data-aksrt-points-value data-aksrt-count-format="cut-count"/)
|
||||
assert.match(creditsJs, /element\.dataset\.aksrtCountFormat === 'cut-count' \? formatCutCount\(/)
|
||||
assert.match(creditsJs, /querySelectorAll\('\[data-jio-wallet-value\]'/)
|
||||
assert.match(creditsJs, /querySelectorAll\('\[data-jio-points-value\]'/)
|
||||
assert.match(sidebar, /data-jio-wallet-value/)
|
||||
assert.match(sidebar, /data-jio-points-value/)
|
||||
assert.match(header, /data-jio-wallet-value/)
|
||||
assert.match(header, /data-jio-points-value/)
|
||||
assert.match(header, /data-jio-wallet-value data-jio-count-format="cut-count"/)
|
||||
assert.match(header, /data-jio-points-value data-jio-count-format="cut-count"/)
|
||||
assert.match(creditsJs, /element\.dataset\.jioCountFormat === 'cut-count' \? formatCutCount\(/)
|
||||
})
|
||||
|
||||
test('header wallet count formatting matches the server cut-count thresholds', () => {
|
||||
@@ -450,9 +450,9 @@ test('recharge completion always credits the ledger and expiry SQL is strict-mod
|
||||
const charge = fs.readFileSync(path.join(root, 'src', 'Credits', 'BalanceServiceCharge.php'), 'utf8')
|
||||
const gateway = fs.readFileSync(path.join(root, 'src', 'Payment', 'Gateway.php'), 'utf8')
|
||||
|
||||
assert.match(repository, /\$new_status === \\AKSRT\\Core\\DomainModel::ORDER_STATUS_PAID/)
|
||||
assert.match(charge, /'status'\s*=>\s*\\AKSRT\\Payment\\Order::STATUS_PAID/)
|
||||
assert.match(charge, /\\AKSRT\\Payment\\Order::STATUS_PENDING,\s*\\AKSRT\\Payment\\Order::STATUS_PAID/)
|
||||
assert.match(repository, /\$new_status === \\Jio\\Core\\DomainModel::ORDER_STATUS_PAID/)
|
||||
assert.match(charge, /'status'\s*=>\s*\\Jio\\Payment\\Order::STATUS_PAID/)
|
||||
assert.match(charge, /\\Jio\\Payment\\Order::STATUS_PENDING,\s*\\Jio\\Payment\\Order::STATUS_PAID/)
|
||||
assert.doesNotMatch(charge, /Order::STATUS_(?:CREATED|COMPLETED)/)
|
||||
assert.match(gateway, /ORDER_PRODUCT_RECHARGE[\s\S]*?update_status\(\$order_number, Order::STATUS_PAID/)
|
||||
assert.doesNotMatch(repository, /expires_at\s*<>\s*''/)
|
||||
|
||||
@@ -22,7 +22,7 @@ test('modal and sign page use one renderer and one state machine', () => {
|
||||
assert.match(shared, /data-auth-panel="bind"/);
|
||||
assert.match(shared, /data-auth-panel="oauth"/);
|
||||
assert.match(scripts, /assets\/js\/auth\.js/);
|
||||
assert.match(scripts, /'aksrt-auth',[\s\S]*?assets\/js\/auth\.js[\s\S]*?\['loader_js'\]/);
|
||||
assert.match(scripts, /'jio-auth',[\s\S]*?assets\/js\/auth\.js[\s\S]*?\['loader_js'\]/);
|
||||
assert.doesNotMatch(scripts, /modal-auth\.js|sign-register\.js/);
|
||||
assert.doesNotMatch(runtime, /signsubmit-loader|tbquire\(\['sign-register'\]\)|\.on\('click', '\.(?:signin|signup)-loader'/);
|
||||
assert.doesNotMatch(loader, /sign-register/);
|
||||
@@ -56,15 +56,15 @@ test('authentication settings use reference defaults and canonical keys', () =>
|
||||
});
|
||||
|
||||
test('authentication galleries save attachment IDs and are consumed by both surfaces', () => {
|
||||
const field = read('assets/admin/src/components/form/AksField.vue');
|
||||
const gallery = read('assets/admin/src/components/form/AksGalleryField.vue');
|
||||
const field = read('assets/admin/src/components/form/JioField.vue');
|
||||
const gallery = read('assets/admin/src/components/form/JioGalleryField.vue');
|
||||
const provider = read('src/Admin/SchemaProvider.php');
|
||||
const reader = read('src/Settings/AuthReader.php');
|
||||
const modal = read('templates/parts/modal-auth.php');
|
||||
const page = read('templates/pages/page-sign.php');
|
||||
const css = read('assets/css/routes/sign.css');
|
||||
|
||||
assert.match(field, /<AksGalleryField[\s\S]*?schema\.type === 'gallery'/);
|
||||
assert.match(field, /<JioGalleryField[\s\S]*?schema\.type === 'gallery'/);
|
||||
assert.match(gallery, /multiple:\s*true/);
|
||||
assert.match(gallery, /Number\.parseInt\(attachment\.id/);
|
||||
assert.match(provider, /'gallery'\s*=>\s*'attachment_ids'/);
|
||||
@@ -88,10 +88,10 @@ test('only the shared authentication REST routes and /sign/ renderer are registe
|
||||
assert.match(controller, new RegExp(route.replace('/', '\\/')));
|
||||
});
|
||||
assert.doesNotMatch(controller, /auth\/(?:modal-|phone-|send-sms-code|bind-phone)/);
|
||||
assert.match(router, /'sign'\s*=>\s*'index\.php\?aksrt_sign=1'/);
|
||||
assert.match(router, /'sign'\s*=>\s*'index\.php\?jio_sign=1'/);
|
||||
assert.doesNotMatch(router, /'login'\s*=>|'register'\s*=>|'reset-password'\s*=>|'verify-email'\s*=>|'bind-account'\s*=>/);
|
||||
assert.match(router, /aksrt_bypass/);
|
||||
assert.match(router, /\$action === 'logout'[\s\S]*?aksrt_get_logout_url/);
|
||||
assert.match(router, /jio_bypass/);
|
||||
assert.match(router, /\$action === 'logout'[\s\S]*?jio_get_logout_url/);
|
||||
assert.doesNotMatch(router, /is_user_logged_in\(\) && current_user_can\('manage_options'\)|wp_doing_ajax\(\)/);
|
||||
assert.match(urls, /home_url\('\/sign\/'\)/);
|
||||
});
|
||||
@@ -136,7 +136,7 @@ test('unified handlers enforce nonce, captcha, rate limits, configured channels,
|
||||
assert.match(handlers, /账号或密码错误/);
|
||||
assert.match(handlers, /账号或验证码错误/);
|
||||
assert.match(service, /signin_phone_enabled/);
|
||||
assert.doesNotMatch(service + serviceA + serviceB, /aksrt_require_captcha/);
|
||||
assert.doesNotMatch(service + serviceA + serviceB, /jio_require_captcha/);
|
||||
assert.doesNotMatch(service + serviceA + serviceB, /_auth_code_verified|_defer_login/);
|
||||
assert.doesNotMatch(service, /captcha_scene/);
|
||||
assert.match(handlers, /public function user_bind_phone[\s\S]*?auth_require_captcha\('bind_phone'/);
|
||||
@@ -177,7 +177,7 @@ test('shared renderer contracts every configured authentication state', () => {
|
||||
assert.match(shared, /oauth-button-lg/);
|
||||
assert.match(shared, /data-auth-qrcode/);
|
||||
assert.match(modal, /data-auto=/);
|
||||
assert.match(client, /aksrtAuthAutoUntil/);
|
||||
assert.match(client, /jioAuthAutoUntil/);
|
||||
assert.match(client, /data-password-show/);
|
||||
assert.match(client, /pollQr/);
|
||||
assert.match(client, /AbortController/);
|
||||
@@ -205,8 +205,8 @@ test('OAuth frontend is accessible, redirect-aware, and removes retired provider
|
||||
const signCss = read('assets/css/routes/sign.css');
|
||||
const mainCss = read('assets/css/main.css');
|
||||
|
||||
assert.match(modal, /role="dialog"[^>]*aria-modal="true"[^>]*aria-labelledby="aksrt-auth-modal-title"/);
|
||||
assert.match(modal, /id="aksrt-auth-modal-title"/);
|
||||
assert.match(modal, /role="dialog"[^>]*aria-modal="true"[^>]*aria-labelledby="jio-auth-modal-title"/);
|
||||
assert.match(modal, /id="jio-auth-modal-title"/);
|
||||
assert.match(client, /focusableModalItems/);
|
||||
assert.match(client, /lastModalTrigger/);
|
||||
assert.match(client, /restore\.focus\(\)/);
|
||||
@@ -253,7 +253,7 @@ test('the social-login area drops the implicit agreement line', () => {
|
||||
});
|
||||
|
||||
test('brand icons resolve Gitee and Google to the reference SVG sprite', () => {
|
||||
const icons = read('inc/helpers/aksrt-icons.php');
|
||||
const icons = read('inc/helpers/jio-icons.php');
|
||||
const sprites = read('assets/js/runtime/svg-icon.js');
|
||||
|
||||
// FA5 Free ships no Gitee brand glyph and no multi-colour Google mark, so both
|
||||
@@ -262,17 +262,17 @@ test('brand icons resolve Gitee and Google to the reference SVG sprite', () => {
|
||||
assert.match(icons, /'google'\s*=>\s*'zibsvg-google-color'/);
|
||||
assert.match(icons, /'github'\s*=>\s*'fa fab fa-github'/);
|
||||
assert.match(icons, /'wechat'\s*=>\s*'fa fab fa-weixin'/);
|
||||
assert.match(sprites, /aksrt_svgs\['gitee'\]/);
|
||||
assert.match(sprites, /aksrt_svgs\['google-color'\]/);
|
||||
assert.match(sprites, /jio_svgs\['gitee'\]/);
|
||||
assert.match(sprites, /jio_svgs\['google-color'\]/);
|
||||
// Sprite values must reach the shared icon renderer instead of being wrapped
|
||||
// in a bogus <i class="zibsvg-…">.
|
||||
assert.match(icons, /strpos\(\$class, 'zibsvg-'\) === 0[\s\S]*?aksrt_icon_markup\(\$class, \$extra_classes\)/);
|
||||
assert.match(icons, /strpos\(\$class, 'zibsvg-'\) === 0[\s\S]*?jio_icon_markup\(\$class, \$extra_classes\)/);
|
||||
});
|
||||
|
||||
test('the top-nav user popover reproduces the reference quick-login block', () => {
|
||||
const oauth = read('src/Auth/OAuth.php');
|
||||
const header = read('inc/helpers/header/aksrt-header-navbar.php');
|
||||
const links = read('inc/helpers/aksrt-links.php');
|
||||
const header = read('inc/helpers/header/jio-header-navbar.php');
|
||||
const links = read('inc/helpers/jio-links.php');
|
||||
const legacyCss = read('assets/css/frontend/pages/base-1.css');
|
||||
|
||||
assert.match(oauth, /social-separator separator muted-3-color em09 mt10/);
|
||||
@@ -284,9 +284,9 @@ test('the top-nav user popover reproduces the reference quick-login block', () =
|
||||
assert.match(oauth, /oauth_button_lg/);
|
||||
|
||||
// Callers append the block verbatim — no hand-rolled separator or wrapper.
|
||||
assert.match(header, /render_social_buttons\(__\('快速登录', 'aksrt'\)\)/);
|
||||
assert.match(header, /render_social_buttons\(__\('快速登录', 'jio'\)\)/);
|
||||
assert.doesNotMatch(header, /social-separator|social_loginbar/);
|
||||
assert.match(links, /render_social_buttons\(__\('使用以下方式快速登录', 'aksrt'\)\)/);
|
||||
assert.match(links, /render_social_buttons\(__\('使用以下方式快速登录', 'jio'\)\)/);
|
||||
assert.doesNotMatch(links, /social_loginbar/);
|
||||
|
||||
// The retired .social-login-divider / .oauth-row / .oauth-btn markup and its
|
||||
@@ -299,7 +299,7 @@ test('provider URLs stay raw in the data layer and are escaped once per surface'
|
||||
const oauth = read('src/Auth/OAuth.php');
|
||||
const shared = read('templates/parts/auth-content.php');
|
||||
const userRenderer = read('src/WidgetSystem/Renderers/UserRenderer.php');
|
||||
const comments = read('inc/helpers/aksrt-comments.php');
|
||||
const comments = read('inc/helpers/jio-comments.php');
|
||||
|
||||
// get_auth_providers_data() hands out raw URLs, exactly like its sibling
|
||||
// get_frontend_bindings_data() does. esc_url() is idempotent on its own
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
* 的 opacity:0 形成隐形遮罩 → 整页所有按钮不可点击(2026-09-17 实测故障)。
|
||||
*
|
||||
* 因此 credits.css 必须以 (0,2,0) 特异性压制:
|
||||
* .modal[data-aksrt-charge-modal] { display: none; }
|
||||
* .modal[data-aksrt-charge-modal].in { display: flex; }
|
||||
* .modal[data-jio-charge-modal] { display: none; }
|
||||
* .modal[data-jio-charge-modal].in { display: flex; }
|
||||
* 展示态由 credits.js 自包含开关的内联 display:flex 驱动(不依赖
|
||||
* jQuery/Bootstrap 运行时;block 会破坏 .flex.jc 双轴居中——
|
||||
* .modal.flex .modal-dialog{margin:0} 在 block 下使对话框顶在左上角),
|
||||
@@ -30,32 +30,32 @@ const creditsCss = read('assets', 'css', 'routes', 'user-center', 'credits.css')
|
||||
const balanceRest = read('src', 'Credits', 'BalanceServiceRest.php');
|
||||
|
||||
test('弹窗壳为 Zibll refresh_modal 同构静态壳并带 data 钩子', () => {
|
||||
assert.match(wallet, /class="modal flex jc fade"[^>]*data-aksrt-charge-modal/);
|
||||
assert.match(wallet, /data-aksrt-charge-form/);
|
||||
assert.match(wallet, /class="modal flex jc fade"[^>]*data-jio-charge-modal/);
|
||||
assert.match(wallet, /data-jio-charge-form/);
|
||||
assert.match(wallet, /data-dismiss="modal"/);
|
||||
assert.match(wallet, /initiate-pay/);
|
||||
});
|
||||
|
||||
test('弹窗壳静止态必须被 credits.css 显式隐藏(.flex 覆盖回归护栏)', () => {
|
||||
// (0,2,0) 特异性压制 main.css 的 .flex{display:flex}。
|
||||
assert.match(creditsCss, /\.modal\[data-aksrt-charge-modal\]\s*\{\s*display:\s*none;/);
|
||||
assert.match(creditsCss, /\.modal\[data-jio-charge-modal\]\s*\{\s*display:\s*none;/);
|
||||
// 打开态必须是 flex 而非 block:.modal.flex .modal-dialog{margin:0} 在
|
||||
// block 下使对话框顶在左上角(无头 Chrome 几何实证),flex 下由壳的
|
||||
// .flex.jc 双轴居中(Zibll 生产环境即此形态)。
|
||||
assert.match(creditsCss, /\.modal\[data-aksrt-charge-modal\]\.in\s*\{\s*display:\s*flex;/);
|
||||
assert.doesNotMatch(creditsCss, /\.modal\[data-aksrt-charge-modal\]\.in\s*\{\s*display:\s*block;/);
|
||||
assert.match(creditsCss, /\.modal\[data-jio-charge-modal\]\.in\s*\{\s*display:\s*flex;/);
|
||||
assert.doesNotMatch(creditsCss, /\.modal\[data-jio-charge-modal\]\.in\s*\{\s*display:\s*block;/);
|
||||
// 购买积分弹窗壳同构(服务端静态输出 .modal.flex.jc.fade),静止态同样
|
||||
// 必须显式隐藏,否则隐形遮罩拦截整页点击(2026-09-18 实测回归)。
|
||||
const balance = read('inc', 'helpers', 'user-center-renderers', 'credits-balance.php');
|
||||
assert.match(balance, /class="modal flex jc fade"[^>]*data-aksrt-points-pay-modal/);
|
||||
assert.match(creditsCss, /\.modal\[data-aksrt-points-pay-modal\]\s*\{\s*display:\s*none;/);
|
||||
assert.match(creditsCss, /\.modal\[data-aksrt-points-pay-modal\]\.in\s*\{\s*display:\s*flex;/);
|
||||
assert.match(balance, /class="modal flex jc fade"[^>]*data-jio-points-pay-modal/);
|
||||
assert.match(creditsCss, /\.modal\[data-jio-points-pay-modal\]\s*\{\s*display:\s*none;/);
|
||||
assert.match(creditsCss, /\.modal\[data-jio-points-pay-modal\]\.in\s*\{\s*display:\s*flex;/);
|
||||
});
|
||||
|
||||
test('开关自包含:不依赖 jQuery/Bootstrap modal 运行时', () => {
|
||||
assert.match(creditsJs, /function openChargeModal/);
|
||||
assert.match(creditsJs, /function closeChargeModal/);
|
||||
assert.match(creditsJs, /data-aksrt-charge-open/);
|
||||
assert.match(creditsJs, /data-jio-charge-open/);
|
||||
// 动态创建遮罩层(bootstrap.min.css 已有 .modal-backdrop 规则)。
|
||||
assert.match(creditsJs, /modal-backdrop fade in/);
|
||||
// 充值弹窗开关不得回退到 jQuery modal 运行时(时序异常时弹窗打不开)。
|
||||
|
||||
@@ -44,24 +44,24 @@ test('ban queue merges all records, reports status counts, and clamps pages abov
|
||||
test('real PHP rendering keeps review forms in templates and completed records read-only', () => {
|
||||
const { html, filtered_html: filteredHtml } = phpFixture();
|
||||
|
||||
assert.equal((html.match(/<dialog id="aksrt-ban-dialog"/g) || []).length, 1);
|
||||
assert.equal((html.match(/<template id="aksrt-ban-detail-/g) || []).length, 20);
|
||||
assert.match(html, /<template id="aksrt-ban-detail-report-1">\s*<form method="post"/);
|
||||
assert.match(html, /<template id="aksrt-ban-detail-appeal-501">\s*<form method="post"/);
|
||||
const resolved = html.match(/<template id="aksrt-ban-detail-report-2">([\s\S]*?)<\/template>/)?.[1] || '';
|
||||
assert.equal((html.match(/<dialog id="jio-ban-dialog"/g) || []).length, 1);
|
||||
assert.equal((html.match(/<template id="jio-ban-detail-/g) || []).length, 20);
|
||||
assert.match(html, /<template id="jio-ban-detail-report-1">\s*<form method="post"/);
|
||||
assert.match(html, /<template id="jio-ban-detail-appeal-501">\s*<form method="post"/);
|
||||
const resolved = html.match(/<template id="jio-ban-detail-report-2">([\s\S]*?)<\/template>/)?.[1] || '';
|
||||
assert.notEqual(resolved, '');
|
||||
assert.doesNotMatch(resolved, /<form|data-aksrt-ban-submit/);
|
||||
assert.doesNotMatch(resolved, /<form|data-jio-ban-submit/);
|
||||
assert.match(resolved, /该记录已处理/);
|
||||
|
||||
for (const [name, value] of Object.entries({
|
||||
action: 'aksrt_users_update_report', report_id: '9001', filter_type: 'report', filter_status: 'pending',
|
||||
action: 'jio_users_update_report', report_id: '9001', filter_type: 'report', filter_status: 'pending',
|
||||
s: 'needle', send_user: '7', orderby: 'updated', order: 'ASC', paged: '1',
|
||||
})) {
|
||||
assert.equal(inputValue(filteredHtml, name), value, `review form should carry ${name}`);
|
||||
}
|
||||
assert.match(filteredHtml, /name="_wpnonce" value="aksrt_users_update_report_9001-nonce"/);
|
||||
assert.match(filteredHtml, /name="_wpnonce" value="jio_users_update_report_9001-nonce"/);
|
||||
assert.match(html, /name="user_id" value="501"/);
|
||||
assert.match(html, /name="_wpnonce" value="aksrt_users_process_appeal_501-nonce"/);
|
||||
assert.match(html, /name="_wpnonce" value="jio_users_process_appeal_501-nonce"/);
|
||||
assert.match(html, /<input type="radio"[^>]*required>/);
|
||||
assert.doesNotMatch(html, /<input type="radio"[^>]*checked/);
|
||||
});
|
||||
@@ -84,19 +84,19 @@ test('PHP templates escape all appeal data and report evidence while preserving
|
||||
|
||||
test('report details display readable categories separately from optional content and evidence', () => {
|
||||
const { html } = phpFixture();
|
||||
const detail = html.match(/<template id="aksrt-ban-detail-report-1">([\s\S]*?)<\/template>/)?.[1] || '';
|
||||
const category = detail.match(/<h3>举报分类<\/h3><div class="aksrt-ban-copy">([\s\S]*?)<\/div>/)?.[1];
|
||||
const content = detail.match(/<h3>举报内容与举证<\/h3><div class="aksrt-ban-copy">([\s\S]*?)<\/div>/)?.[1] || '';
|
||||
const detail = html.match(/<template id="jio-ban-detail-report-1">([\s\S]*?)<\/template>/)?.[1] || '';
|
||||
const category = detail.match(/<h3>举报分类<\/h3><div class="jio-ban-copy">([\s\S]*?)<\/div>/)?.[1];
|
||||
const content = detail.match(/<h3>举报内容与举证<\/h3><div class="jio-ban-copy">([\s\S]*?)<\/div>/)?.[1] || '';
|
||||
assert.equal(category, '版权投诉');
|
||||
assert.match(content, /Evidence/);
|
||||
assert.match(content, /https:\/\/evidence\.example\.test\/proof\.jpg/);
|
||||
assert.doesNotMatch(content, /版权投诉/);
|
||||
assert.match(html, /class="aksrt-ban-summary">版权投诉<\/div>/);
|
||||
assert.match(html, /class="jio-ban-summary">版权投诉<\/div>/);
|
||||
|
||||
const categoryOnly = html.match(/<template id="aksrt-ban-detail-report-2">([\s\S]*?)<\/template>/)?.[1] || '';
|
||||
assert.match(categoryOnly, /<h3>举报分类<\/h3><div class="aksrt-ban-copy">人身攻击<\/div>/);
|
||||
assert.match(categoryOnly, /<h3>举报内容与举证<\/h3><div class="aksrt-ban-copy">未填写<\/div>/);
|
||||
assert.match(html, /class="aksrt-ban-summary">人身攻击<\/div>/);
|
||||
const categoryOnly = html.match(/<template id="jio-ban-detail-report-2">([\s\S]*?)<\/template>/)?.[1] || '';
|
||||
assert.match(categoryOnly, /<h3>举报分类<\/h3><div class="jio-ban-copy">人身攻击<\/div>/);
|
||||
assert.match(categoryOnly, /<h3>举报内容与举证<\/h3><div class="jio-ban-copy">未填写<\/div>/);
|
||||
assert.match(html, /class="jio-ban-summary">人身攻击<\/div>/);
|
||||
});
|
||||
|
||||
test('dedicated ban assets enqueue only on the matching native page', () => {
|
||||
@@ -104,11 +104,11 @@ test('dedicated ban assets enqueue only on the matching native page', () => {
|
||||
|
||||
assert.equal(assets.styles.length, 1);
|
||||
assert.deepEqual(assets.styles[0].slice(0, 3), [
|
||||
'aksrt-admin-user-ban', 'https://theme.example.test/assets/css/admin/user-ban.css', ['aksrt-admin-native-ui'],
|
||||
'jio-admin-user-ban', 'https://theme.example.test/assets/css/admin/user-ban.css', ['jio-admin-native-ui'],
|
||||
]);
|
||||
assert.equal(assets.scripts.length, 1);
|
||||
assert.deepEqual(assets.scripts[0].slice(0, 3), [
|
||||
'aksrt-admin-user-ban', 'https://theme.example.test/assets/js/admin-user-ban.js', [],
|
||||
'jio-admin-user-ban', 'https://theme.example.test/assets/js/admin-user-ban.js', [],
|
||||
]);
|
||||
assert.equal(assets.scripts[0][4], true);
|
||||
});
|
||||
@@ -117,9 +117,9 @@ test('PHP actions reject blank notes and stale versions, and preserve filter rou
|
||||
const { actions } = phpFixture();
|
||||
|
||||
assert.equal(actions.blank_report.message, 'report_failed');
|
||||
assert.match(actions.blank_report.args.aksrt_ban_error, /必须填写处理说明/);
|
||||
assert.match(actions.blank_report.args.jio_ban_error, /必须填写处理说明/);
|
||||
assert.equal(actions.stale_report.message, 'report_failed');
|
||||
assert.match(actions.stale_report.args.aksrt_ban_error, /已变化或已处理/);
|
||||
assert.match(actions.stale_report.args.jio_ban_error, /已变化或已处理/);
|
||||
assert.equal(actions.valid_report.message, 'report_updated');
|
||||
assert.deepEqual({
|
||||
type: actions.valid_report.args.type,
|
||||
@@ -132,7 +132,7 @@ test('PHP actions reject blank notes and stale versions, and preserve filter rou
|
||||
}, { type: 'report', status: 'pending', s: 'needle', send_user: 7, orderby: 'updated', order: 'ASC', paged: 2 });
|
||||
|
||||
assert.equal(actions.blank_appeal.message, 'appeal_failed');
|
||||
assert.match(actions.blank_appeal.args.aksrt_ban_error, /必须填写拒绝原因/);
|
||||
assert.match(actions.blank_appeal.args.jio_ban_error, /必须填写拒绝原因/);
|
||||
assert.equal(actions.valid_appeal.message, 'appeal_rejected');
|
||||
assert.equal(actions.appeal_status, 'rejected');
|
||||
assert.equal(actions.appeal_payload.admin_note, 'Missing evidence');
|
||||
@@ -153,15 +153,15 @@ test('ban admin contract keeps the single-dialog flow and separate native operat
|
||||
const css = read('assets/css/admin/user-ban.css');
|
||||
|
||||
assert.match(users, /use UsersMenusBan;/);
|
||||
assert.match(users, /admin_post_aksrt_users_update_report/);
|
||||
assert.match(users, /admin_post_aksrt_users_process_appeal/);
|
||||
assert.match(users, /admin_post_jio_users_update_report/);
|
||||
assert.match(users, /admin_post_jio_users_process_appeal/);
|
||||
assert.doesNotMatch(users, /use UsersMenusTickets;/);
|
||||
assert.ok(!fs.existsSync(path.join(root, 'src/Admin/Native/UsersMenusTickets.php')));
|
||||
assert.match(page, /if \(\$hook !== 'users_page_aksrt_user_ban'\) return/);
|
||||
assert.match(page, /<dialog id="aksrt-ban-dialog"/);
|
||||
assert.match(dialogs, /<template id="aksrt-ban-detail-/);
|
||||
assert.match(page, /if \(\$hook !== 'users_page_jio_user_ban'\) return/);
|
||||
assert.match(page, /<dialog id="jio-ban-dialog"/);
|
||||
assert.match(dialogs, /<template id="jio-ban-detail-/);
|
||||
assert.doesNotMatch(page, /update_user_meta|update_status|do_action\(/);
|
||||
assert.doesNotMatch(page, /data-aksrt-ticket-toggle|data-aksrt-ticket-cancel|data-aksrt-ban-toggle/);
|
||||
assert.doesNotMatch(page, /data-jio-ticket-toggle|data-jio-ticket-cancel|data-jio-ban-toggle/);
|
||||
|
||||
assert.match(query, /BAN_PER_PAGE = 20/);
|
||||
assert.match(query, /number'\s*=>\s*-1/);
|
||||
@@ -179,8 +179,8 @@ test('ban admin contract keeps the single-dialog flow and separate native operat
|
||||
assert.match(dialogs, /esc_html\(\$value !== '' \? \$value : '—'\)/);
|
||||
assert.match(dialogs, /target="_blank" rel="noopener noreferrer"/);
|
||||
|
||||
assert.match(actions, /check_admin_referer\('aksrt_users_update_report_'/);
|
||||
assert.match(actions, /check_admin_referer\('aksrt_users_process_appeal_'/);
|
||||
assert.match(actions, /check_admin_referer\('jio_users_update_report_'/);
|
||||
assert.match(actions, /check_admin_referer\('jio_users_process_appeal_'/);
|
||||
assert.match(actions, /hash_equals\(\$this->ban_record_version\(\$row\), \$submitted\)/);
|
||||
assert.match(actions, /\$this->ban_request_is_current\(\$row, 'pending'\)/);
|
||||
assert.match(actions, /\$decision === 'reject' && \$note === ''/);
|
||||
@@ -190,5 +190,5 @@ test('ban admin contract keeps the single-dialog flow and separate native operat
|
||||
assert.match(script, /dialog\.replaceChildren\(\)/);
|
||||
assert.match(script, /previousOpener\.focus\(\)/);
|
||||
assert.match(script, /window\.addEventListener\('pageshow'/);
|
||||
assert.match(css, /html\.aksrt-ban-modal-open \{ overflow: hidden/);
|
||||
assert.match(css, /html\.jio-ban-modal-open \{ overflow: hidden/);
|
||||
});
|
||||
@@ -2,16 +2,16 @@
|
||||
/**
|
||||
* Exercise the native ban queue, detail templates, and post handlers with WordPress fixtures.
|
||||
*
|
||||
* @package AKSRT
|
||||
* @package Jio
|
||||
*/
|
||||
|
||||
namespace AKSRT\Core {
|
||||
namespace Jio\Core {
|
||||
final class DBMigrator {
|
||||
public static function table(string $name): string { return 'wp_aksrt_' . $name; }
|
||||
public static function table(string $name): string { return 'wp_jio_' . $name; }
|
||||
}
|
||||
}
|
||||
|
||||
namespace AKSRT\User {
|
||||
namespace Jio\User {
|
||||
final class Restriction {
|
||||
public const TYPE_WARN = 'warn';
|
||||
public const TYPE_MUTE = 'mute';
|
||||
@@ -25,27 +25,27 @@ namespace AKSRT\User {
|
||||
}
|
||||
|
||||
final class BanAppeal {
|
||||
public const META_STATUS = '_aksrt_ban_appeal_status';
|
||||
public const META_PAYLOAD = '_aksrt_ban_appeal_payload';
|
||||
public const META_HISTORY = '_aksrt_ban_appeal_history';
|
||||
public const META_STATUS = '_jio_ban_appeal_status';
|
||||
public const META_PAYLOAD = '_jio_ban_appeal_payload';
|
||||
public const META_HISTORY = '_jio_ban_appeal_history';
|
||||
public static function get_status(int $user_id): string {
|
||||
return (string) ($GLOBALS['aksrt_test_user_meta'][$user_id][self::META_STATUS] ?? '');
|
||||
return (string) ($GLOBALS['jio_test_user_meta'][$user_id][self::META_STATUS] ?? '');
|
||||
}
|
||||
public static function get_payload(int $user_id): array {
|
||||
$value = $GLOBALS['aksrt_test_user_meta'][$user_id][self::META_PAYLOAD] ?? [];
|
||||
$value = $GLOBALS['jio_test_user_meta'][$user_id][self::META_PAYLOAD] ?? [];
|
||||
return is_array($value) ? $value : [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace AKSRT\Messages {
|
||||
namespace Jio\Messages {
|
||||
final class ReportCenter {
|
||||
private static ?self $instance = null;
|
||||
public static function get_instance(): self { return self::$instance ??= new self(); }
|
||||
public function update_status(int $id, string $status, string $note): bool {
|
||||
$GLOBALS['aksrt_test_reports'][$id]['status'] = $status;
|
||||
$GLOBALS['aksrt_test_reports'][$id]['updated_at'] = '2026-10-30 13:00:00';
|
||||
$GLOBALS['aksrt_test_report_actions'][] = [$id, $status, $note];
|
||||
$GLOBALS['jio_test_reports'][$id]['status'] = $status;
|
||||
$GLOBALS['jio_test_reports'][$id]['updated_at'] = '2026-10-30 13:00:00';
|
||||
$GLOBALS['jio_test_report_actions'][] = [$id, $status, $note];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -54,15 +54,15 @@ namespace AKSRT\Messages {
|
||||
private static ?self $instance = null;
|
||||
public static function get_instance(): self { return self::$instance ??= new self(); }
|
||||
public function send(int $user_id, string $type, string $message, array $args = []): void {
|
||||
$GLOBALS['aksrt_test_messages'][] = [$user_id, $type, $message, $args];
|
||||
$GLOBALS['jio_test_messages'][] = [$user_id, $type, $message, $args];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace AKSRT\Admin\Native {
|
||||
namespace Jio\Admin\Native {
|
||||
final class AdminListPagination {
|
||||
public static function render(string $slug, int $total, int $page, int $per_page, array $filters = [], string $parent = ''): void {
|
||||
$GLOBALS['aksrt_test_pagination'][] = [$slug, $total, $page, $per_page, $filters, $parent];
|
||||
$GLOBALS['jio_test_pagination'][] = [$slug, $total, $page, $per_page, $filters, $parent];
|
||||
echo '<nav data-test-pagination="' . esc_attr((string) $total) . ':' . esc_attr((string) $page) . '"></nav>';
|
||||
}
|
||||
}
|
||||
@@ -70,8 +70,8 @@ namespace AKSRT\Admin\Native {
|
||||
|
||||
namespace {
|
||||
define('ABSPATH', dirname(__DIR__, 2) . DIRECTORY_SEPARATOR);
|
||||
define('AKSRT_DIR', dirname(__DIR__, 2) . DIRECTORY_SEPARATOR);
|
||||
define('AKSRT_URI', 'https://theme.example.test');
|
||||
define('JIO_DIR', dirname(__DIR__, 2) . DIRECTORY_SEPARATOR);
|
||||
define('JIO_URI', 'https://theme.example.test');
|
||||
define('ARRAY_A', 'ARRAY_A');
|
||||
|
||||
class WP_User {
|
||||
@@ -98,13 +98,13 @@ namespace {
|
||||
if (str_contains($query, 'notes_json')) {
|
||||
return array_map(static fn (int $id): array => [
|
||||
'id' => $id,
|
||||
'notes_json' => $GLOBALS['aksrt_test_reports'][$id]['notes_json'] ?? '[]',
|
||||
'notes_json' => $GLOBALS['jio_test_reports'][$id]['notes_json'] ?? '[]',
|
||||
], $this->args);
|
||||
}
|
||||
if (str_contains($query, 'aksrt_user_restriction')) {
|
||||
return $GLOBALS['aksrt_test_restrictions'];
|
||||
if (str_contains($query, 'jio_user_restriction')) {
|
||||
return $GLOBALS['jio_test_restrictions'];
|
||||
}
|
||||
$reports = array_values($GLOBALS['aksrt_test_reports']);
|
||||
$reports = array_values($GLOBALS['jio_test_reports']);
|
||||
$search = '';
|
||||
$send_user = 0;
|
||||
foreach ($this->args as $arg) {
|
||||
@@ -119,14 +119,14 @@ namespace {
|
||||
}
|
||||
public function get_row(string $query, $output = null) {
|
||||
$id = (int) ($this->args[0] ?? 0);
|
||||
return $GLOBALS['aksrt_test_reports'][$id] ?? null;
|
||||
return $GLOBALS['jio_test_reports'][$id] ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
$GLOBALS['wpdb'] = new TestWpdb();
|
||||
$GLOBALS['aksrt_test_reports'] = [];
|
||||
$GLOBALS['aksrt_test_user_meta'] = [];
|
||||
$GLOBALS['aksrt_test_restrictions'] = [[
|
||||
$GLOBALS['jio_test_reports'] = [];
|
||||
$GLOBALS['jio_test_user_meta'] = [];
|
||||
$GLOBALS['jio_test_restrictions'] = [[
|
||||
'user_id' => 7,
|
||||
'type' => 'ban',
|
||||
'scope' => 'site',
|
||||
@@ -134,12 +134,12 @@ namespace {
|
||||
'created_at' => '2026-10-01 08:00:00',
|
||||
'expires_at' => null,
|
||||
]];
|
||||
$GLOBALS['aksrt_test_assets'] = ['styles' => [], 'scripts' => []];
|
||||
$GLOBALS['aksrt_test_pagination'] = [];
|
||||
$GLOBALS['aksrt_test_report_actions'] = [];
|
||||
$GLOBALS['aksrt_test_messages'] = [];
|
||||
$GLOBALS['aksrt_test_nonce_checks'] = [];
|
||||
$GLOBALS['aksrt_test_capability'] = true;
|
||||
$GLOBALS['jio_test_assets'] = ['styles' => [], 'scripts' => []];
|
||||
$GLOBALS['jio_test_pagination'] = [];
|
||||
$GLOBALS['jio_test_report_actions'] = [];
|
||||
$GLOBALS['jio_test_messages'] = [];
|
||||
$GLOBALS['jio_test_nonce_checks'] = [];
|
||||
$GLOBALS['jio_test_capability'] = true;
|
||||
|
||||
function __($text, $domain = null): string { return (string) $text; }
|
||||
function esc_html__($text, $domain = null): string { return esc_html((string) $text); }
|
||||
@@ -174,28 +174,28 @@ namespace {
|
||||
$prefix .= $parts['path'] ?? '';
|
||||
return $prefix . '?' . http_build_query($query);
|
||||
}
|
||||
function current_user_can(string $capability): bool { return $capability === 'manage_options' && $GLOBALS['aksrt_test_capability']; }
|
||||
function current_user_can(string $capability): bool { return $capability === 'manage_options' && $GLOBALS['jio_test_capability']; }
|
||||
function wp_die($message): void { throw new \RuntimeException((string) $message); }
|
||||
function wp_nonce_field(string $action): void { echo '<input type="hidden" name="_wpnonce" value="' . esc_attr($action . '-nonce') . '">'; }
|
||||
function check_admin_referer(string $action): void {
|
||||
$GLOBALS['aksrt_test_nonce_checks'][] = $action;
|
||||
$GLOBALS['jio_test_nonce_checks'][] = $action;
|
||||
if (($_POST['_wpnonce'] ?? '') !== $action . '-nonce') throw new \RuntimeException('nonce mismatch');
|
||||
}
|
||||
function wp_enqueue_style(string $handle, string $src, array $deps = [], string $version = ''): void {
|
||||
$GLOBALS['aksrt_test_assets']['styles'][] = [$handle, $src, $deps, $version];
|
||||
$GLOBALS['jio_test_assets']['styles'][] = [$handle, $src, $deps, $version];
|
||||
}
|
||||
function wp_enqueue_script(string $handle, string $src, array $deps = [], string $version = '', bool $footer = false): void {
|
||||
$GLOBALS['aksrt_test_assets']['scripts'][] = [$handle, $src, $deps, $version, $footer];
|
||||
$GLOBALS['jio_test_assets']['scripts'][] = [$handle, $src, $deps, $version, $footer];
|
||||
}
|
||||
function get_userdata(int $user_id) { return $user_id > 0 ? new WP_User($user_id, $user_id === 7 ? 'Report <script>alert(1)</script>' : 'User ' . $user_id) : false; }
|
||||
function get_current_user_id(): int { return 999; }
|
||||
function get_user_meta(int $user_id, string $key, bool $single = false) { return $GLOBALS['aksrt_test_user_meta'][$user_id][$key] ?? ''; }
|
||||
function update_user_meta(int $user_id, string $key, $value): bool { $GLOBALS['aksrt_test_user_meta'][$user_id][$key] = $value; return true; }
|
||||
function delete_user_meta(int $user_id, string $key): bool { unset($GLOBALS['aksrt_test_user_meta'][$user_id][$key]); return true; }
|
||||
function get_user_meta(int $user_id, string $key, bool $single = false) { return $GLOBALS['jio_test_user_meta'][$user_id][$key] ?? ''; }
|
||||
function update_user_meta(int $user_id, string $key, $value): bool { $GLOBALS['jio_test_user_meta'][$user_id][$key] = $value; return true; }
|
||||
function delete_user_meta(int $user_id, string $key): bool { unset($GLOBALS['jio_test_user_meta'][$user_id][$key]); return true; }
|
||||
function get_users(array $args = []): array {
|
||||
$ids = array_keys($GLOBALS['aksrt_test_user_meta']);
|
||||
$ids = array_keys($GLOBALS['jio_test_user_meta']);
|
||||
if (isset($args['include'])) $ids = array_values(array_intersect($ids, $args['include']));
|
||||
$ids = array_values(array_filter($ids, static fn (int $id): bool => \AKSRT\User\BanAppeal::get_status($id) !== ''));
|
||||
$ids = array_values(array_filter($ids, static fn (int $id): bool => \Jio\User\BanAppeal::get_status($id) !== ''));
|
||||
return $args['fields'] === 'ID' ? $ids : array_map('get_userdata', $ids);
|
||||
}
|
||||
function update_meta_cache(string $type, array $ids): void { unset($type, $ids); }
|
||||
@@ -213,7 +213,7 @@ namespace {
|
||||
function get_comment(int $id) { return false; }
|
||||
function get_comment_link(int $id): string { return 'https://content.example.test/comment/' . $id; }
|
||||
function get_author_posts_url(int $user_id): string { return 'https://content.example.test/author/' . $user_id; }
|
||||
function aksrt_get_user_home_url(int $user_id): string { return 'https://content.example.test/user/' . $user_id; }
|
||||
function jio_get_user_home_url(int $user_id): string { return 'https://content.example.test/user/' . $user_id; }
|
||||
function do_action(string $hook, ...$args): void { unset($hook, $args); }
|
||||
|
||||
function fixture_report(int $id, int $reporter, string $status, string $time, string $reason = ''): array {
|
||||
@@ -238,17 +238,17 @@ namespace {
|
||||
? 'Evidence <script>alert(1)</script> https://evidence.example.test/proof.jpg javascript:alert(2)'
|
||||
: 'report reason ' . $id;
|
||||
$time = $id === 1 ? '2026-10-30 12:00:00' : ($id === 2 ? '2026-10-29 11:00:00' : $time);
|
||||
$GLOBALS['aksrt_test_reports'][$id] = fixture_report($id, $id === 1 ? 7 : 20 + $id, $status, $time, $reason);
|
||||
$GLOBALS['jio_test_reports'][$id] = fixture_report($id, $id === 1 ? 7 : 20 + $id, $status, $time, $reason);
|
||||
}
|
||||
$GLOBALS['aksrt_test_reports'][1]['category'] = '版权投诉';
|
||||
$GLOBALS['aksrt_test_reports'][2]['category'] = '人身攻击';
|
||||
$GLOBALS['aksrt_test_reports'][2]['reason'] = '';
|
||||
$GLOBALS['aksrt_test_reports'][9001] = fixture_report(9001, 7, 'pending', '2026-09-01 09:00:00', 'needle action report');
|
||||
$GLOBALS['aksrt_test_reports'][9002] = fixture_report(9002, 7, 'pending', '2026-09-02 09:00:00', 'needle stale report');
|
||||
$GLOBALS['jio_test_reports'][1]['category'] = '版权投诉';
|
||||
$GLOBALS['jio_test_reports'][2]['category'] = '人身攻击';
|
||||
$GLOBALS['jio_test_reports'][2]['reason'] = '';
|
||||
$GLOBALS['jio_test_reports'][9001] = fixture_report(9001, 7, 'pending', '2026-09-01 09:00:00', 'needle action report');
|
||||
$GLOBALS['jio_test_reports'][9002] = fixture_report(9002, 7, 'pending', '2026-09-02 09:00:00', 'needle stale report');
|
||||
|
||||
function fixture_appeal(int $user_id, string $status, string $time, array $payload = []): void {
|
||||
$GLOBALS['aksrt_test_user_meta'][$user_id][\AKSRT\User\BanAppeal::META_STATUS] = $status;
|
||||
$GLOBALS['aksrt_test_user_meta'][$user_id][\AKSRT\User\BanAppeal::META_PAYLOAD] = array_merge([
|
||||
$GLOBALS['jio_test_user_meta'][$user_id][\Jio\User\BanAppeal::META_STATUS] = $status;
|
||||
$GLOBALS['jio_test_user_meta'][$user_id][\Jio\User\BanAppeal::META_PAYLOAD] = array_merge([
|
||||
'time' => $time,
|
||||
'reason_key' => 'ban reason',
|
||||
'reason' => 'appeal explanation',
|
||||
@@ -270,14 +270,14 @@ namespace {
|
||||
fixture_appeal(7001, 'pending', '2026-09-03 09:00:00', ['reason' => 'pending appeal action']);
|
||||
fixture_appeal(7002, 'pending', '2026-09-04 09:00:00', ['reason' => 'pending appeal approve']);
|
||||
|
||||
require_once AKSRT_DIR . 'src/Admin/Native/NativeUi.php';
|
||||
require_once AKSRT_DIR . 'src/Admin/Native/UsersMenusBanQuery.php';
|
||||
require_once AKSRT_DIR . 'src/Admin/Native/UsersMenusBanDialogs.php';
|
||||
require_once AKSRT_DIR . 'src/Admin/Native/UsersMenusBanActions.php';
|
||||
require_once AKSRT_DIR . 'src/Admin/Native/UsersMenusBan.php';
|
||||
require_once JIO_DIR . 'src/Admin/Native/NativeUi.php';
|
||||
require_once JIO_DIR . 'src/Admin/Native/UsersMenusBanQuery.php';
|
||||
require_once JIO_DIR . 'src/Admin/Native/UsersMenusBanDialogs.php';
|
||||
require_once JIO_DIR . 'src/Admin/Native/UsersMenusBanActions.php';
|
||||
require_once JIO_DIR . 'src/Admin/Native/UsersMenusBan.php';
|
||||
|
||||
final class BanFixtureMenu {
|
||||
use \AKSRT\Admin\Native\UsersMenusBan;
|
||||
use \Jio\Admin\Native\UsersMenusBan;
|
||||
public const CAPABILITY = 'manage_options';
|
||||
public function ensure_cap(): void {}
|
||||
public function flash(): void {}
|
||||
@@ -324,15 +324,15 @@ namespace {
|
||||
'type' => 'report', 'status' => '', 's' => 'needle', 'send_user' => '7',
|
||||
'orderby' => 'updated', 'order' => 'ASC',
|
||||
]);
|
||||
$html = render_ban_page(['page' => 'aksrt_user_ban', 'status' => '', 'orderby' => 'time', 'order' => 'DESC']);
|
||||
$html = render_ban_page(['page' => 'jio_user_ban', 'status' => '', 'orderby' => 'time', 'order' => 'DESC']);
|
||||
$filteredHtml = render_ban_page([
|
||||
'page' => 'aksrt_user_ban', 'type' => 'report', 'status' => 'pending', 's' => 'needle',
|
||||
'page' => 'jio_user_ban', 'type' => 'report', 'status' => 'pending', 's' => 'needle',
|
||||
'send_user' => '7', 'orderby' => 'updated', 'order' => 'ASC', 'paged' => '1',
|
||||
]);
|
||||
|
||||
$GLOBALS['aksrt_test_assets'] = ['styles' => [], 'scripts' => []];
|
||||
$GLOBALS['jio_test_assets'] = ['styles' => [], 'scripts' => []];
|
||||
$assetMenu = new BanFixtureMenu();
|
||||
$assetMenu->enqueue_ban_assets('users_page_aksrt_user_ban');
|
||||
$assetMenu->enqueue_ban_assets('users_page_jio_user_ban');
|
||||
$assetMenu->enqueue_ban_assets('users_page_other');
|
||||
|
||||
$menu = new BanFixtureMenu();
|
||||
@@ -343,9 +343,9 @@ namespace {
|
||||
'filter_type' => 'report', 'filter_status' => 'pending', 's' => 'needle', 'send_user' => '7',
|
||||
'orderby' => 'updated', 'order' => 'ASC', 'paged' => '2',
|
||||
];
|
||||
$blankReport = call_action($menu, 'handle_update_report', 'aksrt_users_update_report_9001', array_merge($reportBase, ['new_status' => 'dismissed', 'note' => ' ']));
|
||||
$staleReport = call_action($menu, 'handle_update_report', 'aksrt_users_update_report_9002', array_merge($reportBase, ['report_id' => '9002', 'record_version' => 'stale-version']));
|
||||
$validReport = call_action($menu, 'handle_update_report', 'aksrt_users_update_report_9001', $reportBase);
|
||||
$blankReport = call_action($menu, 'handle_update_report', 'jio_users_update_report_9001', array_merge($reportBase, ['new_status' => 'dismissed', 'note' => ' ']));
|
||||
$staleReport = call_action($menu, 'handle_update_report', 'jio_users_update_report_9002', array_merge($reportBase, ['report_id' => '9002', 'record_version' => 'stale-version']));
|
||||
$validReport = call_action($menu, 'handle_update_report', 'jio_users_update_report_9001', $reportBase);
|
||||
|
||||
$appealRow = $menu->query_record('appeal', 7001);
|
||||
$appealVersion = $menu->version($appealRow);
|
||||
@@ -354,15 +354,15 @@ namespace {
|
||||
'filter_type' => 'appeal', 'filter_status' => 'pending', 's' => 'needle', 'send_user' => '7001',
|
||||
'orderby' => 'updated', 'order' => 'ASC', 'paged' => '3',
|
||||
];
|
||||
$blankAppeal = call_action($menu, 'handle_process_appeal', 'aksrt_users_process_appeal_7001', array_merge($appealBase, ['note' => ' ']));
|
||||
$validAppeal = call_action($menu, 'handle_process_appeal', 'aksrt_users_process_appeal_7001', $appealBase);
|
||||
$blankAppeal = call_action($menu, 'handle_process_appeal', 'jio_users_process_appeal_7001', array_merge($appealBase, ['note' => ' ']));
|
||||
$validAppeal = call_action($menu, 'handle_process_appeal', 'jio_users_process_appeal_7001', $appealBase);
|
||||
|
||||
$approveRow = $menu->query_record('appeal', 7002);
|
||||
$approve = call_action($menu, 'handle_process_appeal', 'aksrt_users_process_appeal_7002', [
|
||||
$approve = call_action($menu, 'handle_process_appeal', 'jio_users_process_appeal_7002', [
|
||||
'user_id' => '7002', 'decision' => 'approve', 'note' => '', 'record_version' => $menu->version($approveRow),
|
||||
]);
|
||||
|
||||
$filtersHtml = render_ban_page(['page' => 'aksrt_user_ban', 'type' => 'report', 'status' => 'pending', 's' => 'needle', 'send_user' => '7', 'orderby' => 'updated', 'order' => 'ASC']);
|
||||
$filtersHtml = render_ban_page(['page' => 'jio_user_ban', 'type' => 'report', 'status' => 'pending', 's' => 'needle', 'send_user' => '7', 'orderby' => 'updated', 'order' => 'ASC']);
|
||||
|
||||
echo json_encode([
|
||||
'queue' => [
|
||||
@@ -372,15 +372,15 @@ namespace {
|
||||
'filters' => $filters,
|
||||
'html' => $html,
|
||||
'filtered_html' => $filteredHtml,
|
||||
'assets' => $GLOBALS['aksrt_test_assets'],
|
||||
'assets' => $GLOBALS['jio_test_assets'],
|
||||
'actions' => [
|
||||
'blank_report' => $blankReport, 'stale_report' => $staleReport, 'valid_report' => $validReport,
|
||||
'blank_appeal' => $blankAppeal, 'valid_appeal' => $validAppeal, 'approve_appeal' => $approve,
|
||||
'report_calls' => $GLOBALS['aksrt_test_report_actions'], 'messages' => $GLOBALS['aksrt_test_messages'],
|
||||
'appeal_status' => \AKSRT\User\BanAppeal::get_status(7001),
|
||||
'appeal_payload' => \AKSRT\User\BanAppeal::get_payload(7001),
|
||||
'approved_status' => \AKSRT\User\BanAppeal::get_status(7002),
|
||||
'restrictions_lifted' => \AKSRT\User\Restriction::$lifted,
|
||||
'report_calls' => $GLOBALS['jio_test_report_actions'], 'messages' => $GLOBALS['jio_test_messages'],
|
||||
'appeal_status' => \Jio\User\BanAppeal::get_status(7001),
|
||||
'appeal_payload' => \Jio\User\BanAppeal::get_payload(7001),
|
||||
'approved_status' => \Jio\User\BanAppeal::get_status(7002),
|
||||
'restrictions_lifted' => \Jio\User\Restriction::$lifted,
|
||||
],
|
||||
], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);
|
||||
}
|
||||
@@ -65,7 +65,7 @@ function runtime({ completed = false } = {}) {
|
||||
}
|
||||
map(selector, elements) { this.queries.set(selector, elements); return this; }
|
||||
closest(selector) {
|
||||
if (selector === '[data-aksrt-dialog-close]' && this.attrs['data-aksrt-dialog-close'] !== undefined) return this;
|
||||
if (selector === '[data-jio-dialog-close]' && this.attrs['data-jio-dialog-close'] !== undefined) return this;
|
||||
return null;
|
||||
}
|
||||
focus() { doc.activeElement = this; }
|
||||
@@ -76,11 +76,11 @@ function runtime({ completed = false } = {}) {
|
||||
|
||||
function fragment({ readOnly = false, initiallyChecked = false } = {}) {
|
||||
const part = new Element();
|
||||
const title = new Element({ id: 'aksrt-ban-dialog-title', autofocus: '' });
|
||||
const closeIcon = new Element({ 'data-aksrt-dialog-close': '' });
|
||||
const closeFooter = new Element({ 'data-aksrt-dialog-close': '' });
|
||||
part.map('#aksrt-ban-dialog-title', title)
|
||||
.map('[data-aksrt-dialog-close]', [closeIcon, closeFooter]);
|
||||
const title = new Element({ id: 'jio-ban-dialog-title', autofocus: '' });
|
||||
const closeIcon = new Element({ 'data-jio-dialog-close': '' });
|
||||
const closeFooter = new Element({ 'data-jio-dialog-close': '' });
|
||||
part.map('#jio-ban-dialog-title', title)
|
||||
.map('[data-jio-dialog-close]', [closeIcon, closeFooter]);
|
||||
const controls = [closeIcon, closeFooter];
|
||||
let form = null;
|
||||
let radios = [];
|
||||
@@ -92,31 +92,31 @@ function runtime({ completed = false } = {}) {
|
||||
new Element({ type: 'radio', value: 'reject', required: '', checked: initiallyChecked }),
|
||||
];
|
||||
note = new Element({ name: 'note', value: 'old note', 'data-required-error': 'Please enter a nonblank rejection reason' });
|
||||
const label = new Element({ 'data-aksrt-ban-note-label': '', 'data-optional': 'Optional note', 'data-required': 'Reject reason required' });
|
||||
submit = new Element({ 'data-aksrt-ban-submit': '', 'data-idle-label': 'Choose decision', 'data-approve-label': 'Confirm approval', 'data-reject-label': 'Confirm rejection', 'data-busy-label': 'Submitting' });
|
||||
const label = new Element({ 'data-jio-ban-note-label': '', 'data-optional': 'Optional note', 'data-required': 'Reject reason required' });
|
||||
submit = new Element({ 'data-jio-ban-submit': '', 'data-idle-label': 'Choose decision', 'data-approve-label': 'Confirm approval', 'data-reject-label': 'Confirm rejection', 'data-busy-label': 'Submitting' });
|
||||
controls.push(submit);
|
||||
form = new Element();
|
||||
form.dataset = {};
|
||||
form.elements = { radios, note };
|
||||
form.map('input[type="radio"]', radios)
|
||||
.map('textarea[name="note"]', note)
|
||||
.map('[data-aksrt-ban-note-label]', label)
|
||||
.map('[data-aksrt-ban-submit]', submit);
|
||||
.map('[data-jio-ban-note-label]', label)
|
||||
.map('[data-jio-ban-submit]', submit);
|
||||
form.reportValidity = () => {
|
||||
const hasRequiredRadio = radios.some((radio) => radio.required);
|
||||
const radioValid = !hasRequiredRadio || radios.some((radio) => radio.checked);
|
||||
return radioValid && !note.validity && (!note.required || note.value.trim() !== '');
|
||||
};
|
||||
part.map('form[data-aksrt-ban-review]', form);
|
||||
part.map('form[data-jio-ban-review]', form);
|
||||
}
|
||||
part.map('[data-aksrt-ban-submit], [data-aksrt-dialog-close]', controls);
|
||||
part.map('[data-jio-ban-submit], [data-jio-dialog-close]', controls);
|
||||
return { part, form, radios, note, submit, title, closeIcon, closeFooter };
|
||||
}
|
||||
|
||||
const root = new Element();
|
||||
const window = new Element();
|
||||
doc.documentElement = new Element();
|
||||
doc.querySelector = (selector) => selector === '.aksrt-ban-page' ? root : null;
|
||||
doc.querySelector = (selector) => selector === '.jio-ban-page' ? root : null;
|
||||
doc.getElementById = (id) => ids.get(id) || null;
|
||||
const dialog = new Element();
|
||||
const originalDialogQuery = dialog.querySelector.bind(dialog);
|
||||
@@ -125,7 +125,7 @@ function runtime({ completed = false } = {}) {
|
||||
dialog.querySelectorAll = (selector) => dialog.currentContent ? dialog.currentContent.querySelectorAll(selector) : originalDialogQueryAll(selector);
|
||||
dialog.showModal = () => {
|
||||
dialog.open = true;
|
||||
const title = dialog.querySelector('#aksrt-ban-dialog-title');
|
||||
const title = dialog.querySelector('#jio-ban-dialog-title');
|
||||
if (title) title.focus();
|
||||
};
|
||||
dialog.close = () => {
|
||||
@@ -133,20 +133,20 @@ function runtime({ completed = false } = {}) {
|
||||
dialog.open = false;
|
||||
dialog.fire('close');
|
||||
};
|
||||
ids.set('aksrt-ban-dialog', dialog);
|
||||
ids.set('jio-ban-dialog', dialog);
|
||||
|
||||
const opener = new Element({ 'data-aksrt-ban-view': 'report-1' });
|
||||
const readonlyOpener = new Element({ 'data-aksrt-ban-view': 'report-2' });
|
||||
const opener = new Element({ 'data-jio-ban-view': 'report-1' });
|
||||
const readonlyOpener = new Element({ 'data-jio-ban-view': 'report-2' });
|
||||
const templates = new Map();
|
||||
templates.set('aksrt-ban-detail-report-1', {
|
||||
templates.set('jio-ban-detail-report-1', {
|
||||
content: { cloneNode: () => fragment().part },
|
||||
});
|
||||
templates.set('aksrt-ban-detail-report-2', {
|
||||
templates.set('jio-ban-detail-report-2', {
|
||||
content: { cloneNode: () => fragment({ readOnly: true }).part },
|
||||
});
|
||||
ids.set('aksrt-ban-detail-report-1', templates.get('aksrt-ban-detail-report-1'));
|
||||
ids.set('aksrt-ban-detail-report-2', templates.get('aksrt-ban-detail-report-2'));
|
||||
root.map('[data-aksrt-ban-view]', [opener, readonlyOpener]);
|
||||
ids.set('jio-ban-detail-report-1', templates.get('jio-ban-detail-report-1'));
|
||||
ids.set('jio-ban-detail-report-2', templates.get('jio-ban-detail-report-2'));
|
||||
root.map('[data-jio-ban-view]', [opener, readonlyOpener]);
|
||||
|
||||
vm.runInNewContext(fs.readFileSync(path.join(rootDir, 'assets/js/admin-user-ban.js'), 'utf8'), { document: doc, window });
|
||||
return { doc, window, root, dialog, opener, readonlyOpener };
|
||||
@@ -155,15 +155,15 @@ function runtime({ completed = false } = {}) {
|
||||
function openReview(runtimeState, button = runtimeState.opener) {
|
||||
button.fire('click');
|
||||
const frame = runtimeState.dialog.currentContent;
|
||||
const form = frame.querySelector('form[data-aksrt-ban-review]');
|
||||
const form = frame.querySelector('form[data-jio-ban-review]');
|
||||
return {
|
||||
frame,
|
||||
form,
|
||||
radios: form.querySelectorAll('input[type="radio"]'),
|
||||
note: form.querySelector('textarea[name="note"]'),
|
||||
label: form.querySelector('[data-aksrt-ban-note-label]'),
|
||||
submit: form.querySelector('[data-aksrt-ban-submit]'),
|
||||
closeButtons: frame.querySelectorAll('[data-aksrt-ban-submit], [data-aksrt-dialog-close]').filter((button) => button.attrs['data-aksrt-dialog-close'] !== undefined),
|
||||
label: form.querySelector('[data-jio-ban-note-label]'),
|
||||
submit: form.querySelector('[data-jio-ban-submit]'),
|
||||
closeButtons: frame.querySelectorAll('[data-jio-ban-submit], [data-jio-dialog-close]').filter((button) => button.attrs['data-jio-dialog-close'] !== undefined),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -172,12 +172,12 @@ test('opening review clears choices, focuses the dialog, and locks page scrollin
|
||||
const review = openReview(r);
|
||||
|
||||
assert.equal(r.dialog.open, true);
|
||||
assert.equal(r.doc.activeElement, review.frame.querySelector('#aksrt-ban-dialog-title'));
|
||||
assert.equal(r.doc.activeElement, review.frame.querySelector('#jio-ban-dialog-title'));
|
||||
assert.deepEqual(review.radios.map((radio) => radio.checked), [false, false]);
|
||||
assert.equal(review.note.value, '');
|
||||
assert.equal(review.submit.disabled, true);
|
||||
assert.equal(review.label.textContent, 'Optional note');
|
||||
assert.equal(r.doc.documentElement.classList.contains('aksrt-ban-modal-open'), true);
|
||||
assert.equal(r.doc.documentElement.classList.contains('jio-ban-modal-open'), true);
|
||||
});
|
||||
|
||||
test('reject decisions require a nonblank trimmed note before submission', () => {
|
||||
@@ -225,12 +225,12 @@ test('submission blocks duplicate posts and closing until pageshow restores stat
|
||||
assert.equal(review.form.getAttribute('aria-busy'), null);
|
||||
assert.equal(review.submit.disabled, false);
|
||||
assert.ok(review.closeButtons.every((button) => !button.disabled));
|
||||
assert.equal(r.doc.documentElement.classList.contains('aksrt-ban-modal-open'), true);
|
||||
assert.equal(r.doc.documentElement.classList.contains('jio-ban-modal-open'), true);
|
||||
|
||||
r.dialog.fire('click', { target: review.closeButtons[0] });
|
||||
assert.equal(r.dialog.open, false);
|
||||
assert.equal(r.dialog.currentContent, null);
|
||||
assert.equal(r.doc.documentElement.classList.contains('aksrt-ban-modal-open'), false);
|
||||
assert.equal(r.doc.documentElement.classList.contains('jio-ban-modal-open'), false);
|
||||
assert.equal(r.doc.activeElement, r.opener);
|
||||
});
|
||||
|
||||
@@ -239,10 +239,10 @@ test('completed records open read-only without a form or submit controls', () =>
|
||||
r.readonlyOpener.fire('click');
|
||||
|
||||
assert.equal(r.dialog.open, true);
|
||||
assert.equal(r.dialog.querySelector('form[data-aksrt-ban-review]'), null);
|
||||
assert.equal(r.dialog.querySelector('[data-aksrt-ban-submit]'), null);
|
||||
assert.ok(r.dialog.querySelector('[data-aksrt-dialog-close]'));
|
||||
r.dialog.fire('click', { target: r.dialog.querySelector('[data-aksrt-dialog-close]') });
|
||||
assert.equal(r.dialog.querySelector('form[data-jio-ban-review]'), null);
|
||||
assert.equal(r.dialog.querySelector('[data-jio-ban-submit]'), null);
|
||||
assert.ok(r.dialog.querySelector('[data-jio-dialog-close]'));
|
||||
r.dialog.fire('click', { target: r.dialog.querySelector('[data-jio-dialog-close]') });
|
||||
assert.equal(r.dialog.open, false);
|
||||
assert.equal(r.doc.activeElement, r.readonlyOpener);
|
||||
});
|
||||
@@ -256,5 +256,5 @@ test('backdrop closes only when the pointer starts and ends outside the dialog',
|
||||
r.dialog.fire('pointerdown', { clientX: 0, clientY: 0 });
|
||||
r.dialog.fire('click', { clientX: 0, clientY: 0 });
|
||||
assert.equal(r.dialog.open, false);
|
||||
assert.equal(r.doc.documentElement.classList.contains('aksrt-ban-modal-open'), false);
|
||||
assert.equal(r.doc.documentElement.classList.contains('jio-ban-modal-open'), false);
|
||||
});
|
||||
@@ -19,10 +19,10 @@ set_error_handler(static function (int $severity, string $message, string $file,
|
||||
throw new ErrorException($message, 0, $severity, $file, $line);
|
||||
});
|
||||
|
||||
$GLOBALS['aksrt_test_state'] = [];
|
||||
$GLOBALS['jio_test_state'] = [];
|
||||
|
||||
function aksrt_test_reset_state(): void {
|
||||
$GLOBALS['aksrt_test_state'] = [
|
||||
function jio_test_reset_state(): void {
|
||||
$GLOBALS['jio_test_state'] = [
|
||||
'options' => [],
|
||||
'sidebars' => [],
|
||||
'routes' => [],
|
||||
@@ -37,7 +37,7 @@ function aksrt_test_reset_state(): void {
|
||||
$GLOBALS['wp_registered_sidebars'] = [];
|
||||
}
|
||||
|
||||
aksrt_test_reset_state();
|
||||
jio_test_reset_state();
|
||||
|
||||
class WP_Error {
|
||||
public function __construct(private string $code = '', private string $message = '', private array $data = []) {}
|
||||
@@ -81,7 +81,7 @@ class WP_Query {
|
||||
public int $max_num_pages = 1;
|
||||
public int $found_posts = 0;
|
||||
public function __construct(array $args = []) {
|
||||
$GLOBALS['aksrt_test_state']['query_args'][] = $args;
|
||||
$GLOBALS['jio_test_state']['query_args'][] = $args;
|
||||
}
|
||||
public function have_posts(): bool { return false; }
|
||||
public function the_post(): void {}
|
||||
@@ -147,40 +147,40 @@ function wp_trim_words($text, int $count = 55): string {
|
||||
return implode(' ', array_slice($words, 0, $count));
|
||||
}
|
||||
|
||||
function get_option(string $name, $default = false) { return $GLOBALS['aksrt_test_state']['options'][$name] ?? $default; }
|
||||
function update_option(string $name, $value): bool { $GLOBALS['aksrt_test_state']['options'][$name] = $value; return true; }
|
||||
function wp_get_sidebars_widgets(): array { return $GLOBALS['aksrt_test_state']['sidebars']; }
|
||||
function wp_set_sidebars_widgets(array $sidebars): void { $GLOBALS['aksrt_test_state']['sidebars'] = $sidebars; }
|
||||
function get_option(string $name, $default = false) { return $GLOBALS['jio_test_state']['options'][$name] ?? $default; }
|
||||
function update_option(string $name, $value): bool { $GLOBALS['jio_test_state']['options'][$name] = $value; return true; }
|
||||
function wp_get_sidebars_widgets(): array { return $GLOBALS['jio_test_state']['sidebars']; }
|
||||
function wp_set_sidebars_widgets(array $sidebars): void { $GLOBALS['jio_test_state']['sidebars'] = $sidebars; }
|
||||
function register_rest_route(string $namespace, string $route, array $args): bool {
|
||||
$GLOBALS['aksrt_test_state']['routes'][$namespace . $route] = $args;
|
||||
$GLOBALS['jio_test_state']['routes'][$namespace . $route] = $args;
|
||||
return true;
|
||||
}
|
||||
function rest_ensure_response($data): WP_REST_Response { return $data instanceof WP_REST_Response ? $data : new WP_REST_Response($data); }
|
||||
function is_wp_error($value): bool { return $value instanceof WP_Error; }
|
||||
function is_singular(): bool { return (bool) $GLOBALS['aksrt_test_state']['singular']; }
|
||||
function get_queried_object_id(): int { return (int) $GLOBALS['aksrt_test_state']['current_id']; }
|
||||
function is_singular(): bool { return (bool) $GLOBALS['jio_test_state']['singular']; }
|
||||
function get_queried_object_id(): int { return (int) $GLOBALS['jio_test_state']['current_id']; }
|
||||
function get_post($post = null) {
|
||||
if ($post instanceof WP_Post) return $post;
|
||||
return $GLOBALS['aksrt_test_state']['current_post'];
|
||||
return $GLOBALS['jio_test_state']['current_post'];
|
||||
}
|
||||
function aksrt_get_widget_null(string $message): string { return '<div class="aksrt-widget__empty" role="status">' . esc_html($message) . '</div>'; }
|
||||
function aksrt_get_pay_meta($post = null): array { unset($post); return $GLOBALS['aksrt_test_state']['pay_meta']; }
|
||||
function aksrt_has_paid_access($post = null): bool { unset($post); return (bool) $GLOBALS['aksrt_test_state']['paid_access']; }
|
||||
function aksrt_get_paywall_html(WP_Post $post, array|bool $options = false): string {
|
||||
function jio_get_widget_null(string $message): string { return '<div class="jio-widget__empty" role="status">' . esc_html($message) . '</div>'; }
|
||||
function jio_get_pay_meta($post = null): array { unset($post); return $GLOBALS['jio_test_state']['pay_meta']; }
|
||||
function jio_has_paid_access($post = null): bool { unset($post); return (bool) $GLOBALS['jio_test_state']['paid_access']; }
|
||||
function jio_get_paywall_html(WP_Post $post, array|bool $options = false): string {
|
||||
unset($post, $options);
|
||||
$meta = $GLOBALS['aksrt_test_state']['pay_meta'];
|
||||
$meta = $GLOBALS['jio_test_state']['pay_meta'];
|
||||
if ((string) ($meta['pay_type'] ?? 'no') === 'no') {
|
||||
return '';
|
||||
}
|
||||
if ((bool) $GLOBALS['aksrt_test_state']['paid_access']) {
|
||||
if ((bool) $GLOBALS['jio_test_state']['paid_access']) {
|
||||
return '<div data-test-paywall="unlocked">内容已解锁</div>';
|
||||
}
|
||||
$state = $GLOBALS['aksrt_test_state']['logged_in'] ? 'member-locked' : 'guest-locked';
|
||||
$state = $GLOBALS['jio_test_state']['logged_in'] ? 'member-locked' : 'guest-locked';
|
||||
return '<div data-test-paywall="' . $state . '">PAYWALL</div>';
|
||||
}
|
||||
function aksrt_get_pay_attributes_html($post, string $class): string { unset($post); return '<ul class="' . esc_attr($class) . '"></ul>'; }
|
||||
function aksrt_get_demo_link_html($post, string $class): string { unset($post); return '<a class="' . esc_attr($class) . '">DEMO</a>'; }
|
||||
function is_user_logged_in(): bool { return (bool) $GLOBALS['aksrt_test_state']['logged_in']; }
|
||||
function jio_get_pay_attributes_html($post, string $class): string { unset($post); return '<ul class="' . esc_attr($class) . '"></ul>'; }
|
||||
function jio_get_demo_link_html($post, string $class): string { unset($post); return '<a class="' . esc_attr($class) . '">DEMO</a>'; }
|
||||
function is_user_logged_in(): bool { return (bool) $GLOBALS['jio_test_state']['logged_in']; }
|
||||
function wp_parse_args($args, array $defaults = []): array {
|
||||
return array_merge($defaults, is_array($args) ? $args : []);
|
||||
}
|
||||
@@ -191,20 +191,20 @@ function wp_kses(string $value, array $allowed_html = [], array $allowed_protoco
|
||||
// 桩环境信任来源:生产侧的 wp_kses 白名单允许 div/badge 等组合卡标记。
|
||||
return (string) $value;
|
||||
}
|
||||
function aksrt_is_lazy(string $key, bool $default = false): bool { unset($key, $default); return true; }
|
||||
function aksrt_get_lazy_thumb(string $size = ''): string {
|
||||
function jio_is_lazy(string $key, bool $default = false): bool { unset($key, $default); return true; }
|
||||
function jio_get_lazy_thumb(string $size = ''): string {
|
||||
return 'https://example.test/thumbnail' . ($size !== '' ? '-' . sanitize_key($size) : '') . '.svg';
|
||||
}
|
||||
function aksrt_get_lazy_attr(string $key, string $src, string $class = '', string $lazy_src = ''): string {
|
||||
function jio_get_lazy_attr(string $key, string $src, string $class = '', string $lazy_src = ''): string {
|
||||
unset($key);
|
||||
$class = trim($class);
|
||||
if ($src === '') {
|
||||
return ' class="' . esc_attr($class) . '" src=""';
|
||||
}
|
||||
$lazy_src = $lazy_src !== '' ? $lazy_src : aksrt_get_lazy_thumb();
|
||||
$lazy_src = $lazy_src !== '' ? $lazy_src : jio_get_lazy_thumb();
|
||||
return ' class="lazyload ' . esc_attr($class) . '" src="' . esc_url($lazy_src) . '" data-src="' . esc_url($src) . '"';
|
||||
}
|
||||
|
||||
require dirname(__DIR__, 2) . '/inc/helpers/aksrt-icons.php';
|
||||
require dirname(__DIR__, 2) . '/inc/helpers/aksrt-graphic-card.php';
|
||||
require dirname(__DIR__, 2) . '/inc/helpers/jio-icons.php';
|
||||
require dirname(__DIR__, 2) . '/inc/helpers/jio-graphic-card.php';
|
||||
require dirname(__DIR__, 2) . '/vendor/autoload.php';
|
||||
@@ -12,11 +12,11 @@ test('comment REST admission is request-local and preprocess keeps native captch
|
||||
const controller = read('src/Core/CommentActions.php');
|
||||
const moderation = read('src/Auth/Moderation.php');
|
||||
|
||||
assert.match(controller, /aksrt_require_captcha\('comment', \$params\)/);
|
||||
assert.match(controller, /jio_require_captcha\('comment', \$params\)/);
|
||||
assert.match(controller, /begin_captcha_verified_comment_request\(\)[\s\S]*?try \{[\s\S]*?wp_new_comment[\s\S]*?finally \{[\s\S]*?end_captcha_verified_comment_request\(\)/);
|
||||
assert.match(moderation, /private \$captcha_verified_comment_request_depth = 0/);
|
||||
assert.match(moderation, /if \(\$this->captcha_verified_comment_request_depth === 0\) \{/);
|
||||
assert.doesNotMatch(controller + moderation, /function_exists\('aksrt_require_captcha'\)/);
|
||||
assert.doesNotMatch(controller + moderation, /function_exists\('jio_require_captcha'\)/);
|
||||
assert.doesNotMatch(controller + moderation, /\$_(?:POST|REQUEST)\[[^\]]*captcha[^\]]*verified|set_transient\([^\n]*comment/i);
|
||||
});
|
||||
|
||||
@@ -42,22 +42,22 @@ test('password and email account flows verify captcha once per distinct HTTP req
|
||||
return account.slice(start, end === -1 ? account.length : end);
|
||||
};
|
||||
|
||||
const send = methodBody('wp_ajax_aksrt_send_account_email_code', 'wp_ajax_aksrt_verify_account_email');
|
||||
const password = methodBody('wp_ajax_aksrt_change_password', 'wp_ajax_aksrt_change_email');
|
||||
const email = methodBody('wp_ajax_aksrt_change_email');
|
||||
const send = methodBody('wp_ajax_jio_send_account_email_code', 'wp_ajax_jio_verify_account_email');
|
||||
const password = methodBody('wp_ajax_jio_change_password', 'wp_ajax_jio_change_email');
|
||||
const email = methodBody('wp_ajax_jio_change_email');
|
||||
|
||||
assert.equal((send.match(/aksrt_require_captcha\(/g) || []).length, 1);
|
||||
assert.match(send, /aksrt_require_captcha\(\s*'change_email'/);
|
||||
assert.equal((password.match(/aksrt_require_captcha\(/g) || []).length, 1);
|
||||
assert.match(password, /aksrt_require_captcha\(\s*'change_password'/);
|
||||
assert.equal((email.match(/aksrt_require_captcha\(/g) || []).length, 1);
|
||||
assert.match(email, /aksrt_require_captcha\(\s*'change_email'/);
|
||||
assert.equal((send.match(/jio_require_captcha\(/g) || []).length, 1);
|
||||
assert.match(send, /jio_require_captcha\(\s*'change_email'/);
|
||||
assert.equal((password.match(/jio_require_captcha\(/g) || []).length, 1);
|
||||
assert.match(password, /jio_require_captcha\(\s*'change_password'/);
|
||||
assert.equal((email.match(/jio_require_captcha\(/g) || []).length, 1);
|
||||
assert.match(email, /jio_require_captcha\(\s*'change_email'/);
|
||||
});
|
||||
|
||||
test('legacy public SMS-code AJAX surface is removed while SMS service remains', () => {
|
||||
const sms = read('src/Sms/SMS.php');
|
||||
|
||||
assert.doesNotMatch(sms, /aksrt_send_sms_code/);
|
||||
assert.doesNotMatch(sms, /jio_send_sms_code/);
|
||||
assert.doesNotMatch(sms, /function ajax_send_code\s*\(/);
|
||||
assert.match(sms, /public function send_code\(string \$phone, string \$code\): array/);
|
||||
});
|
||||
@@ -67,8 +67,8 @@ test('ticket create and reply endpoints verify captcha once per request', () =>
|
||||
|
||||
// 两个新场景已在场景白名单与后台「启用场景」勾选列表同步注册
|
||||
const scenes = read('inc/modules/captcha.php');
|
||||
assert.match(scenes, /'ticket_create'\s*=> __\('提交工单', 'aksrt'\)/);
|
||||
assert.match(scenes, /'ticket_reply'\s*=> __\('回复工单', 'aksrt'\)/);
|
||||
assert.match(scenes, /'ticket_create'\s*=> __\('提交工单', 'jio'\)/);
|
||||
assert.match(scenes, /'ticket_reply'\s*=> __\('回复工单', 'jio'\)/);
|
||||
const permissions = read('src/Admin/Schema/SchemaPermissions.php');
|
||||
assert.match(permissions, /'label' => '提交工单', 'value' => 'ticket_create'/);
|
||||
assert.match(permissions, /'label' => '回复工单', 'value' => 'ticket_reply'/);
|
||||
@@ -76,8 +76,8 @@ test('ticket create and reply endpoints verify captcha once per request', () =>
|
||||
assert.match(adminSchema, /label: '提交工单', value: 'ticket_create'/);
|
||||
assert.match(adminSchema, /label: '回复工单', value: 'ticket_reply'/);
|
||||
|
||||
// REST 新建 / 回复各过一次场景校验(统一入口 helper 内恰一次 aksrt_require_captcha);
|
||||
// 场景未启用时 aksrt_require_captcha 直接放行
|
||||
// REST 新建 / 回复各过一次场景校验(统一入口 helper 内恰一次 jio_require_captcha);
|
||||
// 场景未启用时 jio_require_captcha 直接放行
|
||||
const submit = service.slice(service.indexOf('public function create_ticket'), service.indexOf('public function reply'));
|
||||
const cancel = service.slice(service.indexOf('public function reply'), service.indexOf('public function urge'));
|
||||
assert.match(submit, /require_captcha\(\$request, self::CAPTCHA_SCENE_CREATE\)/);
|
||||
@@ -85,6 +85,6 @@ test('ticket create and reply endpoints verify captcha once per request', () =>
|
||||
assert.match(service, /const CAPTCHA_SCENE_CREATE = 'ticket_create'/);
|
||||
assert.match(service, /const CAPTCHA_SCENE_REPLY = 'ticket_reply'/);
|
||||
assert.equal((service.match(/require_captcha\(\$request, self::CAPTCHA_SCENE_/g) || []).length, 2);
|
||||
assert.equal((service.match(/aksrt_require_captcha\(/g) || []).length, 1);
|
||||
assert.match(service, /function_exists\('aksrt_require_captcha'\)/);
|
||||
assert.equal((service.match(/jio_require_captcha\(/g) || []).length, 1);
|
||||
assert.match(service, /function_exists\('jio_require_captcha'\)/);
|
||||
});
|
||||
@@ -18,13 +18,13 @@ test('captcha exchange binds provider tokens to scene browser and one-time busin
|
||||
const module = read('inc/modules/captcha.php');
|
||||
const front = read('assets/js/captcha-front.js');
|
||||
assert.match(module, /\/captcha\/exchange/);
|
||||
assert.match(module, /aksrt_captcha_issue_challenge/);
|
||||
assert.match(module, /aksrt_captcha_validate_challenge/);
|
||||
assert.match(module, /aksrt_captcha_issue_proof/);
|
||||
assert.match(module, /aksrt_captcha_consume_proof/);
|
||||
assert.match(module, /jio_captcha_issue_challenge/);
|
||||
assert.match(module, /jio_captcha_validate_challenge/);
|
||||
assert.match(module, /jio_captcha_issue_proof/);
|
||||
assert.match(module, /jio_captcha_consume_proof/);
|
||||
assert.match(module, /browser_hash/);
|
||||
assert.match(front, /exchange_url/);
|
||||
assert.match(front, /aksrt_captcha_proof/);
|
||||
assert.match(front, /jio_captcha_proof/);
|
||||
assert.match(front, /runLocked/);
|
||||
});
|
||||
|
||||
@@ -32,24 +32,24 @@ test('captcha defaults are opt-in and provider metadata is centralized', () => {
|
||||
const module = read('inc/modules/captcha.php');
|
||||
const phpSchema = read('src/Admin/Schema/SchemaPermissions.php');
|
||||
const uiSchema = read('assets/admin/src/schema/sections/permissions/index.js');
|
||||
assert.match(module, /function aksrt_captcha_provider_metadata/);
|
||||
assert.match(module, /function jio_captcha_provider_metadata/);
|
||||
assert.match(module, /'enabled_scenarios'\s*=>\s*\[\]/);
|
||||
assert.match(phpSchema, /'default'\s*=>\s*\[\]/);
|
||||
assert.match(uiSchema, /enabled_scenarios:\s*\[\]/);
|
||||
assert.match(phpSchema, /aksrt_captcha_provider_metadata/);
|
||||
assert.match(phpSchema, /jio_captcha_provider_metadata/);
|
||||
});
|
||||
|
||||
test('captcha verification is fail-closed with a true-or-WP_Error contract', () => {
|
||||
const module = read('inc/modules/captcha.php');
|
||||
assert.match(module, /return aksrt_captcha_error\('aksrt_captcha_config'/);
|
||||
assert.match(module, /return jio_captcha_error\('jio_captcha_config'/);
|
||||
assert.match(module, /@return true\|\\WP_Error/);
|
||||
assert.doesNotMatch(module, /is_wp_error\(\$result\) \? \$result->get_error_message\(\) : true/);
|
||||
});
|
||||
|
||||
test('cloudflare siteverify performs one request and strictly binds action, cdata, and hostname', () => {
|
||||
const module = read('inc/modules/captcha.php');
|
||||
const start = module.indexOf('function aksrt_verify_cloudflare');
|
||||
const end = module.indexOf('function aksrt_verify_geetest', start);
|
||||
const start = module.indexOf('function jio_verify_cloudflare');
|
||||
const end = module.indexOf('function jio_verify_geetest', start);
|
||||
const cloudflare = module.slice(start, end);
|
||||
assert.equal((cloudflare.match(/wp_remote_post\(/g) || []).length, 1);
|
||||
assert.match(cloudflare, /array_key_exists\('action'/);
|
||||
@@ -72,7 +72,7 @@ test('nested captcha secrets are masked, preserved, explicitly clearable, and ex
|
||||
assert.match(provider, /merge_captcha_provider_config/);
|
||||
assert.match(provider, /\$value === '' \|\| \$value === '\*\*\*'/);
|
||||
assert.match(settings, /SCHEMA_MANAGED_STANDALONE_OPTION_KEYS/);
|
||||
assert.match(settings, /'aksrt_captcha_config'/);
|
||||
assert.match(settings, /'jio_captcha_config'/);
|
||||
assert.match(page, /captchaSecretsToClear/);
|
||||
assert.match(page, /clear-secret/);
|
||||
});
|
||||
@@ -82,10 +82,10 @@ test('admin captcha test obtains a frontend token then calls the protected serve
|
||||
const api = read('assets/admin/src/api/modules/schema.js');
|
||||
const page = read('assets/admin/src/pages/SchemaPage.vue');
|
||||
assert.match(controller, /'\/captcha\/test'/);
|
||||
assert.match(controller, /aksrt_verify_captcha_provider/);
|
||||
assert.match(controller, /jio_verify_captcha_provider/);
|
||||
assert.match(api, /testCaptcha\(provider, token\)/);
|
||||
assert.match(page, /submitCaptchaTest/);
|
||||
assert.match(page, /action:\s*'aksrt-admin-test'/);
|
||||
assert.match(page, /action:\s*'jio-admin-test'/);
|
||||
assert.match(page, /cdata:\s*'admin-captcha-test'/);
|
||||
assert.doesNotMatch(page, /配置正确/);
|
||||
});
|
||||
@@ -111,25 +111,25 @@ test('cloud captcha verify requests use a 5s timeout and log raw failures', () =
|
||||
for (const timeout of timeouts) {
|
||||
assert.strictEqual(timeout, 5);
|
||||
}
|
||||
assert.match(module, /error_log\('\[AKSRT Captcha\]/);
|
||||
assert.match(module, /error_log\('\[Jio Captcha\]/);
|
||||
});
|
||||
|
||||
test('business requests consume one-time proofs without a shared download bypass', () => {
|
||||
const module = read('inc/modules/captcha.php');
|
||||
assert.match(module, /aksrt_captcha_consume_proof/);
|
||||
assert.doesNotMatch(module, /aksrt_captcha_pass_ttl|aksrt_captcha_pass_key|aksrt_captcha_pass_/);
|
||||
assert.match(module, /jio_captcha_consume_proof/);
|
||||
assert.doesNotMatch(module, /jio_captcha_pass_ttl|jio_captcha_pass_key|jio_captcha_pass_/);
|
||||
});
|
||||
|
||||
test('cloudflare siteverify stays bound to the aksrt action and scene', () => {
|
||||
test('cloudflare siteverify stays bound to the jio action and scene', () => {
|
||||
const module = read('inc/modules/captcha.php');
|
||||
assert.match(module, /aksrt_verify_cloudflare\(array \$pcfg, array \$post_data, string \$client_ip, string \$scene = ''(?:, string \$action = 'aksrt-verify')?\)/);
|
||||
assert.match(module, /jio_verify_cloudflare\(array \$pcfg, array \$post_data, string \$client_ip, string \$scene = ''(?:, string \$action = 'jio-verify')?\)/);
|
||||
assert.match(module, /action mismatch/);
|
||||
assert.match(module, /cdata mismatch/);
|
||||
});
|
||||
|
||||
test('captcha render is a data-driven mount without inline scripts or stylesheets', () => {
|
||||
const module = read('inc/modules/captcha.php');
|
||||
assert.match(module, /data-aksrt-captcha-config/);
|
||||
assert.match(module, /data-jio-captcha-config/);
|
||||
assert.doesNotMatch(module, /<script|<link rel/);
|
||||
});
|
||||
|
||||
@@ -143,17 +143,17 @@ test('captcha front runtime ships inside the public bundle', () => {
|
||||
assert.match(build, /'assets\/js\/captcha-front\.js'/);
|
||||
assert.match(build, /'assets\/css\/shared\/captcha\.css'/);
|
||||
const front = read('assets/js/captcha-front.js');
|
||||
assert.match(front, /AKSRTCaptcha/);
|
||||
assert.match(front, /data-aksrt-captcha-config/);
|
||||
assert.match(front, /JioCaptcha/);
|
||||
assert.match(front, /data-jio-captcha-config/);
|
||||
assert.match(front, /cdata: C\.scene/, 'turnstile widget must bind cdata to the scene');
|
||||
});
|
||||
|
||||
test('captcha public runtime exposes only the shared API and resolves scoped uid instances', () => {
|
||||
const front = read('assets/js/captcha-front.js');
|
||||
assert.match(front, /window\.AKSRTCaptcha\s*=\s*Object\.freeze\(\{[\s\S]*verify:\s*verify,[\s\S]*collect:\s*collect,[\s\S]*reset:\s*reset,[\s\S]*run:\s*run,[\s\S]*TOKEN_FIELDS:\s*TOKEN_FIELDS/);
|
||||
assert.match(front, /window\.JioCaptcha\s*=\s*Object\.freeze\(\{[\s\S]*verify:\s*verify,[\s\S]*collect:\s*collect,[\s\S]*reset:\s*reset,[\s\S]*run:\s*run,[\s\S]*TOKEN_FIELDS:\s*TOKEN_FIELDS/);
|
||||
assert.match(front, /instances\[instance\.uid\]\s*=\s*instance/);
|
||||
assert.match(front, /function resolve\(scene, scope\)/);
|
||||
assert.doesNotMatch(front, /AKSRTCaptchaState/);
|
||||
assert.doesNotMatch(front, /JioCaptchaState/);
|
||||
assert.doesNotMatch(front, /tencent_sid/);
|
||||
});
|
||||
|
||||
@@ -166,9 +166,9 @@ test('captcha consumers use the shared API and reset every token-bearing request
|
||||
'assets/js/signin.js',
|
||||
'assets/js/front-submit.js',
|
||||
].map(read).join('\n');
|
||||
assert.match(consumers, /AKSRTCaptcha\.run|captcha\.run/);
|
||||
assert.match(consumers, /AKSRTCaptcha\.reset|captcha\.reset|resetCaptcha/);
|
||||
assert.doesNotMatch(consumers, /AKSRTCaptchaState/);
|
||||
assert.match(consumers, /JioCaptcha\.run|captcha\.run/);
|
||||
assert.match(consumers, /JioCaptcha\.reset|captcha\.reset|resetCaptcha/);
|
||||
assert.doesNotMatch(consumers, /JioCaptchaState/);
|
||||
assert.doesNotMatch(consumers, /tencent_sid/);
|
||||
assert.doesNotMatch(read('assets/js/core/comments.js'), /wp-comments-post|performLegacySubmit/);
|
||||
});
|
||||
@@ -177,10 +177,10 @@ test('sign-in waits for main and fails closed when its enabled captcha mount is
|
||||
const signin = read('assets/js/signin.js');
|
||||
const scripts = read('src/Core/ScriptsPart1c.php');
|
||||
assert.match(signin, /tbquire\(\['main'\], boot\)/);
|
||||
assert.match(signin, /var captchaRequired = !!window\.aksrtData\.captchaSignin/);
|
||||
assert.match(signin, /var captchaRequired = !!window\.jioData\.captchaSignin/);
|
||||
assert.match(signin, /captchaRequired && \(!captcha \|\| !mount\)[\s\S]*?return;/);
|
||||
assert.doesNotMatch(signin, /captcha \? captcha\.run\('signin', mount\) : Promise\.resolve\(\{\}\)/);
|
||||
assert.match(scripts, /'captchaSignin'\s*=>\s*function_exists\('aksrt_is_captcha_enabled_for'\)\s*&&\s*aksrt_is_captcha_enabled_for\('signin'\)/);
|
||||
assert.match(scripts, /'captchaSignin'\s*=>\s*function_exists\('jio_is_captcha_enabled_for'\)\s*&&\s*jio_is_captcha_enabled_for\('signin'\)/);
|
||||
});
|
||||
|
||||
test('auth and account security flows consume fresh captcha tickets', () => {
|
||||
|
||||
@@ -16,8 +16,8 @@ function createInput(name, value = '') {
|
||||
|
||||
function createHarness() {
|
||||
const fields = new Map();
|
||||
const challenge = createInput('aksrt_captcha_challenge', 'challenge-token');
|
||||
const proof = createInput('aksrt_captcha_proof', '');
|
||||
const challenge = createInput('jio_captcha_challenge', 'challenge-token');
|
||||
const proof = createInput('jio_captcha_proof', '');
|
||||
const verified = createInput('captcha_verified', '0');
|
||||
fields.set(challenge.name, challenge);
|
||||
fields.set(proof.name, proof);
|
||||
@@ -56,7 +56,7 @@ function createHarness() {
|
||||
const document = {
|
||||
readyState: 'complete',
|
||||
documentElement: { nodeType: 1 },
|
||||
querySelectorAll(selector) { return selector === '[data-aksrt-captcha-config]' ? [mount] : []; },
|
||||
querySelectorAll(selector) { return selector === '[data-jio-captcha-config]' ? [mount] : []; },
|
||||
querySelector: () => null,
|
||||
getElementById(id) { return id === 'captcha-box' ? box : verified; },
|
||||
createElement(tag) {
|
||||
@@ -88,7 +88,7 @@ function createHarness() {
|
||||
Promise,
|
||||
};
|
||||
vm.runInNewContext(runtime, { window, document, Promise, MutationObserver: undefined, setTimeout, clearTimeout, console });
|
||||
return { api: window.AKSRTCaptcha, container };
|
||||
return { api: window.JioCaptcha, container };
|
||||
}
|
||||
|
||||
test('captcha run is single-flight until the owning business request resets', async () => {
|
||||
@@ -98,7 +98,7 @@ test('captcha run is single-flight until the owning business request resets', as
|
||||
const first = api.run('login', container);
|
||||
const second = api.run('login', container);
|
||||
const firstPayload = await first;
|
||||
assert.equal(firstPayload.aksrt_captcha_proof, 'proof-token');
|
||||
assert.equal(firstPayload.jio_captcha_proof, 'proof-token');
|
||||
|
||||
let secondSettled = false;
|
||||
second.then(() => { secondSettled = true; });
|
||||
@@ -133,7 +133,7 @@ test('sign-in waits for main before binding and invokes the signin captcha', asy
|
||||
};
|
||||
const jqueryApi = { off() { return this; }, on() { return this; } };
|
||||
const jQuery = () => jqueryApi;
|
||||
const window = { aksrtData: { captchaSignin: true } };
|
||||
const window = { jioData: { captchaSignin: true } };
|
||||
let mainReady;
|
||||
let captchaCall = null;
|
||||
const tbquire = (deps, callback) => {
|
||||
@@ -147,15 +147,15 @@ test('sign-in waits for main before binding and invokes the signin captcha', asy
|
||||
assert.equal(listeners.click, undefined);
|
||||
assert.equal(typeof mainReady, 'function');
|
||||
|
||||
window.AksrtRequest = {
|
||||
window.JioRequest = {
|
||||
request(endpoint) {
|
||||
if (endpoint === '/signin/today') return Promise.resolve({ ok: true, status: 200, json: { enabled: true, signed: false, streak: 0, reward_if_signin: 1 } });
|
||||
if (endpoint === '/signin') return Promise.resolve({ ok: true, status: 200, json: { message: '签到成功', balance: 1 } });
|
||||
throw new Error('unexpected endpoint ' + endpoint);
|
||||
},
|
||||
};
|
||||
window.AKSRTCaptcha = {
|
||||
run(scene, scope) { captchaCall = { scene, scope }; return Promise.resolve({ aksrt_captcha_proof: 'proof' }); },
|
||||
window.JioCaptcha = {
|
||||
run(scene, scope) { captchaCall = { scene, scope }; return Promise.resolve({ jio_captcha_proof: 'proof' }); },
|
||||
reset() {},
|
||||
};
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace {
|
||||
define('MINUTE_IN_SECONDS', 60);
|
||||
define('MONTH_IN_SECONDS', 2592000);
|
||||
define('ARRAY_A', 'ARRAY_A');
|
||||
$GLOBALS['aksrt_test_options'] = [];
|
||||
$GLOBALS['jio_test_options'] = [];
|
||||
$GLOBALS['wp_cache'] = [];
|
||||
|
||||
final class HarnessWpdb {
|
||||
@@ -21,8 +21,8 @@ namespace {
|
||||
public function query($prepared): int {
|
||||
[, $args] = $prepared;
|
||||
[$key, $serialized] = $args;
|
||||
if (!array_key_exists($key, $GLOBALS['aksrt_test_options']) || serialize($GLOBALS['aksrt_test_options'][$key]) !== $serialized) return 0;
|
||||
unset($GLOBALS['aksrt_test_options'][$key]);
|
||||
if (!array_key_exists($key, $GLOBALS['jio_test_options']) || serialize($GLOBALS['jio_test_options'][$key]) !== $serialized) return 0;
|
||||
unset($GLOBALS['jio_test_options'][$key]);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -42,16 +42,16 @@ namespace {
|
||||
function wp_kses_post($value): string { return strip_tags((string) $value, '<b><strong><em><i><a><p><br>'); }
|
||||
function esc_url_raw($value): string { return trim((string) $value); }
|
||||
function wp_unslash($value) { return $value; }
|
||||
function get_option(string $key, $default = false) { return array_key_exists($key, $GLOBALS['aksrt_test_options']) ? $GLOBALS['aksrt_test_options'][$key] : $default; }
|
||||
function update_option(string $key, $value): bool { $GLOBALS['aksrt_test_options'][$key] = $value; return true; }
|
||||
function get_option(string $key, $default = false) { return array_key_exists($key, $GLOBALS['jio_test_options']) ? $GLOBALS['jio_test_options'][$key] : $default; }
|
||||
function update_option(string $key, $value): bool { $GLOBALS['jio_test_options'][$key] = $value; return true; }
|
||||
function add_option(string $key, $value, string $deprecated = '', bool $autoload = true): bool {
|
||||
if (array_key_exists($key, $GLOBALS['aksrt_test_options'])) return false;
|
||||
$GLOBALS['aksrt_test_options'][$key] = $value;
|
||||
if (array_key_exists($key, $GLOBALS['jio_test_options'])) return false;
|
||||
$GLOBALS['jio_test_options'][$key] = $value;
|
||||
return true;
|
||||
}
|
||||
function delete_option(string $key): bool {
|
||||
if (!array_key_exists($key, $GLOBALS['aksrt_test_options'])) return false;
|
||||
unset($GLOBALS['aksrt_test_options'][$key]);
|
||||
if (!array_key_exists($key, $GLOBALS['jio_test_options'])) return false;
|
||||
unset($GLOBALS['jio_test_options'][$key]);
|
||||
return true;
|
||||
}
|
||||
function maybe_serialize($value): string { return serialize($value); }
|
||||
@@ -77,7 +77,7 @@ namespace {
|
||||
function wp_unique_id(string $prefix = ''): string { return $prefix . '1'; }
|
||||
}
|
||||
|
||||
namespace AKSRT\Settings {
|
||||
namespace Jio\Settings {
|
||||
final class Settings {
|
||||
public static function flush_settings_cache(): void {}
|
||||
public static function sync_native_site_icon(string $value): void {}
|
||||
@@ -99,10 +99,10 @@ namespace {
|
||||
}
|
||||
}
|
||||
|
||||
check(aksrt_captcha_default_config()['enabled_scenarios'] === [], 'captcha scenarios are not opt-in');
|
||||
check(!in_array('domain', array_keys(aksrt_captcha_provider_defaults('cloudflare')), true), 'Cloudflare domain remains allowed');
|
||||
check(jio_captcha_default_config()['enabled_scenarios'] === [], 'captcha scenarios are not opt-in');
|
||||
check(!in_array('domain', array_keys(jio_captcha_provider_defaults('cloudflare')), true), 'Cloudflare domain remains allowed');
|
||||
|
||||
$GLOBALS['aksrt_test_options']['aksrt_captcha_config'] = [
|
||||
$GLOBALS['jio_test_options']['jio_captcha_config'] = [
|
||||
'enabled' => true,
|
||||
'active_provider' => 'cloudflare',
|
||||
'enabled_scenarios' => ['login'],
|
||||
@@ -114,9 +114,9 @@ namespace {
|
||||
'domain' => 'should-be-dropped.test',
|
||||
],
|
||||
];
|
||||
aksrt_captcha_config_reset();
|
||||
jio_captcha_config_reset();
|
||||
|
||||
$provider = \AKSRT\Admin\SchemaProvider::get_instance();
|
||||
$provider = \Jio\Admin\SchemaProvider::get_instance();
|
||||
$read = $provider->read_settings('permissions', 'login');
|
||||
check(($read['cloudflare']['secret_key'] ?? '') === '***', 'nested secret was not masked');
|
||||
check(!isset($read['cloudflare']['domain']), 'unknown provider key was exposed');
|
||||
@@ -130,7 +130,7 @@ namespace {
|
||||
'domain' => 'ignored.test',
|
||||
],
|
||||
], 'login');
|
||||
$stored = $GLOBALS['aksrt_test_options']['aksrt_captcha_config']['cloudflare'];
|
||||
$stored = $GLOBALS['jio_test_options']['jio_captcha_config']['cloudflare'];
|
||||
check($stored['secret_key'] === 'stored-secret', 'masked secret did not preserve stored value');
|
||||
check($stored['site_key'] === 'new-site-key', 'public provider field was not updated');
|
||||
check(!isset($stored['domain']), 'unknown provider key was persisted');
|
||||
@@ -138,47 +138,47 @@ namespace {
|
||||
$provider->write_settings('permissions', [
|
||||
'cloudflare' => ['secret_key' => ''],
|
||||
], 'login');
|
||||
check($GLOBALS['aksrt_test_options']['aksrt_captcha_config']['cloudflare']['secret_key'] === 'stored-secret', 'empty secret did not preserve stored value');
|
||||
check($GLOBALS['jio_test_options']['jio_captcha_config']['cloudflare']['secret_key'] === 'stored-secret', 'empty secret did not preserve stored value');
|
||||
|
||||
$provider->write_settings('permissions', [
|
||||
'__clear_secrets' => ['cloudflare.secret_key'],
|
||||
], 'login');
|
||||
check($GLOBALS['aksrt_test_options']['aksrt_captcha_config']['cloudflare']['secret_key'] === '', 'explicit nested secret clear failed');
|
||||
check($GLOBALS['jio_test_options']['jio_captcha_config']['cloudflare']['secret_key'] === '', 'explicit nested secret clear failed');
|
||||
|
||||
$GLOBALS['aksrt_test_options']['aksrt_captcha_config'] = [
|
||||
$GLOBALS['jio_test_options']['jio_captcha_config'] = [
|
||||
'enabled' => true,
|
||||
'active_provider' => '',
|
||||
'enabled_scenarios' => ['login'],
|
||||
];
|
||||
aksrt_captcha_config_reset();
|
||||
$failed_closed = aksrt_require_captcha('login', []);
|
||||
jio_captcha_config_reset();
|
||||
$failed_closed = jio_require_captcha('login', []);
|
||||
check(is_wp_error($failed_closed), 'enabled scene with missing provider failed open');
|
||||
check($failed_closed->get_error_code() === 'aksrt_captcha_config', 'fail-closed error code mismatch');
|
||||
check($failed_closed->get_error_code() === 'jio_captcha_config', 'fail-closed error code mismatch');
|
||||
|
||||
$GLOBALS['aksrt_test_options']['aksrt_captcha_config'] = [
|
||||
$GLOBALS['jio_test_options']['jio_captcha_config'] = [
|
||||
'enabled' => true,
|
||||
'active_provider' => 'cloudflare',
|
||||
'enabled_scenarios' => ['login', 'comment'],
|
||||
'cloudflare' => ['site_key' => 'site', 'secret_key' => 'secret', 'theme' => 'auto', 'size' => 'normal'],
|
||||
];
|
||||
aksrt_captcha_config_reset();
|
||||
$_COOKIE[AKSRT_CAPTCHA_BROWSER_COOKIE] = str_repeat('A', 43);
|
||||
$challenge = aksrt_captcha_issue_challenge('login', 'cloudflare');
|
||||
check(aksrt_captcha_validate_challenge($challenge, 'login', 'cloudflare', false), 'valid scene challenge failed');
|
||||
check(!aksrt_captcha_validate_challenge($challenge, 'comment', 'cloudflare', false), 'cross-scene challenge accepted');
|
||||
check(!aksrt_captcha_validate_challenge($challenge, 'login', 'geetest', false), 'cross-provider challenge accepted');
|
||||
$_COOKIE[AKSRT_CAPTCHA_BROWSER_COOKIE] = str_repeat('B', 43);
|
||||
check(!aksrt_captcha_validate_challenge($challenge, 'login', 'cloudflare', false), 'cross-browser challenge accepted');
|
||||
$_COOKIE[AKSRT_CAPTCHA_BROWSER_COOKIE] = str_repeat('A', 43);
|
||||
check(aksrt_captcha_validate_challenge($challenge, 'login', 'cloudflare', true), 'valid challenge consume failed');
|
||||
check(!aksrt_captcha_validate_challenge($challenge, 'login', 'cloudflare', true), 'challenge replay accepted');
|
||||
jio_captcha_config_reset();
|
||||
$_COOKIE[JIO_CAPTCHA_BROWSER_COOKIE] = str_repeat('A', 43);
|
||||
$challenge = jio_captcha_issue_challenge('login', 'cloudflare');
|
||||
check(jio_captcha_validate_challenge($challenge, 'login', 'cloudflare', false), 'valid scene challenge failed');
|
||||
check(!jio_captcha_validate_challenge($challenge, 'comment', 'cloudflare', false), 'cross-scene challenge accepted');
|
||||
check(!jio_captcha_validate_challenge($challenge, 'login', 'geetest', false), 'cross-provider challenge accepted');
|
||||
$_COOKIE[JIO_CAPTCHA_BROWSER_COOKIE] = str_repeat('B', 43);
|
||||
check(!jio_captcha_validate_challenge($challenge, 'login', 'cloudflare', false), 'cross-browser challenge accepted');
|
||||
$_COOKIE[JIO_CAPTCHA_BROWSER_COOKIE] = str_repeat('A', 43);
|
||||
check(jio_captcha_validate_challenge($challenge, 'login', 'cloudflare', true), 'valid challenge consume failed');
|
||||
check(!jio_captcha_validate_challenge($challenge, 'login', 'cloudflare', true), 'challenge replay accepted');
|
||||
|
||||
$proof = aksrt_captcha_issue_proof('login', 'cloudflare');
|
||||
check(!aksrt_captcha_consume_proof($proof, 'comment', 'cloudflare'), 'cross-scene proof accepted');
|
||||
check(aksrt_captcha_consume_proof($proof, 'login', 'cloudflare'), 'valid proof consume failed');
|
||||
check(!aksrt_captcha_consume_proof($proof, 'login', 'cloudflare'), 'proof replay accepted');
|
||||
$proof = jio_captcha_issue_proof('login', 'cloudflare');
|
||||
check(!jio_captcha_consume_proof($proof, 'comment', 'cloudflare'), 'cross-scene proof accepted');
|
||||
check(jio_captcha_consume_proof($proof, 'login', 'cloudflare'), 'valid proof consume failed');
|
||||
check(!jio_captcha_consume_proof($proof, 'login', 'cloudflare'), 'proof replay accepted');
|
||||
|
||||
$headers = aksrt_tencent_tc3_sign('id', 'key', 'captcha', 1700000000, '2023-11-14', 'DescribeCaptchaResult', '2019-07-22', '{}');
|
||||
$headers = jio_tencent_tc3_sign('id', 'key', 'captcha', 1700000000, '2023-11-14', 'DescribeCaptchaResult', '2019-07-22', '{}');
|
||||
check(str_starts_with($headers['Authorization'], 'TC3-HMAC-SHA256 Credential=id/2023-11-14/captcha/tc3_request'), 'TC3 authorization scope mismatch');
|
||||
|
||||
echo "Captcha security harness passed.\n";
|
||||
|
||||
@@ -30,7 +30,7 @@ const payVipJs = read('assets', 'js', 'pay-vip.js');
|
||||
const chargeCard = read('src', 'Payment', 'ChargeCard.php');
|
||||
const userOrders = read('src', 'Payment', 'UserOrders.php');
|
||||
const vipPackages = read('src', 'Content', 'VipServicePackages.php');
|
||||
const aksrtPay = read('inc', 'helpers', 'aksrt-pay.php');
|
||||
const jioPay = read('inc', 'helpers', 'jio-pay.php');
|
||||
const mainCss = read('assets', 'css', 'main.css');
|
||||
const vipPurchase = read('src', 'Content', 'VipServicePurchase.php');
|
||||
const vipModal = read('src', 'Content', 'VipServiceModal.php');
|
||||
@@ -44,9 +44,9 @@ test('独立入口与通用弹窗已删除', () => { assert.equal(
|
||||
'credits-vip.php 死文件应已删除'
|
||||
);
|
||||
// 通用卡密弹窗与 initCardPass 不回潮。
|
||||
assert.doesNotMatch(wallet, /data-aksrt-card-pass-modal|data-aksrt-card-redeem/);
|
||||
assert.doesNotMatch(wallet, /data-jio-card-pass-modal|data-jio-card-redeem/);
|
||||
assert.doesNotMatch(creditsJs, /function initCardPass\(|initCardPass\(\)/);
|
||||
assert.doesNotMatch(creditsRenderer, /data-aksrt-card-pass-modal|data-aksrt-card-redeem/);
|
||||
assert.doesNotMatch(creditsRenderer, /data-jio-card-pass-modal|data-jio-card-redeem/);
|
||||
// 卡密 logo 就位(三个弹窗共用)。
|
||||
assert.equal(fs.existsSync(path.join(root, 'assets', 'img', 'pay-card-pass-logo.svg')), true);
|
||||
});
|
||||
@@ -67,13 +67,13 @@ test('余额充值弹窗:卡密渠道为支付方式之一,卡号/卡密输
|
||||
|
||||
test('购买积分弹窗:Zibll points-pay modal 结构 + 卡密渠道', () => {
|
||||
// 商品/自定义选择 + 支付方式 + hidden + 立即支付。
|
||||
assert.match(creditsRenderer, /data-aksrt-points-pay-modal/);
|
||||
assert.match(creditsRenderer, /data-aksrt-points-pay-form/);
|
||||
assert.match(creditsRenderer, /data-aksrt-points-pay-open/);
|
||||
assert.doesNotMatch(creditsRenderer, /data-aksrt-points-exchange-(open|modal|form|amount|preview|close)/);
|
||||
assert.match(creditsRenderer, /data-jio-points-pay-modal/);
|
||||
assert.match(creditsRenderer, /data-jio-points-pay-form/);
|
||||
assert.match(creditsRenderer, /data-jio-points-pay-open/);
|
||||
assert.doesNotMatch(creditsRenderer, /data-jio-points-exchange-(open|modal|form|amount|preview|close)/);
|
||||
// 商品/自定义提示语跟随自定义货币名称(后台「积分配置 → 自定义货币名称」)。
|
||||
assert.match(creditsRenderer, /请选择需购买的%s', 'aksrt'\), esc_html\(\$currency\)/);
|
||||
assert.match(creditsRenderer, /请输入需购买的%s数额', 'aksrt'\), esc_html\(\$currency\)/);
|
||||
assert.match(creditsRenderer, /请选择需购买的%s', 'jio'\), esc_html\(\$currency\)/);
|
||||
assert.match(creditsRenderer, /请输入需购买的%s数额', 'jio'\), esc_html\(\$currency\)/);
|
||||
assert.match(creditsRenderer, /name="points_product"/);
|
||||
assert.match(creditsRenderer, /name="points_custom"/);
|
||||
assert.match(creditsRenderer, /name="payment_method"/);
|
||||
@@ -86,7 +86,7 @@ test('购买积分弹窗:Zibll points-pay modal 结构 + 卡密渠道', () =>
|
||||
|
||||
test('购买积分前端:商品/支付方式选中模型 + 余额/卡密提交分流', () => {
|
||||
assert.match(creditsJs, /function initPointsPay\(\)/);
|
||||
assert.match(creditsJs, /data-aksrt-points-pay-open/);
|
||||
assert.match(creditsJs, /data-jio-points-pay-open/);
|
||||
assert.match(creditsJs, /\[data-for="points_product"\]/);
|
||||
// 卡密选中:隐藏 .charge-box、显示卡密输入盒(契约与参考主题一致)。
|
||||
assert.match(creditsJs, /data-controller="payment_method"\]\[data-value="card_pass"/);
|
||||
@@ -102,7 +102,7 @@ test('购买积分前端:商品/支付方式选中模型 + 余额/卡密提交
|
||||
|
||||
test('会员弹窗:卡密渠道注入与直兑分流(不创建支付订单)', () => {
|
||||
// 后端:available_payment_methods 追加 card_pass + 视图模型透传。
|
||||
assert.match(vipPackages, /'id' => 'card_pass', 'name' => __\('卡密', 'aksrt'\)/);
|
||||
assert.match(vipPackages, /'id' => 'card_pass', 'name' => __\('卡密', 'jio'\)/);
|
||||
assert.match(vipPackages, /function vip_card_pass_context\(\)/);
|
||||
assert.match(vipPackages, /vip_card_pass_context\(\)/);
|
||||
assert.match(vipModal, /'card_pass' => \$card_pass/);
|
||||
@@ -113,7 +113,7 @@ test('会员弹窗:卡密渠道注入与直兑分流(不创建支付订单
|
||||
assert.match(vipModal, /'wallet_balance' => \(string\) \(\$catalog\['wallet_balance'\]/);
|
||||
assert.match(vipModal, /'currency_mark' =>/);
|
||||
// 后端防御:purchase() 拒绝 card_pass(卡密直兑走 redeem)。
|
||||
assert.match(vipPurchase, /aksrt_card_pass_redeem_required/);
|
||||
assert.match(vipPurchase, /jio_card_pass_redeem_required/);
|
||||
// 前端:Zibll initiate_pay_input 同构组件——payment-method-radio logo 行、
|
||||
// 我的余额盒(data-controller == balance)、卡密盒(password_box 类结构)、
|
||||
// hidden payment_method、initiate-pay 按钮。
|
||||
@@ -126,15 +126,15 @@ test('会员弹窗:卡密渠道注入与直兑分流(不创建支付订单
|
||||
assert.match(payVipJs, /"data-value", "card_pass"/);
|
||||
assert.match(payVipJs, /hidden\.name = "payment_method"/);
|
||||
assert.match(payVipJs, /mt6 but jb-red initiate-pay btn-block radius/);
|
||||
assert.doesNotMatch(payVipJs, /uc-vip-shop__method|aksrt_vip_pay_method|aksrt-vip-submit/);
|
||||
assert.doesNotMatch(payVipJs, /uc-vip-shop__method|jio_vip_pay_method|jio-vip-submit/);
|
||||
// 按钮字样对齐参考主题:会员弹窗即 initiate_pay_input(4) 默认「立即支付」;
|
||||
// 无可用支付方式渲染 Zibll 同款 badg 提示(而非禁用按钮/自实现文案)。
|
||||
assert.doesNotMatch(payVipJs, /cta_label/);
|
||||
assert.match(payVipJs, /canPurchase \? "立即支付"/);
|
||||
assert.match(payVipJs, /badg px12 c-yellow-2/, "暂时无法支付,请与客服联系");
|
||||
// 渠道展示名对齐参考主题 zibpay_get_payment_method_args(余额/微信/支付宝)。
|
||||
assert.match(aksrtPay, /'balance'\s+=> __\('余额', 'aksrt'\)/);
|
||||
assert.match(aksrtPay, /'wechatpay'\s+=> __\('微信', 'aksrt'\)/);
|
||||
assert.match(jioPay, /'balance'\s+=> __\('余额', 'jio'\)/);
|
||||
assert.match(jioPay, /'wechatpay'\s+=> __\('微信', 'jio'\)/);
|
||||
// 显隐:通用 data-controller(==/!=)匹配 + 卡密选中隐藏套餐。
|
||||
assert.match(payVipJs, /function applyVipMethodVisibility\(/);
|
||||
assert.match(payVipJs, /querySelectorAll\('\[data-controller="payment_method"\]'\)/);
|
||||
@@ -205,7 +205,7 @@ test('核销订单契约:金额恒为 0,订单名称按 note 生成', () =>
|
||||
});
|
||||
|
||||
test('多渠道防挤压:图标 flex:none + 行不换行项目收缩成芯片(对齐 Zibll)', () => {
|
||||
// 图标禁用 flex 压缩并抵消 AKSRT bootstrap 全局 img{max-width:100%}
|
||||
// 图标禁用 flex 压缩并抵消 Jio bootstrap 全局 img{max-width:100%}
|
||||
// (flex 压缩时把图标钳制到被压窄的项宽,实测 5 渠道 400px 弹窗图标
|
||||
// 被压成 16px 细条)。行保持 Zibll 原样不换行:空间不足时项目收缩为
|
||||
// 「图标上/文字下」芯片(实测 5 渠道一行 5 个、6 渠道一行 6 个,图标
|
||||
|
||||
@@ -20,14 +20,14 @@ function walk(dir, exts, out = []) {
|
||||
return out;
|
||||
}
|
||||
|
||||
test('n-color-picker is used only inside the shared AksColorPicker', () => {
|
||||
test('n-color-picker is used only inside the shared JioColorPicker', () => {
|
||||
const offenders = walk('assets/admin/src', ['.vue', '.js'])
|
||||
.filter((file) => /n-color-picker|NColorPicker/.test(read(file)))
|
||||
.map((file) => file.replace(/\\/g, '/'));
|
||||
assert.deepEqual(offenders, ['assets/admin/src/components/form/AksColorPicker.vue']);
|
||||
assert.deepEqual(offenders, ['assets/admin/src/components/form/JioColorPicker.vue']);
|
||||
});
|
||||
|
||||
test('PHP color sanitizing goes through AKSRT\\Core\\Color only', () => {
|
||||
test('PHP color sanitizing goes through Jio\\Core\\Color only', () => {
|
||||
const offenders = [...walk('src', ['.php']), ...walk('inc', ['.php'])]
|
||||
.filter((file) => /sanitize_hex_color\(|rgba\?\\\(/.test(read(file)))
|
||||
.map((file) => file.replace(/\\/g, '/'))
|
||||
@@ -38,15 +38,15 @@ test('PHP color sanitizing goes through AKSRT\\Core\\Color only', () => {
|
||||
test('schema color fields sanitize via Color with showAlpha opt-in', () => {
|
||||
const provider = read('src/Admin/SchemaProvider.php');
|
||||
assert.match(provider, /'color'\s*=>\s*'color'/);
|
||||
assert.match(provider, /case 'color_alpha':\s*return \\AKSRT\\Core\\Color::sanitize\(\$value, true\)/);
|
||||
assert.match(provider, /case 'color_alpha':\s*return \\Jio\\Core\\Color::sanitize\(\$value, true\)/);
|
||||
assert.match(provider, /showAlpha/);
|
||||
const field = read('assets/admin/src/components/form/AksField.vue');
|
||||
assert.match(field, /<AksColorPicker[\s\S]*?:alpha="!!schema\.showAlpha"/);
|
||||
const field = read('assets/admin/src/components/form/JioField.vue');
|
||||
assert.match(field, /<JioColorPicker[\s\S]*?:alpha="!!schema\.showAlpha"/);
|
||||
});
|
||||
|
||||
test('dead composite branches are gone', () => {
|
||||
const composite = read('assets/admin/src/components/form/AksCompositeField.vue');
|
||||
assert.doesNotMatch(composite, /'dimensions'|'spacing'|'background'|AksMediaField/);
|
||||
const composite = read('assets/admin/src/components/form/JioCompositeField.vue');
|
||||
assert.doesNotMatch(composite, /'dimensions'|'spacing'|'background'|JioMediaField/);
|
||||
assert.doesNotMatch(read('assets/js/gutenberg-blocks.js'), /ColorPicker/);
|
||||
});
|
||||
|
||||
@@ -61,7 +61,7 @@ test('JS normalizeColor mirrors PHP Color::sanitize', async () => {
|
||||
['red', true], ['', true], [' #FFF ', false],
|
||||
];
|
||||
const php = `require 'src/Core/Color.php';`
|
||||
+ `foreach (json_decode($argv[1], true) as [$v, $a]) { echo AKSRT\\Core\\Color::sanitize($v, $a), "\\n"; }`;
|
||||
+ `foreach (json_decode($argv[1], true) as [$v, $a]) { echo Jio\\Core\\Color::sanitize($v, $a), "\\n"; }`;
|
||||
const out = execFileSync('php', ['-d', 'display_errors=1', '-r', `define('ABSPATH', '/');${php}`, JSON.stringify(cases)], {
|
||||
cwd: root,
|
||||
encoding: 'utf8',
|
||||
@@ -71,17 +71,17 @@ test('JS normalizeColor mirrors PHP Color::sanitize', async () => {
|
||||
|
||||
test('widget background color input is wpColorPicker-enhanced like top-level color fields', () => {
|
||||
const renderer = read('src/WidgetSystem/FormRenderer.php');
|
||||
assert.match(renderer, /\$key \. '\[color\]'[^\n]+'widefat aksrt-widget-color'\)/);
|
||||
assert.match(renderer, /\$key \. '\[color\]'[^\n]+'widefat jio-widget-color'\)/);
|
||||
assert.match(renderer, /class="' \. esc_attr\(\$class\) \. '"/);
|
||||
assert.match(read('assets/widgets/admin.js'), /\.aksrt-widget-color'\)\.wpColorPicker\(\)/);
|
||||
assert.match(read('assets/widgets/admin.js'), /\.jio-widget-color'\)\.wpColorPicker\(\)/);
|
||||
});
|
||||
|
||||
test('page config nav color uses wpColorPicker instead of native input[type=color]', () => {
|
||||
const metaBox = read('src/Admin/PageConfigMetaBox.php');
|
||||
assert.doesNotMatch(metaBox, /type="color"/);
|
||||
assert.match(metaBox, /class="aksrt-page-config-color" type="text" name="page_config\[header_slider_nav_color\]"/);
|
||||
assert.match(metaBox, /class="jio-page-config-color" type="text" name="page_config\[header_slider_nav_color\]"/);
|
||||
assert.match(metaBox, /wp_enqueue_script\('wp-color-picker'\)/);
|
||||
assert.match(metaBox, /\.aksrt-page-config-color'\)\.wpColorPicker\(\)/);
|
||||
assert.match(metaBox, /\.jio-page-config-color'\)\.wpColorPicker\(\)/);
|
||||
assert.match(metaBox, /Color::sanitize\(\(string\) \(\$raw\['header_slider_nav_color'\]/);
|
||||
});
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ test('quick reply is removed across the owned comment stack', () => {
|
||||
'assets/css/main.css',
|
||||
'assets/css/build/comments.css',
|
||||
'assets/js/comment-tools.js',
|
||||
'inc/helpers/aksrt-comments.php',
|
||||
'inc/helpers/jio-comments.php',
|
||||
'inc/helpers/comments/form-parts.php',
|
||||
'src/Admin/Schema/SchemaPostList.php',
|
||||
'src/Core/ScriptsPart1c.php',
|
||||
@@ -157,28 +157,28 @@ test('comment markup keeps the 1:1 legacy DOM contract', () => {
|
||||
const item = read('inc/helpers/comments/item.php');
|
||||
// 点赞按钮(legacy: zib_get_comment_like — svg + text + count)。
|
||||
assert.ok(item.includes("<text></text><count>"), 'like button must render <text> before <count>');
|
||||
assert.ok(item.includes('aksrt_get_comment_icon(\'like\''), 'like button must use the sprite icon');
|
||||
assert.ok(item.includes('aksrt_get_comment_icon(\'topping\')'), 'topping badge must use the sprite icon');
|
||||
assert.ok(item.includes('aksrt_get_comment_icon(\'approve\''), 'approve action must use the sprite icon');
|
||||
assert.ok(item.includes('aksrt_get_comment_icon(\'menu_2\')'), 'action dropdown must use the sprite icon');
|
||||
assert.ok(item.includes('jio_get_comment_icon(\'like\''), 'like button must use the sprite icon');
|
||||
assert.ok(item.includes('jio_get_comment_icon(\'topping\')'), 'topping badge must use the sprite icon');
|
||||
assert.ok(item.includes('jio_get_comment_icon(\'approve\''), 'approve action must use the sprite icon');
|
||||
assert.ok(item.includes('jio_get_comment_icon(\'menu_2\')'), 'action dropdown must use the sprite icon');
|
||||
assert.ok(item.includes('data-toggle="tooltip"'), 'time/reply tooltip parity');
|
||||
assert.equal(item.includes('fa fa-thumbs-up'), false, 'legacy fa like icon must be gone');
|
||||
|
||||
const comments = read('inc/helpers/aksrt-comments.php');
|
||||
const comments = read('inc/helpers/jio-comments.php');
|
||||
assert.ok(comments.includes('post_password_required() || !comments_open()'), 'closed comments must render nothing');
|
||||
assert.ok(comments.includes('user_url_enabled'), 'guest url field toggle must be wired');
|
||||
assert.ok(comments.includes("name=\"url\""), 'guest url input must render');
|
||||
assert.ok(comments.includes("aksrt_get_comment_icon('signup', 'icon mr10')"), 'signarea signup must use the sprite icon');
|
||||
assert.ok(comments.includes("aksrt_get_vip_icon($current_user->ID, 'em12 mr3')"), 'comt-title must render the direct VIP image with reference-theme spacing');
|
||||
assert.ok(comments.includes("aksrt_get_avatar_html($current_user->ID, 96, ['class' => 'avatar'])"), 'logged-in avatar must use the canonical avatar helper at the reference source size');
|
||||
assert.ok(comments.includes("aksrt_get_avatar_html(0, 96, ['class' => 'avatar'])"), 'guest avatar must use the canonical default-avatar helper');
|
||||
assert.equal(comments.includes('aksrt_render_vip_badge'), false, 'legacy VIP wrapper helper must not remain in the comment form');
|
||||
assert.equal(comments.includes('aksrt_render_user_identity_chips'), false, 'identity chips must not crowd the narrow comt-title');
|
||||
assert.ok(comments.includes("jio_get_comment_icon('signup', 'icon mr10')"), 'signarea signup must use the sprite icon');
|
||||
assert.ok(comments.includes("jio_get_vip_icon($current_user->ID, 'em12 mr3')"), 'comt-title must render the direct VIP image with reference-theme spacing');
|
||||
assert.ok(comments.includes("jio_get_avatar_html($current_user->ID, 96, ['class' => 'avatar'])"), 'logged-in avatar must use the canonical avatar helper at the reference source size');
|
||||
assert.ok(comments.includes("jio_get_avatar_html(0, 96, ['class' => 'avatar'])"), 'guest avatar must use the canonical default-avatar helper');
|
||||
assert.equal(comments.includes('jio_render_vip_badge'), false, 'legacy VIP wrapper helper must not remain in the comment form');
|
||||
assert.equal(comments.includes('jio_render_user_identity_chips'), false, 'identity chips must not crowd the narrow comt-title');
|
||||
|
||||
const vip = read('inc/helpers/aksrt-vip.php');
|
||||
const vip = read('inc/helpers/jio-vip.php');
|
||||
assert.ok(vip.includes("trim('img-icon ' . $class)"), 'VIP helper must emit the reference img-icon contract');
|
||||
assert.equal(vip.includes('function aksrt_render_vip_badge'), false, 'legacy wrapped VIP helper must be removed');
|
||||
assert.equal(vip.includes('<span class="aksrt-vip-badge'), false, 'VIP helper must not add a badge wrapper');
|
||||
assert.equal(vip.includes('function jio_render_vip_badge'), false, 'legacy wrapped VIP helper must be removed');
|
||||
assert.equal(vip.includes('<span class="jio-vip-badge'), false, 'VIP helper must not add a badge wrapper');
|
||||
|
||||
const formParts = read('inc/helpers/comments/form-parts.php');
|
||||
assert.ok(formParts.includes("case 'link':"), 'link dropdown case must exist');
|
||||
@@ -186,7 +186,7 @@ test('comment markup keeps the 1:1 legacy DOM contract', () => {
|
||||
assert.equal(formParts.includes('fa-bolt'), false, 'quick button icon must be gone');
|
||||
|
||||
const icons = read('inc/helpers/comments/icons.php');
|
||||
assert.ok(icons.includes('function aksrt_get_comment_icon'), 'sprite icon helper must exist');
|
||||
assert.ok(icons.includes('function jio_get_comment_icon'), 'sprite icon helper must exist');
|
||||
const bootstrap = read('inc/bootstrap.php');
|
||||
assert.ok(bootstrap.includes("comments/icons.php"), 'bootstrap must load the icon helper');
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ test('comment image settings use one two-switch contract', () => {
|
||||
}
|
||||
assert.match(display, /!empty\(\$settings\['comment_upload_img'\]\)/);
|
||||
assert.match(display, /is_user_logged_in\(\)/);
|
||||
assert.match(display, /aksrt_user_can\('upload_comment_image'/);
|
||||
assert.match(display, /jio_user_can\('upload_comment_image'/);
|
||||
assert.doesNotMatch(runtime, /commentUploadEnabled/);
|
||||
assert.doesNotMatch(runtime, /'comment_upload_img'\s*=>/);
|
||||
});
|
||||
@@ -64,7 +64,7 @@ test('comment upload REST endpoint has explicit nonce, switch, capability, and i
|
||||
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, /aksrt_user_can\('upload_comment_image'/);
|
||||
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\)/);
|
||||
|
||||
@@ -16,16 +16,16 @@ final class WP_Comment_Query {
|
||||
}
|
||||
}
|
||||
|
||||
final class AKSRT_Test_WPDB {
|
||||
final class JIO_Test_WPDB {
|
||||
public string $comments = 'wp_comments';
|
||||
public string $commentmeta = 'wp_commentmeta';
|
||||
}
|
||||
|
||||
$GLOBALS['wpdb'] = new AKSRT_Test_WPDB();
|
||||
$GLOBALS['aksrt_test_filters'] = [];
|
||||
$GLOBALS['wpdb'] = new JIO_Test_WPDB();
|
||||
$GLOBALS['jio_test_filters'] = [];
|
||||
|
||||
function add_filter(string $tag, callable $callback, int $priority = 10, int $accepted_args = 1): void {
|
||||
$GLOBALS['aksrt_test_filters'][$tag][] = [$callback, $priority, $accepted_args];
|
||||
$GLOBALS['jio_test_filters'][$tag][] = [$callback, $priority, $accepted_args];
|
||||
}
|
||||
|
||||
function sanitize_key(string $key): string {
|
||||
@@ -36,8 +36,8 @@ function wp_unslash($value) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
function aksrt_test_apply_comments_clauses(array $clauses, WP_Comment_Query $query): array {
|
||||
$filters = $GLOBALS['aksrt_test_filters']['comments_clauses'] ?? [];
|
||||
function jio_test_apply_comments_clauses(array $clauses, WP_Comment_Query $query): array {
|
||||
$filters = $GLOBALS['jio_test_filters']['comments_clauses'] ?? [];
|
||||
usort($filters, static fn (array $a, array $b): int => $a[1] <=> $b[1]);
|
||||
foreach ($filters as [$callback, $priority, $accepted_args]) {
|
||||
$clauses = $callback($clauses, $query);
|
||||
@@ -45,19 +45,19 @@ function aksrt_test_apply_comments_clauses(array $clauses, WP_Comment_Query $que
|
||||
return $clauses;
|
||||
}
|
||||
|
||||
require __DIR__ . '/../../inc/helpers/aksrt-comments.php';
|
||||
require __DIR__ . '/../../inc/helpers/jio-comments.php';
|
||||
|
||||
$newest_args = aksrt_comments_template_query_args(['order' => 'ASC']);
|
||||
$newest_args = jio_comments_template_query_args(['order' => 'ASC']);
|
||||
if (($newest_args['order'] ?? '') !== 'DESC' || ($newest_args['orderby'] ?? '') !== 'comment_date_gmt') {
|
||||
throw new RuntimeException('The newest tab must use descending comment date order regardless of the WordPress discussion setting.');
|
||||
}
|
||||
|
||||
$_GET['aksrt_comment_order'] = 'hottest';
|
||||
$hottest_args = aksrt_comments_template_query_args([]);
|
||||
if (empty($hottest_args['aksrt_hottest_order']) || ($hottest_args['order'] ?? '') !== 'DESC') {
|
||||
$_GET['jio_comment_order'] = 'hottest';
|
||||
$hottest_args = jio_comments_template_query_args([]);
|
||||
if (empty($hottest_args['jio_hottest_order']) || ($hottest_args['order'] ?? '') !== 'DESC') {
|
||||
throw new RuntimeException('The hottest tab must set a descending, query-local hottest marker.');
|
||||
}
|
||||
unset($_GET['aksrt_comment_order']);
|
||||
unset($_GET['jio_comment_order']);
|
||||
|
||||
$base = [
|
||||
'join' => '',
|
||||
@@ -65,15 +65,15 @@ $base = [
|
||||
'orderby' => 'wp_comments.comment_date_gmt DESC',
|
||||
];
|
||||
|
||||
$hot = aksrt_test_apply_comments_clauses($base, new WP_Comment_Query([
|
||||
$hot = jio_test_apply_comments_clauses($base, new WP_Comment_Query([
|
||||
'post_id' => 42,
|
||||
'aksrt_hottest_order' => true,
|
||||
'jio_hottest_order' => true,
|
||||
]));
|
||||
|
||||
if (!str_contains($hot['join'], 'LEFT JOIN wp_commentmeta AS aksrt_cm_hot')) {
|
||||
if (!str_contains($hot['join'], 'LEFT JOIN wp_commentmeta AS jio_cm_hot')) {
|
||||
throw new RuntimeException('Hottest ordering must use LEFT JOIN so comments without like metadata remain in the result.');
|
||||
}
|
||||
if (!str_contains($hot['orderby'], 'COALESCE(aksrt_cm_hot.meta_value, 0) AS UNSIGNED) DESC')) {
|
||||
if (!str_contains($hot['orderby'], 'COALESCE(jio_cm_hot.meta_value, 0) AS UNSIGNED) DESC')) {
|
||||
throw new RuntimeException('Hottest ordering must sort likes from high to low and treat missing metadata as zero.');
|
||||
}
|
||||
if (!str_contains($hot['orderby'], 'wp_comments.comment_date_gmt DESC')) {
|
||||
@@ -83,7 +83,7 @@ if ($hot['where'] !== $base['where']) {
|
||||
throw new RuntimeException('Hottest sorting must not alter the query scope.');
|
||||
}
|
||||
|
||||
$ordinary = aksrt_test_apply_comments_clauses($base, new WP_Comment_Query([
|
||||
$ordinary = jio_test_apply_comments_clauses($base, new WP_Comment_Query([
|
||||
'post_id' => 99,
|
||||
'orderby' => 'comment_date_gmt',
|
||||
]));
|
||||
|
||||
@@ -9,32 +9,32 @@ const root = path.resolve(__dirname, '..', '..');
|
||||
const read = (file) => fs.readFileSync(path.join(root, file), 'utf8');
|
||||
|
||||
test('comment order matches the reference newest and hottest behavior', () => {
|
||||
const php = read('inc/helpers/aksrt-comments.php');
|
||||
const php = read('inc/helpers/jio-comments.php');
|
||||
|
||||
assert.match(php, /\$args\['order'\] = 'DESC';\s*\$args\['orderby'\] = 'comment_date_gmt';/);
|
||||
assert.match(php, /\$args\['aksrt_hottest_order'\] = true;/);
|
||||
assert.match(php, /CAST\(COALESCE\(aksrt_cm_hot\.meta_value, 0\) AS UNSIGNED\) DESC/);
|
||||
assert.match(php, /\$query->query_vars\['aksrt_hottest_order'\]/);
|
||||
assert.match(php, /\$args\['jio_hottest_order'\] = true;/);
|
||||
assert.match(php, /CAST\(COALESCE\(jio_cm_hot\.meta_value, 0\) AS UNSIGNED\) DESC/);
|
||||
assert.match(php, /\$query->query_vars\['jio_hottest_order'\]/);
|
||||
assert.equal(php.includes("$args['order'] = 'ASC'"), false, 'the reference always orders newest comments first');
|
||||
assert.equal(php.includes("$order === 'oldest'"), false, 'the unsupported oldest tab must not remain in the UI query path');
|
||||
assert.equal(php.includes('aksrt_comments_apply_hottest_order'), false, 'unscoped global hot-order hook must be removed');
|
||||
assert.equal(php.includes('jio_comments_apply_hottest_order'), false, 'unscoped global hot-order hook must be removed');
|
||||
assert.equal(php.includes('reverse_top_level'), false, 'DESC results must not be reversed a second time');
|
||||
});
|
||||
|
||||
test('empty comment state uses the reference illustration and caption', () => {
|
||||
const php = read('inc/helpers/aksrt-comments.php');
|
||||
const php = read('inc/helpers/jio-comments.php');
|
||||
|
||||
assert.match(php, /aksrt_get_null\(\$text, 40, 'comment-null\.svg', 'comment comment-null', 280\)/);
|
||||
assert.match(php, /\$text = __\('暂无评论内容', 'aksrt'\);/);
|
||||
assert.match(php, /jio_get_null\(\$text, 40, 'comment-null\.svg', 'comment comment-null', 280\)/);
|
||||
assert.match(php, /\$text = __\('暂无评论内容', 'jio'\);/);
|
||||
assert.ok(fs.existsSync(path.join(root, 'assets/img/comment-null.svg')), 'the reference empty-state illustration must ship with the theme');
|
||||
assert.equal(php.includes('暂无作者评论'), false, 'empty filter state uses the reference caption');
|
||||
});
|
||||
|
||||
test('the shared empty illustration stays centered after the block-level media reset', () => {
|
||||
assert.match(read('inc/helpers/aksrt-null.php'), /<img class="no-imgbox aksrt-null__img"/);
|
||||
assert.match(read('inc/helpers/jio-null.php'), /<img class="no-imgbox jio-null__img"/);
|
||||
for (const file of ['assets/css/frontend/empty-state.css', 'assets/css/build/public.css']) {
|
||||
const css = read(file);
|
||||
const rule = css.match(/\.aksrt-null\s*>\s*\.aksrt-null__img\s*\{([^}]+)\}/);
|
||||
const rule = css.match(/\.jio-null\s*>\s*\.jio-null__img\s*\{([^}]+)\}/);
|
||||
assert.ok(rule, `${file} must provide the shared illustration alignment`);
|
||||
assert.match(rule[1], /display:\s*block;/);
|
||||
assert.match(rule[1], /margin-right:\s*auto;/);
|
||||
|
||||
@@ -28,7 +28,7 @@ test('comment smilie assets and their runtime chain stay removed', () => {
|
||||
'assets/js/runtime/imgbox.min.js',
|
||||
'assets/js/runtime/main.js',
|
||||
'assets/js/runtime/main.min.js',
|
||||
'inc/helpers/aksrt-comments.php',
|
||||
'inc/helpers/jio-comments.php',
|
||||
'inc/helpers/comments/content.php',
|
||||
'inc/helpers/comments/form-parts.php',
|
||||
'src/Admin/Schema/SchemaPostList.php',
|
||||
@@ -38,7 +38,7 @@ test('comment smilie assets and their runtime chain stay removed', () => {
|
||||
const forbidden = [
|
||||
'comment_smilie',
|
||||
'commentSmiliesBase',
|
||||
'aksrt_comment_smilie',
|
||||
'jio_comment_smilie',
|
||||
'assets/img/smilies',
|
||||
'data-smilie',
|
||||
'dropdown-smilie',
|
||||
|
||||
@@ -16,7 +16,7 @@ test('term aggregation renders the real SVG use element, total count, and config
|
||||
const fixture = renderFixtures();
|
||||
const card = fixture.aggregation;
|
||||
assert.match(card, /<svg class="zibsvg-hot" viewbox="0 0 16 16" aria-hidden="true"><use href="#zibsvg-hot"><\/use><\/svg>/);
|
||||
const sanitizer = read('inc/helpers/aksrt-graphic-card.php');
|
||||
const sanitizer = read('inc/helpers/jio-graphic-card.php');
|
||||
assert.match(sanitizer, /'svg'\s*=>\s*\['class'\s*=>\s*true,\s*'viewbox'\s*=>\s*true,\s*'aria-hidden'\s*=>\s*true\]/);
|
||||
assert.match(sanitizer, /'use'\s*=>\s*\['href'\s*=>\s*true,\s*'xlink:href'\s*=>\s*true\]/);
|
||||
assert.match(card, /7篇文章/);
|
||||
@@ -66,11 +66,11 @@ test('category header matches first-page, later-page, and simple-title render co
|
||||
assert.match(second, /search-trem="7"/);
|
||||
|
||||
const simple = fixture.term_header_simple;
|
||||
assert.match(simple, /class="aksrt-panel relative"/);
|
||||
assert.match(simple, /class="jio-panel relative"/);
|
||||
assert.match(simple, /title-h-left/);
|
||||
assert.match(simple, /共31篇/);
|
||||
assert.match(simple, /简洁描述/);
|
||||
assert.doesNotMatch(simple, /class="page-cover aksrt-panel|<img/);
|
||||
assert.doesNotMatch(simple, /class="page-cover jio-panel|<img/);
|
||||
});
|
||||
|
||||
test('homepage sorting retains distinct dropdown and inline option containers', () => {
|
||||
@@ -97,9 +97,9 @@ test('number pagination renders a page trigger and tab panes distinguish loaded
|
||||
});
|
||||
|
||||
test('archive filters use the shared ajax-next handler without a category REST/archive script path', () => {
|
||||
const filters = read('inc/helpers/aksrt-archive-filters.php');
|
||||
const filters = read('inc/helpers/jio-archive-filters.php');
|
||||
const ajax = read('assets/js/core/ajax-list.js');
|
||||
const archives = read('inc/helpers/aksrt-archive.php');
|
||||
const archives = read('inc/helpers/jio-archive.php');
|
||||
const globalController = read('src/Admin/GlobalController.php');
|
||||
assert.match(filters, /class="ajax-option ajax-replace" win-ajax-replace="filter"/);
|
||||
assert.match(filters, /class="ajax-next/);
|
||||
|
||||
@@ -76,16 +76,16 @@ function wp_kses($value, array $allowed): string {
|
||||
function is_wp_error($value): bool { return $value instanceof WP_Error; }
|
||||
function wp_is_mobile(): bool { return false; }
|
||||
function wp_reset_postdata(): void { $GLOBALS['discovery_current_post'] = null; }
|
||||
function aksrt_is_lazy(string $key, bool $default = false): bool { return false; }
|
||||
function aksrt_get_lazy_attr(string $type, string $url, string $class = '', string $lazy = ''): string { return ' class="' . esc_attr($class) . '" src="' . esc_url($url) . '"'; }
|
||||
function aksrt_format_number($number): string { return number_format((int) $number); }
|
||||
function aksrt_renderer_svg_icon(string $name): string { return '<svg class="zibsvg-' . esc_attr($name) . '" viewbox="0 0 16 16" aria-hidden="true"><use href="#zibsvg-' . esc_attr($name) . '"></use></svg>'; }
|
||||
function aksrt_renderer_collect_post_data(array $config = []): array {
|
||||
function jio_is_lazy(string $key, bool $default = false): bool { return false; }
|
||||
function jio_get_lazy_attr(string $type, string $url, string $class = '', string $lazy = ''): string { return ' class="' . esc_attr($class) . '" src="' . esc_url($url) . '"'; }
|
||||
function jio_format_number($number): string { return number_format((int) $number); }
|
||||
function jio_renderer_svg_icon(string $name): string { return '<svg class="zibsvg-' . esc_attr($name) . '" viewbox="0 0 16 16" aria-hidden="true"><use href="#zibsvg-' . esc_attr($name) . '"></use></svg>'; }
|
||||
function jio_renderer_collect_post_data(array $config = []): array {
|
||||
$post = $GLOBALS['discovery_current_post'];
|
||||
return ['post_id' => $post->ID, 'title' => '文章 ' . $post->ID, 'subtitle' => '', 'permalink' => 'https://example.test/post/' . $post->ID, 'time_ago' => '2天前', 'views' => 42, 'comments' => 3];
|
||||
}
|
||||
function aksrt_get_post_favorites(int $id): int { return 5; }
|
||||
function aksrt_get_post_likes(int $id): int { return 6; }
|
||||
function jio_get_post_favorites(int $id): int { return 5; }
|
||||
function jio_get_post_likes(int $id): int { return 6; }
|
||||
function get_term(int $id, string $taxonomy = 'category') { return $GLOBALS['discovery_terms'][$id] ?? new WP_Error('missing'); }
|
||||
function get_term_meta(int $id, string $key, bool $single = true): int { return $id === 7 ? 77 : 0; }
|
||||
function wp_get_attachment_url(int $id): string { return $id === 77 ? 'https://example.test/category-cover.jpg' : ''; }
|
||||
@@ -94,9 +94,9 @@ function get_the_post_thumbnail_url($id = null, string $size = 'post-thumbnail')
|
||||
function get_term_link(WP_Term $term) { return 'https://example.test/category/' . $term->term_id; }
|
||||
function current_user_can(string $capability): bool { return false; }
|
||||
function get_edit_term_link(int $id, string $taxonomy): string { return ''; }
|
||||
function aksrt_widget_str_cut(string $text, int $length): string { return $text; }
|
||||
function aksrt_render_placeholder(string $style, int $count): string { return '<div data-placeholder="' . esc_attr($style) . ':' . $count . '"></div>'; }
|
||||
function aksrt_render_post_list(array $args, WP_Query $query, bool $echo = true): string { return '<div class="rendered-post-count" data-count="' . count($query->posts) . '"></div>'; }
|
||||
function jio_widget_str_cut(string $text, int $length): string { return $text; }
|
||||
function jio_render_placeholder(string $style, int $count): string { return '<div data-placeholder="' . esc_attr($style) . ':' . $count . '"></div>'; }
|
||||
function jio_render_post_list(array $args, WP_Query $query, bool $echo = true): string { return '<div class="rendered-post-count" data-count="' . count($query->posts) . '"></div>'; }
|
||||
function get_terms(array $args) {
|
||||
$ids = array_map('intval', (array) ($args['include'] ?? []));
|
||||
if (($args['fields'] ?? '') === 'ids') return [];
|
||||
@@ -107,8 +107,8 @@ function get_queried_object() { return $GLOBALS['discovery_queried_term']; }
|
||||
function get_query_var(string $name, $default = '') { return $name === 'paged' ? $GLOBALS['discovery_paged'] : $default; }
|
||||
function get_ancestors(int $id, string $taxonomy, string $resource_type): array { return []; }
|
||||
function wp_parse_args_filter($args, array $defaults): array { return array_merge($defaults, $args); }
|
||||
function aksrt_homepage_get_current_url(): string { return 'https://example.test/?paged=3'; }
|
||||
function aksrt_homepage_url_del_paged(string $url): string { return preg_replace('/[?&]paged=\d+/', '', $url) ?: $url; }
|
||||
function jio_homepage_get_current_url(): string { return 'https://example.test/?paged=3'; }
|
||||
function jio_homepage_url_del_paged(string $url): string { return preg_replace('/[?&]paged=\d+/', '', $url) ?: $url; }
|
||||
function add_query_arg($args, $url = false): string {
|
||||
if (is_array($args)) return (string) $url . (str_contains((string) $url, '?') ? '&' : '?') . http_build_query($args);
|
||||
return 'https://example.test/';
|
||||
@@ -119,12 +119,12 @@ function get_template_directory_uri(): string { return 'https://example.test/the
|
||||
function wp_unslash($value): string { return (string) $value; }
|
||||
|
||||
require dirname(__DIR__, 2) . '/vendor/autoload.php';
|
||||
require dirname(__DIR__, 2) . '/inc/helpers/aksrt-graphic-card.php';
|
||||
require dirname(__DIR__, 2) . '/inc/helpers/aksrt-widgets-markup.php';
|
||||
require dirname(__DIR__, 2) . '/inc/helpers/aksrt-homepage.php';
|
||||
require dirname(__DIR__, 2) . '/inc/helpers/aksrt-archive-filters.php';
|
||||
require dirname(__DIR__, 2) . '/inc/helpers/jio-graphic-card.php';
|
||||
require dirname(__DIR__, 2) . '/inc/helpers/jio-widgets-markup.php';
|
||||
require dirname(__DIR__, 2) . '/inc/helpers/jio-homepage.php';
|
||||
require dirname(__DIR__, 2) . '/inc/helpers/jio-archive-filters.php';
|
||||
|
||||
use AKSRT\WidgetSystem\Renderers\ContentRenderer;
|
||||
use Jio\WidgetSystem\Renderers\ContentRenderer;
|
||||
|
||||
$aggregation = ContentRenderer::render('term_aggregation', [
|
||||
'term_id' => [7, 8], 'pc_row' => 2, 'orderby' => 'modified', 'order' => 'desc', 'count' => 2,
|
||||
@@ -132,14 +132,14 @@ $aggregation = ContentRenderer::render('term_aggregation', [
|
||||
$tabs = ContentRenderer::render('tab_posts', [
|
||||
'style' => 'list', 'count' => 2, 'paginate' => 'number',
|
||||
'tabs' => [['title' => '技术', 'cat' => '7'], ['title' => '空栏目', 'cat' => '8']],
|
||||
], ['widget_id' => 'aksrt_widget_tab_posts-4']);
|
||||
$posts = ContentRenderer::render('posts', ['style' => 'list', 'count' => 2, 'paginate' => 'number'], ['widget_id' => 'aksrt_widget_posts-3']);
|
||||
], ['widget_id' => 'jio_widget_tab_posts-4']);
|
||||
$posts = ContentRenderer::render('posts', ['style' => 'list', 'count' => 2, 'paginate' => 'number'], ['widget_id' => 'jio_widget_posts-3']);
|
||||
|
||||
$filter_group = aksrt_archive_build_term_filter_group('category', [
|
||||
$filter_group = jio_archive_build_term_filter_group('category', [
|
||||
'lists' => [7], 'dropdown' => true, 'dropdown_lists' => [9],
|
||||
], '分类');
|
||||
ob_start();
|
||||
aksrt_archive_render_filter_bar(['groups' => [$filter_group], 'aria_label' => '分类筛选']);
|
||||
jio_archive_render_filter_bar(['groups' => [$filter_group], 'aria_label' => '分类筛选']);
|
||||
$archive_filter_html = (string) ob_get_clean();
|
||||
|
||||
$GLOBALS['discovery_queried_term'] = $GLOBALS['discovery_terms'][7];
|
||||
@@ -147,7 +147,7 @@ $GLOBALS['wp_query'] = new WP_Query();
|
||||
$GLOBALS['wp_query']->found_posts = 31;
|
||||
$GLOBALS['discovery_paged'] = 1;
|
||||
ob_start();
|
||||
aksrt_archive_render_term_header([
|
||||
jio_archive_render_term_header([
|
||||
'header_style' => 'cover', 'show_cover' => true, 'cover_url' => '',
|
||||
'title' => '技术分类', 'description' => '首屏描述', 'post_count' => true,
|
||||
]);
|
||||
@@ -155,7 +155,7 @@ $term_header_first = (string) ob_get_clean();
|
||||
|
||||
$GLOBALS['discovery_paged'] = 2;
|
||||
ob_start();
|
||||
aksrt_archive_render_term_header([
|
||||
jio_archive_render_term_header([
|
||||
'header_style' => 'cover', 'show_cover' => true, 'cover_url' => '',
|
||||
'title' => '技术分类', 'description' => '第二页描述', 'post_count' => true,
|
||||
]);
|
||||
@@ -163,13 +163,13 @@ $term_header_second = (string) ob_get_clean();
|
||||
|
||||
$GLOBALS['discovery_paged'] = 1;
|
||||
ob_start();
|
||||
aksrt_archive_render_term_header([
|
||||
jio_archive_render_term_header([
|
||||
'header_style' => 'simple', 'show_cover' => false,
|
||||
'title' => '技术分类', 'description' => '简洁描述', 'post_count' => true,
|
||||
]);
|
||||
$term_header_simple = (string) ob_get_clean();
|
||||
|
||||
$home_sort = aksrt_homepage_get_option_orderby_but(['lists' => ['date', 'views'], 'dropdown' => true]);
|
||||
$home_sort = jio_homepage_get_option_orderby_but(['lists' => ['date', 'views'], 'dropdown' => true]);
|
||||
|
||||
$result = json_encode([
|
||||
'aggregation' => $aggregation,
|
||||
|
||||
@@ -17,21 +17,21 @@ test('the token relay is fully retired', () => {
|
||||
assert.doesNotMatch(read('functions.php'), /Content\\Download'/);
|
||||
|
||||
// The orphaned cleanup cron is still unscheduled on theme switch.
|
||||
assert.match(read('functions.php'), /aksrt_dl_token_cleanup/);
|
||||
assert.match(read('functions.php'), /jio_dl_token_cleanup/);
|
||||
|
||||
// Header query-var branch and SEO noindex branch are gone with the route.
|
||||
assert.doesNotMatch(read('inc/helpers/homepage/header-components.php'), /aksrt_dl_token/);
|
||||
assert.doesNotMatch(read('inc/helpers/homepage/header-components.php'), /jio_dl_token/);
|
||||
assert.doesNotMatch(read('src/Core/SEOHead.php'), /render_robots/);
|
||||
assert.doesNotMatch(read('src/Core/SEOHead.php'), /aksrt_dl_token/);
|
||||
assert.doesNotMatch(read('src/Core/SEOHead.php'), /jio_dl_token/);
|
||||
});
|
||||
|
||||
test('the download box renders direct anchors with inline extract codes', () => {
|
||||
const box = read('templates/parts/resource-download-box.php');
|
||||
|
||||
// No relay button, captcha surface or low-credit data attributes.
|
||||
assert.doesNotMatch(box, /data-action="aksrt-download"/);
|
||||
assert.doesNotMatch(box, /data-action="jio-download"/);
|
||||
assert.doesNotMatch(box, /data-captcha/);
|
||||
assert.doesNotMatch(box, /data-aksrt-download-captcha/);
|
||||
assert.doesNotMatch(box, /data-jio-download-captcha/);
|
||||
assert.doesNotMatch(box, /captcha_active/);
|
||||
assert.doesNotMatch(box, /data-balance|data-cost/);
|
||||
|
||||
@@ -48,7 +48,7 @@ test('the download box renders direct anchors with inline extract codes', () =>
|
||||
assert.match(box, /data-clipboard-tag="<\?php echo esc_attr\(\$has_copy \?/);
|
||||
|
||||
// Custom icon / label / color from the pay meta feed the anchor.
|
||||
assert.match(box, /aksrt_icon_markup\(\$icon\)/);
|
||||
assert.match(box, /jio_icon_markup\(\$icon\)/);
|
||||
assert.match(box, /\$btn_text = \$label;/);
|
||||
assert.match(box, /resource-download-box__btn--custom/);
|
||||
|
||||
@@ -65,10 +65,10 @@ test('the download box renders direct anchors with inline extract codes', () =>
|
||||
// Hovering the remark shows 点击复制{copy name}, matching the reference theme,
|
||||
// and only when a copy name exists.
|
||||
assert.match(box, /\$has_copy && \$copy_key !== ''\) : \?>/);
|
||||
assert.match(box, /sprintf\(__\('点击复制%s', 'aksrt'\), \$copy_key\)/);
|
||||
assert.match(box, /sprintf\(__\('点击复制%s', 'jio'\), \$copy_key\)/);
|
||||
assert.match(box, /data-toggle="tooltip"/);
|
||||
const pay = read('inc/helpers/aksrt-pay.php');
|
||||
assert.match(pay, /sprintf\(__\('点击复制%s', 'aksrt'\), \$copy_key\)/);
|
||||
const pay = read('inc/helpers/jio-pay.php');
|
||||
assert.match(pay, /sprintf\(__\('点击复制%s', 'jio'\), \$copy_key\)/);
|
||||
// Bootstrap tooltips must be initialized for the attribute to do anything.
|
||||
const main = read('assets/js/runtime/main.js');
|
||||
assert.match(main, /jQuery\("\[data-toggle='tooltip'\]"\)\.tooltip\(/);
|
||||
@@ -79,11 +79,11 @@ test('the download box renders direct anchors with inline extract codes', () =>
|
||||
});
|
||||
|
||||
test('the paid card download actions render direct anchors', () => {
|
||||
const pay = read('inc/helpers/aksrt-pay.php');
|
||||
const pay = read('inc/helpers/jio-pay.php');
|
||||
|
||||
assert.doesNotMatch(pay, /data-action="aksrt-download"/);
|
||||
assert.doesNotMatch(pay, /data-action="jio-download"/);
|
||||
assert.doesNotMatch(pay, /data-captcha/);
|
||||
assert.doesNotMatch(pay, /data-aksrt-download-captcha/);
|
||||
assert.doesNotMatch(pay, /data-jio-download-captcha/);
|
||||
assert.doesNotMatch(pay, /captcha_active/);
|
||||
|
||||
// Direct anchor to the raw source URL, empty sources skipped.
|
||||
@@ -99,28 +99,28 @@ test('cms-download.js only serves the cashier now', () => {
|
||||
|
||||
// The download request flow is gone.
|
||||
assert.doesNotMatch(js, /download\/request/);
|
||||
assert.doesNotMatch(js, /data-action="aksrt-download"/);
|
||||
assert.doesNotMatch(js, /data-action="jio-download"/);
|
||||
assert.doesNotMatch(js, /renderSuccess|startDownload|performDownloadRequest|requestDownload/);
|
||||
assert.doesNotMatch(js, /AKSRTCaptcha/);
|
||||
assert.doesNotMatch(js, /JioCaptcha/);
|
||||
|
||||
// Cashier, scan-pay and error views remain.
|
||||
assert.match(js, /data-aksrt-pay-content/);
|
||||
assert.match(js, /window\.aksrtOpenCashier = function/);
|
||||
assert.match(js, /data-jio-pay-content/);
|
||||
assert.match(js, /window\.jioOpenCashier = function/);
|
||||
assert.match(js, /function renderCashier/);
|
||||
assert.match(js, /function renderScanPay/);
|
||||
assert.match(js, /function renderError/);
|
||||
|
||||
// The script is still enqueued for resource / paid singles.
|
||||
assert.match(read('src/Core/ScriptsPart1b.php'), /'aksrt-cms-download'/);
|
||||
assert.match(read('src/Core/ScriptsPart1b.php'), /'jio-cms-download'/);
|
||||
});
|
||||
|
||||
test('the admin download-logs page and its wiring are gone', () => {
|
||||
// 下载记录菜单页面:类文件、菜单注册、init 接线、各入口链接全部移除。
|
||||
assert.equal(exists('src/Admin/Native/PayDownloadLogs.php'), false);
|
||||
assert.doesNotMatch(read('src/Admin/Native/PayMenus.php'), /download_logs|aksrt_download_logs|PayDownloadLogs/)
|
||||
assert.doesNotMatch(read('src/Admin/Native/PayMenus.php'), /download_logs|jio_download_logs|PayDownloadLogs/)
|
||||
assert.doesNotMatch(read('functions.php'), /PayDownloadLogs/)
|
||||
assert.doesNotMatch(read('src/Admin/Native/PayProducts.php'), /aksrt_download_logs/)
|
||||
assert.doesNotMatch(read('src/User/CreditsAdmin.php'), /aksrt_download_logs|下载记录/)
|
||||
assert.doesNotMatch(read('src/Admin/Native/PayProducts.php'), /jio_download_logs/)
|
||||
assert.doesNotMatch(read('src/User/CreditsAdmin.php'), /jio_download_logs|下载记录/)
|
||||
})
|
||||
|
||||
test('download log retention settings no longer exist', () => {
|
||||
@@ -133,11 +133,11 @@ test('download log retention settings no longer exist', () => {
|
||||
test('downloads stay unrecorded', () => {
|
||||
// 下载不做记录:没有任何 PHP 写入下载计数或下载令牌。
|
||||
const php = [
|
||||
'inc/helpers/aksrt-pay.php',
|
||||
'inc/helpers/aksrt-resource.php',
|
||||
'inc/helpers/jio-pay.php',
|
||||
'inc/helpers/jio-resource.php',
|
||||
'src/Content/PayRest.php',
|
||||
'src/Content/Visibility.php',
|
||||
].map(read).join('\n')
|
||||
assert.doesNotMatch(php, /update_post_meta\([^)]*download_count/)
|
||||
assert.doesNotMatch(php, /aksrt_update_post_counter\([^)]*download/)
|
||||
assert.doesNotMatch(php, /jio_update_post_counter\([^)]*download/)
|
||||
});
|
||||
@@ -36,16 +36,16 @@ test('forum-only events stay out of the email catalog', () => {
|
||||
})
|
||||
|
||||
test('email settings keep one canonical SMTP schema', () => {
|
||||
assert.match(templates, /'option_key'\s*=>\s*'aksrt_settings'/)
|
||||
assert.match(templates, /'option_key'\s*=>\s*'jio_settings'/)
|
||||
assert.match(templates, /'mail_passwd'.*'secret'\s*=>\s*true/)
|
||||
assert.match(templates, /'email_test_send'.*'ui_only'\s*=>\s*true/)
|
||||
assert.match(mail, /Settings::get_all\(\)/)
|
||||
assert.match(mail, /'auth'\s*=>\s*\$username\s*!==\s*''\s*&&\s*\$password\s*!==\s*''/)
|
||||
assert.doesNotMatch(templates, /mail_smtpauth/)
|
||||
assert.doesNotMatch(mail, /mail_smtpauth/)
|
||||
assert.doesNotMatch(settings, /aksrt_mail_smtp/)
|
||||
assert.doesNotMatch(globalController, /aksrt_mail_config|\/settings\/mail'/)
|
||||
assert.doesNotMatch(userController, /aksrt_mail_templates|mail-templates/)
|
||||
assert.doesNotMatch(settings, /jio_mail_smtp/)
|
||||
assert.doesNotMatch(globalController, /jio_mail_config|\/settings\/mail'/)
|
||||
assert.doesNotMatch(userController, /jio_mail_templates|mail-templates/)
|
||||
})
|
||||
|
||||
test('email event schema exposes only notification switches', () => {
|
||||
@@ -76,7 +76,7 @@ test('SMTP authentication and failures use the canonical mail service', () => {
|
||||
assert.doesNotMatch(coreHooks, /\$phpmailer->setFrom\(/)
|
||||
assert.match(mail, /add_action\('wp_mail_failed',\s*\$capture_failure\)/)
|
||||
assert.match(mail, /remove_action\('wp_mail_failed',\s*\$capture_failure\)/)
|
||||
assert.match(mail, /error_log\('\[AKSRT\] SMTP 测试失败:'/)
|
||||
assert.match(mail, /error_log\('\[Jio\] SMTP 测试失败:'/)
|
||||
assert.match(globalController, /详细原因已写入服务器错误日志/)
|
||||
})
|
||||
|
||||
@@ -102,9 +102,9 @@ test('only the canonical mail service calls wp_mail', () => {
|
||||
|
||||
test('financial and account hooks are wired to the notification service', () => {
|
||||
for (const hook of [
|
||||
'aksrt_order_paid', 'aksrt_order_status_changed', 'aksrt_asset_transaction_committed',
|
||||
'aksrt_withdrawal_submitted', 'aksrt_withdrawal_status_changed',
|
||||
'aksrt_user_phone_changed',
|
||||
'jio_order_paid', 'jio_order_status_changed', 'jio_asset_transaction_committed',
|
||||
'jio_withdrawal_submitted', 'jio_withdrawal_status_changed',
|
||||
'jio_user_phone_changed',
|
||||
]) {
|
||||
assert.match(notifications, new RegExp(`add_action\\('${hook}'`), hook)
|
||||
}
|
||||
|
||||
@@ -8,10 +8,10 @@ const read = file => fs.readFileSync(path.join(root, file), 'utf8')
|
||||
|
||||
test('financial flow and withdrawal numbers use the new-only schema', () => {
|
||||
const schema = read('src/Core/DBMigratorPart2.php')
|
||||
assert.match(schema, /CREATE TABLE \{\$prefix\}aksrt_financial_flow \([\s\S]*?UNIQUE KEY uniq_flow_number \(flow_number\)/)
|
||||
assert.match(schema, /CREATE TABLE \{\$prefix\}jio_financial_flow \([\s\S]*?UNIQUE KEY uniq_flow_number \(flow_number\)/)
|
||||
assert.match(schema, /UNIQUE KEY uniq_flow_operation \(operation_key\)/)
|
||||
assert.match(schema, /CREATE TABLE \{\$prefix\}aksrt_financial_flow_entry \([\s\S]*?UNIQUE KEY uniq_flow_line \(flow_id, line_no\)/)
|
||||
assert.match(schema, /CREATE TABLE \{\$prefix\}aksrt_withdrawal \([\s\S]*?withdrawal_number VARCHAR\(32\) NOT NULL[\s\S]*?UNIQUE KEY uniq_withdrawal_number \(withdrawal_number\)/)
|
||||
assert.match(schema, /CREATE TABLE \{\$prefix\}jio_financial_flow_entry \([\s\S]*?UNIQUE KEY uniq_flow_line \(flow_id, line_no\)/)
|
||||
assert.match(schema, /CREATE TABLE \{\$prefix\}jio_withdrawal \([\s\S]*?withdrawal_number VARCHAR\(32\) NOT NULL[\s\S]*?UNIQUE KEY uniq_withdrawal_number \(withdrawal_number\)/)
|
||||
})
|
||||
|
||||
test('order, flow and withdrawal numbers share one prefix-free allocator', () => {
|
||||
@@ -93,10 +93,10 @@ test('user withdrawal history uses one paginated ten-item endpoint', () => {
|
||||
assert.match(controller, /'user_id'\s*=>\s*\$user_id/)
|
||||
assert.doesNotMatch(renderer, /get_user_withdrawals\(/)
|
||||
assert.doesNotMatch(renderer, /withdraw_history/)
|
||||
assert.match(renderer, /data-aksrt-withdraw-history-list/)
|
||||
assert.match(renderer, /data-jio-withdraw-history-list/)
|
||||
assert.match(script, /\/earnings\/withdrawals\?page=/)
|
||||
assert.match(script, /&per_page=10/)
|
||||
assert.match(script, /data-aksrt-withdraw-history-page/)
|
||||
assert.match(script, /data-jio-withdraw-history-page/)
|
||||
assert.match(script, /实际到账:/)
|
||||
assert.match(script, /元)/)
|
||||
assert.match(script, /appendText\(row, text\(item && item\.created_at\)/)
|
||||
@@ -136,12 +136,12 @@ test('order detail page drops tabs; ticket center replaces the after-sale submen
|
||||
assert.ok(!page.includes('stat_card'), 'stat cards must be removed')
|
||||
assert.ok(!repository.includes('get_admin_order_stats'), 'dead stats query must be removed')
|
||||
// 售后整体移除:工单改走 users.php 的「工单管理」子菜单,支付中心不再承载售后
|
||||
assert.ok(!menus.includes('aksrt_after_sales'), 'after-sales submenu slug must be gone')
|
||||
assert.ok(!menus.includes('jio_after_sales'), 'after-sales submenu slug must be gone')
|
||||
assert.ok(!page.includes('render_after_sales_admin_page'), 'after-sales admin page entry must be gone')
|
||||
assert.ok(!page.includes('PAGE_AFTER_SALES_SLUG'), 'after-sales slug constant must be gone')
|
||||
assert.ok(!page.includes('after_sale'), 'no after-sale handler may remain on the orders page')
|
||||
const usersMenus = read('src/Admin/Native/UsersMenus.php')
|
||||
assert.ok(usersMenus.includes("'aksrt_tickets'"), 'ticket submenu slug registered under users.php')
|
||||
assert.ok(usersMenus.includes("'jio_tickets'"), 'ticket submenu slug registered under users.php')
|
||||
assert.ok(usersMenus.includes('render_tickets_page'), 'ticket admin page entry registered')
|
||||
const ticketTrait = read('src/Admin/Native/UsersMenusTicketsCenter.php')
|
||||
assert.ok(ticketTrait.includes('use UsersMenusTicketsList;'), 'ticket list renderer trait composed')
|
||||
@@ -155,11 +155,11 @@ test('order detail page drops tabs; ticket center replaces the after-sale submen
|
||||
test('paid orders expose a refund button next to the detail link', () => {
|
||||
const page = ['PayOrders', 'PayOrdersList', 'PayOrdersDetail'].map((f) => read(`src/Admin/Native/${f}.php`)).join('\n')
|
||||
// 「标记退款」紧跟在「详情」右侧(行操作区由 order_actions_html 输出),仅已支付订单可见——已退款是已支付的唯一后继状态
|
||||
assert.match(page, /详情', 'aksrt'\) \. '<\/a>'\s*\n\s*\. \$this->order_actions_html\(\$order_number, \$status\)/)
|
||||
assert.match(page, /\/\/ 已支付是已退款的唯一前置状态。\s*\n\s*if \(\$status === DomainModel::ORDER_STATUS_PAID\) \{[\s\S]*?'aksrt_pay_refund_order'/,
|
||||
assert.match(page, /详情', 'jio'\) \. '<\/a>'\s*\n\s*\. \$this->order_actions_html\(\$order_number, \$status\)/)
|
||||
assert.match(page, /\/\/ 已支付是已退款的唯一前置状态。\s*\n\s*if \(\$status === DomainModel::ORDER_STATUS_PAID\) \{[\s\S]*?'jio_pay_refund_order'/,
|
||||
'refund button only renders for paid orders')
|
||||
assert.ok(page.includes('aksrt_pay_refund_order_'), 'per-order nonce covers the refund')
|
||||
assert.ok(page.includes("add_action('admin_post_aksrt_pay_refund_order', [$this, 'handle_refund_order'])"),
|
||||
assert.ok(page.includes('jio_pay_refund_order_'), 'per-order nonce covers the refund')
|
||||
assert.ok(page.includes("add_action('admin_post_jio_pay_refund_order', [$this, 'handle_refund_order'])"),
|
||||
'refund action registered')
|
||||
assert.ok(page.includes('handle_refund_order'), 'refund handler exists')
|
||||
// 退款闸门只剩「订单为已支付」:工单系统与订单退款已解耦,不再要求先有进行中的工单
|
||||
@@ -198,7 +198,7 @@ test('caller-supplied order numbers are constrained to allocator shapes', () =>
|
||||
// 不合规时必须回落到分配器,而不是原样写库。
|
||||
assert.match(repository, /\$order_number = \$caller_supplied \? \$declared_number : \$this->allocate_order_number\(\);/)
|
||||
// 弃用不受控号码时在 WP_DEBUG 下留痕。
|
||||
assert.match(repository, /error_log\(sprintf\('\[AKSRT Order\] 弃用不合规的自带订单号:%s'/)
|
||||
assert.match(repository, /error_log\(sprintf\('\[Jio Order\] 弃用不合规的自带订单号:%s'/)
|
||||
})
|
||||
|
||||
test('order cleanup no longer guards on financial records', () => {
|
||||
|
||||
@@ -27,16 +27,16 @@ const runtimeFiles = [
|
||||
test('front submit owns the typed media REST routes and media trait wiring', () => {
|
||||
assert.match(frontSubmit, /use FrontSubmitMedia\s*;/);
|
||||
assert.match(frontSubmit, /add_action\('rest_api_init', \[\$this, 'register_rest_routes'\]\)/);
|
||||
assert.doesNotMatch(frontSubmit, /add_action\('aksrt_new_(?:add|edit)_posts', \[\$this, 'sync_post_attachments'\]\)/);
|
||||
assert.doesNotMatch(frontSubmit, /add_action\('jio_new_(?:add|edit)_posts', \[\$this, 'sync_post_attachments'\]\)/);
|
||||
assert.match(saveTrait, /\$this->sync_post_attachments\(\$post\);\s*if \(!\$pay_result\['success'\]\)/);
|
||||
|
||||
assert.match(frontSubmit, /register_rest_route\('aksrt\/v1', '\/front-submit\/media\/\(\?P<type>image\|video\|file\)'/);
|
||||
assert.match(frontSubmit, /register_rest_route\('jio\/v1', '\/front-submit\/media\/\(\?P<type>image\|video\|file\)'/);
|
||||
assert.match(frontSubmit, /'methods'\s*=>\s*\\WP_REST_Server::READABLE/);
|
||||
assert.match(frontSubmit, /'callback'\s*=>\s*\[\$this, 'rest_list_attachments'\]/);
|
||||
assert.match(frontSubmit, /'post_id'\s*=>\s*\[/);
|
||||
assert.match(frontSubmit, /'permission_callback'\s*=>\s*\[\$this, 'can_list_attachments'\]/);
|
||||
|
||||
assert.match(frontSubmit, /register_rest_route\('aksrt\/v1', '\/front-submit\/upload\/\(\?P<kind>image\|video\|resource\)'/);
|
||||
assert.match(frontSubmit, /register_rest_route\('jio\/v1', '\/front-submit\/upload\/\(\?P<kind>image\|video\|resource\)'/);
|
||||
assert.match(frontSubmit, /'methods'\s*=>\s*'POST'/);
|
||||
assert.match(frontSubmit, /'callback'\s*=>\s*\[\$this, 'rest_upload_attachment'\]/);
|
||||
assert.match(frontSubmit, /'permission_callback'\s*=>\s*\[\$this, 'can_upload_attachment'\]/);
|
||||
@@ -76,8 +76,8 @@ test('front submit media responses and uploads use the attachment contract', ()
|
||||
assert.match(mediaTrait, /'filesizeInBytes'\s*=>/);
|
||||
|
||||
assert.match(frontSubmit, /\$files\['file'\]/);
|
||||
assert.match(frontSubmit, /media_handle_upload\('aksrt_front_submit_file'/);
|
||||
assert.match(frontSubmit, /update_post_meta\(\$attachment_id, '_aksrt_front_submit_media', \$kind\)/);
|
||||
assert.match(frontSubmit, /media_handle_upload\('jio_front_submit_file'/);
|
||||
assert.match(frontSubmit, /update_post_meta\(\$attachment_id, '_jio_front_submit_media', \$kind\)/);
|
||||
assert.match(frontSubmit, /\$attachment\['success'\]\s*=\s*true/);
|
||||
assert.match(editor, /body\.append\('file', file\)/);
|
||||
assert.match(editor, /method:\s*'POST'/);
|
||||
@@ -90,29 +90,29 @@ test('saved submission media sync enforces ownership and cleans stale parent/met
|
||||
assert.match(mediaTrait, /\(int\) \$attachment->post_author !== \$user_id/);
|
||||
assert.match(mediaTrait, /\['inherit', 'private'\]/);
|
||||
assert.match(mediaTrait, /\(int\) \$attachment->post_parent !== 0/);
|
||||
assert.match(mediaTrait, /'meta_key'\s*=>\s*'_aksrt_front_submit_media'/);
|
||||
assert.match(mediaTrait, /delete_post_meta\(\$attachment_id, '_aksrt_front_submit_media'\)/);
|
||||
assert.match(mediaTrait, /update_post_meta\(\$attachment_id, '_aksrt_front_submit_media', \$kind\)/);
|
||||
assert.match(mediaTrait, /'meta_key'\s*=>\s*'_jio_front_submit_media'/);
|
||||
assert.match(mediaTrait, /delete_post_meta\(\$attachment_id, '_jio_front_submit_media'\)/);
|
||||
assert.match(mediaTrait, /update_post_meta\(\$attachment_id, '_jio_front_submit_media', \$kind\)/);
|
||||
assert.match(mediaTrait, /featured_video_attachment_id/);
|
||||
assert.match(mediaTrait, /data-aksrt-file-id/);
|
||||
assert.match(mediaTrait, /data-jio-file-id/);
|
||||
assert.match(mediaTrait, /\['post_parent'\s*=>\s*\$post_id\]/);
|
||||
});
|
||||
|
||||
test('submit page configures media URLs, REST nonce, and the three media copy groups without mini-upload', () => {
|
||||
assert.match(page, /'restNonce'\s*=>\s*wp_create_nonce\('wp_rest'\)/);
|
||||
assert.match(page, /'mediaUrl'\s*=>\s*esc_url_raw\(rest_url\('aksrt\/v1\/front-submit\/media'\)\)/);
|
||||
assert.match(page, /'image'\s*=>\s*\['url'\s*=>\s*esc_url_raw\(rest_url\('aksrt\/v1\/front-submit\/media\/image'\)\)\]/);
|
||||
assert.match(page, /'video'\s*=>\s*\['url'\s*=>\s*esc_url_raw\(rest_url\('aksrt\/v1\/front-submit\/media\/video'\)\)\]/);
|
||||
assert.match(page, /'file'\s*=>\s*\['url'\s*=>\s*esc_url_raw\(rest_url\('aksrt\/v1\/front-submit\/media\/file'\)\)\]/);
|
||||
assert.match(page, /'mediaUrl'\s*=>\s*esc_url_raw\(rest_url\('jio\/v1\/front-submit\/media'\)\)/);
|
||||
assert.match(page, /'image'\s*=>\s*\['url'\s*=>\s*esc_url_raw\(rest_url\('jio\/v1\/front-submit\/media\/image'\)\)\]/);
|
||||
assert.match(page, /'video'\s*=>\s*\['url'\s*=>\s*esc_url_raw\(rest_url\('jio\/v1\/front-submit\/media\/video'\)\)\]/);
|
||||
assert.match(page, /'file'\s*=>\s*\['url'\s*=>\s*esc_url_raw\(rest_url\('jio\/v1\/front-submit\/media\/file'\)\)\]/);
|
||||
for (const copy of ['mineImage', 'mineVideo', 'mineFile', 'uploadImage', 'uploadVideo', 'uploadFile']) {
|
||||
assert.match(page, new RegExp(`['"]${copy}['"]`), `missing copy ${copy}`);
|
||||
}
|
||||
assert.doesNotMatch(page, /aksrt-mini-upload/);
|
||||
assert.doesNotMatch(page, /jio-mini-upload/);
|
||||
assert.equal(fs.existsSync(path.join(root, 'assets/js/mini-upload.js')), false, 'mini-upload.js must stay removed');
|
||||
});
|
||||
|
||||
test('shared submit media picker exposes mine search paging selection and nonce-backed upload', () => {
|
||||
assert.match(editor, /window\.AksrtSubmitMedia\s*=\s*\{\s*open:/);
|
||||
assert.match(editor, /window\.JioSubmitMedia\s*=\s*\{\s*open:/);
|
||||
assert.match(editor, /data-editor-media-tab="mine"/);
|
||||
assert.match(editor, /data-editor-media-search/);
|
||||
assert.match(editor, /state\.search\.length >= 2/);
|
||||
@@ -126,13 +126,13 @@ test('shared submit media picker exposes mine search paging selection and nonce-
|
||||
assert.match(editor, /if \(!multiple\) maxItems = 1/);
|
||||
assert.match(editor, /state\.selected\.length >= maxItems/);
|
||||
assert.match(editor, /headers:\s*\{\s*'X-WP-Nonce': cfg\.restNonce \|\| ''\s*\}/);
|
||||
assert.match(editor, /data-aksrt-file-id/);
|
||||
assert.match(editor, /data-jio-file-id/);
|
||||
assert.doesNotMatch(editor, /data-download-file/);
|
||||
assert.match(submit, /data-aksrt-submit-ready/);
|
||||
assert.match(submit, /data-jio-submit-ready/);
|
||||
});
|
||||
|
||||
test('paid downloads and featured covers use the shared picker and attachment IDs', () => {
|
||||
assert.match(payDownload, /window\.AksrtSubmitMedia\.open\(/);
|
||||
assert.match(payDownload, /window\.JioSubmitMedia\.open\(/);
|
||||
assert.match(payDownload, /type:\s*'file'/);
|
||||
assert.match(payDownload, /item\.attachment_id \|\| item\.id/);
|
||||
assert.match(payDownload, /data-pay-resource-field="attachment_id"/);
|
||||
@@ -140,7 +140,7 @@ test('paid downloads and featured covers use the shared picker and attachment ID
|
||||
assert.match(domainModel, /max\(0, \(int\) \$row\['attachment_id'\]\)/);
|
||||
|
||||
assert.match(submit, /openMediaPicker:\s*function \(options\)/);
|
||||
assert.match(submit, /window\.AksrtSubmitMedia\.open\(options\)/);
|
||||
assert.match(submit, /window\.JioSubmitMedia\.open\(options\)/);
|
||||
assert.match(submit, /type:\s*'image'/);
|
||||
assert.match(submit, /type:\s*'video'/);
|
||||
assert.match(submit, /item\.attachment_id \|\| item\.id/);
|
||||
|
||||
@@ -65,10 +65,10 @@ test('front submit uses the WordPress-managed jQuery runtime', () => {
|
||||
assert.doesNotMatch(source, /jquery-migrate/);
|
||||
assert.equal(fs.existsSync(path.join(root, 'assets/js/runtime/libs/jquery.min.js')), false);
|
||||
assert.equal(fs.existsSync(path.join(root, 'assets/js/runtime/libs/jquery-ui.min.js')), false);
|
||||
assert.doesNotMatch(read('inc/modules/user-parts/urls.php'), /aksrt_get_profile_url/);
|
||||
assert.doesNotMatch(read('inc/modules/user-parts/urls.php'), /jio_get_profile_url/);
|
||||
|
||||
const page = read('templates/pages/page-submit.php');
|
||||
assert.match(page, /\['loader_js', 'aksrt-front-submit-editor', 'aksrt-front-submit-pay-download'\]/);
|
||||
assert.match(page, /\['loader_js', 'jio-front-submit-editor', 'jio-front-submit-pay-download'\]/);
|
||||
});
|
||||
|
||||
test('legacy runtime modules use jQuery explicitly without a dollar alias', () => {
|
||||
@@ -96,8 +96,8 @@ test('theme runtime never changes native listener passivity for third-party edit
|
||||
const buildTool = read('tools/build-public-assets.mjs');
|
||||
const publicRuntime = read('assets/js/runtime/main.min.js');
|
||||
|
||||
assert.doesNotMatch(scripts, /enqueue_passive_polyfill|aksrt-passive-events/);
|
||||
assert.doesNotMatch(scriptsPart2, /enqueue_passive_polyfill|aksrt-passive-events/);
|
||||
assert.doesNotMatch(scripts, /enqueue_passive_polyfill|jio-passive-events/);
|
||||
assert.doesNotMatch(scriptsPart2, /enqueue_passive_polyfill|jio-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);
|
||||
@@ -137,6 +137,6 @@ test('front submit TinyMCE status bar shows one live character counter', () => {
|
||||
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|字数:/);
|
||||
assert.match(submitCss, /\.newposts-editor \.jio-submit-word-count\s*{[^}]*float:\s*right;/);
|
||||
assert.doesNotMatch(legacyPageEdit, /initWordCount|updateWordCount|jio-page-edit__word-count|字数:/);
|
||||
});
|
||||
@@ -143,9 +143,9 @@ namespace {
|
||||
|
||||
function fp_attachment(int $id, int $author = 7, string $mime = 'image/jpeg', int $parent = 0, string $status = 'inherit', bool $pending = true, ?string $kind = 'image'): WP_Post {
|
||||
$post = fp_post($id, 'attachment', $author, $status, '', $parent, $mime);
|
||||
if ($kind !== null) update_post_meta($id, '_aksrt_front_submit_media', $kind);
|
||||
if ($kind !== null) update_post_meta($id, '_jio_front_submit_media', $kind);
|
||||
$GLOBALS['front_pending_state']['url_map']['https://example.test/uploads/media-' . $id . '.' . fp_ext($mime)] = $id;
|
||||
if ($pending) \AKSRT\Attachment\PendingUploads::mark($id);
|
||||
if ($pending) \Jio\Attachment\PendingUploads::mark($id);
|
||||
return $post;
|
||||
}
|
||||
|
||||
@@ -156,7 +156,7 @@ namespace {
|
||||
}
|
||||
|
||||
function fp_pending(int $id): bool {
|
||||
return get_post_meta($id, \AKSRT\Attachment\PendingUploads::META, true) !== '';
|
||||
return get_post_meta($id, \Jio\Attachment\PendingUploads::META, true) !== '';
|
||||
}
|
||||
|
||||
function fp_article_data(string $content, array $extra = []): array {
|
||||
@@ -265,7 +265,7 @@ namespace {
|
||||
}
|
||||
return $id;
|
||||
}
|
||||
function aksrt_get_pay_meta($post_id): array {
|
||||
function jio_get_pay_meta($post_id): array {
|
||||
return $GLOBALS['front_pending_state']['pay_meta'][(int) $post_id] ?? [];
|
||||
}
|
||||
function attachment_url_to_postid($url): int {
|
||||
@@ -343,10 +343,10 @@ namespace {
|
||||
require_once dirname(__DIR__, 2) . '/src/Core/FrontSubmit.php';
|
||||
}
|
||||
|
||||
namespace AKSRT\Core {
|
||||
namespace Jio\Core {
|
||||
final class DomainModel {
|
||||
public const PAY_META_KEY = '_aksrt_pay';
|
||||
public const POST_META_KEY = 'aksrt_post_meta';
|
||||
public const PAY_META_KEY = '_jio_pay';
|
||||
public const POST_META_KEY = 'jio_post_meta';
|
||||
}
|
||||
|
||||
final class FrontSubmitPendingProbe {
|
||||
@@ -397,7 +397,7 @@ namespace AKSRT\Core {
|
||||
}
|
||||
}
|
||||
|
||||
namespace AKSRT\User {
|
||||
namespace Jio\User {
|
||||
final class Restriction {
|
||||
private static ?self $instance = null;
|
||||
public static function get_instance(): self { return self::$instance ??= new self(); }
|
||||
@@ -407,10 +407,10 @@ namespace AKSRT\User {
|
||||
}
|
||||
|
||||
namespace {
|
||||
use AKSRT\Attachment\PendingUploads;
|
||||
use AKSRT\Core\FrontSubmit;
|
||||
use AKSRT\Core\FrontSubmitPendingProbe;
|
||||
use AKSRT\Core\DomainModel;
|
||||
use Jio\Attachment\PendingUploads;
|
||||
use Jio\Core\FrontSubmit;
|
||||
use Jio\Core\FrontSubmitPendingProbe;
|
||||
use Jio\Core\DomainModel;
|
||||
|
||||
$submit = new FrontSubmitPendingProbe();
|
||||
|
||||
@@ -427,7 +427,7 @@ namespace {
|
||||
fp_assert($response instanceof \WP_REST_Response && $response->get_status() === 201, 'real upload persistence should return a created response');
|
||||
$id = (int) ($response->get_data()['attachment_id'] ?? 0);
|
||||
fp_assert($id > 0 && fp_pending($id), 'each normalized upload must be marked pending');
|
||||
fp_assert(($GLOBALS['front_pending_state']['post_meta'][$id]['_aksrt_front_submit_media'] ?? '') === 'resource', 'upload should retain the normalized media type');
|
||||
fp_assert(($GLOBALS['front_pending_state']['post_meta'][$id]['_jio_front_submit_media'] ?? '') === 'resource', 'upload should retain the normalized media type');
|
||||
}
|
||||
fp_assert(count($GLOBALS['front_pending_state']['upload_calls']) === 2, 'single and sideload calls should each persist once');
|
||||
fp_assert(empty($GLOBALS['front_pending_state']['upload_calls'][0]['overrides']['action']), 'ordinary upload should use the upload action');
|
||||
@@ -477,7 +477,7 @@ namespace {
|
||||
$content = 'Draft body with enough text for a save. '
|
||||
. '<img data-edit-file-id="100" src="/image.jpg">'
|
||||
. '<video data-edit-file-id="101" data-video-url="https://video.example.test/movie.mp4"></video>'
|
||||
. '<a data-aksrt-file-id="102" href="/resource.pdf">resource</a>'
|
||||
. '<a data-jio-file-id="102" href="/resource.pdf">resource</a>'
|
||||
. implode(' ', $body_urls);
|
||||
$result = $submit->persist(fp_article_data($content, [
|
||||
'fake_saved_pay_meta' => ['downloads' => $pay_downloads],
|
||||
@@ -534,7 +534,7 @@ namespace {
|
||||
fp_assert(!empty($removed['success']), 'edit removing a media reference should save');
|
||||
fp_assert(isset($GLOBALS['front_pending_state']['posts'][441]), 'unreferenced attached media must not be deleted');
|
||||
fp_assert($GLOBALS['front_pending_state']['posts'][441]->post_parent === 0, 'removed media should be detached from the edited post');
|
||||
fp_assert(get_post_meta(441, '_aksrt_front_submit_media', true) === '', 'removed media should lose only its submit relation marker');
|
||||
fp_assert(get_post_meta(441, '_jio_front_submit_media', true) === '', 'removed media should lose only its submit relation marker');
|
||||
fp_assert(!fp_pending(441) && $GLOBALS['front_pending_state']['deleted'] === [], 'removed media must not be deleted or marked pending again');
|
||||
|
||||
// A payment validation error happens after the body write. The saved body is
|
||||
|
||||
@@ -34,7 +34,7 @@ test('the global avatar shape primitive renders every avatar image circular', ()
|
||||
assert.match(block, /object-fit:\s*cover/);
|
||||
|
||||
// The primitive must follow the existing avatar size system in the same file.
|
||||
const sizeSystem = css.indexOf('.aksrt-avatar--xs {');
|
||||
const sizeSystem = css.indexOf('.jio-avatar--xs {');
|
||||
assert.ok(sizeSystem >= 0 && sizeSystem < start, 'primitive must sit after the avatar size system');
|
||||
});
|
||||
|
||||
@@ -49,8 +49,8 @@ test('the user center and public user home keep their legacy rounded-square avat
|
||||
// img and the upload overlay both inherit the frame radius.
|
||||
assert.match(profile, /\.uc-header__avatar img\s*\{[^}]*border-radius:\s*inherit/s);
|
||||
|
||||
assert.match(userHome, /\.aksrt-author-hero__avatar img\s*\{[^}]*border-radius:\s*var\(--radius\)/s);
|
||||
assert.doesNotMatch(userHome, /\.aksrt-author-hero__avatar img\s*\{[^}]*--radius-avatar/s);
|
||||
assert.match(userHome, /\.jio-author-hero__avatar img\s*\{[^}]*border-radius:\s*var\(--radius\)/s);
|
||||
assert.doesNotMatch(userHome, /\.jio-author-hero__avatar img\s*\{[^}]*--radius-avatar/s);
|
||||
});
|
||||
|
||||
test('every other frontend avatar frame is circular', () => {
|
||||
@@ -79,18 +79,18 @@ test('the VIP modal avatar carries the canonical avatar class', () => {
|
||||
|
||||
test('the VIP icon leaves the avatar corner and leads the badge row', () => {
|
||||
const userRenderer = read('src/WidgetSystem/Renderers/UserRenderer.php');
|
||||
const singleRender = read('inc/helpers/aksrt-single-render.php');
|
||||
const singleRender = read('inc/helpers/jio-single-render.php');
|
||||
const signin = read('src/Credits/Signin.php');
|
||||
|
||||
for (const [file, source] of [['UserRenderer.php', userRenderer], ['aksrt-single-render.php', singleRender]]) {
|
||||
assert.match(source, /aksrt_get_avatar_box\([^)]*'avatar-img avatar-lg',\s*true,\s*false\)/, `${file} must drop the avatar corner badge`);
|
||||
for (const [file, source] of [['UserRenderer.php', userRenderer], ['jio-single-render.php', singleRender]]) {
|
||||
assert.match(source, /jio_get_avatar_box\([^)]*'avatar-img avatar-lg',\s*true,\s*false\)/, `${file} must drop the avatar corner badge`);
|
||||
assert.match(source, /'vip'\s+=>\s+true/, `${file} must render the VIP icon in the name row`);
|
||||
assert.doesNotMatch(source, /'vip'\s+=>\s+false/);
|
||||
}
|
||||
|
||||
assert.match(signin, /aksrt_get_avatar_box\(\$user_id,\s*'avatar-img avatar-mini mr6',\s*false,\s*false\)/);
|
||||
assert.match(signin, /aksrt_get_user_ban_name_badge\(\$user_id\)/);
|
||||
assert.match(signin, /aksrt_get_vip_icon\(\$user_id\)/);
|
||||
assert.match(signin, /jio_get_avatar_box\(\$user_id,\s*'avatar-img avatar-mini mr6',\s*false,\s*false\)/);
|
||||
assert.match(signin, /jio_get_user_ban_name_badge\(\$user_id\)/);
|
||||
assert.match(signin, /jio_get_vip_icon\(\$user_id\)/);
|
||||
assert.match(signin, /\$avatar_img \. esc_html\(\$user->display_name\) \. \$name_badge/);
|
||||
});
|
||||
|
||||
@@ -104,19 +104,19 @@ test('no frontend call site opts into the avatar corner VIP badge', () => {
|
||||
if (entry.isDirectory()) { walk(full); continue; }
|
||||
if (!/\.(php|js)$/.test(entry.name) || entry.name.endsWith('.min.js')) continue;
|
||||
const source = fs.readFileSync(full, 'utf8');
|
||||
const pattern = /aksrt_get_avatar_box\([^;]*?,\s*(?:true|false),\s*true\s*\)/g;
|
||||
const pattern = /jio_get_avatar_box\([^;]*?,\s*(?:true|false),\s*true\s*\)/g;
|
||||
for (const match of source.matchAll(pattern)) {
|
||||
offenders.push(`${path.relative(root, full).split(path.sep).join('/')}: ${match[0]}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
walk(root);
|
||||
assert.deepEqual(offenders, [], 'aksrt_get_avatar_box() must no longer mount a badge on the avatar');
|
||||
assert.deepEqual(offenders, [], 'jio_get_avatar_box() must no longer mount a badge on the avatar');
|
||||
});
|
||||
|
||||
test('the avatar helper keeps its corner badge contract for callers that still want it', () => {
|
||||
const avatar = read('inc/helpers/aksrt-avatar.php');
|
||||
assert.match(avatar, /function aksrt_get_avatar_box\(\$id_or_email, string \$class = 'avatar-img', bool \$link = true, bool \$vip = true\)/);
|
||||
const avatar = read('inc/helpers/jio-avatar.php');
|
||||
assert.match(avatar, /function jio_get_avatar_box\(\$id_or_email, string \$class = 'avatar-img', bool \$link = true, bool \$vip = true\)/);
|
||||
assert.match(avatar, /class="avatar-badge"/);
|
||||
});
|
||||
|
||||
|
||||
@@ -44,18 +44,18 @@ test('main owns the one global auto_fun boot', () => {
|
||||
assert.match(main, /jQuery\(document\)\.trigger\(['"]auto_fun['"]\)/);
|
||||
});
|
||||
|
||||
test('theme and comments do not depend on the removed aksrt namespace APIs', () => {
|
||||
test('theme and comments do not depend on the removed jio namespace APIs', () => {
|
||||
const theme = read('assets/js/core/theme.js');
|
||||
const comments = read('assets/js/comment-ajax.js');
|
||||
const runtime = read('assets/js/runtime/main.min.js');
|
||||
|
||||
assert.match(theme, /window\.aksrtData\.themeMode/);
|
||||
assert.doesNotMatch(theme, /(?:window\.)?aksrt\.toggleTheme/);
|
||||
assert.doesNotMatch(comments, /(?:window\.)?aksrt\.(?:__|confirm)/);
|
||||
assert.doesNotMatch(runtime, /(?:window\.)?aksrt\.(?:toggleTheme|__|confirm)/);
|
||||
assert.match(theme, /window\.jioData\.themeMode/);
|
||||
assert.doesNotMatch(theme, /(?:window\.)?jio\.toggleTheme/);
|
||||
assert.doesNotMatch(comments, /(?:window\.)?jio\.(?:__|confirm)/);
|
||||
assert.doesNotMatch(runtime, /(?:window\.)?jio\.(?:toggleTheme|__|confirm)/);
|
||||
});
|
||||
|
||||
test('theme initializes when the removed aksrt namespace is absent', () => {
|
||||
test('theme initializes when the removed jio namespace is absent', () => {
|
||||
const classList = { toggle() {}, contains() { return false; } };
|
||||
const document = {
|
||||
readyState: 'complete',
|
||||
@@ -87,27 +87,27 @@ test('theme initializes when the removed aksrt namespace is absent', () => {
|
||||
Date,
|
||||
Math,
|
||||
}));
|
||||
assert.equal(window.aksrt, undefined);
|
||||
assert.equal(window.jio, undefined);
|
||||
});
|
||||
|
||||
test('routed modules use one namespaced auto_fun owner and no registry', () => {
|
||||
for (const file of routed) {
|
||||
const source = read(file);
|
||||
assert.match(source, /\.off\(['"]auto_fun\.[^'"]+['"]\)\.on\(['"]auto_fun\.[^'"]+['"]/, file);
|
||||
assert.doesNotMatch(source, /AksrtFrontend|aksrt\.autoFun|aksrt:(?:ready|rehydrated)/, file);
|
||||
assert.doesNotMatch(source, /JioFrontend|jio\.autoFun|jio:(?:ready|rehydrated)/, file);
|
||||
}
|
||||
});
|
||||
|
||||
test('wallet reward content opens in the shared modal runtime once', () => {
|
||||
const reward = read('assets/js/reward.js');
|
||||
const helper = read('inc/helpers/aksrt-rewards.php');
|
||||
const helper = read('inc/helpers/jio-rewards.php');
|
||||
const authorBar = read('templates/parts/single-author-bar.php');
|
||||
const singleActions = read('inc/helpers/aksrt-single-render.php');
|
||||
const singleActions = read('inc/helpers/jio-single-render.php');
|
||||
const scripts = read('src/Core/ScriptsPart1b.php');
|
||||
const scriptsPart1c = read('src/Core/ScriptsPart1c.php');
|
||||
|
||||
assert.match(reward, /runtime\.open\(\{/);
|
||||
assert.match(reward, /className: 'aksrt-reward-modal modal-mini full-sm'/);
|
||||
assert.match(reward, /className: 'jio-reward-modal modal-mini full-sm'/);
|
||||
assert.match(reward, /mobileBottom: true/);
|
||||
assert.match(reward, /shell\.setAttribute\('aria-labelledby', title\.id\)/);
|
||||
assert.match(reward, /'Idempotency-Key': key/);
|
||||
@@ -119,31 +119,31 @@ test('wallet reward content opens in the shared modal runtime once', () => {
|
||||
assert.match(helper, /static \$done = \[\]/);
|
||||
assert.match(helper, /isset\(\$done\[\$key\]\)/);
|
||||
assert.match(helper, /modal-colorful-header colorful-bg jb-blue/);
|
||||
assert.match(helper, /data-aksrt-modal-close/);
|
||||
assert.match(helper, /data-aksrt-reward-submit/);
|
||||
assert.match(authorBar, /aksrt_render_rewards_template_for_user/);
|
||||
assert.match(singleActions, /aksrt_render_rewards_template_for_user/);
|
||||
assert.match(helper, /data-jio-modal-close/);
|
||||
assert.match(helper, /data-jio-reward-submit/);
|
||||
assert.match(authorBar, /jio_render_rewards_template_for_user/);
|
||||
assert.match(singleActions, /jio_render_rewards_template_for_user/);
|
||||
assert.match(scriptsPart1c, /\$requirements\['needs_reward'\]\s*=\s*\$is_article && !empty\(\$settings\['rewards_enabled'\]\)/);
|
||||
assert.match(scriptsPart1c, /\$singular_assets\['needs_reward'\][\s\S]*?\$reward_js[\s\S]*?'aksrt-reward'/);
|
||||
assert.doesNotMatch(helper + authorBar + singleActions, /aksrt_render_rewards_modal_for_user|reward-modal__qr/);
|
||||
assert.match(scriptsPart1c, /\$singular_assets\['needs_reward'\][\s\S]*?\$reward_js[\s\S]*?'jio-reward'/);
|
||||
assert.doesNotMatch(helper + authorBar + singleActions, /jio_render_rewards_modal_for_user|reward-modal__qr/);
|
||||
});
|
||||
|
||||
test('user center navigation initializes once across auto_fun rescans', () => {
|
||||
const source = read('assets/js/routes/user-center.js');
|
||||
const initAt = source.indexOf('function initDashboardNavigation()');
|
||||
const guardAt = source.indexOf('aksrtUserCenterNavigationInitialized', initAt);
|
||||
const guardAt = source.indexOf('jioUserCenterNavigationInitialized', initAt);
|
||||
const initialActivateAt = source.indexOf('activate(initialTab, false)', initAt);
|
||||
|
||||
assert.ok(initAt >= 0);
|
||||
assert.ok(guardAt > initAt && guardAt < initialActivateAt);
|
||||
assert.match(source.slice(initAt, initialActivateAt), /if \(document\.documentElement\.dataset\.aksrtUserCenterNavigationInitialized === 'true'\) return;/);
|
||||
assert.match(source.slice(initAt, initialActivateAt), /document\.documentElement\.dataset\.aksrtUserCenterNavigationInitialized = 'true';/);
|
||||
assert.match(source.slice(initAt, initialActivateAt), /if \(document\.documentElement\.dataset\.jioUserCenterNavigationInitialized === 'true'\) return;/);
|
||||
assert.match(source.slice(initAt, initialActivateAt), /document\.documentElement\.dataset\.jioUserCenterNavigationInitialized = 'true';/);
|
||||
assert.doesNotMatch(source, /name === 'vip' && !forceLoad\) forceLoad = true/);
|
||||
});
|
||||
|
||||
test('homepage tabs refresh the original pane DOM through the runtime post_ajax pipeline', () => {
|
||||
const layout = read('inc/helpers/homepage/layout.php')
|
||||
const homepage = read('inc/helpers/aksrt-homepage.php')
|
||||
const homepage = read('inc/helpers/jio-homepage.php')
|
||||
const controller = read('src/Admin/GlobalController.php')
|
||||
|
||||
// legacy zib_index_tab_html 契约:data-toggle="tab" + data-ajax 栏目链接。
|
||||
@@ -159,17 +159,17 @@ test('homepage tabs refresh the original pane DOM through the runtime post_ajax
|
||||
assert.match(layout, /'nofilter' => 'true'/)
|
||||
// 不再有旁路刷新:REST 面板、骨架屏、面板 footer 全部移除。
|
||||
assert.equal(fs.existsSync(path.join(root, 'assets/js/homepage-fixed.js')), false)
|
||||
assert.doesNotMatch(layout, /data-home-tab-panel|aksrt-homepage__panel|aksrt-homepage__tab|requestHomepagePanel|data-sticky-tabs/)
|
||||
assert.doesNotMatch(layout, /data-home-tab-panel|jio-homepage__panel|jio-homepage__tab|requestHomepagePanel|data-sticky-tabs/)
|
||||
assert.doesNotMatch(controller, /homepage\/tab-posts|rest_homepage_tab_posts/)
|
||||
|
||||
// 排序菜单 = zib_ajax_option_menu('home') 契约:win-ajax-replace="filter" + ajax-next 链接。
|
||||
assert.match(homepage, /function aksrt_homepage_render_ajax_option_menu/)
|
||||
assert.match(homepage, /function jio_homepage_render_ajax_option_menu/)
|
||||
assert.match(homepage, /win-ajax-replace="filter"/)
|
||||
assert.match(homepage, /ajax-replace="true" class="' \. esc_attr\(\$_class\)/)
|
||||
assert.match(homepage, /!empty\(\$_GET\['nofilter'\]\)/)
|
||||
// 主查询吃 ?orderby=(legacy zib_sift_posts_per_orde),排序原位刷新才有效。
|
||||
assert.match(homepage, /function aksrt_homepage_get_request_sort/)
|
||||
assert.match(read('inc/helpers/homepage/content.php'), /aksrt_homepage_get_request_sort\(\)/)
|
||||
assert.match(homepage, /function jio_homepage_get_request_sort/)
|
||||
assert.match(read('inc/helpers/homepage/content.php'), /jio_homepage_get_request_sort\(\)/)
|
||||
|
||||
// runtime 是 tab/排序的唯一引擎。
|
||||
const runtime = read('assets/js/runtime/main.js')
|
||||
@@ -180,7 +180,7 @@ test('homepage tabs refresh the original pane DOM through the runtime post_ajax
|
||||
})
|
||||
|
||||
test('archive streams nest pagination inside the ajaxpager for tab extraction', () => {
|
||||
const archive = read('inc/helpers/aksrt-archive.php')
|
||||
const archive = read('inc/helpers/jio-archive.php')
|
||||
assert.match(archive, /append_inner_html/)
|
||||
assert.doesNotMatch(archive, /content-stream__footer/)
|
||||
assert.match(read('inc/helpers/posts-renderer/markup.php'), /append_inner_html/)
|
||||
@@ -219,6 +219,6 @@ test('non-generated first-party sources contain no deprecated lifecycle API', ()
|
||||
});
|
||||
for (const file of files) {
|
||||
const source = fs.readFileSync(file, 'utf8');
|
||||
assert.doesNotMatch(source, /AksrtFrontend|aksrt\.autoFun|aksrt:(?:ready|rehydrated)/, path.relative(root, file));
|
||||
assert.doesNotMatch(source, /JioFrontend|jio\.autoFun|jio:(?:ready|rehydrated)/, path.relative(root, file));
|
||||
}
|
||||
});
|
||||
@@ -6,22 +6,22 @@ declare(strict_types=1);
|
||||
namespace {
|
||||
define('ABSPATH', __DIR__ . '/');
|
||||
|
||||
$GLOBALS['aksrt_set_settings'] = [];
|
||||
$GLOBALS['aksrt_set_meta'] = [];
|
||||
$GLOBALS['aksrt_set_json'] = null;
|
||||
$GLOBALS['aksrt_set_output'] = '';
|
||||
$GLOBALS['aksrt_set_term'] = null;
|
||||
$GLOBALS['aksrt_set_post'] = null;
|
||||
$GLOBALS['aksrt_set_queried'] = 0;
|
||||
$GLOBALS['aksrt_set_post_type'] = 'post';
|
||||
$GLOBALS['aksrt_set_is_single'] = false;
|
||||
$GLOBALS['aksrt_set_is_page'] = false;
|
||||
$GLOBALS['aksrt_set_is_category'] = false;
|
||||
$GLOBALS['aksrt_set_object'] = null;
|
||||
$GLOBALS['aksrt_set_page_templates'] = [];
|
||||
$GLOBALS['aksrt_set_can'] = true;
|
||||
$GLOBALS['aksrt_set_scripts'] = [];
|
||||
$GLOBALS['aksrt_set_post_format'] = '';
|
||||
$GLOBALS['jio_set_settings'] = [];
|
||||
$GLOBALS['jio_set_meta'] = [];
|
||||
$GLOBALS['jio_set_json'] = null;
|
||||
$GLOBALS['jio_set_output'] = '';
|
||||
$GLOBALS['jio_set_term'] = null;
|
||||
$GLOBALS['jio_set_post'] = null;
|
||||
$GLOBALS['jio_set_queried'] = 0;
|
||||
$GLOBALS['jio_set_post_type'] = 'post';
|
||||
$GLOBALS['jio_set_is_single'] = false;
|
||||
$GLOBALS['jio_set_is_page'] = false;
|
||||
$GLOBALS['jio_set_is_category'] = false;
|
||||
$GLOBALS['jio_set_object'] = null;
|
||||
$GLOBALS['jio_set_page_templates'] = [];
|
||||
$GLOBALS['jio_set_can'] = true;
|
||||
$GLOBALS['jio_set_scripts'] = [];
|
||||
$GLOBALS['jio_set_post_format'] = '';
|
||||
|
||||
function __(string $text, string $domain = ''): string { return $text; }
|
||||
function esc_html__($t, $d = ''): string { return (string) $t; }
|
||||
@@ -35,22 +35,22 @@ namespace {
|
||||
function wp_unslash($v) { return $v; }
|
||||
function absint($v): int { return max(0, (int) $v); }
|
||||
function is_user_logged_in(): bool { return true; }
|
||||
function current_user_can($cap, ...$args): bool { return (bool) $GLOBALS['aksrt_set_can']; }
|
||||
function is_single(): bool { return $GLOBALS['aksrt_set_is_single']; }
|
||||
function is_page(): bool { return $GLOBALS['aksrt_set_is_page']; }
|
||||
function is_category(): bool { return $GLOBALS['aksrt_set_is_category']; }
|
||||
function current_user_can($cap, ...$args): bool { return (bool) $GLOBALS['jio_set_can']; }
|
||||
function is_single(): bool { return $GLOBALS['jio_set_is_single']; }
|
||||
function is_page(): bool { return $GLOBALS['jio_set_is_page']; }
|
||||
function is_category(): bool { return $GLOBALS['jio_set_is_category']; }
|
||||
function is_tag(): bool { return false; }
|
||||
function is_tax(): bool { return false; }
|
||||
function get_queried_object_id(): int { return $GLOBALS['aksrt_set_queried']; }
|
||||
function get_queried_object() { return $GLOBALS['aksrt_set_object']; }
|
||||
function get_post_type(): string { return $GLOBALS['aksrt_set_post_type']; }
|
||||
function get_post($id) { return $GLOBALS['aksrt_set_post']; }
|
||||
function get_post_meta($id, $key = '', $single = false) { return $GLOBALS['aksrt_set_meta'][$key] ?? ''; }
|
||||
function update_post_meta($id, $key, $value): void { $GLOBALS['aksrt_set_meta'][$key] = $value; }
|
||||
function get_queried_object_id(): int { return $GLOBALS['jio_set_queried']; }
|
||||
function get_queried_object() { return $GLOBALS['jio_set_object']; }
|
||||
function get_post_type(): string { return $GLOBALS['jio_set_post_type']; }
|
||||
function get_post($id) { return $GLOBALS['jio_set_post']; }
|
||||
function get_post_meta($id, $key = '', $single = false) { return $GLOBALS['jio_set_meta'][$key] ?? ''; }
|
||||
function update_post_meta($id, $key, $value): void { $GLOBALS['jio_set_meta'][$key] = $value; }
|
||||
function get_the_title($id): string { return '示例标题'; }
|
||||
function get_the_permalink($id): string { return 'https://example.test/post'; }
|
||||
function get_post_format($id) { return $GLOBALS['aksrt_set_post_format'] ?? ''; }
|
||||
function set_post_format($id, $format): void { $GLOBALS['aksrt_set_meta']['__format'] = $format; }
|
||||
function get_post_format($id) { return $GLOBALS['jio_set_post_format'] ?? ''; }
|
||||
function set_post_format($id, $format): void { $GLOBALS['jio_set_meta']['__format'] = $format; }
|
||||
function post_type_supports($type, $feature): bool { return $feature === 'post-formats'; }
|
||||
function get_theme_support($feature) { return $feature === 'post-formats' ? [['image', 'video']] : false; }
|
||||
function get_post_format_string($format): string { return ucfirst((string) $format); }
|
||||
@@ -59,12 +59,12 @@ namespace {
|
||||
function get_edit_term_link($id, $tax = '') { return 'https://example.test/wp-admin/term.php'; }
|
||||
function get_taxonomy($tax) { return (object) ['labels' => (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 get_term($id, $tax = '') { return $GLOBALS['jio_set_term']; }
|
||||
function wp_update_term($id, $tax, $args) { $GLOBALS['jio_set_meta']['__term'] = $args; return 1; }
|
||||
function wp_update_post($args, $wp_error = false) { $GLOBALS['jio_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 is_page_template($tpl): bool { return in_array($tpl, $GLOBALS['jio_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'; }
|
||||
@@ -80,68 +80,68 @@ namespace {
|
||||
}
|
||||
function check_ajax_referer($action = -1, $name = '_wpnonce'): bool { return true; }
|
||||
function wp_send_json($data, $code = null): void {
|
||||
$GLOBALS['aksrt_set_json'] = $data;
|
||||
$GLOBALS['jio_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];
|
||||
$GLOBALS['jio_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 get_template_directory_uri(): string { return 'https://example.test/wp-content/themes/jio-theme'; }
|
||||
|
||||
function set_check(bool $condition, string $message): void {
|
||||
if (!$condition) throw new \RuntimeException($message);
|
||||
}
|
||||
}
|
||||
|
||||
namespace AKSRT\Settings {
|
||||
namespace Jio\Settings {
|
||||
final class ExtensionRuntime {
|
||||
public static array $settings = [];
|
||||
public static function get_system_tools_settings(): array { return self::$settings; }
|
||||
}
|
||||
}
|
||||
|
||||
namespace AKSRT\Core {
|
||||
namespace Jio\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']; }
|
||||
public const META_KEY = 'jio_post_extension';
|
||||
public static function get_meta(int $post_id): array { return $GLOBALS['jio_set_meta']; }
|
||||
}
|
||||
final class DomainModel {
|
||||
public const POST_VIEW_COUNT_KEY = 'aksrt_post_views';
|
||||
public const POST_VIEW_COUNT_KEY = 'jio_post_views';
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
use AKSRT\Frontend\PageAdminSet;
|
||||
use Jio\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;
|
||||
\Jio\Settings\ExtensionRuntime::$settings = ['admin_frontend_set' => true];
|
||||
$GLOBALS['jio_set_queried'] = 0;
|
||||
$GLOBALS['jio_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;
|
||||
$GLOBALS['jio_set_queried'] = 12;
|
||||
set_check(PageAdminSet::enabled() === true, 'enabled() must be true on a single post for an admin');
|
||||
|
||||
$GLOBALS['aksrt_set_can'] = false;
|
||||
$GLOBALS['jio_set_can'] = false;
|
||||
set_check(PageAdminSet::enabled() === false, 'enabled() must respect manage_options');
|
||||
$GLOBALS['aksrt_set_can'] = true;
|
||||
$GLOBALS['jio_set_can'] = true;
|
||||
|
||||
\AKSRT\Settings\ExtensionRuntime::$settings = ['admin_frontend_set' => false];
|
||||
\Jio\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];
|
||||
\Jio\Settings\ExtensionRuntime::$settings = ['admin_frontend_set' => true];
|
||||
|
||||
$GLOBALS['aksrt_set_page_templates'] = ['templates/pages/page-submit.php'];
|
||||
$GLOBALS['jio_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['jio_set_page_templates'] = [];
|
||||
|
||||
$GLOBALS['aksrt_set_post'] = (object) ['ID' => 12, 'post_type' => 'post'];
|
||||
$GLOBALS['jio_set_post'] = (object) ['ID' => 12, 'post_type' => 'post'];
|
||||
|
||||
// --- Modal: reference markup parity. ---
|
||||
ob_start();
|
||||
@@ -154,7 +154,7 @@ namespace {
|
||||
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="action" value="jio_frontend_set_save"') !== false, 'modal must post jio_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');
|
||||
@@ -188,53 +188,53 @@ namespace {
|
||||
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');
|
||||
set_check(is_array($GLOBALS['jio_set_json']), 'save must send a JSON payload');
|
||||
set_check(($GLOBALS['jio_set_json']['msg'] ?? '') === '保存成功,正在刷新页面', 'save must reuse the reference success message');
|
||||
set_check(!empty($GLOBALS['jio_set_json']['reload']), 'save must ask the client to reload');
|
||||
set_check(($GLOBALS['jio_set_meta']['__post']['post_title'] ?? '') === '新标题', 'save must update the post title');
|
||||
set_check(($GLOBALS['jio_set_meta']['__post']['comment_status'] ?? '') === 'closed', 'save must close comments when empty');
|
||||
|
||||
$saved_meta = $GLOBALS['aksrt_set_meta']['aksrt_post_extension'] ?? [];
|
||||
$saved_meta = $GLOBALS['jio_set_meta']['jio_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');
|
||||
set_check((int) $GLOBALS['jio_set_meta']['jio_post_views'] === 42, 'save must persist the view count');
|
||||
set_check((int) $GLOBALS['jio_set_meta']['_jio_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;
|
||||
\Jio\Settings\ExtensionRuntime::$settings = ['admin_frontend_set' => false];
|
||||
$GLOBALS['jio_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');
|
||||
set_check(($GLOBALS['jio_set_json']['error'] ?? 0) === 1, 'disabled switch must reject the save');
|
||||
set_check(!empty($GLOBALS['jio_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' => ''];
|
||||
\Jio\Settings\ExtensionRuntime::$settings = ['admin_frontend_set' => true];
|
||||
$GLOBALS['jio_set_json'] = null;
|
||||
$GLOBALS['jio_set_meta'] = [];
|
||||
$GLOBALS['jio_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');
|
||||
set_check(($GLOBALS['jio_set_meta']['__term']['name'] ?? '') === '新分类', 'term save must update the name');
|
||||
set_check(($GLOBALS['jio_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'] = [];
|
||||
$GLOBALS['jio_set_queried'] = 21;
|
||||
$GLOBALS['jio_set_is_single'] = false;
|
||||
$GLOBALS['jio_set_is_page'] = true;
|
||||
$GLOBALS['jio_set_post_type'] = 'page';
|
||||
$GLOBALS['jio_set_post'] = (object) ['ID' => 21, 'post_type' => 'page'];
|
||||
$GLOBALS['jio_set_json'] = null;
|
||||
$GLOBALS['jio_set_meta'] = [];
|
||||
$_POST = [
|
||||
'id' => '21',
|
||||
'type' => 'post',
|
||||
@@ -248,41 +248,41 @@ namespace {
|
||||
} 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');
|
||||
set_check(($GLOBALS['jio_set_meta']['page_header_style'] ?? '') === '2', 'page save must persist the header style');
|
||||
set_check(($GLOBALS['jio_set_meta']['widgets_register'] ?? '') === '1', 'page save must enable the widget container');
|
||||
set_check(($GLOBALS['jio_set_meta']['widgets_register_container'] ?? []) === ['sidebar', 'top_fluid'], 'page save must persist container positions');
|
||||
|
||||
$GLOBALS['aksrt_set_json'] = null;
|
||||
$GLOBALS['aksrt_set_meta'] = [];
|
||||
$GLOBALS['jio_set_json'] = null;
|
||||
$GLOBALS['jio_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');
|
||||
set_check(($GLOBALS['jio_set_meta']['widgets_register'] ?? '') === '', 'closing the widget container must clear the flag');
|
||||
set_check(($GLOBALS['jio_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;
|
||||
$hint = $GLOBALS['jio_set_scripts']['jio-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'] = [];
|
||||
\Jio\Settings\ExtensionRuntime::$settings = ['admin_frontend_set' => false];
|
||||
$GLOBALS['jio_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];
|
||||
set_check($GLOBALS['jio_set_scripts'] === [], 'the disabled switch must not enqueue the hint fade script');
|
||||
\Jio\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'];
|
||||
$GLOBALS['jio_set_queried'] = 12;
|
||||
$GLOBALS['jio_set_is_single'] = true;
|
||||
$GLOBALS['jio_set_is_page'] = false;
|
||||
$GLOBALS['jio_set_post_type'] = 'post';
|
||||
$GLOBALS['jio_set_post'] = (object) ['ID' => 12, 'post_type' => 'post'];
|
||||
ob_start();
|
||||
(new PageAdminSet())->render_modal();
|
||||
$modal_icons = (string) ob_get_clean();
|
||||
@@ -310,8 +310,8 @@ namespace {
|
||||
set_check(strpos($modal_icons, "value=\"{$extra}\"") === false, "post format options must not leak the theme-only format {$extra}");
|
||||
}
|
||||
// 已设置格式时必须回显选中项。
|
||||
$GLOBALS['aksrt_set_meta']['__format'] = '';
|
||||
$GLOBALS['aksrt_set_post_format'] = 'image';
|
||||
$GLOBALS['jio_set_meta']['__format'] = '';
|
||||
$GLOBALS['jio_set_post_format'] = 'image';
|
||||
ob_start();
|
||||
(new PageAdminSet())->render_modal();
|
||||
$modal_image = (string) ob_get_clean();
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<?php
|
||||
/** Render-level harness for the header multifunction component. */
|
||||
|
||||
namespace AKSRT\Core {
|
||||
namespace Jio\Core {
|
||||
final class Color {
|
||||
public static function sanitize(string $value): string { return $value; }
|
||||
}
|
||||
}
|
||||
|
||||
namespace AKSRT\Settings {
|
||||
namespace Jio\Settings {
|
||||
final class Settings {
|
||||
public static array $headerSlider = [];
|
||||
public static function get_header_slider_settings(): array { return self::$headerSlider; }
|
||||
@@ -16,8 +16,8 @@ namespace AKSRT\Settings {
|
||||
|
||||
namespace {
|
||||
define('ABSPATH', __DIR__);
|
||||
$GLOBALS['aksrt_mobile'] = false;
|
||||
$GLOBALS['aksrt_lazy'] = true;
|
||||
$GLOBALS['jio_mobile'] = false;
|
||||
$GLOBALS['jio_lazy'] = true;
|
||||
|
||||
final class WP_Query {
|
||||
public function get(string $key) { return 0; }
|
||||
@@ -25,7 +25,7 @@ namespace {
|
||||
$GLOBALS['wp_query'] = new WP_Query();
|
||||
|
||||
function is_admin(): bool { return false; }
|
||||
function wp_is_mobile(): bool { return !empty($GLOBALS['aksrt_mobile']); }
|
||||
function wp_is_mobile(): bool { return !empty($GLOBALS['jio_mobile']); }
|
||||
function is_author(): bool { return false; }
|
||||
function is_front_page(): bool { return true; }
|
||||
function is_category(): bool { return false; }
|
||||
@@ -44,19 +44,19 @@ namespace {
|
||||
function esc_html($value): string { return htmlspecialchars((string) $value, ENT_QUOTES); }
|
||||
function wp_strip_all_tags($value): string { return strip_tags((string) $value); }
|
||||
function __($value, $domain = ''): string { return (string) $value; }
|
||||
function get_bloginfo($show = ''): string { return 'AKSRT'; }
|
||||
function get_bloginfo($show = ''): string { return 'Jio'; }
|
||||
function home_url($path = ''): string { return 'https://example.test/' . ltrim((string) $path, '/'); }
|
||||
function wp_kses($value, $allowed = []): string { return (string) $value; }
|
||||
function aksrt_is_lazy(string $key): bool { return !empty($GLOBALS['aksrt_lazy']); }
|
||||
function aksrt_get_lazy_thumb(string $size = ''): string { return 'https://example.test/placeholder.svg'; }
|
||||
function aksrt_get_lazy_attr(string $key, string $src, string $class = '', string $lazy_src = ''): string {
|
||||
if (aksrt_is_lazy($key)) {
|
||||
function jio_is_lazy(string $key): bool { return !empty($GLOBALS['jio_lazy']); }
|
||||
function jio_get_lazy_thumb(string $size = ''): string { return 'https://example.test/placeholder.svg'; }
|
||||
function jio_get_lazy_attr(string $key, string $src, string $class = '', string $lazy_src = ''): string {
|
||||
if (jio_is_lazy($key)) {
|
||||
return ' class="lazyload ' . esc_attr($class) . '" src="' . esc_url($lazy_src) . '" data-src="' . esc_url($src) . '"';
|
||||
}
|
||||
return ' class="' . esc_attr($class) . '" src="' . esc_url($src) . '"';
|
||||
}
|
||||
function aksrt_get_search_box(array $args = []): string { return '<div class="search-input">search-box</div>'; }
|
||||
function aksrt_icon_cover_card(array $card): string {
|
||||
function jio_get_search_box(array $args = []): string { return '<div class="search-input">search-box</div>'; }
|
||||
function jio_icon_cover_card(array $card): string {
|
||||
return '<div class="icon-cover-card flex ac ' . esc_attr((string) ($card['class'] ?? '')) . '">' . esc_html((string) ($card['title'] ?? '')) . '</div>';
|
||||
}
|
||||
|
||||
@@ -81,30 +81,30 @@ namespace {
|
||||
'card_config' => ['show_widget_bg' => false, 'cards' => [['icon' => 'fa fas fa-star', 'title' => 'Card one']]],
|
||||
'filter_blur' => ['pc_s', 'm_s'],
|
||||
];
|
||||
\AKSRT\Settings\Settings::$headerSlider = $settings;
|
||||
\Jio\Settings\Settings::$headerSlider = $settings;
|
||||
|
||||
ob_start();
|
||||
aksrt_header_slide();
|
||||
jio_header_slide();
|
||||
$desktop = (string) ob_get_clean();
|
||||
|
||||
$GLOBALS['aksrt_mobile'] = true;
|
||||
$GLOBALS['jio_mobile'] = true;
|
||||
ob_start();
|
||||
aksrt_header_slide();
|
||||
jio_header_slide();
|
||||
$mobile = (string) ob_get_clean();
|
||||
|
||||
$settings['card_config']['show_widget_bg'] = true;
|
||||
\AKSRT\Settings\Settings::$headerSlider = $settings;
|
||||
\Jio\Settings\Settings::$headerSlider = $settings;
|
||||
ob_start();
|
||||
aksrt_header_slide();
|
||||
jio_header_slide();
|
||||
$module_background = (string) ob_get_clean();
|
||||
|
||||
$GLOBALS['aksrt_mobile'] = false;
|
||||
$GLOBALS['aksrt_lazy'] = false;
|
||||
$lazy_disabled = aksrt_get_header_slider_markup([
|
||||
$GLOBALS['jio_mobile'] = false;
|
||||
$GLOBALS['jio_lazy'] = false;
|
||||
$lazy_disabled = jio_get_header_slider_markup([
|
||||
'slides' => [['background' => 'https://example.test/eager.jpg']],
|
||||
'class' => 'slide-index slide-header',
|
||||
]);
|
||||
$single_slide = aksrt_get_header_slider_markup([
|
||||
$single_slide = jio_get_header_slider_markup([
|
||||
'slides' => [['background' => 'https://example.test/single.jpg']],
|
||||
'class' => 'slide-index slide-header',
|
||||
'pagination' => true,
|
||||
@@ -112,7 +112,7 @@ namespace {
|
||||
]);
|
||||
|
||||
require_once dirname(__DIR__, 2) . '/src/Admin/PageConfigMetaBox.php';
|
||||
$sanitize_cards = new \ReflectionMethod('AKSRT\\Admin\\PageConfigMetaBox', 'sanitize_card_options');
|
||||
$sanitize_cards = new \ReflectionMethod('Jio\\Admin\\PageConfigMetaBox', 'sanitize_card_options');
|
||||
$page_config_save = $sanitize_cards->invoke(null, ['show_widget_bg' => true, 'cards' => []]);
|
||||
|
||||
echo json_encode([
|
||||
|
||||
@@ -28,7 +28,7 @@ test('header multifunction renders reference slide, search, and card contracts o
|
||||
assert.doesNotMatch(desktop, /<video controls/);
|
||||
assert.match(desktop, /header-slider-search-more text-center before/);
|
||||
assert.match(desktop, /<div class="search-input">search-box<\/div>/);
|
||||
assert.match(desktop, /<div class="icon-cover-card flex ac aksrt-panel">Card one<\/div>/);
|
||||
assert.match(desktop, /<div class="icon-cover-card flex ac jio-panel">Card one<\/div>/);
|
||||
|
||||
assert.doesNotMatch(mobile, /background\.mp4/);
|
||||
assert.match(mobile, /data-src="https:\/\/example\.test\/mobile-only\.jpg"/);
|
||||
@@ -36,9 +36,9 @@ test('header multifunction renders reference slide, search, and card contracts o
|
||||
assert.match(mobile, /data-src="https:\/\/example\.test\/poster\.jpg"/);
|
||||
assert.match(mobile, /class="new-dplayer" video-url="https:\/\/example\.test\/content\.mp4"/);
|
||||
assert.match(mobile, /header-slider-search-more text-center before/);
|
||||
assert.match(mobile, /<div class="icon-cover-card flex ac aksrt-panel">Card one<\/div>/);
|
||||
assert.match(mobile, /<div class="icon-cover-card flex ac jio-panel">Card one<\/div>/);
|
||||
|
||||
assert.match(moduleBackground, /<div class="aksrt-panel padding-10"><div class="flex jse/);
|
||||
assert.match(moduleBackground, /<div class="jio-panel padding-10"><div class="flex jse/);
|
||||
assert.match(moduleBackground, /<div class="icon-cover-card flex ac padding-10">Card one<\/div>/);
|
||||
assert.equal(JSON.parse(rendered.stdout).page_config_save.show_widget_bg, true);
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ const root = path.resolve(__dirname, '..', '..');
|
||||
const read = (file) => fs.readFileSync(path.join(root, file), 'utf8');
|
||||
|
||||
test('icon card renderers keep vertical and horizontal legacy DOM contracts separate', () => {
|
||||
const helper = read('inc/helpers/aksrt-icon-cards.php');
|
||||
const helper = read('inc/helpers/jio-icon-cards.php');
|
||||
const widgets = read('src/WidgetSystem/Renderers/GeneralRenderer.php');
|
||||
const header = read('inc/helpers/homepage/header-components.php');
|
||||
const mediaBrand = read('inc/helpers/template-tags/media-brand.php');
|
||||
@@ -18,13 +18,13 @@ test('icon card renderers keep vertical and horizontal legacy DOM contracts sepa
|
||||
assert.match(helper, /<div class="icon-cover-card flex ac /);
|
||||
assert.match(helper, /icon-cover-icon badg cir/);
|
||||
assert.match(helper, /icon-cover-desc ml10 flex1 px12-sm/);
|
||||
assert.doesNotMatch(helper, /aksrt-icon-cover-card/);
|
||||
assert.doesNotMatch(helper, /jio-icon-cover-card/);
|
||||
assert.match(widgets, /'customize_icon' => \$card\['customize_icon'\] \?\? ''/);
|
||||
assert.doesNotMatch(widgets, /\$card\['custom_icon'\]/);
|
||||
// combo_info legitimately renders icon_image cards; icon cards must not leak media-card fields.
|
||||
assert.doesNotMatch(widgets, /\$card\['description'\]/);
|
||||
assert.match(header, /empty\(\$card\['custom_icon'\]\)/);
|
||||
assert.doesNotMatch(mediaBrand, /function aksrt_render_icon_cover_card/);
|
||||
assert.doesNotMatch(mediaBrand, /function jio_render_icon_cover_card/);
|
||||
});
|
||||
|
||||
test('homepage horizontal cards render fully from legacy CSS with no dedicated stylesheet', () => {
|
||||
@@ -35,7 +35,7 @@ test('homepage horizontal cards render fully from legacy CSS with no dedicated s
|
||||
const scripts = read('src/Core/ScriptsPart1b.php');
|
||||
|
||||
assert.equal(fs.existsSync(path.join(root, 'assets/css/frontend/header-cards.css')), false);
|
||||
assert.doesNotMatch(baseCss, /\.icon-cover-card\.aksrt-icon-cover-card/);
|
||||
assert.doesNotMatch(baseCss, /\.icon-cover-card\.jio-icon-cover-card/);
|
||||
assert.doesNotMatch(baseCss, /\.header-components-card/);
|
||||
assert.doesNotMatch(baseCss, /\.header-slider-card\s+\.icon-cover-card\s*\{[^}]*font-size:/s);
|
||||
assert.doesNotMatch(legacyUtilities, /\.px12-sm\s*\{[^}]*font-size:\s*12px/);
|
||||
@@ -45,7 +45,7 @@ test('homepage horizontal cards render fully from legacy CSS with no dedicated s
|
||||
assert.match(mainCss.slice(mobileBreakpoint, mobileTextSize + 120), /max-width:\s*640px/);
|
||||
assert.match(mainCss.slice(mobileTextSize, mobileTextSize + 120), /font-size:\s*12px/);
|
||||
assert.doesNotMatch(scripts, /header-cards/);
|
||||
assert.doesNotMatch(widgetCss, /aksrt-widget-icon-cards \.icon-cover-card/);
|
||||
assert.doesNotMatch(widgetCss, /jio-widget-icon-cards \.icon-cover-card/);
|
||||
});
|
||||
|
||||
test('widget icon cards restore the reference surface height without compact overrides', () => {
|
||||
@@ -78,22 +78,22 @@ test('header icon-card defaults use bundled Font Awesome icons', () => {
|
||||
});
|
||||
|
||||
test('icon card renderers route every picker format through the shared icon renderer', () => {
|
||||
const helper = read('inc/helpers/aksrt-icon-cards.php');
|
||||
const helper = read('inc/helpers/jio-icon-cards.php');
|
||||
|
||||
// One resolver owns the icon-value contract for both card shapes.
|
||||
assert.match(helper, /function aksrt_card_icon_inner\(string \$value, string \$extra = ''\): string/);
|
||||
assert.match(helper, /if \(function_exists\('aksrt_icon_markup'\)\) \{\s*return aksrt_icon_markup\(\$value, \$extra\);/s);
|
||||
assert.match(helper, /function jio_card_icon_inner\(string \$value, string \$extra = ''\): string/);
|
||||
assert.match(helper, /if \(function_exists\('jio_icon_markup'\)\) \{\s*return jio_icon_markup\(\$value, \$extra\);/s);
|
||||
|
||||
// Both renderers must delegate: emitting a bare FA class string produced
|
||||
// `<i class="fa fas fa-zibsvg-hot">`, i.e. an invisible glyph, for the two
|
||||
// SVG formats the admin picker can store.
|
||||
assert.match(helper, /\$icon_inner = aksrt_card_icon_inner\(\$icon_fa, 'em09'\);/);
|
||||
assert.match(helper, /\$icon_inner = aksrt_card_icon_inner\(\$icon_fa\);/);
|
||||
assert.match(helper, /\$icon_inner = jio_card_icon_inner\(\$icon_fa, 'em09'\);/);
|
||||
assert.match(helper, /\$icon_inner = jio_card_icon_inner\(\$icon_fa\);/);
|
||||
|
||||
// The picker prefixes must drive the SVG branches, and the stale
|
||||
// `aksrtsvg-` prefix (never emitted by any picker) must be gone.
|
||||
// `jiosvg-` prefix (never emitted by any picker) must be gone.
|
||||
assert.match(helper, /str_starts_with\(\$value, 'zibsvg-'\) \|\| str_starts_with\(\$value, 'customsvg-'\)/);
|
||||
assert.doesNotMatch(helper, /aksrtsvg-/);
|
||||
assert.doesNotMatch(helper, /jiosvg-/);
|
||||
assert.doesNotMatch(helper, /'fa fas fa-'\s*\.\s*\$icon_fa/);
|
||||
assert.doesNotMatch(helper, /<i class="'\s*\.\s*esc_attr\(\$icon_fa\)\s*\.\s*' em09"/);
|
||||
});
|
||||
@@ -104,14 +104,14 @@ test('custom SVG payloads survive the admin save path for nested icon sub-fields
|
||||
|
||||
// header_slider_card_option.cards[].icon is a group sub-field: plain
|
||||
// sanitize_text_field stripped "customsvg-<svg>" down to a bare prefix.
|
||||
assert.match(provider, /if \(\$type === 'icon'\) \{\s*return function_exists\('aksrt_sanitize_icon_value'\)/s);
|
||||
assert.match(metaBox, /'icon'\s*=>\s*function_exists\('aksrt_sanitize_icon_value'\)/);
|
||||
assert.match(provider, /if \(\$type === 'icon'\) \{\s*return function_exists\('jio_sanitize_icon_value'\)/s);
|
||||
assert.match(metaBox, /'icon'\s*=>\s*function_exists\('jio_sanitize_icon_value'\)/);
|
||||
assert.doesNotMatch(metaBox, /'icon'\s*=>\s*sanitize_text_field\(\(string\) \(\$card\['icon'\]/);
|
||||
});
|
||||
|
||||
test('inline custom SVG shapes survive the kses allow list', () => {
|
||||
const icons = read('inc/helpers/aksrt-icons.php');
|
||||
const start = icons.indexOf('function aksrt_svg_kses_tags');
|
||||
const icons = read('inc/helpers/jio-icons.php');
|
||||
const start = icons.indexOf('function jio_svg_kses_tags');
|
||||
const ksesBody = icons.slice(start, icons.indexOf('endif;', start));
|
||||
|
||||
// Any element the picker's cleanSvg() lets through must also be accepted
|
||||
@@ -126,7 +126,7 @@ test('inline custom SVG shapes survive the kses allow list', () => {
|
||||
}
|
||||
|
||||
for (const tag of ['circle', 'ellipse', 'rect', 'polygon', 'line', 'path', 'g', 'use']) {
|
||||
assert.match(ksesBody, new RegExp(`'${tag}'\\s*=>`), `aksrt_svg_kses_tags() is missing <${tag}>`);
|
||||
assert.match(ksesBody, new RegExp(`'${tag}'\\s*=>`), `jio_svg_kses_tags() is missing <${tag}>`);
|
||||
}
|
||||
assert.match(ksesBody, /'viewbox'/);
|
||||
assert.match(ksesBody, /'xlink:href'/);
|
||||
|
||||
@@ -22,27 +22,27 @@ test('universal icon picker ships catalog data for the bundled FA5 fonts', () =>
|
||||
|
||||
test('icon catalog REST route is registered with permission + sprite single source', () => {
|
||||
const admin = read('src/Admin/Admin.php');
|
||||
assert.match(admin, /\AKSRT\\Icons\\IconController::class/);
|
||||
assert.match(admin, /\Jio\\Icons\\IconController::class/);
|
||||
const controller = read('src/Icons/IconController.php');
|
||||
assert.match(controller, /\/icons\/catalog/);
|
||||
assert.match(controller, /permission_callback.*check_permission/s);
|
||||
const catalog = read('src/Icons/IconCatalog.php');
|
||||
assert.match(catalog, /svg-icon\.js/);
|
||||
assert.match(catalog, /aksrt_svgs/);
|
||||
assert.match(catalog, /jio_svgs/);
|
||||
assert.match(catalog, /fa5-icons\.php/);
|
||||
});
|
||||
|
||||
test('widget and nav-menu icon fields render the shared picker', () => {
|
||||
const formRenderer = read('src/WidgetSystem/FormRenderer.php');
|
||||
assert.match(formRenderer, /case 'icon':\s*\n\s*echo aksrt_icon_picker_field\(\$field_name, \$field_id, \(string\) \$value\);/);
|
||||
assert.doesNotMatch(formRenderer, /aksrt-widget-icon/);
|
||||
assert.match(formRenderer, /case 'icon':\s*\n\s*echo jio_icon_picker_field\(\$field_name, \$field_id, \(string\) \$value\);/);
|
||||
assert.doesNotMatch(formRenderer, /jio-widget-icon/);
|
||||
const adminForm = read('src/WidgetSystem/AdminFormController.php');
|
||||
assert.match(adminForm, /aksrt_icon_picker_assets\(/);
|
||||
assert.match(adminForm, /jio_icon_picker_assets\(/);
|
||||
const navMenu = read('src/Admin/NavMenuOptions.php');
|
||||
assert.match(navMenu, /aksrt_icon_picker_field\(/);
|
||||
assert.match(navMenu, /aksrt_sanitize_icon_value\(\(string\) \(\$raw\['icon'\] \?\? ''\)\)/);
|
||||
const helper = read('inc/helpers/aksrt-icon-picker.php');
|
||||
for (const token of ['function aksrt_icon_picker_assets', 'function aksrt_icon_picker_field', 'id="aksrt-modal-icon"', 'aksrt-icon-picker__value']) {
|
||||
assert.match(navMenu, /jio_icon_picker_field\(/);
|
||||
assert.match(navMenu, /jio_sanitize_icon_value\(\(string\) \(\$raw\['icon'\] \?\? ''\)\)/);
|
||||
const helper = read('inc/helpers/jio-icon-picker.php');
|
||||
for (const token of ['function jio_icon_picker_assets', 'function jio_icon_picker_field', 'id="jio-modal-icon"', 'jio-icon-picker__value']) {
|
||||
assert.match(helper, new RegExp(token));
|
||||
}
|
||||
assert.match(helper, /assets\/js\/runtime\/svg-icon\.js/, 'admin previews reuse the frontend sprite');
|
||||
@@ -50,7 +50,7 @@ test('widget and nav-menu icon fields render the shared picker', () => {
|
||||
|
||||
test('picker JS composes the three value formats and loads the REST catalog', () => {
|
||||
const js = read('assets/js/admin-icon-picker.js');
|
||||
for (const token of ['X-WP-Nonce', 'customsvg-', 'zibsvg-', '`fa ${group.id} fa-${name}`', 'data-aksrt-icon-picker', 'aksrt-icon-picker__value']) {
|
||||
for (const token of ['X-WP-Nonce', 'customsvg-', 'zibsvg-', '`fa ${group.id} fa-${name}`', 'data-jio-icon-picker', 'jio-icon-picker__value']) {
|
||||
assert.match(js, new RegExp(token.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')));
|
||||
}
|
||||
assert.match(js, /DOMParser/, 'custom SVG is sanitized client side');
|
||||
@@ -59,39 +59,39 @@ test('picker JS composes the three value formats and loads the REST catalog', ()
|
||||
// Tab/search switching toggles the [hidden] attribute; without this guard
|
||||
// the explicit display rules on grids/items override it and every group
|
||||
// stays visible, so the tab bar appears dead.
|
||||
assert.match(read(cssPath), /#aksrt-modal-icon \[hidden]\s*\{\s*display:\s*none\s*!important/);
|
||||
assert.match(read(cssPath), /#jio-modal-icon \[hidden]\s*\{\s*display:\s*none\s*!important/);
|
||||
});
|
||||
|
||||
test('icon values sanitize through the SVG-aware helper on every save path', () => {
|
||||
const iconsHelper = read('inc/helpers/aksrt-icons.php');
|
||||
assert.match(iconsHelper, /function aksrt_sanitize_icon_value/);
|
||||
assert.match(iconsHelper, /function aksrt_icon_markup/);
|
||||
assert.match(iconsHelper, /function aksrt_enqueue_fontawesome/);
|
||||
const iconsHelper = read('inc/helpers/jio-icons.php');
|
||||
assert.match(iconsHelper, /function jio_sanitize_icon_value/);
|
||||
assert.match(iconsHelper, /function jio_icon_markup/);
|
||||
assert.match(iconsHelper, /function jio_enqueue_fontawesome/);
|
||||
assert.match(iconsHelper, /str_starts_with\(\$value, 'customsvg-'\)/);
|
||||
const schemaProvider = read('src/Admin/SchemaProvider.php');
|
||||
assert.match(schemaProvider, /'icon'\s+=>\s+'icon'/);
|
||||
assert.match(schemaProvider, /case 'icon':\s*\n\s*return function_exists\('aksrt_sanitize_icon_value'\)/);
|
||||
assert.match(schemaProvider, /case 'icon':\s*\n\s*return function_exists\('jio_sanitize_icon_value'\)/);
|
||||
const fieldSchema = read('src/WidgetSystem/FieldSchema.php');
|
||||
assert.match(fieldSchema, /case 'icon':\s*\n\s*return function_exists\('aksrt_sanitize_icon_value'\)/);
|
||||
assert.match(fieldSchema, /case 'icon':\s*\n\s*return function_exists\('jio_sanitize_icon_value'\)/);
|
||||
});
|
||||
|
||||
test('FA5 loads under a single canonical handle everywhere', () => {
|
||||
const picker = read('inc/helpers/aksrt-icon-picker.php');
|
||||
const picker = read('inc/helpers/jio-icon-picker.php');
|
||||
const admin = read('src/Admin/Admin.php');
|
||||
// 文章列表(edit.php)的 FA 入队随列增强迁到了 PostListEnhance。
|
||||
const postList = read('src/Admin/Native/PostListEnhance.php');
|
||||
const part1b = read('src/Core/ScriptsPart1b.php');
|
||||
assert.match(picker, /aksrt_enqueue_fontawesome\(\)/);
|
||||
assert.match(admin, /aksrt_enqueue_fontawesome\(\)/);
|
||||
assert.match(postList, /aksrt_enqueue_fontawesome\(\)/);
|
||||
assert.match(part1b, /aksrt_enqueue_fontawesome\(\['bootstrap'\]\)/);
|
||||
assert.match(part1b, /\['aksrt-font-awesome'\]/, 'public main css depends on the canonical handle');
|
||||
assert.match(picker, /jio_enqueue_fontawesome\(\)/);
|
||||
assert.match(admin, /jio_enqueue_fontawesome\(\)/);
|
||||
assert.match(postList, /jio_enqueue_fontawesome\(\)/);
|
||||
assert.match(part1b, /jio_enqueue_fontawesome\(\['bootstrap'\]\)/);
|
||||
assert.match(part1b, /\['jio-font-awesome'\]/, 'public main css depends on the canonical handle');
|
||||
for (const file of [picker, admin, postList, part1b]) {
|
||||
assert.doesNotMatch(file, /wp_enqueue_style\(\s*'aksrt-font-awesome'/, 'no duplicate FA registrations');
|
||||
assert.doesNotMatch(file, /wp_enqueue_style\(\s*'jio-font-awesome'/, 'no duplicate FA registrations');
|
||||
}
|
||||
// core-hooks.php 不该再自己注册或重复入队 FA。
|
||||
assert.doesNotMatch(read('inc/core-hooks.php'), /aksrt_enqueue_fontawesome|wp_enqueue_style\(\s*'aksrt-font-awesome'/);
|
||||
for (const file of ['inc/helpers/aksrt-widgets-markup.php', 'src/WidgetSystem/Renderers/ContentRenderer.php']) {
|
||||
assert.doesNotMatch(read('inc/core-hooks.php'), /jio_enqueue_fontawesome|wp_enqueue_style\(\s*'jio-font-awesome'/);
|
||||
for (const file of ['inc/helpers/jio-widgets-markup.php', 'src/WidgetSystem/Renderers/ContentRenderer.php']) {
|
||||
const source = read(file);
|
||||
assert.doesNotMatch(source, /fa-clock-o|fa-folder-open-o|fa-file-text-o/, `${file} uses FA5 names`);
|
||||
}
|
||||
@@ -119,13 +119,13 @@ test('custom settings pages and standalone save paths speak the three icon forma
|
||||
assert.match(read('assets/admin/src/components/floating/FloatMoreButtonsEditor.vue'), /import IconValue from '@\/components\/form\/IconValue\.vue'/);
|
||||
// Their REST save paths keep customsvg markup intact.
|
||||
const controller = read('src/Admin/GlobalControllerPart1.php');
|
||||
assert.match(controller, /aksrt_sanitize_icon_value\(/);
|
||||
assert.match(controller, /jio_sanitize_icon_value\(/);
|
||||
assert.doesNotMatch(controller, /preg_match\('\/\^fa\\s\+/, 'no FA-only regex gate on icon fields');
|
||||
assert.match(read('src/Settings/FloatButtons.php'), /aksrt_sanitize_icon_value\(/);
|
||||
assert.match(read('src/Settings/FloatButtons.php'), /jio_sanitize_icon_value\(/);
|
||||
// Frontend consumers render every format.
|
||||
const footer = read('footer.php');
|
||||
assert.match(footer, /aksrt_icon_markup\(\(string\) \$tab_icon\)/);
|
||||
assert.match(read('inc/helpers/aksrt-float-buttons.php'), /aksrt_icon_markup\(\(string\) \(\$opt\['icon'\]/);
|
||||
assert.match(footer, /jio_icon_markup\(\(string\) \$tab_icon\)/);
|
||||
assert.match(read('inc/helpers/jio-float-buttons.php'), /jio_icon_markup\(\(string\) \(\$opt\['icon'\]/);
|
||||
// The popover tabs must wrap instead of overflowing the panel.
|
||||
assert.match(read('assets/admin/src/components/form/FaIconPicker.vue'), /flex-wrap: wrap/);
|
||||
});
|
||||
@@ -133,12 +133,12 @@ test('custom settings pages and standalone save paths speak the three icon forma
|
||||
test('nav menu icons render every format without legacy fa-prefix mangling', () => {
|
||||
const helper = read('inc/helpers/menu-decoration.php');
|
||||
assert.match(helper, /str_starts_with\(\$icon_class, 'zibsvg-'\)/);
|
||||
assert.match(helper, /aksrt_icon_markup\(\$icon_class, 'icon mr3 menu-item-icon'\)/);
|
||||
assert.match(helper, /jio_icon_markup\(\$icon_class, 'icon mr3 menu-item-icon'\)/);
|
||||
assert.doesNotMatch(helper, /'<i class="' \. esc_attr\(\$icon_class\)/, 'no raw i-tag bypassing the unified helper');
|
||||
});
|
||||
|
||||
test('icon markup upgrades legacy FA4 values to FA5 and never emits the FA4 form', () => {
|
||||
const helper = read('inc/helpers/aksrt-icons.php');
|
||||
const helper = read('inc/helpers/jio-icons.php');
|
||||
// Bare glyph, legacy double-fa value and style-only value all normalize to FA5 solid.
|
||||
assert.match(helper, /preg_match\('\/\^fa\\s\+fa-\/i', \$value\)/, 'legacy double-fa value is detected');
|
||||
assert.match(helper, /preg_replace\('\/\^fa\\s\+\/i', 'fa fas ', \$value, 1\)/, 'legacy double-fa value upgrades to FA5');
|
||||
@@ -156,7 +156,7 @@ test('brand icons render despite the zibll-legacy fa-fab double prefix', () => {
|
||||
}
|
||||
assert.match(read('assets/css/main.min.css'), rule, 'minified main css carries the fix');
|
||||
// Empty sprite entries surface as blank picker items — none may ship.
|
||||
assert.doesNotMatch(read('assets/js/runtime/svg-icon.js'), /aksrt_svgs\['[a-zA-Z0-9_]+'\]\s*=\s*'';/);
|
||||
assert.doesNotMatch(read('assets/js/runtime/svg-icon.js'), /jio_svgs\['[a-zA-Z0-9_]+'\]\s*=\s*'';/);
|
||||
});
|
||||
|
||||
test('icon assets are consolidated and dead icon assets are gone', () => {
|
||||
|
||||
@@ -28,7 +28,7 @@ function input(html, name) {
|
||||
return match[0];
|
||||
}
|
||||
function fieldset(html, dependency) {
|
||||
const tags = [...html.matchAll(/<fieldset\b[^>]*data-aksrt-inv-dep="([^"]+)"[^>]*>/g)];
|
||||
const tags = [...html.matchAll(/<fieldset\b[^>]*data-jio-inv-dep="([^"]+)"[^>]*>/g)];
|
||||
const found = tags.find(([, value]) => value === dependency);
|
||||
assert.ok(found, `missing conditional fieldset ${dependency}`);
|
||||
return found[0];
|
||||
@@ -42,18 +42,18 @@ function hasBooleanAttr(tag, name) {
|
||||
|
||||
test('filters, selection, table and pagination share one list card', () => {
|
||||
const { html } = harness();
|
||||
const cards = [...html.matchAll(/<section\b[^>]*class="[^"]*\baksrt-native-card\b[^"]*"[^>]*>[\s\S]*?<\/section>/g)];
|
||||
const cards = [...html.matchAll(/<section\b[^>]*class="[^"]*\bjio-native-card\b[^"]*"[^>]*>[\s\S]*?<\/section>/g)];
|
||||
assert.equal(cards.length, 1);
|
||||
const card = cards[0][0];
|
||||
assert.match(card, /aksrt-inv-list-card/);
|
||||
assert.match(card, /<h2 class="aksrt-native-card__title">邀请码列表<\/h2>/);
|
||||
for (const marker of ['class="subsubsub"', 'class="aksrt-native-filters"', '清除筛选', 'data-aksrt-inv-selection', 'aksrt-inv-table', 'class="tablenav bottom"']) {
|
||||
assert.match(card, /jio-inv-list-card/);
|
||||
assert.match(card, /<h2 class="jio-native-card__title">邀请码列表<\/h2>/);
|
||||
for (const marker of ['class="subsubsub"', 'class="jio-native-filters"', '清除筛选', 'data-jio-inv-selection', 'jio-inv-table', 'class="tablenav bottom"']) {
|
||||
assert.ok(card.includes(marker), `${marker} belongs to the list card`);
|
||||
}
|
||||
// 批量操作行同时带共享的 aksrt-native-bulk 类,按 class 列表中的词匹配,不要求整串相等。
|
||||
assert.match(card, /class="[^"]*\baksrt-inv-bulk\b[^"]*"/, 'aksrt-inv-bulk belongs to the list card');
|
||||
assert.doesNotMatch(html, /aksrt-inv-filter-summary|关键词:/);
|
||||
assert.doesNotMatch(read('assets', 'css', 'admin', 'invite-codes.css'), /aksrt-inv-filter-summary/);
|
||||
// 批量操作行同时带共享的 jio-native-bulk 类,按 class 列表中的词匹配,不要求整串相等。
|
||||
assert.match(card, /class="[^"]*\bjio-inv-bulk\b[^"]*"/, 'jio-inv-bulk belongs to the list card');
|
||||
assert.doesNotMatch(html, /jio-inv-filter-summary|关键词:/);
|
||||
assert.doesNotMatch(read('assets', 'css', 'admin', 'invite-codes.css'), /jio-inv-filter-summary/);
|
||||
});
|
||||
|
||||
test('search and bulk actions remain independent sibling forms inside the list card', () => {
|
||||
@@ -71,8 +71,8 @@ test('search and bulk actions remain independent sibling forms inside the list c
|
||||
}
|
||||
assert.equal(depth, 0);
|
||||
assert.equal(count, 4);
|
||||
const searchEnd = html.indexOf('</form>', html.indexOf('class="aksrt-native-filters"'));
|
||||
const listStart = html.lastIndexOf('<form', html.indexOf('data-aksrt-inv-list'));
|
||||
const searchEnd = html.indexOf('</form>', html.indexOf('class="jio-native-filters"'));
|
||||
const listStart = html.lastIndexOf('<form', html.indexOf('data-jio-inv-list'));
|
||||
assert.ok(searchEnd < listStart);
|
||||
const listEnd = html.indexOf('</form>', listStart);
|
||||
assert.ok(html.indexOf('class="tablenav bottom"', listStart) < listEnd);
|
||||
@@ -81,13 +81,13 @@ test('search and bulk actions remain independent sibling forms inside the list c
|
||||
|
||||
test('page renders add and export dialogs beside, not inside, the list form', () => {
|
||||
const { html } = harness();
|
||||
const listMarker = html.indexOf('data-aksrt-inv-list');
|
||||
const listMarker = html.indexOf('data-jio-inv-list');
|
||||
assert.ok(listMarker > 0, 'list form is rendered');
|
||||
const listStart = html.lastIndexOf('<form', listMarker);
|
||||
const listEnd = html.indexOf('</form>', listMarker);
|
||||
const addStart = html.indexOf('<dialog', html.indexOf('id="aksrt-inv-add"') - 100);
|
||||
const addDialog = dialog(html, 'aksrt-inv-add');
|
||||
const exportDialog = dialog(html, 'aksrt-inv-export');
|
||||
const addStart = html.indexOf('<dialog', html.indexOf('id="jio-inv-add"') - 100);
|
||||
const addDialog = dialog(html, 'jio-inv-add');
|
||||
const exportDialog = dialog(html, 'jio-inv-export');
|
||||
const addIndex = html.indexOf(addDialog);
|
||||
const exportIndex = html.indexOf(exportDialog);
|
||||
assert.ok(listStart >= 0 && listEnd > listStart);
|
||||
@@ -100,18 +100,18 @@ test('page renders add and export dialogs beside, not inside, the list form', ()
|
||||
|
||||
test('all admin forms carry the expected method, action, and escaped nonce', () => {
|
||||
const { html } = harness();
|
||||
const list = html.slice(html.lastIndexOf('<form', html.indexOf('data-aksrt-inv-list')), html.indexOf('</form>', html.indexOf('data-aksrt-inv-list')) + 7);
|
||||
const add = dialog(html, 'aksrt-inv-add');
|
||||
const exportForm = dialog(html, 'aksrt-inv-export');
|
||||
const list = html.slice(html.lastIndexOf('<form', html.indexOf('data-jio-inv-list')), html.indexOf('</form>', html.indexOf('data-jio-inv-list')) + 7);
|
||||
const add = dialog(html, 'jio-inv-add');
|
||||
const exportForm = dialog(html, 'jio-inv-export');
|
||||
assert.match(list, /<form method="post" action="https:\/\/example\.test\/wp-admin\/admin-post\.php"/);
|
||||
assert.match(list, /name="action" value="aksrt_invite_delete"/);
|
||||
assert.match(list, /name="action" value="jio_invite_delete"/);
|
||||
for (const form of [list, add, exportForm]) {
|
||||
assert.match(form, /name="aksrt_nonce" value="nonce"<script>&"/);
|
||||
assert.match(form, /name="jio_nonce" value="nonce"<script>&"/);
|
||||
}
|
||||
assert.match(add, /<form method="post" action="https:\/\/example\.test\/wp-admin\/admin-post\.php"/);
|
||||
assert.match(add, /name="action" value="aksrt_invite_add"/);
|
||||
assert.match(add, /name="action" value="jio_invite_add"/);
|
||||
assert.match(exportForm, /<form method="get" action="https:\/\/example\.test\/wp-admin\/admin-post\.php"/);
|
||||
assert.match(exportForm, /name="action" value="aksrt_invite_export"/);
|
||||
assert.match(exportForm, /name="action" value="jio_invite_export"/);
|
||||
});
|
||||
|
||||
test('rendered code, notice, user, date, and row values are escaped', () => {
|
||||
@@ -162,20 +162,20 @@ test('numeric fields expose browser constraints and VIP days apply to both level
|
||||
|
||||
test('export UI and service consistently name the spreadsheet format CSV', () => {
|
||||
const { html } = harness();
|
||||
const exportForm = dialog(html, 'aksrt-inv-export');
|
||||
const exportForm = dialog(html, 'jio-inv-export');
|
||||
assert.match(exportForm, /name="export_format" value="csv" checked/);
|
||||
assert.match(exportForm, /Excel 表格(\.csv)/);
|
||||
assert.match(exportForm, /文本文档(\.txt)/);
|
||||
const service = read('src', 'Payment', 'InviteCode.php');
|
||||
assert.match(service, /\$_GET\['export_format'\] \?\? 'csv'\) === 'text' \? 'text' : 'csv'/);
|
||||
assert.match(service, /\[__\('邀请码', 'aksrt'\), __\('使用奖励', 'aksrt'\), __\('创建时间', 'aksrt'\), __\('更新时间', 'aksrt'\), __\('状态', 'aksrt'\), __\('标识', 'aksrt'\)\]/);
|
||||
assert.match(service, /aksrt-invite-codes\.csv/);
|
||||
assert.match(service, /\[__\('邀请码', 'jio'\), __\('使用奖励', 'jio'\), __\('创建时间', 'jio'\), __\('更新时间', 'jio'\), __\('状态', 'jio'\), __\('标识', 'jio'\)\]/);
|
||||
assert.match(service, /jio-invite-codes\.csv/);
|
||||
assert.doesNotMatch(service, /\bxls\b|export_format[^\n]*xls/);
|
||||
});
|
||||
|
||||
test('search form and filter links preserve sanitized keyword and remark context', () => {
|
||||
const { html } = harness();
|
||||
const marker = html.indexOf('class="aksrt-native-filters"');
|
||||
const marker = html.indexOf('class="jio-native-filters"');
|
||||
const start = html.lastIndexOf('<form', marker);
|
||||
const end = html.indexOf('</form>', marker);
|
||||
const form = html.slice(start, end + 7);
|
||||
@@ -210,7 +210,7 @@ test('page uses no list/add/export tab route and service returns to the canonica
|
||||
const page = read('src', 'Admin', 'Native', 'UsersMenusInviteCode.php');
|
||||
const forms = read('src', 'Admin', 'Native', 'UsersMenusInviteForms.php');
|
||||
assert.doesNotMatch(page + forms + html, /page-title-action|[?&](?:tab|view)=(?:list|add|export)|tab=(?:list|add|export)/);
|
||||
assert.equal(return_url, 'https://example.test/wp-admin/users.php?page=aksrt_invite_codes');
|
||||
assert.equal(return_url, 'https://example.test/wp-admin/users.php?page=jio_invite_codes');
|
||||
assert.doesNotMatch(return_url, /(?:tab|view|add)=/);
|
||||
});
|
||||
|
||||
@@ -229,9 +229,9 @@ test('invite CSS and JS are enqueued only for the invite submenu hook', () => {
|
||||
assert.deepEqual(wrong_hook_assets, []);
|
||||
assert.equal(page_assets.length, 2);
|
||||
assert.deepEqual(page_assets[0].slice(0, 4), [
|
||||
'style', 'aksrt-admin-invite-codes', 'https://example.test/theme/assets/css/admin/invite-codes.css', ['aksrt-admin-native-ui'],
|
||||
'style', 'jio-admin-invite-codes', 'https://example.test/theme/assets/css/admin/invite-codes.css', ['jio-admin-native-ui'],
|
||||
]);
|
||||
assert.deepEqual(page_assets[1].slice(0, 4), [
|
||||
'script', 'aksrt-admin-invite-codes', 'https://example.test/theme/assets/js/admin-invite-codes.js', [],
|
||||
'script', 'jio-admin-invite-codes', 'https://example.test/theme/assets/js/admin-invite-codes.js', [],
|
||||
]);
|
||||
});
|
||||
@@ -1,16 +1,16 @@
|
||||
<?php
|
||||
/** WordPress-free render harness for the native invite admin page. */
|
||||
namespace AKSRT\Core {
|
||||
namespace Jio\Core {
|
||||
final class DBMigrator {
|
||||
public static function table($key) { return 'wp_aksrt_' . $key; }
|
||||
public static function table($key) { return 'wp_jio_' . $key; }
|
||||
}
|
||||
}
|
||||
namespace {
|
||||
define('ABSPATH', __DIR__);
|
||||
define('ARRAY_A', 'ARRAY_A');
|
||||
$root = dirname(__DIR__, 2);
|
||||
define('AKSRT_DIR', $root);
|
||||
define('AKSRT_URI', 'https://example.test/theme');
|
||||
define('JIO_DIR', $root);
|
||||
define('JIO_URI', 'https://example.test/theme');
|
||||
|
||||
class WP_Error {
|
||||
public $code;
|
||||
@@ -88,10 +88,10 @@ namespace {
|
||||
return ['all_count' => 2, 'unused_count' => 1, 'used_count' => 1];
|
||||
}
|
||||
public function get_results($sql, $format = null) {
|
||||
if (str_contains($sql, 'FROM wp_aksrt_invite_code_usage u')) {
|
||||
if (str_contains($sql, 'FROM wp_jio_invite_code_usage u')) {
|
||||
return [['code_id' => 41, 'user_id' => 7, 'used_at' => '2026-10-30 11:00:00', 'display_name' => 'Admin"><script>alert(1)</script>']];
|
||||
}
|
||||
if (str_contains($sql, 'SELECT * FROM wp_aksrt_invite_code')) {
|
||||
if (str_contains($sql, 'SELECT * FROM wp_jio_invite_code')) {
|
||||
return [
|
||||
[
|
||||
'id' => 41,
|
||||
@@ -133,18 +133,18 @@ namespace {
|
||||
require $root . '/src/Admin/Native/UsersMenusInviteCode.php';
|
||||
|
||||
final class InviteAdminProbe {
|
||||
use \AKSRT\Admin\Native\UsersMenusInviteCode;
|
||||
use \Jio\Admin\Native\UsersMenusInviteCode;
|
||||
}
|
||||
|
||||
$_GET = [
|
||||
'page' => 'aksrt_invite_codes',
|
||||
'page' => 'jio_invite_codes',
|
||||
'status' => 'used',
|
||||
's' => 'search <script>alert(2)</script> & phrase',
|
||||
'remark' => 'campaign & east',
|
||||
'orderby' => 'code',
|
||||
'desc' => 'ASC',
|
||||
'aksrt_inv_title' => '<script>alert(1)</script>Completed',
|
||||
'aksrt_inv_msg' => '<img src=x onerror=alert(1)>notice',
|
||||
'jio_inv_title' => '<script>alert(1)</script>Completed',
|
||||
'jio_inv_msg' => '<img src=x onerror=alert(1)>notice',
|
||||
];
|
||||
$probe = new InviteAdminProbe();
|
||||
ob_start();
|
||||
@@ -152,12 +152,12 @@ namespace {
|
||||
$page = ob_get_clean();
|
||||
|
||||
$GLOBALS['assets'] = [];
|
||||
$probe->enqueue_invite_assets('users_page_aksrt_messages');
|
||||
$probe->enqueue_invite_assets('users_page_jio_messages');
|
||||
$wrong_hook_assets = $GLOBALS['assets'];
|
||||
$probe->enqueue_invite_assets('users_page_aksrt_invite_codes');
|
||||
$probe->enqueue_invite_assets('users_page_jio_invite_codes');
|
||||
$page_assets = $GLOBALS['assets'];
|
||||
|
||||
$service = \AKSRT\Payment\InviteCode::get_instance();
|
||||
$service = \Jio\Payment\InviteCode::get_instance();
|
||||
$guard = new \ReflectionMethod($service, 'guard_and_redirect');
|
||||
$list_result = $service->list_codes(['status' => 'used']);
|
||||
$import_lines = [];
|
||||
|
||||
@@ -8,13 +8,13 @@ const read = (...segments) => fs.readFileSync(path.join(root, ...segments), 'utf
|
||||
|
||||
test('invite code table has a unique code key and a usage log table', () => {
|
||||
const schema = read('src', 'Core', 'DBMigratorPart2.php')
|
||||
assert.match(schema, /CREATE TABLE \{\$prefix\}aksrt_invite_code \([\s\S]*?UNIQUE KEY uniq_invite_code \(code\)/)
|
||||
assert.match(schema, /CREATE TABLE \{\$prefix\}aksrt_invite_code \([\s\S]*?\) ENGINE=InnoDB/)
|
||||
assert.match(schema, /CREATE TABLE \{\$prefix\}jio_invite_code \([\s\S]*?UNIQUE KEY uniq_invite_code \(code\)/)
|
||||
assert.match(schema, /CREATE TABLE \{\$prefix\}jio_invite_code \([\s\S]*?\) ENGINE=InnoDB/)
|
||||
// 使用记录表:运营需要知道"哪个用户用了哪个码",且 (code_id, user_id) 唯一防重复记账。
|
||||
assert.match(schema, /CREATE TABLE \{\$prefix\}aksrt_invite_code_usage \([\s\S]*?UNIQUE KEY uniq_invite_usage \(code_id, user_id\)/)
|
||||
assert.match(schema, /CREATE TABLE \{\$prefix\}jio_invite_code_usage \([\s\S]*?UNIQUE KEY uniq_invite_usage \(code_id, user_id\)/)
|
||||
const registry = read('src', 'Core', 'DBMigratorPart3.php')
|
||||
assert.match(registry, /'invite_code'\s+=>\s+'aksrt_invite_code'/)
|
||||
assert.match(registry, /'invite_code_usage'\s+=>\s+'aksrt_invite_code_usage'/)
|
||||
assert.match(registry, /'invite_code'\s+=>\s+'jio_invite_code'/)
|
||||
assert.match(registry, /'invite_code_usage'\s+=>\s+'jio_invite_code_usage'/)
|
||||
})
|
||||
|
||||
test('consumption is a single conditional UPDATE, not select-then-update', () => {
|
||||
@@ -50,7 +50,7 @@ test('registration services gate on the invite-code field mode', () => {
|
||||
test('rewards defer to email verification when signup verification is on', () => {
|
||||
const service = read('src', 'Payment', 'InviteCode.php')
|
||||
assert.match(service, /signup_verification_enabled\(\)/)
|
||||
assert.match(service, /aksrt_email_verified/)
|
||||
assert.match(service, /jio_email_verified/)
|
||||
assert.match(service, /META_PENDING/)
|
||||
})
|
||||
|
||||
@@ -71,9 +71,9 @@ test('invite-code surfaces: form field, admin page, menus and both schemas', ()
|
||||
assert.match(page, /orderby-but/)
|
||||
assert.match(page, /bulk_action/)
|
||||
assert.match(page, /paginate_links/)
|
||||
assert.match(page, /aksrt_invite_delete/)
|
||||
assert.match(forms, /aksrt_invite_add/)
|
||||
assert.match(forms, /aksrt_invite_export/)
|
||||
assert.match(page, /jio_invite_delete/)
|
||||
assert.match(forms, /jio_invite_add/)
|
||||
assert.match(forms, /jio_invite_export/)
|
||||
// 列表内不再有列表/添加/导出三视图路由;状态筛选仍是独立的列表筛选器。
|
||||
assert.doesNotMatch(page + forms, /page-title-action|[?&](?:tab|view)=(?:add|export)|tab=(?:list|add|export)/)
|
||||
// 管理页不再依赖 REST 与 wp_rest nonce。
|
||||
@@ -81,7 +81,7 @@ test('invite-code surfaces: form field, admin page, menus and both schemas', ()
|
||||
|
||||
const menus = read('src', 'Admin', 'Native', 'UsersMenus.php')
|
||||
assert.match(menus, /use UsersMenusInviteCode/)
|
||||
assert.match(menus, /aksrt_invite_codes/)
|
||||
assert.match(menus, /jio_invite_codes/)
|
||||
|
||||
const bootstrap = read('functions.php')
|
||||
assert.match(bootstrap, /InviteCode::get_instance\(\)->init\(\)/)
|
||||
@@ -97,14 +97,14 @@ test('invite-code surfaces: form field, admin page, menus and both schemas', ()
|
||||
|
||||
test('admin operations run through admin_post handlers with capability and nonce guards', () => {
|
||||
const service = read('src', 'Payment', 'InviteCode.php')
|
||||
assert.match(service, /admin_post_aksrt_invite_add/)
|
||||
assert.match(service, /admin_post_aksrt_invite_delete/)
|
||||
assert.match(service, /admin_post_aksrt_invite_export/)
|
||||
assert.match(service, /check_admin_referer\('aksrt_invite_admin', 'aksrt_nonce'\)/)
|
||||
assert.match(service, /admin_post_jio_invite_add/)
|
||||
assert.match(service, /admin_post_jio_invite_delete/)
|
||||
assert.match(service, /admin_post_jio_invite_export/)
|
||||
assert.match(service, /check_admin_referer\('jio_invite_admin', 'jio_nonce'\)/)
|
||||
assert.match(service, /current_user_can\('edit_users'\)/)
|
||||
// 管理端 REST 已移除:除公开 verify 外不再注册邀请码路由(导出文件名除外)。
|
||||
assert.doesNotMatch(service, /register_rest_route\('aksrt\/v1', '\/invite-codes/)
|
||||
assert.match(service, /register_rest_route\('aksrt\/v1', '\/invite-code\/verify'/)
|
||||
assert.doesNotMatch(service, /register_rest_route\('jio\/v1', '\/invite-codes/)
|
||||
assert.match(service, /register_rest_route\('jio\/v1', '\/invite-code\/verify'/)
|
||||
// 列表/导出共用状态过滤;批量删除连使用记录一起清。
|
||||
assert.match(service, /private function status_where\(string \$status\): array/)
|
||||
assert.match(service, /'unused' => \['used_count = 0', \[\]\]/)
|
||||
@@ -157,8 +157,8 @@ test('per-code remark persists through generate/import and both exports round-tr
|
||||
assert.match(service, /\$parts === \[\] \? '无'/)
|
||||
// 文本导出与导入格式互通;CSV(Excel)含中文表头与状态列。
|
||||
assert.match(service, /\$row\['code'\] \. \$division \. \$this->reward_param_string/)
|
||||
assert.match(service, /__\('邀请码', 'aksrt'\), __\('使用奖励', 'aksrt'\)/)
|
||||
assert.match(service, /\$used > 0 \? __\('已使用', 'aksrt'\) : __\('未使用', 'aksrt'\)/)
|
||||
assert.match(service, /__\('邀请码', 'jio'\), __\('使用奖励', 'jio'\)/)
|
||||
assert.match(service, /\$used > 0 \? __\('已使用', 'jio'\) : __\('未使用', 'jio'\)/)
|
||||
|
||||
const forms = read('src', 'Admin', 'Native', 'UsersMenusInviteForms.php')
|
||||
// 自动生成标识字段默认 invita_YmdHis;列表标识列可点击定位。
|
||||
|
||||
@@ -53,9 +53,9 @@ function sanitize_key($value) { return preg_replace('/[^a-z0-9_\\-]/', '', strto
|
||||
function esc_attr($value) { return htmlspecialchars((string) $value, ENT_QUOTES); }
|
||||
function esc_url($value) { return (string) $value; }
|
||||
function get_template_directory_uri() { return 'https://theme.test'; }
|
||||
function aksrt_get_default_avatar_url() { return 'https://theme.test/default-avatar.png'; }
|
||||
eval('namespace AKSRT\\\\Settings; class Settings { public static $values = []; public static function get_value($key, $default = null) { return array_key_exists($key, self::$values) ? self::$values[$key] : $default; } }');
|
||||
\\AKSRT\\Settings\\Settings::$values = json_decode(${JSON.stringify(JSON.stringify(values))}, true);
|
||||
function jio_get_default_avatar_url() { return 'https://theme.test/default-avatar.png'; }
|
||||
eval('namespace Jio\\\\Settings; class Settings { public static $values = []; public static function get_value($key, $default = null) { return array_key_exists($key, self::$values) ? self::$values[$key] : $default; } }');
|
||||
\\Jio\\Settings\\Settings::$values = json_decode(${JSON.stringify(JSON.stringify(values))}, true);
|
||||
require ${JSON.stringify(helper)};
|
||||
${body}
|
||||
`;
|
||||
@@ -89,32 +89,32 @@ test('admin schemas expose the ten legacy lazy fields and defaults', () => {
|
||||
});
|
||||
|
||||
test('PHP helpers emit placeholder/data-src, direct src, and crawler bypass', () => {
|
||||
const enabled = phpHarness("echo aksrt_get_lazy_attr('lazy_posts_thumb', 'https://cdn.test/post.webp', 'fit-cover');");
|
||||
const enabled = phpHarness("echo jio_get_lazy_attr('lazy_posts_thumb', 'https://cdn.test/post.webp', 'fit-cover');");
|
||||
assert.match(enabled, /class="lazyload fit-cover"/);
|
||||
assert.match(enabled, /src="https:\/\/theme\.test\/assets\/img\/thumbnail\.svg"/);
|
||||
assert.match(enabled, /data-src="https:\/\/cdn\.test\/post\.webp"/);
|
||||
|
||||
const disabled = phpHarness("echo aksrt_get_lazy_attr('lazy_posts_thumb', 'https://cdn.test/post.webp', 'fit-cover');", { lazy_posts_thumb: false });
|
||||
const disabled = phpHarness("echo jio_get_lazy_attr('lazy_posts_thumb', 'https://cdn.test/post.webp', 'fit-cover');", { lazy_posts_thumb: false });
|
||||
assert.equal(disabled, 'class="fit-cover" src="https://cdn.test/post.webp"');
|
||||
|
||||
const crawler = phpHarness("echo aksrt_get_lazy_attr('lazy_posts_thumb', 'https://cdn.test/post.webp', 'fit-cover');", {}, 'Googlebot/2.1');
|
||||
const crawler = phpHarness("echo jio_get_lazy_attr('lazy_posts_thumb', 'https://cdn.test/post.webp', 'fit-cover');", {}, 'Googlebot/2.1');
|
||||
assert.equal(crawler, 'class="fit-cover" src="https://cdn.test/post.webp"');
|
||||
|
||||
const empty = phpHarness("echo aksrt_get_lazy_attr('lazy_other', '', 'media');");
|
||||
const empty = phpHarness("echo jio_get_lazy_attr('lazy_other', '', 'media');");
|
||||
assert.equal(empty, 'class="media" src=""');
|
||||
});
|
||||
|
||||
test('content, avatar, and responsive image paths follow one contract', () => {
|
||||
const content = phpHarness(`
|
||||
$html = '<img class="hero" src="https://cdn.test/photo.jpg" srcset="https://cdn.test/photo-2x.jpg 2x">';
|
||||
echo aksrt_lazy_content_images($html);
|
||||
echo jio_lazy_content_images($html);
|
||||
`, { lazy_posts_content: true });
|
||||
assert.match(content, /thumbnail-lg\.svg/);
|
||||
assert.match(content, /data-src="https:\/\/cdn\.test\/photo\.jpg"/);
|
||||
assert.match(content, /data-srcset="https:\/\/cdn\.test\/photo-2x\.jpg 2x"/);
|
||||
assert.doesNotMatch(content, /\ssrcset=/);
|
||||
|
||||
const avatar = phpHarness("echo aksrt_lazy_avatar_html('<img class=\"avatar\" src=\"https://cdn.test/avatar.png\" loading=\"lazy\">');");
|
||||
const avatar = phpHarness("echo jio_lazy_avatar_html('<img class=\"avatar\" src=\"https://cdn.test/avatar.png\" loading=\"lazy\">');");
|
||||
assert.match(avatar, /class="lazyload avatar"/);
|
||||
assert.match(avatar, /src="https:\/\/theme\.test\/default-avatar\.png"/);
|
||||
assert.match(avatar, /data-src="https:\/\/cdn\.test\/avatar\.png"/);
|
||||
@@ -128,7 +128,7 @@ test('lazySizes has one auto_fun integration owner and loader boots main once',
|
||||
const routeScripts = read('src/Core/ScriptsPart1b.php');
|
||||
const builder = read('tools/build-public-assets.mjs');
|
||||
const loader = read('assets/js/runtime/loader.js');
|
||||
const searchShell = read('inc/helpers/header/aksrt-header-menu-search.php');
|
||||
const searchShell = read('inc/helpers/header/jio-header-menu-search.php');
|
||||
const pagination = read('assets/js/core/pagination.js');
|
||||
const widgets = read('assets/widgets/frontend.js');
|
||||
|
||||
@@ -152,7 +152,7 @@ test('lazySizes has one auto_fun integration owner and loader boots main once',
|
||||
assert.match(runtime, /var lazyload_action = _this\.attr\('lazyload-action'\)/);
|
||||
assert.match(runtime, /if \(_this\.attr\('remote-box'\)\)[\s\S]*?_this\.click\(\)/);
|
||||
assert.match(searchShell, /'type'\s*=>\s*'click'/);
|
||||
assert.match(runtime, /aksrt:remote-loaded/);
|
||||
assert.match(runtime, /jio:remote-loaded/);
|
||||
assert.doesNotMatch(runtime, /action:\s*'get_current_user'/);
|
||||
assert.match(runtime, /var cases = jQuery\('\.vanilla-tilt:not\(\.is-initialized\)'\);[\s\S]*?if \(!cases\.length\)[\s\S]*?tbquire\(\['tilt'\]/);
|
||||
assert.doesNotMatch(pagination, /IntersectionObserver|new Image\(/);
|
||||
@@ -164,18 +164,18 @@ test('lazySizes has one auto_fun integration owner and loader boots main once',
|
||||
read('assets/js/pay-vip.js'),
|
||||
loader,
|
||||
].join('\n');
|
||||
const legacyDynamicImageApi = 'aksrt' + 'GetLazyImageAttr';
|
||||
const legacyDynamicImageApi = 'jio' + 'GetLazyImageAttr';
|
||||
assert.equal(dynamicSources.includes(legacyDynamicImageApi), false);
|
||||
assert.match(read('assets/js/order-center.js'), /<img src="' \+ escapeHtml\(image\)/);
|
||||
assert.match(read('assets/js/page-navs.js'), /<img src="' \+ this\.escHtml\(item\.thumbnail\)/);
|
||||
assert.match(read('assets/js/pay-vip.js'), /document\.createElement\("img"\)/);
|
||||
});
|
||||
|
||||
test('widget video init flag stays out of the AksrtVideoPlayer attribute namespace', () => {
|
||||
test('widget video init flag stays out of the JioVideoPlayer attribute namespace', () => {
|
||||
const widgets = read('assets/widgets/frontend.js');
|
||||
// data-aksrt-video 是 video-player.js 的自动初始化选择器:小工具容器若带上
|
||||
// data-jio-video 是 video-player.js 的自动初始化选择器:小工具容器若带上
|
||||
// 该属性,会被再挂一个无源播放器(黑条 + 遮罩叠在 DPlayer 上)。
|
||||
assert.doesNotMatch(widgets, /dataset\.aksrtVideo\s*=/);
|
||||
assert.doesNotMatch(widgets, /dataset\.jioVideo\s*=/);
|
||||
assert.match(widgets, /once\(el, 'WidgetVideo'\)/);
|
||||
});
|
||||
|
||||
@@ -190,9 +190,9 @@ test('post lists have no second lazy switch and keep legacy fallback thumbnails'
|
||||
for (const source of [config, postData, markup, phpSchema, vueSchema]) {
|
||||
assert.equal(source.includes(legacyKey), false);
|
||||
}
|
||||
assert.match(postData, /aksrt_get_lazy_attr\('lazy_posts_thumb', \(string\) \$data\['primary'\], 'fit-cover radius8'\)/);
|
||||
assert.match(markup, /aksrt_get_lazy_attr\('lazy_posts_thumb', \(string\) \$data\['primary'\], 'fit-cover radius8'\)/);
|
||||
assert.match(config, /\$primary = aksrt_renderer_spare_image\(\$thumb\);[\s\S]*?\$primary = \(string\) aksrt_get_lazy_thumb\(\);/);
|
||||
assert.match(postData, /jio_get_lazy_attr\('lazy_posts_thumb', \(string\) \$data\['primary'\], 'fit-cover radius8'\)/);
|
||||
assert.match(markup, /jio_get_lazy_attr\('lazy_posts_thumb', \(string\) \$data\['primary'\], 'fit-cover radius8'\)/);
|
||||
assert.match(config, /\$primary = jio_renderer_spare_image\(\$thumb\);[\s\S]*?\$primary = \(string\) jio_get_lazy_thumb\(\);/);
|
||||
assert.doesNotMatch(config, /rand_thumb/);
|
||||
});
|
||||
|
||||
@@ -203,13 +203,13 @@ test('legacy lazy identifiers and theme-native loading attributes are gone', ()
|
||||
'data-' + 'lazy-animation',
|
||||
'lazyload_' + 'enabled',
|
||||
'get_' + 'lazyload_config',
|
||||
'aksrt_' + 'lazy_img_attrs',
|
||||
'aksrt_' + 'lazy_loading',
|
||||
'jio_' + 'lazy_img_attrs',
|
||||
'jio_' + 'lazy_loading',
|
||||
'thumb_' + 'lazy',
|
||||
];
|
||||
for (const token of forbidden) assert.equal(source.includes(token), false, token);
|
||||
assert.doesNotMatch(source, /aksrt-lazy(?!sizes|-image-attr)/);
|
||||
assert.doesNotMatch(source, /aksrt-loaded/);
|
||||
assert.doesNotMatch(source, /jio-lazy(?!sizes|-image-attr)/);
|
||||
assert.doesNotMatch(source, /jio-loaded/);
|
||||
assert.doesNotMatch(source, /loading=["'](?:lazy|eager)["']/);
|
||||
assert.doesNotMatch(source, /\.loading\s*=\s*["'](?:lazy|eager)["']/);
|
||||
assert.doesNotMatch(source, /setAttribute\(\s*["']loading["']/);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* 消息管理后台(users.php → aksrt_messages)契约:
|
||||
* 消息管理后台(users.php → jio_messages)契约:
|
||||
* 1) 页面由独立 trait 承载,UsersMenus 只做组合与路由注册;
|
||||
* 2) 写操作走 admin_post + nonce + 能力校验;
|
||||
* 3) SQL 全部在 NotificationAdminQuery 内且经 prepare;
|
||||
@@ -20,25 +20,25 @@ const read = (file) => fs.readFileSync(path.join(root, file), 'utf8');
|
||||
test('messages page is composed from its own trait', () => {
|
||||
const menus = read('src/Admin/Native/UsersMenus.php');
|
||||
assert.ok(menus.includes('use UsersMenusMessages;'));
|
||||
assert.ok(menus.includes("'aksrt_messages'"));
|
||||
assert.ok(menus.includes("add_action('admin_post_aksrt_users_send_message', [$this, 'handle_send_message'])"));
|
||||
assert.ok(menus.includes("add_action('admin_post_aksrt_users_bulk_messages', [$this, 'handle_bulk_messages'])"));
|
||||
assert.ok(menus.includes("'jio_messages'"));
|
||||
assert.ok(menus.includes("add_action('admin_post_jio_users_send_message', [$this, 'handle_send_message'])"));
|
||||
assert.ok(menus.includes("add_action('admin_post_jio_users_bulk_messages', [$this, 'handle_bulk_messages'])"));
|
||||
assert.ok(menus.includes("add_action('admin_enqueue_scripts', [$this, 'enqueue_message_assets'])"));
|
||||
// 旧实现不得残留在壳类中。
|
||||
assert.doesNotMatch(menus, /function render_messages_page|function handle_send_message|message_sent|message_failed/);
|
||||
assert.doesNotMatch(menus, /aksrt_notification/);
|
||||
assert.doesNotMatch(menus, /jio_notification/);
|
||||
});
|
||||
|
||||
test('write handlers check capability and nonce', () => {
|
||||
const page = read('src/Admin/Native/UsersMenusMessages.php');
|
||||
assert.match(page, /function handle_send_message\(\): void \{\s*\$this->ensure_cap\(\);\s*check_admin_referer\('aksrt_users_send_message'\);/);
|
||||
assert.match(page, /function handle_bulk_messages\(\): void \{\s*\$this->ensure_cap\(\);\s*check_admin_referer\('aksrt_users_bulk_messages'\);/);
|
||||
assert.match(page, /function handle_send_message\(\): void \{\s*\$this->ensure_cap\(\);\s*check_admin_referer\('jio_users_send_message'\);/);
|
||||
assert.match(page, /function handle_bulk_messages\(\): void \{\s*\$this->ensure_cap\(\);\s*check_admin_referer\('jio_users_bulk_messages'\);/);
|
||||
const compose = read('src/Admin/Native/UsersMenusMessagesCompose.php');
|
||||
const list = read('src/Admin/Native/UsersMenusMessagesList.php');
|
||||
assert.ok(page.includes('use UsersMenusMessagesCompose;'));
|
||||
assert.ok(page.includes('use UsersMenusMessagesList;'));
|
||||
assert.ok(compose.includes("wp_nonce_field('aksrt_users_send_message')"));
|
||||
assert.ok(list.includes("wp_nonce_field('aksrt_users_bulk_messages')"));
|
||||
assert.ok(compose.includes("wp_nonce_field('jio_users_send_message')"));
|
||||
assert.ok(list.includes("wp_nonce_field('jio_users_bulk_messages')"));
|
||||
// 页面层不直接碰数据库,分页走共享组件。
|
||||
assert.doesNotMatch(page + compose + list, /\$wpdb/);
|
||||
assert.ok(page.includes('AdminListPagination::render('));
|
||||
@@ -53,7 +53,7 @@ test('admin query keeps SQL prepared and bulk size capped', () => {
|
||||
});
|
||||
|
||||
test('dead notification helpers are removed', () => {
|
||||
assert.doesNotMatch(read('inc/modules/user-notifications.php'), /aksrt_notification_type_label/);
|
||||
assert.doesNotMatch(read('inc/modules/user-notifications.php'), /jio_notification_type_label/);
|
||||
assert.doesNotMatch(read('src/Messages/Message.php'), /get_all_notifications/);
|
||||
assert.ok(fs.existsSync(path.join(root, 'assets/css/admin/messages.css')));
|
||||
});
|
||||
@@ -72,10 +72,10 @@ test('list toolbar is merged into the table card and inline confirmation is remo
|
||||
const page = read('src/Admin/Native/UsersMenusMessages.php');
|
||||
const list = read('src/Admin/Native/UsersMenusMessagesList.php');
|
||||
assert.ok(page.indexOf("NativeUi::card_open(__('站内通知'") < page.indexOf('$this->render_message_filter_form'));
|
||||
assert.doesNotMatch(page + list, /onclick=|confirm\(|<details|aksrt-native-toolbar/);
|
||||
assert.doesNotMatch(page + list, /onclick=|confirm\(|<details|jio-native-toolbar/);
|
||||
const { table, empty } = harness();
|
||||
assert.match(table, /data-aksrt-msg-action="delete" disabled/);
|
||||
assert.match(table, /data-aksrt-msg-selection/);
|
||||
assert.match(table, /data-jio-msg-action="delete" disabled/);
|
||||
assert.match(table, /data-jio-msg-selection/);
|
||||
assert.match(table, /name="period" value="today"/);
|
||||
assert.match(empty, /当前筛选条件下没有消息/);
|
||||
});
|
||||
@@ -89,27 +89,27 @@ test('message row actions live only in a dedicated rightmost operations column',
|
||||
const cells = [...row.matchAll(/<(?:th|td)\b[^>]*>[\s\S]*?<\/(?:th|td)>/g)].map(([cell]) => cell);
|
||||
assert.equal(cells.length, 7);
|
||||
assert.match(cells[1], /class="column-title"/);
|
||||
assert.doesNotMatch(cells[1], /<button|data-aksrt-msg-view|data-aksrt-msg-action/);
|
||||
assert.doesNotMatch(cells[1], /<button|data-jio-msg-view|data-jio-msg-action/);
|
||||
assert.match(cells[5], /class="column-time"/);
|
||||
assert.match(cells[6], /class="column-actions"/);
|
||||
assert.match(cells[6], /data-aksrt-msg-view=/);
|
||||
assert.match(cells[6], /data-aksrt-msg-action="delete"/);
|
||||
assert.match(cells[6], /data-jio-msg-view=/);
|
||||
assert.match(cells[6], /data-jio-msg-action="delete"/);
|
||||
});
|
||||
assert.match(empty, /colspan="7"/);
|
||||
const css = read('assets/css/admin/messages.css');
|
||||
assert.match(css, /\.aksrt-messages-admin \.aksrt-messages-admin__table \.column-actions\s*\{[^}]*padding-left:\s*20px;[^}]*padding-right:\s*20px;[^}]*text-align:\s*center;/);
|
||||
assert.match(css, /\.aksrt-msg-row-actions\s*\{[^}]*justify-content:\s*center;/);
|
||||
assert.match(css, /\.jio-messages-admin \.jio-messages-admin__table \.column-actions\s*\{[^}]*padding-left:\s*20px;[^}]*padding-right:\s*20px;[^}]*text-align:\s*center;/);
|
||||
assert.match(css, /\.jio-msg-row-actions\s*\{[^}]*justify-content:\s*center;/);
|
||||
});
|
||||
|
||||
test('details preserve full content, escape HTML and unsafe links, and never mark read', () => {
|
||||
const { table, cache_calls } = harness();
|
||||
assert.match(table, /<template id="aksrt-msg-detail-8">/);
|
||||
assert.match(table, /<template id="jio-msg-detail-8">/);
|
||||
assert.match(table, /完整内容(?:完整内容){99}/);
|
||||
assert.match(table, /<script>x<\/script>/);
|
||||
assert.doesNotMatch(table, /<script>|href="javascript:/);
|
||||
assert.match(table, /2026-10-30 11:30/);
|
||||
assert.match(table, /查看详情不会改变收件人的已读状态/);
|
||||
assert.equal((table.match(/data-aksrt-msg-action="mark_read" data-id=/g) || []).length, 1);
|
||||
assert.equal((table.match(/data-jio-msg-action="mark_read" data-id=/g) || []).length, 1);
|
||||
assert.deepEqual(cache_calls, [[7]]);
|
||||
});
|
||||
|
||||
@@ -122,8 +122,8 @@ test('role choices display counts and disable only empty or oversized roles', ()
|
||||
assert.doesNotMatch(compose, /value="contributor"[^>]* disabled/);
|
||||
assert.match(compose, /data-count-limit="191"/);
|
||||
assert.match(compose, /data-count-limit="2000"/);
|
||||
assert.match(compose, /aria-describedby="aksrt-msg-content-count"/);
|
||||
assert.match(compose, /data-aksrt-msg-pane="role" hidden/);
|
||||
assert.match(compose, /aria-describedby="jio-msg-content-count"/);
|
||||
assert.match(compose, /data-jio-msg-pane="role" hidden/);
|
||||
});
|
||||
|
||||
test('overview links, status tabs, filter forms and mutations preserve period filtering', () => {
|
||||
@@ -157,7 +157,7 @@ test('role broadcasts require confirmation of the current role and count on the
|
||||
const { send, cap_checked, nonce_checked } = harness();
|
||||
['unconfirmed', 'changed_count', 'changed_role', 'too_large', 'long_title'].forEach((key) => {
|
||||
assert.equal(send[key].sent, 0, key);
|
||||
assert.equal(send[key].redirect.aksrt_msg, 'send_failed', key);
|
||||
assert.equal(send[key].redirect.jio_msg, 'send_failed', key);
|
||||
});
|
||||
assert.equal(send.confirmed.sent, 3);
|
||||
assert.equal(send.users.sent, 1);
|
||||
@@ -176,7 +176,7 @@ test('asset types use Chinese labels in filters, row badges and details', () =>
|
||||
for (const [type, [label]] of Object.entries(asset_types)) {
|
||||
assert.ok(filter_form.includes(`<option value="${type}">${label}</option>`));
|
||||
}
|
||||
assert.match(asset_table, /class="aksrt-msg-badge is-success"[^>]*>余额增加<\/a>/);
|
||||
assert.match(asset_table, /class="jio-msg-badge is-success"[^>]*>余额增加<\/a>/);
|
||||
assert.match(asset_table, /<dt>消息类型<\/dt><dd>余额增加<\/dd>/);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<?php
|
||||
/** WordPress-free message admin render / handler / SQL harness. No actual notifications are sent. */
|
||||
namespace AKSRT\Core {
|
||||
namespace Jio\Core {
|
||||
final class DBMigrator {
|
||||
public static function table($key) { return 'wp_aksrt_' . $key; }
|
||||
public static function table($key) { return 'wp_jio_' . $key; }
|
||||
}
|
||||
}
|
||||
namespace AKSRT\Messages {
|
||||
namespace Jio\Messages {
|
||||
final class Message {
|
||||
public static $sent = [];
|
||||
public static function get_instance() { return new self(); }
|
||||
@@ -19,8 +19,8 @@ namespace {
|
||||
define('ABSPATH', __DIR__);
|
||||
define('ARRAY_A', 'ARRAY_A');
|
||||
$root = dirname(__DIR__, 2);
|
||||
define('AKSRT_DIR', $root);
|
||||
define('AKSRT_URI', 'https://example.test/theme');
|
||||
define('JIO_DIR', $root);
|
||||
define('JIO_URI', 'https://example.test/theme');
|
||||
class WP_Error {
|
||||
public function __construct(public $code, public $message) {}
|
||||
public function get_error_message() { return $this->message; }
|
||||
@@ -97,7 +97,7 @@ namespace {
|
||||
require $root . '/src/Admin/Native/UsersMenusMessagesCompose.php';
|
||||
require $root . '/src/Admin/Native/UsersMenusMessages.php';
|
||||
class Probe {
|
||||
use \AKSRT\Admin\Native\UsersMenusMessages;
|
||||
use \Jio\Admin\Native\UsersMenusMessages;
|
||||
public $cap_checked = 0;
|
||||
private function ensure_cap(): void { $this->cap_checked++; }
|
||||
public function invoke($method, ...$args) { return $this->$method(...$args); }
|
||||
@@ -122,18 +122,18 @@ namespace {
|
||||
$out['cache_calls'] = $GLOBALS['cache_calls'];
|
||||
$out['asset_table'] = $probe->html('render_message_table', [array_merge($row, ['type' => 'asset_balance_increase'])], $filters, 1, false);
|
||||
foreach (['asset_balance_increase', 'asset_balance_decrease', 'asset_points_increase', 'asset_points_decrease'] as $type) {
|
||||
$out['asset_types'][$type] = [\AKSRT\Messages\MessageTypes::label($type), \AKSRT\Messages\MessageTypes::tone($type)];
|
||||
$out['asset_types'][$type] = [\Jio\Messages\MessageTypes::label($type), \Jio\Messages\MessageTypes::tone($type)];
|
||||
}
|
||||
|
||||
$wpdb->prepared = [];
|
||||
\AKSRT\Messages\NotificationAdminQuery::paginate($out['query_filters'], 1);
|
||||
\AKSRT\Messages\NotificationAdminQuery::status_counts($out['query_filters']);
|
||||
\AKSRT\Messages\NotificationAdminQuery::overview();
|
||||
\Jio\Messages\NotificationAdminQuery::paginate($out['query_filters'], 1);
|
||||
\Jio\Messages\NotificationAdminQuery::status_counts($out['query_filters']);
|
||||
\Jio\Messages\NotificationAdminQuery::overview();
|
||||
$out['sql'] = $wpdb->prepared;
|
||||
$GLOBALS['zone'] = 'America/New_York';
|
||||
$GLOBALS['now'] = '2026-11-01 12:00:00';
|
||||
$wpdb->prepared = [];
|
||||
\AKSRT\Messages\NotificationAdminQuery::overview();
|
||||
\Jio\Messages\NotificationAdminQuery::overview();
|
||||
$out['dst_sql'] = $wpdb->prepared;
|
||||
|
||||
$base = ['recipient_mode' => 'role', 'role' => 'subscriber', 'content' => 'Hello', 'title' => 'Title'];
|
||||
@@ -149,10 +149,10 @@ namespace {
|
||||
foreach ($cases as $name => $post) {
|
||||
$_POST = $post;
|
||||
$GLOBALS['role_count'] = $name === 'too_large' ? 501 : 3;
|
||||
\AKSRT\Messages\Message::$sent = [];
|
||||
\Jio\Messages\Message::$sent = [];
|
||||
try { $probe->handle_send_message(); } catch (Redirect $redirect) {
|
||||
parse_str(parse_url($redirect->getMessage(), PHP_URL_QUERY), $query);
|
||||
$out['send'][$name] = ['redirect' => $query, 'sent' => count(\AKSRT\Messages\Message::$sent)];
|
||||
$out['send'][$name] = ['redirect' => $query, 'sent' => count(\Jio\Messages\Message::$sent)];
|
||||
}
|
||||
}
|
||||
$_POST = ['bulk_action' => 'delete', 'message_id' => 8, 'ids' => [9, 10], 'period' => 'today', 'status' => 'unread', 'paged' => 3];
|
||||
|
||||
@@ -55,7 +55,7 @@ function runtime({ autoopen = false, empty = false } = {}) {
|
||||
const window = new Element();
|
||||
doc.documentElement = new Element();
|
||||
doc.getElementById = (id) => ids.get(id) || null;
|
||||
doc.querySelector = (selector) => selector === '.aksrt-messages-admin' ? root : null;
|
||||
doc.querySelector = (selector) => selector === '.jio-messages-admin' ? root : null;
|
||||
const dialogs = [];
|
||||
function dialog(id) {
|
||||
const node = new Element();
|
||||
@@ -65,23 +65,23 @@ function runtime({ autoopen = false, empty = false } = {}) {
|
||||
node.cancel = () => { node.fire('cancel'); node.close(); };
|
||||
node.closeButton = new Element();
|
||||
node.closeButton.addEventListener('click', () => {});
|
||||
node.map('[data-aksrt-dialog-close]', [node.closeButton]);
|
||||
node.map('[data-jio-dialog-close]', [node.closeButton]);
|
||||
ids.set(id, node);
|
||||
dialogs.push(node);
|
||||
return node;
|
||||
}
|
||||
const compose = dialog('aksrt-msg-compose');
|
||||
const detail = dialog('aksrt-msg-detail');
|
||||
const confirm = dialog('aksrt-msg-confirm');
|
||||
const compose = dialog('jio-msg-compose');
|
||||
const detail = dialog('jio-msg-detail');
|
||||
const confirm = dialog('jio-msg-confirm');
|
||||
confirm.attrs = { 'data-i18n-delete-title': '删除消息', 'data-i18n-delete-text': '删除 {count} 条消息', 'data-i18n-delete-send': '确认删除' };
|
||||
const accept = new Element();
|
||||
const cancel = confirm.closeButton;
|
||||
const confirmation = new Element();
|
||||
confirm.map('[data-aksrt-msg-confirm]', accept).map('[data-aksrt-dialog-title]', new Element()).map('#aksrt-msg-confirm-text', confirmation).map('[data-aksrt-dialog-close][autofocus]', cancel);
|
||||
confirm.map('[data-jio-msg-confirm]', accept).map('[data-jio-dialog-title]', new Element()).map('#jio-msg-confirm-text', confirmation).map('[data-jio-dialog-close][autofocus]', cancel);
|
||||
const detailBody = new Element();
|
||||
detail.map('[data-aksrt-msg-detail-body]', detailBody);
|
||||
ids.set('aksrt-msg-detail-8', { content: { cloneNode: () => ({ text: 'Full <script> message' }) } });
|
||||
root.map('.aksrt-msg-dialog', dialogs);
|
||||
detail.map('[data-jio-msg-detail-body]', detailBody);
|
||||
ids.set('jio-msg-detail-8', { content: { cloneNode: () => ({ text: 'Full <script> message' }) } });
|
||||
root.map('.jio-msg-dialog', dialogs);
|
||||
const originalQuery = root.querySelector.bind(root);
|
||||
root.querySelector = (selector) => selector === 'dialog[open]' ? dialogs.find((node) => node.open) || null : originalQuery(selector);
|
||||
|
||||
@@ -112,16 +112,16 @@ function runtime({ autoopen = false, empty = false } = {}) {
|
||||
const list = form({ bulk_action: field('bulk_action'), message_id: field('message_id') }, checks);
|
||||
const checkAll = new Element();
|
||||
const selection = new Element({ 'data-label': '已选 %d 条' });
|
||||
const bulkRead = new Element({ 'data-aksrt-msg-action': 'mark_read' });
|
||||
const bulkDelete = new Element({ 'data-aksrt-msg-action': 'delete' });
|
||||
const rowRead = new Element({ 'data-aksrt-msg-action': 'mark_read', 'data-id': '8' });
|
||||
const rowDelete = new Element({ 'data-aksrt-msg-action': 'delete', 'data-id': '8' });
|
||||
const view = new Element({ 'data-aksrt-msg-view': '8' });
|
||||
const bulkRead = new Element({ 'data-jio-msg-action': 'mark_read' });
|
||||
const bulkDelete = new Element({ 'data-jio-msg-action': 'delete' });
|
||||
const rowRead = new Element({ 'data-jio-msg-action': 'mark_read', 'data-id': '8' });
|
||||
const rowDelete = new Element({ 'data-jio-msg-action': 'delete', 'data-id': '8' });
|
||||
const view = new Element({ 'data-jio-msg-view': '8' });
|
||||
const actions = [bulkRead, bulkDelete, rowRead, rowDelete];
|
||||
list.map('[data-aksrt-msg-check]', checks).map('[data-aksrt-msg-check-all]', checkAll).map('[data-aksrt-msg-selection]', selection)
|
||||
.map('[data-aksrt-msg-action]:not([data-id])', [bulkRead, bulkDelete]).map('[data-aksrt-msg-action]', actions)
|
||||
.map('button', [...actions, view]).map('button[data-id], button[data-aksrt-msg-view]', [rowRead, rowDelete, view]);
|
||||
root.map('[data-aksrt-msg-list]', list).map('[data-aksrt-msg-view]', [view]);
|
||||
list.map('[data-jio-msg-check]', checks).map('[data-jio-msg-check-all]', checkAll).map('[data-jio-msg-selection]', selection)
|
||||
.map('[data-jio-msg-action]:not([data-id])', [bulkRead, bulkDelete]).map('[data-jio-msg-action]', actions)
|
||||
.map('button', [...actions, view]).map('button[data-id], button[data-jio-msg-view]', [rowRead, rowDelete, view]);
|
||||
root.map('[data-jio-msg-list]', list).map('[data-jio-msg-view]', [view]);
|
||||
|
||||
const role = field('role', '', true);
|
||||
role.options = [new Element({ 'data-count': '0' }), new Element({ 'data-count': '3', 'data-label': '订阅者' })];
|
||||
@@ -138,8 +138,8 @@ function runtime({ autoopen = false, empty = false } = {}) {
|
||||
const usersRadio = new Element({ value: 'users' });
|
||||
usersRadio.checked = true;
|
||||
const roleRadio = new Element({ value: 'role' });
|
||||
const usersPane = new Element({ 'data-aksrt-msg-pane': 'users' });
|
||||
const rolePane = new Element({ 'data-aksrt-msg-pane': 'role' });
|
||||
const usersPane = new Element({ 'data-jio-msg-pane': 'users' });
|
||||
const rolePane = new Element({ 'data-jio-msg-pane': 'role' });
|
||||
usersPane.map('input, select, textarea', [composeForm.elements.users]);
|
||||
rolePane.map('input, select, textarea', [role]);
|
||||
const titleCount = new Element();
|
||||
@@ -148,15 +148,15 @@ function runtime({ autoopen = false, empty = false } = {}) {
|
||||
const send = new Element();
|
||||
composeForm.elements.title.attrs['data-count-limit'] = '191';
|
||||
composeForm.elements.content.attrs['data-count-limit'] = '2000';
|
||||
composeForm.map('[data-aksrt-msg-send]', send).map('[data-count-limit]', [composeForm.elements.title, composeForm.elements.content])
|
||||
.map('[data-count-for="title"]', titleCount).map('[data-count-for="content"]', contentCount).map('[data-aksrt-msg-role-hint]', roleHint)
|
||||
.map('[data-aksrt-msg-pane]', [usersPane, rolePane]).map('input[name="recipient_mode"]', [usersRadio, roleRadio]);
|
||||
composeForm.map('[data-jio-msg-send]', send).map('[data-count-limit]', [composeForm.elements.title, composeForm.elements.content])
|
||||
.map('[data-count-for="title"]', titleCount).map('[data-count-for="content"]', contentCount).map('[data-jio-msg-role-hint]', roleHint)
|
||||
.map('[data-jio-msg-pane]', [usersPane, rolePane]).map('input[name="recipient_mode"]', [usersRadio, roleRadio]);
|
||||
const formQuery = composeForm.querySelector.bind(composeForm);
|
||||
composeForm.querySelector = (selector) => selector === 'input[name="recipient_mode"]:checked' ? (usersRadio.checked ? usersRadio : roleRadio) : formQuery(selector);
|
||||
compose.map('[data-aksrt-msg-compose]', composeForm);
|
||||
if (autoopen) compose.attrs['data-aksrt-dialog-autoopen'] = '';
|
||||
compose.map('[data-jio-msg-compose]', composeForm);
|
||||
if (autoopen) compose.attrs['data-jio-dialog-autoopen'] = '';
|
||||
const trigger = new Element();
|
||||
root.map('[data-aksrt-dialog-open]', [trigger]);
|
||||
root.map('[data-jio-dialog-open]', [trigger]);
|
||||
|
||||
vm.runInNewContext(fs.readFileSync(path.resolve(__dirname, '../../assets/js/admin-messages.js'), 'utf8'), { document: doc, window });
|
||||
function chooseRole() {
|
||||
@@ -266,7 +266,7 @@ test('role sending uses a nested confirmation with the confirmed role and count'
|
||||
r.cancel.fire('click');
|
||||
assert.equal(r.compose.open, true);
|
||||
assert.equal(r.composeForm.submissions.length, 0);
|
||||
assert.equal(r.doc.documentElement.classList.contains('aksrt-msg-modal-open'), true);
|
||||
assert.equal(r.doc.documentElement.classList.contains('jio-msg-modal-open'), true);
|
||||
r.composeForm.requestSubmit(r.send); r.accept.fire('click');
|
||||
assert.equal(r.composeForm.submissions.length, 1);
|
||||
assert.equal(r.composeForm.submissions[0].confirmed_role, 'subscriber');
|
||||
@@ -317,5 +317,5 @@ test('failed sends reopen compose and backdrop clicks must start outside the dia
|
||||
r.compose.fire('pointerdown', { clientX: 0, clientY: 0 });
|
||||
r.compose.fire('click', { clientX: 0, clientY: 0 });
|
||||
assert.equal(r.compose.open, false);
|
||||
assert.equal(r.doc.documentElement.classList.contains('aksrt-msg-modal-open'), false);
|
||||
assert.equal(r.doc.documentElement.classList.contains('jio-msg-modal-open'), false);
|
||||
});
|
||||
@@ -12,18 +12,18 @@ test('message center exposes only the four configured-alias parameter-free route
|
||||
const urls = read('inc/modules/user-parts/urls.php');
|
||||
const registry = read('inc/modules/user-parts/template-registry.php');
|
||||
|
||||
assert.match(urls, /function aksrt_get_messages_rewrite_slug[\s\S]*?Settings::notification_settings\(\)[\s\S]*?\['slug'\]/);
|
||||
assert.match(routing, /\$message_slug = preg_quote\(aksrt_get_messages_rewrite_slug\(\), '#'\)/);
|
||||
assert.match(routing, /add_rewrite_rule\('\^' \. \$message_slug \. '\/\?\$',[\s\S]*?aksrt_message_view=unread/);
|
||||
assert.match(urls, /function jio_get_messages_rewrite_slug[\s\S]*?Settings::notification_settings\(\)[\s\S]*?\['slug'\]/);
|
||||
assert.match(routing, /\$message_slug = preg_quote\(jio_get_messages_rewrite_slug\(\), '#'\)/);
|
||||
assert.match(routing, /add_rewrite_rule\('\^' \. \$message_slug \. '\/\?\$',[\s\S]*?jio_message_view=unread/);
|
||||
assert.match(routing, /add_rewrite_rule\('\^' \. \$message_slug \. '\/\(posts\|like\|system\)\/\?\$'/);
|
||||
assert.match(routing, /message-routing:' \. aksrt_get_messages_rewrite_slug\(\)/);
|
||||
assert.match(routing, /message-routing:' \. jio_get_messages_rewrite_slug\(\)/);
|
||||
assert.doesNotMatch(routing, /add_rewrite_rule\('\^message/);
|
||||
assert.doesNotMatch(routing, /message\/\(\[0-9]|aksrt_message_id/);
|
||||
assert.doesNotMatch(routing, /message\/\(\[0-9]|jio_message_id/);
|
||||
const messageUrlFunction = urls.slice(
|
||||
urls.indexOf('function aksrt_get_messages_page_url'),
|
||||
urls.indexOf('endif;', urls.indexOf('function aksrt_get_messages_page_url')),
|
||||
urls.indexOf('function jio_get_messages_page_url'),
|
||||
urls.indexOf('endif;', urls.indexOf('function jio_get_messages_page_url')),
|
||||
);
|
||||
assert.match(messageUrlFunction, /aksrt_get_messages_rewrite_slug\(\)/);
|
||||
assert.match(messageUrlFunction, /jio_get_messages_rewrite_slug\(\)/);
|
||||
assert.doesNotMatch(messageUrlFunction, /add_query_arg|\/messages\/|\/message\//);
|
||||
assert.doesNotMatch(registry, /'messages'\s*=>/);
|
||||
assert.match(registry, /page-messages\.php'\)\s*\{[\s\S]*?return ''/);
|
||||
@@ -77,7 +77,7 @@ test('message centre shows a skeleton during the AJAX load-more cycle', () => {
|
||||
|
||||
// The skeleton ships inside .messages-list so the loader is anchored to the
|
||||
// end of the list, mirroring the reference post_ajax beforeSend hook.
|
||||
assert.match(list, /aksrt_get_message_placeholder\(4\)/);
|
||||
assert.match(list, /jio_get_message_placeholder\(4\)/);
|
||||
assert.match(placeholder, /message-item--placeholder/);
|
||||
assert.match(placeholder, /class="post_ajax_loader"/);
|
||||
// The message skeleton reuses the shared .placeholder shimmer contract.
|
||||
@@ -113,8 +113,8 @@ test('tabs, pagination, actions, and detail all replace DOM fragments', () => {
|
||||
for (const label of ['未读消息', '文章评论', '点赞喜欢', '系统通知']) {
|
||||
assert.ok(center.includes(label));
|
||||
}
|
||||
assert.match(center, /wp_ajax_aksrt_load_message_center/);
|
||||
assert.match(center, /aksrt_render_message_center_detail/);
|
||||
assert.match(center, /wp_ajax_jio_load_message_center/);
|
||||
assert.match(center, /jio_render_message_center_detail/);
|
||||
assert.match(template, /data-message-content/);
|
||||
assert.match(script, /nav\.innerHTML = result\.data\.nav/);
|
||||
assert.match(script, /content\.innerHTML = result\.data\.content/);
|
||||
@@ -126,11 +126,11 @@ test('tabs, pagination, actions, and detail all replace DOM fragments', () => {
|
||||
|
||||
test('message detail exposes neither internal type labels nor a related-content link', () => {
|
||||
const center = read('inc/modules/message-center.php');
|
||||
const detailStart = center.indexOf('function aksrt_render_message_center_detail');
|
||||
const detailEnd = center.indexOf("add_action('wp_ajax_aksrt_load_message_center'", detailStart);
|
||||
const detailStart = center.indexOf('function jio_render_message_center_detail');
|
||||
const detailEnd = center.indexOf("add_action('wp_ajax_jio_load_message_center'", detailStart);
|
||||
const detail = center.slice(detailStart, detailEnd);
|
||||
|
||||
assert.doesNotMatch(detail, /aksrt_notification_type_label/);
|
||||
assert.doesNotMatch(detail, /jio_notification_type_label/);
|
||||
assert.doesNotMatch(detail, /查看相关内容|message-detail__primary|target_url|\['url'\]/);
|
||||
assert.doesNotMatch(detail, /<a\s/i);
|
||||
});
|
||||
@@ -149,8 +149,8 @@ test('asset notifications are absent and interaction notification hooks are pres
|
||||
]) {
|
||||
assert.equal(combined.includes(retired), false, `${retired} should be absent`);
|
||||
}
|
||||
assert.doesNotMatch(notifications, /add_action\('aksrt_asset_transaction_committed'/);
|
||||
for (const hook of ['aksrt_post_favorited', 'aksrt_post_liked', 'aksrt_comment_liked', 'aksrt_user_followed']) {
|
||||
assert.doesNotMatch(notifications, /add_action\('jio_asset_transaction_committed'/);
|
||||
for (const hook of ['jio_post_favorited', 'jio_post_liked', 'jio_comment_liked', 'jio_user_followed']) {
|
||||
assert.ok(notifications.includes(`add_action('${hook}'`));
|
||||
}
|
||||
});
|
||||
@@ -160,6 +160,6 @@ test('notification storage has no message-center legacy metadata fallback', () =
|
||||
const service = read('src/Messages/Message.php');
|
||||
const combined = `${notifications}\n${service}`;
|
||||
|
||||
assert.doesNotMatch(combined, /aksrt_notifications|aksrt_unread_message_count/);
|
||||
assert.doesNotMatch(combined, /jio_notifications|jio_unread_message_count/);
|
||||
assert.doesNotMatch(combined, /legacy|兼容|降级/i);
|
||||
});
|
||||
@@ -22,7 +22,7 @@ namespace {
|
||||
function get_date_from_gmt($date, $format): string { return (string) $date; }
|
||||
function add_action($hook, $callback): void {}
|
||||
|
||||
function aksrt_get_user_all_notifications(int $user_id): array {
|
||||
function jio_get_user_all_notifications(int $user_id): array {
|
||||
$items = [];
|
||||
for ($number = 45; $number >= 1; $number--) {
|
||||
$items[] = [
|
||||
@@ -39,7 +39,7 @@ namespace {
|
||||
}
|
||||
}
|
||||
|
||||
namespace AKSRT\Settings {
|
||||
namespace Jio\Settings {
|
||||
final class Settings {
|
||||
public static string $paginateType = 'ajax_lists';
|
||||
|
||||
@@ -53,9 +53,9 @@ namespace {
|
||||
require_once dirname(__DIR__, 2) . '/inc/modules/message-center.php';
|
||||
|
||||
function render_pagination_mode(string $type): string {
|
||||
\AKSRT\Settings\Settings::$paginateType = $type;
|
||||
\Jio\Settings\Settings::$paginateType = $type;
|
||||
ob_start();
|
||||
aksrt_render_message_center_list(1, 'unread', 2);
|
||||
jio_render_message_center_list(1, 'unread', 2);
|
||||
return (string) ob_get_clean();
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
namespace {
|
||||
define('ABSPATH', __DIR__);
|
||||
|
||||
class AKSRT_User_Notifications {}
|
||||
class JIO_User_Notifications {}
|
||||
|
||||
function sanitize_key($value): string {
|
||||
return preg_replace('/[^a-z0-9_\-]/', '', strtolower((string) $value)) ?? '';
|
||||
@@ -17,7 +17,7 @@ namespace {
|
||||
}
|
||||
}
|
||||
|
||||
namespace AKSRT\Settings {
|
||||
namespace Jio\Settings {
|
||||
final class Settings {
|
||||
public static function notification_settings(): array {
|
||||
return ['slug' => 'inbox-center'];
|
||||
@@ -33,10 +33,10 @@ namespace {
|
||||
require_once dirname(__DIR__, 2) . '/inc/modules/user-parts/urls.php';
|
||||
|
||||
echo json_encode([
|
||||
'slug' => aksrt_get_messages_rewrite_slug(),
|
||||
'unread' => aksrt_get_messages_page_url(),
|
||||
'posts' => aksrt_get_messages_page_url('posts'),
|
||||
'like' => aksrt_get_messages_page_url('like'),
|
||||
'system' => aksrt_get_messages_page_url('system'),
|
||||
'slug' => jio_get_messages_rewrite_slug(),
|
||||
'unread' => jio_get_messages_page_url(),
|
||||
'posts' => jio_get_messages_page_url('posts'),
|
||||
'like' => jio_get_messages_page_url('like'),
|
||||
'system' => jio_get_messages_page_url('system'),
|
||||
], JSON_UNESCAPED_SLASHES);
|
||||
}
|
||||
@@ -11,5 +11,5 @@ test('modal runtime compensates the removed scrollbar like Bootstrap setScrollba
|
||||
assert.match(runtime, /scrollLock\.push\(padRight\(body, gap\)\)/);
|
||||
assert.match(runtime, /var SCROLLBAR_FIXED = "\.nav-fixed \.header, \.float-right";/);
|
||||
assert.match(runtime, /entry\.element\.style\.paddingRight = entry\.inline;/);
|
||||
assert.doesNotMatch(runtime, /classList\.toggle\("aksrt-modal-runtime-open"/);
|
||||
assert.doesNotMatch(runtime, /classList\.toggle\("jio-modal-runtime-open"/);
|
||||
});
|
||||
@@ -28,10 +28,10 @@ class Element {
|
||||
}
|
||||
set innerHTML(value) {
|
||||
this._innerHTML = value;
|
||||
if (value.includes('aksrt-modal-runtime__content')) {
|
||||
if (value.includes('jio-modal-runtime__content')) {
|
||||
this.dialog = new Element(this.ownerDocument);
|
||||
const content = new Element(this.ownerDocument);
|
||||
content.className = 'aksrt-modal-runtime__content';
|
||||
content.className = 'jio-modal-runtime__content';
|
||||
content.parentNode = this;
|
||||
this.content = content;
|
||||
}
|
||||
@@ -42,8 +42,8 @@ class Element {
|
||||
getAttribute(name) { return this.attributes[name] || null; }
|
||||
addEventListener(type, listener) { (this.listeners[type] ||= []).push(listener); }
|
||||
querySelector(selector) {
|
||||
if (selector === '.aksrt-modal-runtime__dialog') return this.dialog || null;
|
||||
if (selector === '.aksrt-modal-runtime__content') return this.content || null;
|
||||
if (selector === '.jio-modal-runtime__dialog') return this.dialog || null;
|
||||
if (selector === '.jio-modal-runtime__content') return this.content || null;
|
||||
return null;
|
||||
}
|
||||
querySelectorAll() { return []; }
|
||||
@@ -92,16 +92,16 @@ function createRuntime() {
|
||||
setTimeout(callback, delay) { callback.delay = delay; timers.push(callback); return timers.length; },
|
||||
AbortController,
|
||||
jQuery: jquery,
|
||||
AksrtModalTelemetry: { start: () => '', mark() {} },
|
||||
AksrtModalState: { create: () => ({}) },
|
||||
AksrtModalCache: {
|
||||
JioModalTelemetry: { start: () => '', mark() {} },
|
||||
JioModalState: { create: () => ({}) },
|
||||
JioModalCache: {
|
||||
acquire(key, request) {
|
||||
const promise = request();
|
||||
return { promise, release() {} };
|
||||
},
|
||||
invalidate() {},
|
||||
},
|
||||
AksrtRequest: {
|
||||
JioRequest: {
|
||||
url: (url) => url,
|
||||
request(url, options) {
|
||||
const pending = {};
|
||||
@@ -113,7 +113,7 @@ function createRuntime() {
|
||||
};
|
||||
const context = { window, document, CustomEvent: class { constructor(type, options) { this.type = type; this.detail = options?.detail; } }, Date, Promise, String, Number, Object, Array, Math, Error, AbortController };
|
||||
vm.runInNewContext(fs.readFileSync(path.join(root, 'assets/js/core/modal-runtime.js'), 'utf8'), context);
|
||||
return { runtime: window.AksrtModalRuntime, document, window, requests, timers, getSwipeConfig: () => swipeConfig };
|
||||
return { runtime: window.JioModalRuntime, document, window, requests, timers, getSwipeConfig: () => swipeConfig };
|
||||
}
|
||||
|
||||
function flushTimers(timers) {
|
||||
@@ -130,11 +130,11 @@ test('nested remote dialogs preserve the parent, restore focus, and hold one pag
|
||||
const childLoad = runtime.openRemote({ url: '/badge', cache: false, newModal: true });
|
||||
const child = runtime.getActive();
|
||||
assert.equal(parent.classList.contains('is-open'), true);
|
||||
assert.equal(parent.classList.contains('aksrt-modal-runtime--parent'), true);
|
||||
assert.equal(parent.classList.contains('jio-modal-runtime--parent'), true);
|
||||
assert.equal(parent.getAttribute('aria-modal'), 'false');
|
||||
assert.equal(parent.getAttribute('aria-hidden'), 'true');
|
||||
assert.equal(child.getAttribute('aria-modal'), 'true');
|
||||
assert.equal(document.documentElement.classList.contains('aksrt-modal-runtime-open'), true);
|
||||
assert.equal(document.documentElement.classList.contains('jio-modal-runtime-open'), true);
|
||||
|
||||
requests[0].pending.resolve({ text: '<div>details</div>' });
|
||||
await childLoad;
|
||||
@@ -142,17 +142,17 @@ test('nested remote dialogs preserve the parent, restore focus, and hold one pag
|
||||
flushTimers(timers);
|
||||
|
||||
assert.equal(runtime.getActive(), parent);
|
||||
assert.equal(parent.classList.contains('aksrt-modal-runtime--parent'), false);
|
||||
assert.equal(parent.classList.contains('jio-modal-runtime--parent'), false);
|
||||
assert.equal(parent.getAttribute('aria-modal'), 'true');
|
||||
assert.equal(parent.getAttribute('aria-hidden'), null);
|
||||
assert.equal(parent.getAttribute('inert'), null);
|
||||
assert.equal(document.activeElement, trigger);
|
||||
assert.equal(document.documentElement.classList.contains('aksrt-modal-runtime-open'), true);
|
||||
assert.equal(document.documentElement.classList.contains('jio-modal-runtime-open'), true);
|
||||
|
||||
runtime.close('control');
|
||||
flushTimers(timers);
|
||||
assert.equal(runtime.getActive(), null);
|
||||
assert.equal(document.documentElement.classList.contains('aksrt-modal-runtime-open'), false);
|
||||
assert.equal(document.documentElement.classList.contains('jio-modal-runtime-open'), false);
|
||||
});
|
||||
|
||||
test('replacing a nested top dialog keeps the original parent and does not reload it', async () => {
|
||||
@@ -170,7 +170,7 @@ test('replacing a nested top dialog keeps the original parent and does not reloa
|
||||
|
||||
assert.notEqual(secondChild, firstChild);
|
||||
assert.equal(runtime.getActive(), secondChild);
|
||||
assert.equal(parent.classList.contains('aksrt-modal-runtime--parent'), true);
|
||||
assert.equal(parent.classList.contains('jio-modal-runtime--parent'), true);
|
||||
assert.equal(requests.length, 2);
|
||||
runtime.close('escape');
|
||||
flushTimers(timers);
|
||||
@@ -266,17 +266,17 @@ test('Escape from the child closes only that layer and aborts its request', asyn
|
||||
test('remote trigger forwards the explicit new modal flag', () => {
|
||||
const source = fs.readFileSync(path.join(root, 'assets/js/core/modal-triggers.js'), 'utf8');
|
||||
const attrs = {
|
||||
'data-aksrt-modal-url': '/badge',
|
||||
'data-aksrt-modal-new': 'true',
|
||||
'data-aksrt-modal-loading': 'spinner',
|
||||
'data-aksrt-modal-swipe-close': 'true',
|
||||
'data-jio-modal-url': '/badge',
|
||||
'data-jio-modal-new': 'true',
|
||||
'data-jio-modal-loading': 'spinner',
|
||||
'data-jio-modal-swipe-close': 'true',
|
||||
};
|
||||
const element = { getAttribute: (name) => attrs[name] || null };
|
||||
const document = {
|
||||
addEventListener(type, listener) { this.listener = listener; },
|
||||
};
|
||||
let options;
|
||||
const window = { AksrtModalRuntime: { openRemote(value) { options = value; } } };
|
||||
const window = { JioModalRuntime: { openRemote(value) { options = value; } } };
|
||||
vm.runInNewContext(source, { window, document, parseInt, Object });
|
||||
document.listener({
|
||||
target: { closest: () => element },
|
||||
@@ -293,11 +293,11 @@ test('bottom dialogs use the shared spinner and minitouch swipe handle', () => {
|
||||
const shell = runtime.open({ bottom: true, swipeClose: true, loading: 'spinner' });
|
||||
const dialog = shell.innerHTML;
|
||||
|
||||
assert.match(dialog, /class="aksrt-modal-runtime__loading flex jc"/);
|
||||
assert.match(dialog, /class="jio-modal-runtime__loading flex jc"/);
|
||||
assert.match(dialog, /class="loading-circle" viewBox="0 0 50 50"/);
|
||||
assert.match(dialog, /class="touch-close" aria-hidden="true"/);
|
||||
assert.equal(getSwipeConfig().direction, 'bottom');
|
||||
assert.equal(getSwipeConfig().selector, '.aksrt-modal-runtime__dialog');
|
||||
assert.equal(getSwipeConfig().selector, '.jio-modal-runtime__dialog');
|
||||
assert.equal(getSwipeConfig().start_selector, '.modal-colorful-header,.touch-close,.touch');
|
||||
assert.equal(getSwipeConfig().depreciation, 50);
|
||||
|
||||
|
||||
@@ -8,14 +8,14 @@ const read = (file) => fs.readFileSync(path.join(root, file), 'utf8');
|
||||
|
||||
test('message notification settings are a canonical user/msg inline panel', () => {
|
||||
const center = read('src/User/Center.php');
|
||||
const helpers = read('inc/helpers/aksrt-user-center.php');
|
||||
const helpers = read('inc/helpers/jio-user-center.php');
|
||||
const sidebar = read('inc/helpers/user-center-renderers/sidebar-nav.php');
|
||||
const routing = read('inc/modules/user-parts/routing.php');
|
||||
|
||||
assert.match(center, /'msg'\s*=>\s*\[[\s\S]*?'type'\s*=>\s*'panel'/);
|
||||
assert.doesNotMatch(center, /'notif'\s*=>/);
|
||||
assert.match(helpers, /'msg'\s*=>\s*'msg'/);
|
||||
assert.match(sidebar, /aksrt_get_user_center_tab_url\('msg'\)/);
|
||||
assert.match(sidebar, /jio_get_user_center_tab_url\('msg'\)/);
|
||||
assert.match(sidebar, /'tab'\s*=>\s*'msg'/);
|
||||
assert.doesNotMatch(sidebar, /'url'\s*=>\s*\(string\) \(\$messages_url/);
|
||||
assert.match(routing, /'revenue', 'msg', 'settings'/);
|
||||
@@ -25,10 +25,10 @@ test('notification icon positions map to their four render locations', () => {
|
||||
const phpSchema = read('src/Admin/Schema/SchemaUsers.php');
|
||||
const jsSchema = read('assets/admin/src/schema/sections/users/notifications.js');
|
||||
const integration = read('src/Settings/Integration.php');
|
||||
const header = read('inc/helpers/header/aksrt-header-navbar.php');
|
||||
const header = read('inc/helpers/header/jio-header-navbar.php');
|
||||
const headerStyles = read('assets/css/frontend/header-userbox.css');
|
||||
const legacyMainCss = read('assets/css/main.css');
|
||||
const userCenter = read('inc/helpers/aksrt-user-center.php');
|
||||
const userCenter = read('inc/helpers/jio-user-center.php');
|
||||
const userHome = read('templates/parts/user/author-hero.php');
|
||||
|
||||
for (const source of [phpSchema, jsSchema]) {
|
||||
@@ -47,8 +47,8 @@ test('notification icon positions map to their four render locations', () => {
|
||||
assert.match(header, /toggle-radius msg-icon/);
|
||||
assert.match(header, /<badge class="top">/);
|
||||
assert.match(legacyMainCss, /\.sub-user-box \.msg-news-icon \{[\s\S]*?top: 0\.4em;[\s\S]*?right: 0;/);
|
||||
assert.doesNotMatch(headerStyles, /\.aksrt-header-userbox \.user-info > \.abs-right/);
|
||||
assert.match(header, /aksrt-nav-icon aksrt-nav-notification/);
|
||||
assert.doesNotMatch(headerStyles, /\.jio-header-userbox \.user-info > \.abs-right/);
|
||||
assert.match(header, /jio-nav-icon jio-nav-notification/);
|
||||
assert.match(header, /\$notification_but \. \$sign_but \. \$search_but/);
|
||||
assert.match(userCenter, /in_array\('user_center_header'/);
|
||||
assert.match(userHome, /in_array\('user_home_header'/);
|
||||
@@ -57,9 +57,9 @@ test('notification icon positions map to their four render locations', () => {
|
||||
test('notification preference endpoint stores only the three boolean categories', () => {
|
||||
const controller = read('src/User/NotificationPreferences.php');
|
||||
|
||||
assert.match(controller, /META_KEY = 'aksrt_notification_preferences'/);
|
||||
assert.match(controller, /META_KEY = 'jio_notification_preferences'/);
|
||||
assert.match(controller, /CATEGORIES = \['posts', 'like', 'system'\]/);
|
||||
assert.match(controller, /register_rest_route\('aksrt\/v1', '\/user\/notification-preferences'/);
|
||||
assert.match(controller, /register_rest_route\('jio\/v1', '\/user\/notification-preferences'/);
|
||||
assert.match(controller, /permission_callback/);
|
||||
assert.match(controller, /current_user_can\('read'\)/);
|
||||
assert.match(controller, /'additionalProperties'\s*=>\s*false/);
|
||||
@@ -85,7 +85,7 @@ test('notification preference UI uses the global toast lifecycle', () => {
|
||||
assert.match(template, /role="switch"/);
|
||||
assert.match(styles, /\.uc-notification-switch input\[type='checkbox'\][\s\S]*?opacity: 0 !important/);
|
||||
assert.match(styles, /\.uc-notification-switch input\[type='checkbox'\]::before,[\s\S]*?\.uc-notification-switch input\[type='checkbox'\]::after[\s\S]*?content: none !important/);
|
||||
assert.match(script, /aksrt-notification-preferences-toast/);
|
||||
assert.match(script, /jio-notification-preferences-toast/);
|
||||
assert.match(script, /notyf\([^;]+?'load'[^;]+?toastId/);
|
||||
assert.match(script, /notyf\([^;]+?'success'[^;]+?toastId/);
|
||||
assert.match(script, /notyf\([^;]+?'danger'[^;]+?toastId/);
|
||||
|
||||
@@ -130,7 +130,7 @@ test('OAuth bind starts require a one-time captcha bind pass', () => {
|
||||
assert.match(oauth, /validate_bind_pass\(\$bind_pass, \$provider, \$user_id, true\)/)
|
||||
assert.match(oauth, /validate_bind_pass\(\$bind_pass, 'clogin_' \. \$type, get_current_user_id\(\), true\)/)
|
||||
assert.match(weixingzh, /OAuth::validate_bind_pass\(\$bind_pass, 'weixingzh'/)
|
||||
assert.match(bindings, /aksrt_render_captcha\('bind'\)/)
|
||||
assert.match(bindings, /jio_render_captcha\('bind'\)/)
|
||||
})
|
||||
|
||||
test('OAuth result handler maps safe errors and preserves unrelated URL state', () => {
|
||||
@@ -156,7 +156,7 @@ test('OAuth result handler maps safe errors and preserves unrelated URL state',
|
||||
}
|
||||
const jQueryStub = () => jQueryApi
|
||||
const window = {
|
||||
aksrtData: {},
|
||||
jioData: {},
|
||||
URL,
|
||||
location,
|
||||
history: { replaceState: (_state, _title, value) => { replaced = value } },
|
||||
|
||||
@@ -16,7 +16,7 @@ $GLOBALS['wp_options'] = [];
|
||||
$GLOBALS['wp_cache'] = [];
|
||||
$GLOBALS['captcha_enabled'] = true;
|
||||
|
||||
function aksrt_is_captcha_enabled_for(string $scene): bool { return $scene === 'bind' && !empty($GLOBALS['captcha_enabled']); }
|
||||
function jio_is_captcha_enabled_for(string $scene): bool { return $scene === 'bind' && !empty($GLOBALS['captcha_enabled']); }
|
||||
function __(string $text, string $domain = ''): string { return $text; }
|
||||
function sanitize_key(string $value): string { return preg_replace('/[^a-z0-9_\-]/', '', strtolower($value)) ?? ''; }
|
||||
function sanitize_text_field(string $value): string { return trim(strip_tags($value)); }
|
||||
@@ -76,7 +76,7 @@ final class HarnessWpdb {
|
||||
$GLOBALS['wpdb'] = new HarnessWpdb();
|
||||
}
|
||||
|
||||
namespace AKSRT\Settings {
|
||||
namespace Jio\Settings {
|
||||
final class Settings {
|
||||
public static function flush_settings_cache(): void {}
|
||||
public static function sync_native_site_icon(string $value): void {}
|
||||
@@ -99,51 +99,51 @@ namespace {
|
||||
if (!$condition) throw new RuntimeException($message);
|
||||
}
|
||||
|
||||
$_COOKIE['aksrt_oauth_browser'] = str_repeat('A', 43);
|
||||
$token = \AKSRT\Auth\OAuthTransactionStore::issue('direct', ['provider' => 'github'], 60, true);
|
||||
check(\AKSRT\Auth\OAuthTransactionStore::peek($token, 'direct', true) !== null, 'transaction peek failed');
|
||||
check(\AKSRT\Auth\OAuthTransactionStore::peek($token, 'other', true) === null, 'transaction kind mismatch accepted');
|
||||
$_COOKIE['aksrt_oauth_browser'] = str_repeat('B', 43);
|
||||
check(\AKSRT\Auth\OAuthTransactionStore::claim($token, 'direct', true) === null, 'wrong browser claimed transaction');
|
||||
$_COOKIE['aksrt_oauth_browser'] = str_repeat('A', 43);
|
||||
check(\AKSRT\Auth\OAuthTransactionStore::claim($token, 'direct', true) !== null, 'valid browser failed claim');
|
||||
check(\AKSRT\Auth\OAuthTransactionStore::claim($token, 'direct', true) === null, 'transaction replay accepted');
|
||||
$_COOKIE['jio_oauth_browser'] = str_repeat('A', 43);
|
||||
$token = \Jio\Auth\OAuthTransactionStore::issue('direct', ['provider' => 'github'], 60, true);
|
||||
check(\Jio\Auth\OAuthTransactionStore::peek($token, 'direct', true) !== null, 'transaction peek failed');
|
||||
check(\Jio\Auth\OAuthTransactionStore::peek($token, 'other', true) === null, 'transaction kind mismatch accepted');
|
||||
$_COOKIE['jio_oauth_browser'] = str_repeat('B', 43);
|
||||
check(\Jio\Auth\OAuthTransactionStore::claim($token, 'direct', true) === null, 'wrong browser claimed transaction');
|
||||
$_COOKIE['jio_oauth_browser'] = str_repeat('A', 43);
|
||||
check(\Jio\Auth\OAuthTransactionStore::claim($token, 'direct', true) !== null, 'valid browser failed claim');
|
||||
check(\Jio\Auth\OAuthTransactionStore::claim($token, 'direct', true) === null, 'transaction replay accepted');
|
||||
|
||||
$bindPass = \AKSRT\Auth\OAuthTransactionStore::issue('oauth_bind_pass', ['user_id' => 17, 'provider' => 'github'], 300, true);
|
||||
check(\AKSRT\Auth\OAuth::validate_bind_pass($bindPass, 'github', 17, false), 'valid bind pass peek failed');
|
||||
check(!\AKSRT\Auth\OAuth::validate_bind_pass($bindPass, 'gitee', 17, true), 'wrong bind provider consumed as valid');
|
||||
check(!\AKSRT\Auth\OAuth::validate_bind_pass($bindPass, 'github', 18, true), 'wrong bind user consumed as valid');
|
||||
check(\AKSRT\Auth\OAuth::validate_bind_pass($bindPass, 'github', 17, true), 'valid bind pass claim failed after rejected mismatches');
|
||||
check(!\AKSRT\Auth\OAuth::validate_bind_pass($bindPass, 'github', 17, true), 'bind pass replay accepted');
|
||||
$bindPass = \Jio\Auth\OAuthTransactionStore::issue('oauth_bind_pass', ['user_id' => 17, 'provider' => 'github'], 300, true);
|
||||
check(\Jio\Auth\OAuth::validate_bind_pass($bindPass, 'github', 17, false), 'valid bind pass peek failed');
|
||||
check(!\Jio\Auth\OAuth::validate_bind_pass($bindPass, 'gitee', 17, true), 'wrong bind provider consumed as valid');
|
||||
check(!\Jio\Auth\OAuth::validate_bind_pass($bindPass, 'github', 18, true), 'wrong bind user consumed as valid');
|
||||
check(\Jio\Auth\OAuth::validate_bind_pass($bindPass, 'github', 17, true), 'valid bind pass claim failed after rejected mismatches');
|
||||
check(!\Jio\Auth\OAuth::validate_bind_pass($bindPass, 'github', 17, true), 'bind pass replay accepted');
|
||||
$GLOBALS['captcha_enabled'] = false;
|
||||
check(\AKSRT\Auth\OAuth::validate_bind_pass('', 'github', 17, true), 'disabled bind captcha required a pass');
|
||||
check(\Jio\Auth\OAuth::validate_bind_pass('', 'github', 17, true), 'disabled bind captcha required a pass');
|
||||
$GLOBALS['captcha_enabled'] = true;
|
||||
|
||||
$expired = \AKSRT\Auth\OAuthTransactionStore::issue('direct', [], 30, true);
|
||||
$expired = \Jio\Auth\OAuthTransactionStore::issue('direct', [], 30, true);
|
||||
foreach ($GLOBALS['wp_options'] as $key => $row) {
|
||||
if (is_array($row) && ($row['kind'] ?? '') === 'direct') $GLOBALS['wp_options'][$key]['expires_at'] = time() - 1;
|
||||
}
|
||||
check(\AKSRT\Auth\OAuthTransactionStore::peek($expired, 'direct', true) === null, 'expired transaction accepted');
|
||||
check(\Jio\Auth\OAuthTransactionStore::peek($expired, 'direct', true) === null, 'expired transaction accepted');
|
||||
|
||||
$request = [
|
||||
'v' => '2', 'request_id' => str_repeat('C', 43), 'provider' => 'github', 'action' => 'login',
|
||||
'agent_back_url' => 'https://example.com/oauth/proxy/callback/', 'ts' => (string) time(),
|
||||
];
|
||||
$secret = 'test-secret-value';
|
||||
$requestSign = \AKSRT\Auth\OAuthProxyProtocol::sign_request($request, $secret);
|
||||
check(\AKSRT\Auth\OAuthProxyProtocol::verify_request($request, $secret, $requestSign), 'request signature failed');
|
||||
check(!\AKSRT\Auth\OAuthProxyProtocol::verify_callback($request, $secret, $requestSign), 'direction-separated HMAC failed');
|
||||
$requestSign = \Jio\Auth\OAuthProxyProtocol::sign_request($request, $secret);
|
||||
check(\Jio\Auth\OAuthProxyProtocol::verify_request($request, $secret, $requestSign), 'request signature failed');
|
||||
check(!\Jio\Auth\OAuthProxyProtocol::verify_callback($request, $secret, $requestSign), 'direction-separated HMAC failed');
|
||||
$tampered = $request; $tampered['provider'] = 'qq';
|
||||
check(!\AKSRT\Auth\OAuthProxyProtocol::verify_request($tampered, $secret, $requestSign), 'tampered request accepted');
|
||||
check(!\AKSRT\Auth\OAuthProxyProtocol::timestamp_valid((string) (time() - 1000)), 'expired timestamp accepted');
|
||||
check(!\Jio\Auth\OAuthProxyProtocol::verify_request($tampered, $secret, $requestSign), 'tampered request accepted');
|
||||
check(!\Jio\Auth\OAuthProxyProtocol::timestamp_valid((string) (time() - 1000)), 'expired timestamp accepted');
|
||||
|
||||
check(\AKSRT\Auth\OAuthUrlPolicy::public_https('http://example.com') === '', 'HTTP URL accepted');
|
||||
check(\AKSRT\Auth\OAuthUrlPolicy::public_https('https://user@example.com') === '', 'userinfo URL accepted');
|
||||
check(\AKSRT\Auth\OAuthUrlPolicy::public_https('https://127.0.0.1') === '', 'loopback URL accepted');
|
||||
check(\AKSRT\Auth\OAuthUrlPolicy::public_https('https://10.0.0.1') === '', 'private URL accepted');
|
||||
check(\AKSRT\Auth\OAuthProxyProtocol::callback_url('https://example.com/wrong') === '', 'wrong callback path accepted');
|
||||
check(\Jio\Auth\OAuthUrlPolicy::public_https('http://example.com') === '', 'HTTP URL accepted');
|
||||
check(\Jio\Auth\OAuthUrlPolicy::public_https('https://user@example.com') === '', 'userinfo URL accepted');
|
||||
check(\Jio\Auth\OAuthUrlPolicy::public_https('https://127.0.0.1') === '', 'loopback URL accepted');
|
||||
check(\Jio\Auth\OAuthUrlPolicy::public_https('https://10.0.0.1') === '', 'private URL accepted');
|
||||
check(\Jio\Auth\OAuthProxyProtocol::callback_url('https://example.com/wrong') === '', 'wrong callback path accepted');
|
||||
|
||||
$provider = \AKSRT\Admin\SchemaProvider::get_instance();
|
||||
$provider = \Jio\Admin\SchemaProvider::get_instance();
|
||||
$base = $provider->get_defaults('users', 'oauth');
|
||||
$base['oauth_s'] = true;
|
||||
$base['oauth_weixingzh_s'] = true;
|
||||
@@ -151,18 +151,18 @@ namespace {
|
||||
$base['weixingzh_appid'] = 'appid';
|
||||
$base['weixingzh_appsecret'] = 'secret';
|
||||
$base['weixingzh_token'] = '***';
|
||||
$GLOBALS['wp_options']['aksrt_settings'] = [];
|
||||
$GLOBALS['wp_options']['jio_settings'] = [];
|
||||
check(!$provider->validate('users', $base, 'oauth')['valid'], 'masked missing token treated as configured');
|
||||
$GLOBALS['wp_options']['aksrt_settings'] = ['weixingzh_token' => 'stored-token'];
|
||||
$GLOBALS['wp_options']['jio_settings'] = ['weixingzh_token' => 'stored-token'];
|
||||
check($provider->validate('users', $base, 'oauth')['valid'], 'stored token not preserved by mask validation');
|
||||
|
||||
$provider->write_settings('users', ['weixingzh_token' => '***'], 'oauth');
|
||||
check($GLOBALS['wp_options']['aksrt_settings']['weixingzh_token'] === 'stored-token', 'masked token overwrote stored secret');
|
||||
check($GLOBALS['wp_options']['jio_settings']['weixingzh_token'] === 'stored-token', 'masked token overwrote stored secret');
|
||||
$provider->write_settings('users', ['weixingzh_token' => 'replacement'], 'oauth');
|
||||
check($GLOBALS['wp_options']['aksrt_settings']['weixingzh_token'] === 'replacement', 'new token failed to replace secret');
|
||||
check($GLOBALS['wp_options']['jio_settings']['weixingzh_token'] === 'replacement', 'new token failed to replace secret');
|
||||
$provider->write_settings('users', ['__clear_secrets' => ['weixingzh_token']], 'oauth');
|
||||
check(!isset($GLOBALS['wp_options']['aksrt_settings']['weixingzh_token']), 'explicit secret clear failed');
|
||||
$GLOBALS['wp_options']['aksrt_settings']['weixingzh_token'] = 'final-token';
|
||||
check(!isset($GLOBALS['wp_options']['jio_settings']['weixingzh_token']), 'explicit secret clear failed');
|
||||
$GLOBALS['wp_options']['jio_settings']['weixingzh_token'] = 'final-token';
|
||||
$clearData = $base;
|
||||
$clearData['weixingzh_token'] = '***';
|
||||
$clearData['__clear_secrets'] = ['weixingzh_token'];
|
||||
|
||||
@@ -18,15 +18,15 @@ const settingsFacade = read('src/Settings/Settings.php')
|
||||
const scriptsPart1c = read('src/Core/ScriptsPart1c.php')
|
||||
const footerCss = read('assets/css/frontend/footer/shell.css')
|
||||
const frontendBaseCss = read('assets/css/frontend/base.css')
|
||||
const headerMenuButtons = read('inc/helpers/aksrt-header-menu-buttons.php')
|
||||
const headerMenuButtons = read('inc/helpers/jio-header-menu-buttons.php')
|
||||
const frontendRenderer = read('src/WidgetSystem/FrontendRenderer.php')
|
||||
const userRenderer = read('src/WidgetSystem/Renderers/UserRenderer.php')
|
||||
const widgetCss = read('assets/widgets/frontend.css')
|
||||
const headerNavbar = read('inc/helpers/header/aksrt-header-navbar.php')
|
||||
const headerMobile = read('inc/helpers/header/aksrt-header-mobile.php')
|
||||
const headerRender = read('inc/helpers/header/aksrt-header-render.php')
|
||||
const headerUserbox = read('inc/helpers/aksrt-header-userbox.php')
|
||||
const headerSearch = read('inc/helpers/header/aksrt-header-menu-search.php')
|
||||
const headerNavbar = read('inc/helpers/header/jio-header-navbar.php')
|
||||
const headerMobile = read('inc/helpers/header/jio-header-mobile.php')
|
||||
const headerRender = read('inc/helpers/header/jio-header-render.php')
|
||||
const headerUserbox = read('inc/helpers/jio-header-userbox.php')
|
||||
const headerSearch = read('inc/helpers/header/jio-header-menu-search.php')
|
||||
const headerCss = read('assets/css/frontend/header-userbox.css')
|
||||
const coreResetCss = read('assets/css/core/reset.css')
|
||||
const vipCardRenderer = read('src/Content/VipServiceModal.php')
|
||||
@@ -119,7 +119,7 @@ test('defaults, nested structures, dependencies, and excluded product families a
|
||||
assert.equal(schema.includes(dead), false, `legacy/excluded field remains: ${dead}`)
|
||||
}
|
||||
assert.equal(headerMenuButtons.includes('nav_newposts_btns'), false)
|
||||
assert.match(headerMenuButtons, /aksrt_render_new_add_btns\(\['post'\], \$class, \$label\)/)
|
||||
assert.match(headerMenuButtons, /jio_render_new_add_btns\(\['post'\], \$class, \$label\)/)
|
||||
// FA5 spelling of the reference '<i class="fa fa-fw fa-pencil"></i>发布'
|
||||
// default: fa-pencil does not exist in the bundled FA 5.15.4 free set.
|
||||
assert.match(schema, /'nav_newposts_text', '按钮文案', 'textarea', '<i class="fa fas fa-fw fa-pencil-alt"><\/i>发布'/)
|
||||
@@ -150,13 +150,13 @@ test('header VIP hover cards match the legacy markup and style contract', () =>
|
||||
assert.match(headerMenuButtons, /'<a rel="nofollow" class="newadd-btns ' \. esc_attr\(\$class\) \. ' btn-newadd"/)
|
||||
assert.equal((headerMenuButtons.match(/Settings::close_sign\(\)/g) || []).length, 2)
|
||||
for (const dead of ['navbar-cta', 'navbar-cta-text', 'navbar-newposts', 'navbar-vip-hover', 'newadd-btns__', 'vip-level', 'fa-gem']) {
|
||||
assert.equal(headerMenuButtons.includes(dead), false, `AKSRT-only header CTA token remains: ${dead}`)
|
||||
assert.equal(headerMenuButtons.includes(dead), false, `Jio-only header CTA token remains: ${dead}`)
|
||||
}
|
||||
assert.match(vipCardRenderer, /aksrt_get_lazy_attr\('lazy_other', \$icon_url, 'img-icon'\)/)
|
||||
assert.match(vipCardRenderer, /jio_get_lazy_attr\('lazy_other', \$icon_url, 'img-icon'\)/)
|
||||
assert.match(vipCardRenderer, /<div class=\"abs-center vip-baicon\"[^>]*>/)
|
||||
assert.match(vipCardRenderer, /<div class=\"vip-img abs-right\">/)
|
||||
assert.match(vipCardRenderer, /<div class=\"vip-name mb10\"><span class=\"mr6\">/)
|
||||
assert.match(vipCardRenderer, /aksrt_format_vip_equity_html\(\$equity, 'mb10 relative'\)/)
|
||||
assert.match(vipCardRenderer, /jio_format_vip_equity_html\(\$equity, 'mb10 relative'\)/)
|
||||
assert.doesNotMatch(vipCardRenderer, /vip-name__(?:icon|label)/)
|
||||
|
||||
assert.doesNotMatch(vipModalCss, /Header hover VIP cards/)
|
||||
@@ -178,9 +178,9 @@ test('header VIP hover cards match the legacy markup and style contract', () =>
|
||||
test('user card VIP CTA matches the reference zib_get_header_payvip_icon()', () => {
|
||||
assert.match(headerUserbox, /'<a class="pay-vip but radius4 payvip-icon btn-block" href="javascript:;">'/)
|
||||
assert.match(headerUserbox, /<svg class="em12 mr6" aria-hidden="true"><use xlink:href="#icon-vip_1"><\/use><\/svg>'/)
|
||||
assert.match(headerUserbox, /esc_html__\('开通会员 尊享会员权益', 'aksrt'\)/)
|
||||
assert.match(headerUserbox, /esc_html__\('开通会员 尊享会员权益', 'jio'\)/)
|
||||
for (const dead of ['fa-gem', 'vip-level']) {
|
||||
assert.equal(headerUserbox.includes(dead), false, `AKSRT-only user card CTA token remains: ${dead}`)
|
||||
assert.equal(headerUserbox.includes(dead), false, `Jio-only user card CTA token remains: ${dead}`)
|
||||
}
|
||||
assert.doesNotMatch(headerUserbox, /<span>' \. esc_html__\('开通会员 尊享会员权益'/)
|
||||
})
|
||||
@@ -195,10 +195,10 @@ test('VIP purchase modal matches legacy desktop-center and mobile-bottom positio
|
||||
// 高度/滚动 = .modal.flex .modal-content 的 max-height calc(100vh - 20px)
|
||||
// + overflow-y auto(阈值放宽到整屏,正常内容不出现滚动条);
|
||||
// 圆角 = .modal-content 的 var(--main-radius)(8px)。
|
||||
assert.match(vipModalCss, /\.aksrt-vip-modal-shell \.aksrt-modal-runtime__dialog\s*\{[\s\S]*?width:\s*min\(600px, calc\(100vw - 20px\)\);[\s\S]*?max-height:\s*calc\(100vh - 20px\);[\s\S]*?overflow:\s*hidden;[\s\S]*?overflow-y:\s*auto;[\s\S]*?border-radius:\s*var\(--main-radius, 8px\);/)
|
||||
assert.match(vipModalCss, /\.jio-vip-modal-shell \.jio-modal-runtime__dialog\s*\{[\s\S]*?width:\s*min\(600px, calc\(100vw - 20px\)\);[\s\S]*?max-height:\s*calc\(100vh - 20px\);[\s\S]*?overflow:\s*hidden;[\s\S]*?overflow-y:\s*auto;[\s\S]*?border-radius:\s*var\(--main-radius, 8px\);/)
|
||||
// 内边距由 .box-body 15px 提供(参考主题无 .payvip-modal 专属 padding)。
|
||||
assert.doesNotMatch(vipModalCss, /\.payvip-modal\s*\{[^}]*padding:/)
|
||||
assert.match(vipModalCss, /@media \(max-width: 768px\)[\s\S]*?\.aksrt-vip-modal-shell\.aksrt-modal-runtime--bottom \.aksrt-modal-runtime__dialog\s*\{[\s\S]*?width:\s*100%;[\s\S]*?max-height:\s*calc\(100vh - 20px\);[\s\S]*?border-radius:\s*var\(--main-radius, 8px\) var\(--main-radius, 8px\) 0 0;/)
|
||||
assert.match(vipModalCss, /@media \(max-width: 768px\)[\s\S]*?\.jio-vip-modal-shell\.jio-modal-runtime--bottom \.jio-modal-runtime__dialog\s*\{[\s\S]*?width:\s*100%;[\s\S]*?max-height:\s*calc\(100vh - 20px\);[\s\S]*?border-radius:\s*var\(--main-radius, 8px\) var\(--main-radius, 8px\) 0 0;/)
|
||||
})
|
||||
|
||||
test('schema REST saves are privileged, section-whitelisted, and deeply sanitized', () => {
|
||||
@@ -231,8 +231,8 @@ test('legacy routes, components, and storage paths are gone', () => {
|
||||
|
||||
const repository = activeRepositorySource()
|
||||
for (const dead of [
|
||||
'aksrt_footer_settings', 'aksrt_banners', 'aksrt_banner', 'aksrt-banner', 'HomeModules', 'homepage_modules',
|
||||
'/settings/homepage', 'home-modules', 'cms-home-modules', 'aksrt_preview', 'theme-background',
|
||||
'jio_footer_settings', 'jio_banners', 'jio_banner', 'jio-banner', 'HomeModules', 'homepage_modules',
|
||||
'/settings/homepage', 'home-modules', 'cms-home-modules', 'jio_preview', 'theme-background',
|
||||
"templates/parts/home-modules",
|
||||
]) {
|
||||
assert.equal(repository.includes(dead), false, `active legacy implementation remains: ${dead}`)
|
||||
@@ -278,7 +278,7 @@ test('front-end page-display consumers read only the canonical section contracts
|
||||
|
||||
test('footer brand icons keep the Brands font when legacy fa prefix is present', () => {
|
||||
const footer = read('footer.php')
|
||||
const icons = read('inc/helpers/aksrt-icons.php')
|
||||
const icons = read('inc/helpers/jio-icons.php')
|
||||
|
||||
assert.match(footer, /class="fa fab fa-weixin"/)
|
||||
assert.match(footer, /class="fa fab fa-qq"/)
|
||||
@@ -301,8 +301,8 @@ test('header multifunction search keeps focus and submit alignment route-indepen
|
||||
test('user profile and author widgets render the reference user-card contract', () => {
|
||||
assert.match(frontendRenderer, /in_array\(\$definition->slug\(\), \['user_card', 'author_card'\], true\)/)
|
||||
assert.match(frontendRenderer, /self::render_reference_widget\(\$definition, \$instance, \$widget, \$content\)/)
|
||||
assert.match(frontendRenderer, /\$classes = \['zib-widget-wrap', 'aksrt-'/)
|
||||
assert.match(frontendRenderer, /\$classes\[\] = 'aksrt-user-widget-wrap'/)
|
||||
assert.match(frontendRenderer, /\$classes = \['zib-widget-wrap', 'jio-'/)
|
||||
assert.match(frontendRenderer, /\$classes\[\] = 'jio-user-widget-wrap'/)
|
||||
assert.match(frontendRenderer, /echo '<div class="widget-container"><div>';/)
|
||||
assert.match(frontendRenderer, /\$content_classes = \['widget-content'\]/)
|
||||
// 登录卡片与 reference zib_get_user_card_box 同构:封面、头像框、名字行、统计徽章、简介、按钮。
|
||||
@@ -314,17 +314,17 @@ test('user profile and author widgets render the reference user-card contract',
|
||||
assert.match(userRenderer, /author-tag mt10 mini-scrollbar/)
|
||||
assert.match(userRenderer, /user-desc mt10 muted-2-color em09/)
|
||||
assert.match(userRenderer, /user-btns mt20/)
|
||||
assert.match(userRenderer, /aksrt_get_user_badges\(\$user_id\)/)
|
||||
assert.match(userRenderer, /jio_get_user_badges\(\$user_id\)/)
|
||||
assert.match(userRenderer, /'follow' => true,/)
|
||||
// 用户个人信息小工具不显示文章模块,显示签到徽章。
|
||||
assert.match(userRenderer, /'show_posts' => false,/)
|
||||
assert.match(userRenderer, /'show_checkin' => true,/)
|
||||
assert.match(userRenderer, /aksrt_header_user_checkin_btn\(\$user_id, 'img-badge jb-yellow'\)/)
|
||||
assert.match(userRenderer, /jio_header_user_checkin_btn\(\$user_id, 'img-badge jb-yellow'\)/)
|
||||
// 文章作者信息小工具显示文章模块,并走同一 widget 变体。
|
||||
assert.match(userRenderer, /'show_posts' => !isset\(\$i\['show_posts'\]\) \|\| !empty\(\$i\['show_posts'\]\),/)
|
||||
assert.match(userRenderer, /'post_type' => 'post',/)
|
||||
assert.match(userRenderer, /self::card_box\(\(int\) \$user->ID,[\s\S]*?\) \. '<\/div>';/)
|
||||
assert.match(userRenderer, /aksrt_render_user_card_posts\(/)
|
||||
assert.match(userRenderer, /jio_render_user_card_posts\(/)
|
||||
assert.match(userRenderer, /'post_type' => \(string\) \$args\['post_type'\],/)
|
||||
// 未登录卡片使用主题认证真值和 reference 登录/社交 DOM。
|
||||
assert.match(userRenderer, /Settings::close_sign\(\)/)
|
||||
@@ -346,26 +346,26 @@ test('user profile and author widgets render the reference user-card contract',
|
||||
assert.doesNotMatch(widgetCss, /\.widget-content[^{,]*\{[^}]*padding/s)
|
||||
assert.doesNotMatch(read('assets/css/frontend/sidebar.css'), /\.widget-content[^{,]*\{[^}]*padding/s)
|
||||
assert.doesNotMatch(widgetCss, /user-card__auth-btns/)
|
||||
assert.doesNotMatch(widgetCss, /\.aksrt-user-card\s*\{/)
|
||||
assert.doesNotMatch(userRenderer, /aksrt-widget-button is-ghost/)
|
||||
assert.doesNotMatch(widgetCss, /\.jio-user-card\s*\{/)
|
||||
assert.doesNotMatch(userRenderer, /jio-widget-button is-ghost/)
|
||||
})
|
||||
|
||||
test('desktop header actions share one row without generic button masks', () => {
|
||||
assert.match(headerNavbar, /navbar-form navbar-right navbar-but/)
|
||||
assert.match(headerNavbar, /navbar-form navbar-right flex ac/)
|
||||
assert.match(headerNavbar, /\$fold \. \('3' === \$layout \? \$right_but \. \$button : \$button \. \$right_but\)/)
|
||||
assert.match(headerNavbar, /class="aksrt-nav-icon nav-search-btn main-search-btn"/)
|
||||
assert.match(headerNavbar, /class="jio-nav-icon nav-search-btn main-search-btn"/)
|
||||
assert.doesNotMatch(headerNavbar, /class="btn nav-search-btn main-search-btn"/)
|
||||
assert.match(headerNavbar, /<ul class="list-inline splitters relative">/)
|
||||
assert.doesNotMatch(headerNavbar, /aksrt-navbar-account|aksrt-nav-avatar/)
|
||||
assert.doesNotMatch(headerNavbar, /jio-navbar-account|jio-nav-avatar/)
|
||||
assert.match(headerNavbar, /class="navbar-avatar"/)
|
||||
assert.match(legacyMainCss, /\.navbar-avatar\s*\{[^}]*margin-top:\s*3px;[^}]*width:\s*29px;[^}]*height:\s*29px;/s)
|
||||
assert.match(headerNavbar, /aksrt_get_translate_language_btn\('ml10 pull-right'\)/)
|
||||
assert.match(headerNavbar, /jio_get_translate_language_btn\('ml10 pull-right'\)/)
|
||||
assert.match(headerNavbar, /<a data-toggle-class data-target="\.nav\.navbar-nav" href="javascript:;" class="but"/)
|
||||
assert.match(headerNavbar, /<svg class="icon" viewBox="50 0 924 924"/)
|
||||
assert.match(headerNavbar, /<svg class="icon" aria-hidden="true"><use xlink:href="#icon-search"/)
|
||||
assert.doesNotMatch(headerCss, /\.aksrt-navbar-account|\.aksrt-nav-avatar|navbar-form\.flex\.ac\s*\{[^}]*min-height/)
|
||||
assert.match(headerCss, /\.navbar-top \.aksrt-nav-icon\s*\{[^}]*display:\s*inline-block;[^}]*padding:\s*6px 12px;[^}]*font-size:\s*14px;[^}]*line-height:\s*1\.42857143;[^}]*border:\s*1px solid transparent;[^}]*border-radius:\s*4px;/s)
|
||||
assert.doesNotMatch(headerCss, /\.jio-navbar-account|\.jio-nav-avatar|navbar-form\.flex\.ac\s*\{[^}]*min-height/)
|
||||
assert.match(headerCss, /\.navbar-top \.jio-nav-icon\s*\{[^}]*display:\s*inline-block;[^}]*padding:\s*6px 12px;[^}]*font-size:\s*14px;[^}]*line-height:\s*1\.42857143;[^}]*border:\s*1px solid transparent;[^}]*border-radius:\s*4px;/s)
|
||||
// The publish and VIP chips ride the legacy .but geometry verbatim; nothing in
|
||||
// the header module may restate their padding, margin, radius or icon size.
|
||||
// Descendant, not child: the VIP chip lives inside the hover-show wrapper.
|
||||
@@ -373,7 +373,7 @@ test('desktop header actions share one row without generic button masks', () =>
|
||||
assert.doesNotMatch(headerCss, /\.navbar-cta|\.navbar-vip-hover/)
|
||||
// No merged flex container: the three groups keep their own .navbar-form
|
||||
// padding, which is what produces the reference inter-cluster spacing.
|
||||
assert.doesNotMatch(headerCss, /\.aksrt-navbar-actions|\.aksrt-navbar-account|\.aksrt-nav-avatar/)
|
||||
assert.doesNotMatch(headerCss, /\.jio-navbar-actions|\.jio-navbar-account|\.jio-nav-avatar/)
|
||||
})
|
||||
|
||||
test('mobile search follows the reference center and left header placements', () => {
|
||||
@@ -381,15 +381,15 @@ test('mobile search follows the reference center and left header placements', ()
|
||||
assert.match(headerNavbar, /mobile_layout/)
|
||||
assert.match(headerNavbar, /'center' === \(\$ctx\['mobile_layout'\] \?\? 'center'\)/)
|
||||
assert.match(headerMobile, /main-search-btn|toggle-radius/)
|
||||
assert.match(headerMobile, /aksrt_nav_mobile_content\(\$ctx\)/)
|
||||
assert.match(headerMobile, /jio_nav_mobile_content\(\$ctx\)/)
|
||||
assert.doesNotMatch(headerRender, /'fixed'\s*=>/)
|
||||
assert.doesNotMatch(legacyMainCss, /\.mobile-navbar \.navbar-(?:header|brand)/)
|
||||
assert.doesNotMatch(coreResetCss, /img,\s*picture,\s*video,\s*canvas,\s*svg\s*\{/)
|
||||
assert.match(legacyMainCss, /svg\s*\{[^}]*width:\s*1em;[^}]*height:\s*1em;/s)
|
||||
|
||||
const topHeaderSearch = headerRender.indexOf('aksrt_navbar_header($ctx);')
|
||||
const topHeaderSearch = headerRender.indexOf('jio_navbar_header($ctx);')
|
||||
const topHeaderClose = headerRender.indexOf("echo '</div></div></nav></header>';")
|
||||
const mobileDrawer = headerRender.indexOf('aksrt_nav_mobile_shell($ctx);')
|
||||
const mobileDrawer = headerRender.indexOf('jio_nav_mobile_shell($ctx);')
|
||||
assert.ok(topHeaderSearch > -1 && topHeaderSearch < topHeaderClose)
|
||||
assert.ok(topHeaderClose < mobileDrawer)
|
||||
assert.match(legacyMainCss, /\.scroll-down\.nav-fixed \.header\.scroll-down-hide\s*\{[^}]*transform:\s*translate3d\(0, -105%, 0\);/s)
|
||||
@@ -417,21 +417,21 @@ test('header user-card membership stays on one row with a date-only expiry', ()
|
||||
// Reference VIP bar: legacy payvip-icon geometry, the untouched level-icon
|
||||
// contract, and the yellow expiry pill.
|
||||
assert.match(headerUserbox, /radius4 payvip-icon btn-block text-center/)
|
||||
assert.match(headerUserbox, /aksrt_get_vip_icon\(\$user_id, 'em12 mr6', false\)/)
|
||||
assert.match(headerUserbox, /jio_get_vip_icon\(\$user_id, 'em12 mr6', false\)/)
|
||||
assert.match(headerUserbox, /class="ml10 badg jb-yellow vip-expdate-tag"/)
|
||||
assert.match(legacyMainCss, /\.sub-user-box \.payvip-icon\s*\{[^}]*padding:\s*8px;/s)
|
||||
assert.match(legacyMainCss, /\.vip-expdate-tag\s*\{[^}]*font-size:\s*0\.8em;[^}]*padding:\s*0\.1em 6px;[^}]*vertical-align:\s*0\.1em;/s)
|
||||
assert.doesNotMatch(vipModalCss, /\.vip-expdate-tag\s*\{/)
|
||||
assert.doesNotMatch(headerCss, /aksrt-header-payvip/)
|
||||
assert.doesNotMatch(headerCss, /jio-header-payvip/)
|
||||
// The CTA wrapper is the reference padding wrapper, not a bespoke hook.
|
||||
assert.match(headerUserbox, /'<div class="mt10 em09" style="padding:2px;">'/)
|
||||
assert.doesNotMatch(headerUserbox, /aksrt-header-payvip/)
|
||||
assert.doesNotMatch(headerUserbox, /jio-header-payvip/)
|
||||
// Expiry badge follows zib_get_user_vip_exp_date_text(): an expiry that has
|
||||
// passed reads 会员已过期, a live one reads the bare date, 永久会员 is reserved
|
||||
// for an explicit permanent membership, and no expiry data renders no badge.
|
||||
assert.match(headerUserbox, /\$expires <= time\(\)/)
|
||||
assert.match(headerUserbox, /empty\(\$state\['is_permanent'\]\)/)
|
||||
assert.doesNotMatch(headerUserbox, /aksrt_get_vip_info/)
|
||||
assert.doesNotMatch(headerUserbox, /jio_get_vip_info/)
|
||||
})
|
||||
|
||||
test('save contract rejects invalid input and round-trips canonical nested values', () => {
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
|
||||
namespace {
|
||||
define('ABSPATH', __DIR__);
|
||||
$GLOBALS['aksrt_test_options'] = [];
|
||||
$GLOBALS['aksrt_test_manage_options'] = false;
|
||||
$GLOBALS['aksrt_visibility_query'] = '';
|
||||
$GLOBALS['aksrt_visibility_page_config'] = [];
|
||||
$GLOBALS['jio_test_options'] = [];
|
||||
$GLOBALS['jio_test_manage_options'] = false;
|
||||
$GLOBALS['jio_visibility_query'] = '';
|
||||
$GLOBALS['jio_visibility_page_config'] = [];
|
||||
|
||||
final class WP_Query {
|
||||
/** @var array<string,mixed> */
|
||||
@@ -62,7 +62,7 @@ namespace {
|
||||
}
|
||||
|
||||
function get_template_directory_uri(): string {
|
||||
return 'https://example.test/wp-content/themes/aksrt';
|
||||
return 'https://example.test/wp-content/themes/jio';
|
||||
}
|
||||
|
||||
function home_url($path = ''): string {
|
||||
@@ -70,27 +70,27 @@ namespace {
|
||||
}
|
||||
|
||||
function get_bloginfo($show = ''): string {
|
||||
return $show === 'title' ? 'AKSRT' : 'AKSRT';
|
||||
return $show === 'title' ? 'Jio' : 'Jio';
|
||||
}
|
||||
|
||||
function get_option($key, $default = false) {
|
||||
return array_key_exists($key, $GLOBALS['aksrt_test_options']) ? $GLOBALS['aksrt_test_options'][$key] : $default;
|
||||
return array_key_exists($key, $GLOBALS['jio_test_options']) ? $GLOBALS['jio_test_options'][$key] : $default;
|
||||
}
|
||||
|
||||
function get_query_var(string $key) {
|
||||
if ($key === 'aksrt_message_view' && $GLOBALS['aksrt_visibility_query'] === 'message') {
|
||||
if ($key === 'jio_message_view' && $GLOBALS['jio_visibility_query'] === 'message') {
|
||||
return 'unread';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function update_option($key, $value): bool {
|
||||
$GLOBALS['aksrt_test_options'][$key] = $value;
|
||||
$GLOBALS['jio_test_options'][$key] = $value;
|
||||
return true;
|
||||
}
|
||||
|
||||
function current_user_can($capability): bool {
|
||||
return $capability === 'manage_options' && !empty($GLOBALS['aksrt_test_manage_options']);
|
||||
return $capability === 'manage_options' && !empty($GLOBALS['jio_test_manage_options']);
|
||||
}
|
||||
|
||||
function is_admin(): bool { return false; }
|
||||
@@ -98,37 +98,37 @@ namespace {
|
||||
function is_home(): bool {
|
||||
// Custom rewrites can leave WordPress's posts-page flag set even though
|
||||
// the theme resolves the request to another front-end surface.
|
||||
return in_array($GLOBALS['aksrt_visibility_query'], [
|
||||
return in_array($GLOBALS['jio_visibility_query'], [
|
||||
'home', 'page', 'message', 'user_home', 'user_center', 'user_home_page', 'user_center_page',
|
||||
], true);
|
||||
}
|
||||
function is_front_page(): bool { return $GLOBALS['aksrt_visibility_query'] === 'home'; }
|
||||
function is_category(): bool { return $GLOBALS['aksrt_visibility_query'] === 'category'; }
|
||||
function is_tag(): bool { return $GLOBALS['aksrt_visibility_query'] === 'tag'; }
|
||||
function is_author(): bool { return $GLOBALS['aksrt_visibility_query'] === 'author'; }
|
||||
function is_singular(string $type = ''): bool { return $type === 'post' && $GLOBALS['aksrt_visibility_query'] === 'single'; }
|
||||
function is_search(): bool { return $GLOBALS['aksrt_visibility_query'] === 'search'; }
|
||||
function is_front_page(): bool { return $GLOBALS['jio_visibility_query'] === 'home'; }
|
||||
function is_category(): bool { return $GLOBALS['jio_visibility_query'] === 'category'; }
|
||||
function is_tag(): bool { return $GLOBALS['jio_visibility_query'] === 'tag'; }
|
||||
function is_author(): bool { return $GLOBALS['jio_visibility_query'] === 'author'; }
|
||||
function is_singular(string $type = ''): bool { return $type === 'post' && $GLOBALS['jio_visibility_query'] === 'single'; }
|
||||
function is_search(): bool { return $GLOBALS['jio_visibility_query'] === 'search'; }
|
||||
function is_page(): bool {
|
||||
return in_array($GLOBALS['aksrt_visibility_query'], ['page', 'user_home_page', 'user_center_page'], true);
|
||||
return in_array($GLOBALS['jio_visibility_query'], ['page', 'user_home_page', 'user_center_page'], true);
|
||||
}
|
||||
function is_page_template(string $template): bool { return false; }
|
||||
function get_queried_object_id(): int { return 42; }
|
||||
|
||||
/** @return mixed */
|
||||
function get_post_meta(int $post_id, string $key, bool $single = false) {
|
||||
return $GLOBALS['aksrt_visibility_page_config'];
|
||||
return $GLOBALS['jio_visibility_page_config'];
|
||||
}
|
||||
|
||||
function aksrt_is_user_home_page(): bool {
|
||||
return in_array($GLOBALS['aksrt_visibility_query'], ['user_home', 'user_home_page'], true);
|
||||
function jio_is_user_home_page(): bool {
|
||||
return in_array($GLOBALS['jio_visibility_query'], ['user_home', 'user_home_page'], true);
|
||||
}
|
||||
|
||||
function aksrt_is_user_center_page(): bool {
|
||||
return in_array($GLOBALS['aksrt_visibility_query'], ['user_center', 'user_center_page'], true);
|
||||
function jio_is_user_center_page(): bool {
|
||||
return in_array($GLOBALS['jio_visibility_query'], ['user_center', 'user_center_page'], true);
|
||||
}
|
||||
}
|
||||
|
||||
namespace AKSRT\Settings {
|
||||
namespace Jio\Settings {
|
||||
class Settings {
|
||||
/** @var array<string,mixed> */
|
||||
public static array $headerSlider = [];
|
||||
@@ -154,13 +154,13 @@ namespace {
|
||||
require_once dirname(__DIR__, 2) . '/src/Admin/ControllerBase.php';
|
||||
require_once dirname(__DIR__, 2) . '/inc/helpers/homepage/header-components.php';
|
||||
|
||||
$controller = new class extends \AKSRT\Admin\ControllerBase {
|
||||
$controller = new class extends \Jio\Admin\ControllerBase {
|
||||
public function register_routes(): void {}
|
||||
};
|
||||
$provider = \AKSRT\Admin\SchemaProvider::get_instance();
|
||||
$provider = \Jio\Admin\SchemaProvider::get_instance();
|
||||
|
||||
$permission_denied = !$controller->check_permission();
|
||||
$GLOBALS['aksrt_test_manage_options'] = true;
|
||||
$GLOBALS['jio_test_manage_options'] = true;
|
||||
$permission_allowed = $controller->check_permission();
|
||||
$unknown_section = $provider->get_section_schema('page_display', 'missing') === null;
|
||||
$homepage_defaults = $provider->get_defaults('page_display', 'homepage');
|
||||
@@ -220,15 +220,15 @@ namespace {
|
||||
* @param list<string> $show_on
|
||||
* @param array<string,mixed> $page_config
|
||||
*/
|
||||
function aksrt_visibility_case(string $query, array $show_on, array $page_config = [], int $paged = 0): bool {
|
||||
$GLOBALS['aksrt_visibility_query'] = $query;
|
||||
$GLOBALS['aksrt_visibility_page_config'] = $page_config;
|
||||
function jio_visibility_case(string $query, array $show_on, array $page_config = [], int $paged = 0): bool {
|
||||
$GLOBALS['jio_visibility_query'] = $query;
|
||||
$GLOBALS['jio_visibility_page_config'] = $page_config;
|
||||
$GLOBALS['wp_query'] = new WP_Query(['paged' => $paged]);
|
||||
\AKSRT\Settings\Settings::$headerSlider = [
|
||||
\Jio\Settings\Settings::$headerSlider = [
|
||||
'show_on_pages' => $show_on,
|
||||
'show_type' => '',
|
||||
];
|
||||
return aksrt_header_slide_is_show();
|
||||
return jio_header_slide_is_show();
|
||||
}
|
||||
|
||||
$enabled_page_override = [
|
||||
@@ -236,22 +236,22 @@ namespace {
|
||||
'header_slider_show_type' => '',
|
||||
];
|
||||
$visibility = [
|
||||
'disabled' => aksrt_visibility_case('home', []),
|
||||
'disabled_with_page_override' => aksrt_visibility_case('page', [], $enabled_page_override),
|
||||
'page_enabled' => aksrt_visibility_case('page', ['page'], $enabled_page_override),
|
||||
'public_user_home' => aksrt_visibility_case('user_home', ['user_home']),
|
||||
'user_center' => aksrt_visibility_case('user_center', ['user_center']),
|
||||
'legacy_author_public_home' => aksrt_visibility_case('user_home', ['author']),
|
||||
'generic_page_excludes_home' => aksrt_visibility_case('user_home_page', ['page']),
|
||||
'generic_page_excludes_center' => aksrt_visibility_case('user_center_page', ['page']),
|
||||
'templated_public_user_home' => aksrt_visibility_case('user_home_page', ['user_home']),
|
||||
'templated_user_center' => aksrt_visibility_case('user_center_page', ['user_center']),
|
||||
'home_excludes_page' => aksrt_visibility_case('page', ['home']),
|
||||
'message_uses_other_page' => aksrt_visibility_case('message', ['page']),
|
||||
'message_excludes_home' => aksrt_visibility_case('message', ['home']),
|
||||
'home_excludes_public_home' => aksrt_visibility_case('user_home', ['home']),
|
||||
'home_excludes_user_center' => aksrt_visibility_case('user_center', ['home']),
|
||||
'paged_archive_hidden' => aksrt_visibility_case('home', ['home'], [], 2),
|
||||
'disabled' => jio_visibility_case('home', []),
|
||||
'disabled_with_page_override' => jio_visibility_case('page', [], $enabled_page_override),
|
||||
'page_enabled' => jio_visibility_case('page', ['page'], $enabled_page_override),
|
||||
'public_user_home' => jio_visibility_case('user_home', ['user_home']),
|
||||
'user_center' => jio_visibility_case('user_center', ['user_center']),
|
||||
'legacy_author_public_home' => jio_visibility_case('user_home', ['author']),
|
||||
'generic_page_excludes_home' => jio_visibility_case('user_home_page', ['page']),
|
||||
'generic_page_excludes_center' => jio_visibility_case('user_center_page', ['page']),
|
||||
'templated_public_user_home' => jio_visibility_case('user_home_page', ['user_home']),
|
||||
'templated_user_center' => jio_visibility_case('user_center_page', ['user_center']),
|
||||
'home_excludes_page' => jio_visibility_case('page', ['home']),
|
||||
'message_uses_other_page' => jio_visibility_case('message', ['page']),
|
||||
'message_excludes_home' => jio_visibility_case('message', ['home']),
|
||||
'home_excludes_public_home' => jio_visibility_case('user_home', ['home']),
|
||||
'home_excludes_user_center' => jio_visibility_case('user_center', ['home']),
|
||||
'paged_archive_hidden' => jio_visibility_case('home', ['home'], [], 2),
|
||||
];
|
||||
|
||||
echo json_encode([
|
||||
|
||||
@@ -9,10 +9,10 @@ const root = path.resolve(__dirname, '..', '..');
|
||||
const read = (file) => fs.readFileSync(path.join(root, file), 'utf8');
|
||||
|
||||
test('paid article and sidebar widget use the Zibll purchase-card DOM', () => {
|
||||
const pay = read('inc/helpers/aksrt-pay.php');
|
||||
const pay = read('inc/helpers/jio-pay.php');
|
||||
const widget = read('src/WidgetSystem/Renderers/ContentRenderer.php');
|
||||
const shell = read('src/WidgetSystem/FrontendRenderer.php');
|
||||
const single = read('inc/helpers/aksrt-single-render.php');
|
||||
const single = read('inc/helpers/jio-single-render.php');
|
||||
const hooks = read('inc/core-hooks.php');
|
||||
|
||||
assert.match(pay, /zib-widget pay-box pay-widget/);
|
||||
@@ -22,8 +22,8 @@ test('paid article and sidebar widget use the Zibll purchase-card DOM', () => {
|
||||
assert.match(pay, /pay-tag abs-center/);
|
||||
assert.match(pay, /price-box/);
|
||||
assert.match(pay, /vip-price-buts but-average inline/);
|
||||
assert.match(pay, /function aksrt_get_show_pay_price_html/);
|
||||
assert.match(pay, /function aksrt_get_posts_vip_price/);
|
||||
assert.match(pay, /function jio_get_show_pay_price_html/);
|
||||
assert.match(pay, /function jio_get_posts_vip_price/);
|
||||
assert.match(pay, /get_payment_vip_settings/);
|
||||
assert.match(pay, /\['img_icon'\]/);
|
||||
assert.match(pay, /signin-loader/);
|
||||
@@ -35,9 +35,9 @@ test('paid article and sidebar widget use the Zibll purchase-card DOM', () => {
|
||||
assert.doesNotMatch(pay, /\$vip_minor > 0/);
|
||||
assert.doesNotMatch(pay, /\$vip_items/);
|
||||
assert.doesNotMatch(pay, /wp_login_url\(get_permalink\(\$post\)\)/);
|
||||
assert.doesNotMatch(pay, /aksrt_get_user_center_tab_url\('vip'\)/);
|
||||
assert.doesNotMatch(pay, /jio_get_user_center_tab_url\('vip'\)/);
|
||||
assert.match(pay, /cashier-link/);
|
||||
assert.match(pay, /function aksrt_get_paid_box_html/);
|
||||
assert.match(pay, /function jio_get_paid_box_html/);
|
||||
assert.match(pay, /pay-box zib-widget paid-box order-type-/);
|
||||
assert.match(pay, /flex ac jb-green padding-10 em09/);
|
||||
assert.match(pay, /订单号/);
|
||||
@@ -47,14 +47,14 @@ test('paid article and sidebar widget use the Zibll purchase-card DOM', () => {
|
||||
assert.match(pay, /hidden-box show/);
|
||||
assert.match(widget, /'layout' => 'widget'/);
|
||||
assert.match(shell, /'post_pay', 'oneline_posts', 'hot_posts'/);
|
||||
assert.doesNotMatch(single, /aksrt-panel pay-box pay-type-download/);
|
||||
assert.doesNotMatch(single, /jio-panel pay-box pay-type-download/);
|
||||
assert.doesNotMatch(single, /get_template_part\('templates\/parts\/resource-download-box'\)/);
|
||||
assert.match(single, /aksrt_get_paid_box_html\(\$post\)/);
|
||||
assert.doesNotMatch(hooks, /aksrt_paid_content_enhance/);
|
||||
assert.match(single, /jio_get_paid_box_html\(\$post\)/);
|
||||
assert.doesNotMatch(hooks, /jio_paid_content_enhance/);
|
||||
});
|
||||
|
||||
test('the purchase card owns one direct stylesheet and no retired paywall path', () => {
|
||||
const pay = read('inc/helpers/aksrt-pay.php');
|
||||
const pay = read('inc/helpers/jio-pay.php');
|
||||
const shortcodes = read('src/Core/Shortcodes.php');
|
||||
const assets = read('src/Core/ScriptsPart1b.php') + read('src/WidgetSystem/AssetManager.php');
|
||||
const hidden = read('assets/css/frontend/single-post/hidden-content.css');
|
||||
@@ -64,7 +64,7 @@ test('the purchase card owns one direct stylesheet and no retired paywall path',
|
||||
const download = read('templates/parts/resource-download-box.php');
|
||||
|
||||
assert.match(assets, /assets\/css\/frontend\/pay-box\.css|\'pay-box\'/);
|
||||
assert.match(shortcodes, /return aksrt_get_paywall_html\(\$post, \['message' => \$message\]\);/);
|
||||
assert.match(shortcodes, /return jio_get_paywall_html\(\$post, \['message' => \$message\]\);/);
|
||||
assert.match(css, /\.zib-widget\.pay-box/);
|
||||
assert.match(css, /\.pay-box \.pay-vip-level-icon/);
|
||||
assert.match(css, /\.pay-box \.pay-vip-exclusive/);
|
||||
@@ -73,12 +73,12 @@ test('the purchase card owns one direct stylesheet and no retired paywall path',
|
||||
assert.match(publicCss, /\.pay-flexbox\s*\{\s*flex-wrap:\s*wrap;/);
|
||||
|
||||
const retired = pay + hidden + widgets + shortcodes;
|
||||
assert.doesNotMatch(retired, /aksrt-paywall__(?:card|icon|body|title|desc|price|actions|btn|hint|login-link)/);
|
||||
assert.doesNotMatch(retired, /aksrt-post-pay/);
|
||||
assert.doesNotMatch(retired, /str_replace\([\s\S]*aksrt-paywall__desc/);
|
||||
assert.doesNotMatch(pay, /aksrt-paid-(?:gallery|video)__(?:locked|preview|overlay|placeholder|price)/);
|
||||
assert.doesNotMatch(retired, /jio-paywall__(?:card|icon|body|title|desc|price|actions|btn|hint|login-link)/);
|
||||
assert.doesNotMatch(retired, /jio-post-pay/);
|
||||
assert.doesNotMatch(retired, /str_replace\([\s\S]*jio-paywall__desc/);
|
||||
assert.doesNotMatch(pay, /jio-paid-(?:gallery|video)__(?:locked|preview|overlay|placeholder|price)/);
|
||||
assert.doesNotMatch(pay + download, /安全检测|支持 VIP 权益|支持积分结算/);
|
||||
assert.doesNotMatch(download, /function_exists\('aksrt_get_pay_service_html'\)/);
|
||||
assert.doesNotMatch(download, /function_exists\('jio_get_pay_service_html'\)/);
|
||||
});
|
||||
|
||||
test('the article cashier uses the reference modal contract and one direct asset path', () => {
|
||||
@@ -92,18 +92,18 @@ test('the article cashier uses the reference modal contract and one direct asset
|
||||
assert.match(cashier, /muted-box padding-h6 line-16/);
|
||||
assert.match(cashier, /payment-method-radio hollow-radio flex-auto pointer/);
|
||||
assert.match(cashier, /mt6 but jb-red initiate-pay btn-block radius/);
|
||||
assert.match(cashier, /data-aksrt-payment-method/);
|
||||
assert.match(cashier, /data-jio-payment-method/);
|
||||
assert.match(cashier, /pay-(?:balance|alipay|wechat|paypal|stripe)-logo\.svg/);
|
||||
assert.doesNotMatch(cashier, /document\.createElement\(['"]style['"]\)/);
|
||||
assert.doesNotMatch(cashier, /apiKind|_api_kind|payApiBase/);
|
||||
assert.doesNotMatch(cashier, /\/(?:shop|forum|bbs)\//i);
|
||||
|
||||
assert.match(css, /\.aksrt-dl-modal \.modal-colorful-header/);
|
||||
assert.match(css, /\.aksrt-dl-modal \.payment-method-radio/);
|
||||
assert.match(css, /\.aksrt-dl-modal \.but\.initiate-pay/);
|
||||
assert.match(css, /\.aksrt-dl-modal\.is-cashier \.aksrt-dl-modal__body\s*\{[^}]*padding-top:\s*120px;/s);
|
||||
assert.match(css, /\.aksrt-dl-modal \.modal-pay-body\s*\{[^}]*margin-top:\s*0;/s);
|
||||
assert.doesNotMatch(css, /\.aksrt-dl-modal \.modal-pay-body\s*\{[^}]*margin-top:\s*120px;/s);
|
||||
assert.match(css, /\.jio-dl-modal \.modal-colorful-header/);
|
||||
assert.match(css, /\.jio-dl-modal \.payment-method-radio/);
|
||||
assert.match(css, /\.jio-dl-modal \.but\.initiate-pay/);
|
||||
assert.match(css, /\.jio-dl-modal\.is-cashier \.jio-dl-modal__body\s*\{[^}]*padding-top:\s*120px;/s);
|
||||
assert.match(css, /\.jio-dl-modal \.modal-pay-body\s*\{[^}]*margin-top:\s*0;/s);
|
||||
assert.doesNotMatch(css, /\.jio-dl-modal \.modal-pay-body\s*\{[^}]*margin-top:\s*120px;/s);
|
||||
assert.match(assets, /assets\/css\/frontend\/cms-download\.css/);
|
||||
assert.match(assets, /filemtime\(\$cms_download_css_path\)/);
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*
|
||||
* 背景:后台 收款接口 → 收款接口显示顺序 曾整列空白、保存后顺序被静默改写,
|
||||
* 且没有任何前台代码消费该配置。本契约锁定四件事:
|
||||
* 1) sortable 项使用 AksField 实际读取的 `label` 键(写 `title` 会渲染成空白);
|
||||
* 1) sortable 项使用 JioField 实际读取的 `label` 键(写 `title` 会渲染成空白);
|
||||
* 2) 持久化必须保留用户提交的键顺序(键顺序即配置值本身);
|
||||
* 3) 排序只保留一份实现,并且真的作用到收银台;
|
||||
* 4) 相关的重复实现 / 死模块不回潮。
|
||||
@@ -18,9 +18,9 @@ const read = (...seg) => fs.readFileSync(path.join(root, ...seg), 'utf8');
|
||||
|
||||
const schemaPhp = read('src', 'Admin', 'Schema', 'SchemaPayment.php');
|
||||
const schemaJs = read('assets', 'admin', 'src', 'schema', 'sections', 'payment', 'gateway.js');
|
||||
const aksField = read('assets', 'admin', 'src', 'components', 'form', 'AksField.vue');
|
||||
const aksField = read('assets', 'admin', 'src', 'components', 'form', 'JioField.vue');
|
||||
const provider = read('src', 'Admin', 'SchemaProvider.php');
|
||||
const pay = read('inc', 'helpers', 'aksrt-pay.php');
|
||||
const pay = read('inc', 'helpers', 'jio-pay.php');
|
||||
const gateway = read('src', 'Payment', 'Gateway.php');
|
||||
const wallet = read('inc', 'helpers', 'user-center-renderers', 'credits-wallet.php');
|
||||
const pointsRenderer = read('inc', 'helpers', 'user-center-renderers', 'credits-balance.php');
|
||||
@@ -61,8 +61,8 @@ test('sortable 持久化保留提交键顺序', () => {
|
||||
});
|
||||
|
||||
test('排序只有一份实现,无调用方的重复实现已移除', () => {
|
||||
assert.match(pay, /if \(!function_exists\('aksrt_sort_payment_methods'\)\)/);
|
||||
assert.match(pay, /function aksrt_sort_payment_methods\(array \$methods\): array/);
|
||||
assert.match(pay, /if \(!function_exists\('jio_sort_payment_methods'\)\)/);
|
||||
assert.match(pay, /function jio_sort_payment_methods\(array \$methods\): array/);
|
||||
assert.match(pay, /\$configured = is_array\(\$settings\) \? \(\$settings\['pay_sdk_order'\] \?\? \[\]\) : \[\];/);
|
||||
// 槽位映射与后台 sortable 项一一对应,易支付子通道随所属渠道槽位。
|
||||
assert.match(pay, /'wechat'\s+=> \['wechatpay', 'epay_wxpay'\]/);
|
||||
@@ -77,19 +77,19 @@ test('排序只有一份实现,无调用方的重复实现已移除', () => {
|
||||
|
||||
test('基础渠道列表与卡密渠道都经过同一排序实现', () => {
|
||||
// 内容收银台 / 会员购买 / 余额充值共用基础列表。
|
||||
assert.match(pay, /return aksrt_sort_payment_methods\(\$methods\);/);
|
||||
assert.match(balanceCharge, /aksrt_sort_payment_methods\(\$methods\)/);
|
||||
assert.match(vipPackages, /aksrt_sort_payment_methods\(\$methods\)/);
|
||||
assert.match(pay, /return jio_sort_payment_methods\(\$methods\);/);
|
||||
assert.match(balanceCharge, /jio_sort_payment_methods\(\$methods\)/);
|
||||
assert.match(vipPackages, /jio_sort_payment_methods\(\$methods\)/);
|
||||
// 购买积分弹窗自建余额 + 卡密列表,同样排序。
|
||||
assert.match(pointsRenderer, /aksrt_sort_payment_methods\(\$points_methods\)/);
|
||||
assert.match(pointsRenderer, /jio_sort_payment_methods\(\$points_methods\)/);
|
||||
// 卡密输入盒是 method 条件块,位置无关:卡密渠道可被移到任意槽位。
|
||||
assert.match(wallet, /in_array\('card_pass', array_column\(\$methods, 'id'\), true\)/);
|
||||
});
|
||||
|
||||
test('会员弹窗不再保留重复的支付方式列表回退分支', () => {
|
||||
assert.doesNotMatch(vipPackages, /class_exists\('\\\\AKSRT\\\\Payment\\\\Gateway'\)/);
|
||||
assert.doesNotMatch(vipPackages, /class_exists\('\\\\Jio\\\\Payment\\\\Gateway'\)/);
|
||||
// 卡密渠道注入契约保持(对齐参考主题 zibpay_get_payment_methods)。
|
||||
assert.match(vipPackages, /'id' => 'card_pass', 'name' => __\('卡密', 'aksrt'\)/);
|
||||
assert.match(vipPackages, /'id' => 'card_pass', 'name' => __\('卡密', 'jio'\)/);
|
||||
});
|
||||
|
||||
test('未被引用的后台 schema 默认值死模块不回潮', () => {
|
||||
|
||||
@@ -48,7 +48,7 @@ test('front-end remove buttons discard the pending upload', () => {
|
||||
const core = read('assets/js/ticket-center-core.js');
|
||||
const report = read('assets/js/report.js');
|
||||
assert.match(core, /'\/upload\/pending\/' \+ id/);
|
||||
assert.match(core, /data-aksrt-ticket-image-id/);
|
||||
assert.match(core, /data-jio-ticket-image-id/);
|
||||
assert.match(report, /'\/upload\/pending\/'/);
|
||||
});
|
||||
|
||||
@@ -65,11 +65,11 @@ test('front submission uploads are marked and saved references are claimed witho
|
||||
|
||||
test('cleanup is scheduled once hourly and discard requires a logged-in user', () => {
|
||||
const state = harness().registration;
|
||||
assert.ok(state.hooks.includes('aksrt_pending_upload_cleanup'));
|
||||
assert.ok(state.hooks.includes('jio_pending_upload_cleanup'));
|
||||
assert.ok(state.hooks.includes('init'));
|
||||
assert.equal(state.schedule_count, 1);
|
||||
assert.equal(state.recurrence, 'hourly');
|
||||
assert.equal(state.namespace, 'aksrt/v1');
|
||||
assert.equal(state.namespace, 'jio/v1');
|
||||
assert.equal(state.method, 'DELETE');
|
||||
assert.equal(state.member_allowed, true);
|
||||
assert.equal(state.guest_allowed, false);
|
||||
@@ -95,7 +95,7 @@ test('cleanup deletes only expired unattached pending uploads and uses prepared
|
||||
assert.match(state.query.sql, /p\.post_type = 'attachment' AND p\.post_parent = 0/);
|
||||
assert.match(state.query.sql, /m\.meta_key = %s/);
|
||||
assert.match(state.query.sql, /LIMIT %d/);
|
||||
assert.equal(state.query.params[0], '_aksrt_pending_upload');
|
||||
assert.equal(state.query.params[0], '_jio_pending_upload');
|
||||
assert.equal(state.query.params[2], 100);
|
||||
assert.ok(Math.abs(state.query.params[1] - (Math.floor(Date.now() / 1000) - 86400)) < 10);
|
||||
});
|
||||
|
||||
@@ -81,7 +81,7 @@ final class PendingWpdb {
|
||||
}
|
||||
|
||||
require dirname(__DIR__, 2) . '/src/Attachment/PendingUploads.php';
|
||||
use AKSRT\Attachment\PendingUploads;
|
||||
use Jio\Attachment\PendingUploads;
|
||||
|
||||
function reset_fixture(): void {
|
||||
$GLOBALS['posts'] = [];
|
||||
|
||||
@@ -48,12 +48,12 @@ test('front submit follows the reference newposts shell and cards', () => {
|
||||
assert.match(page, /<\/div>\s*<\/div>\s*<div class="sidebar show-sidebar">/);
|
||||
assert.match(layout, /if \(is_page_template\('templates\/pages\/page-submit\.php'\)\) \{\s*return true;/);
|
||||
assert.equal((page.match(/class="sidebar show-sidebar"/g) || []).length, 1);
|
||||
assert.match(page, /'aksrt-submit',[\s\S]*?\['aksrt-public'\]/);
|
||||
assert.match(page, /'jio-submit',[\s\S]*?\['jio-public'\]/);
|
||||
assert.match(main, /zib-widget full-widget-sm editor-main-box/);
|
||||
assert.match(main, /relative newposts-title/);
|
||||
assert.match(terms, /newpost-cat-drop drop-select dropdown dependency-box/);
|
||||
assert.match(terms, /data-toggle-class="open" data-target="\.newpost-cat-drop"/);
|
||||
assert.match(terms, /aksrt_submit_render_term_checks\(\$ctx\['categories'\], array_map\('intval', \$ctx\['current_cats'\]\), 'category'\)/);
|
||||
assert.match(terms, /jio_submit_render_term_checks\(\$ctx\['categories'\], array_map\('intval', \$ctx\['current_cats'\]\), 'category'\)/);
|
||||
assert.doesNotMatch(terms, /aria-expanded=/);
|
||||
assert.doesNotMatch(submitJs, /bindDropSelect|term-select-box \[data-toggle-class/);
|
||||
assert.doesNotMatch(submitCss, /\.term-select-box \.dropdown-menu|\.term-select-box \.drop-select\s*{/);
|
||||
@@ -66,17 +66,17 @@ test('front submit follows the reference newposts shell and cards', () => {
|
||||
assert.equal((submit.match(/fa-fw/g) || []).length, 2);
|
||||
assert.doesNotMatch(submit, /fa-(?:dot-circle|check-square) mr6/);
|
||||
assert.match(publicCss, /\.but-average\s*>\s*\.but\s*{[^}]*flex:\s*auto/);
|
||||
assert.match(submitCss, /#aksrt-submit-form \.new-posts-submit\s*{[^}]*white-space:\s*nowrap;/);
|
||||
assert.match(submitCss, /#aksrt-submit-form \.but-average > \.new-posts-submit\s*{[^}]*min-width:\s*0;[^}]*flex:\s*1 1 50%;/);
|
||||
assert.match(submitCss, /#jio-submit-form \.new-posts-submit\s*{[^}]*white-space:\s*nowrap;/);
|
||||
assert.match(submitCss, /#jio-submit-form \.but-average > \.new-posts-submit\s*{[^}]*min-width:\s*0;[^}]*flex:\s*1 1 50%;/);
|
||||
assert.match(mobileSubmitCss, /body \.newposts-wrap\s*{[^}]*float:\s*none;/);
|
||||
assert.match(mobileSubmitCss, /body \.sidebar\.show-sidebar\s*{[^}]*display:\s*unset;[^}]*width:\s*100%;[^}]*margin-left:\s*0;/);
|
||||
assert.doesNotMatch(submitCss, /\.newposts-wrap\s*>\s*\.(?:content-layout|sidebar)/);
|
||||
assert.doesNotMatch(submitCss, /\.newposts-wrap \.new-posts-submit/);
|
||||
// The form wraps both float columns; flow-root is its only layout job.
|
||||
assert.match(submitCss, /#aksrt-submit-form\s*{[^}]*display:\s*flow-root;/);
|
||||
assert.doesNotMatch(submitCss, /#aksrt-submit-form\s*{[^}]*display:\s*(?:grid|flex);/);
|
||||
assert.match(submitCss, /#jio-submit-form\s*{[^}]*display:\s*flow-root;/);
|
||||
assert.doesNotMatch(submitCss, /#jio-submit-form\s*{[^}]*display:\s*(?:grid|flex);/);
|
||||
// Submit chrome gating lives in footer.php; no dead filter/body-class duplicates.
|
||||
assert.doesNotMatch(read('src/Core/FrontSubmit.php'), /aksrt_floating_buttons_enabled|aksrt_footer_tabbar_enabled|aksrt-hide-float|aksrt-hide-tabbar|disable_chrome_on_submit/);
|
||||
assert.doesNotMatch(read('src/Core/FrontSubmit.php'), /jio_floating_buttons_enabled|jio_footer_tabbar_enabled|jio-hide-float|jio-hide-tabbar|disable_chrome_on_submit/);
|
||||
});
|
||||
|
||||
test('front submit paid resources use the reference modal editor and compact list', () => {
|
||||
@@ -86,8 +86,8 @@ test('front submit paid resources use the reference modal editor and compact lis
|
||||
const downloads = read('assets/js/front-submit-pay-download.js');
|
||||
const model = read('src/Core/DomainModel.php');
|
||||
|
||||
assert.match(page, /aksrt-front-submit-pay-download/);
|
||||
assert.match(submit, /aksrtSubmitPayDownloads\.init/);
|
||||
assert.match(page, /jio-front-submit-pay-download/);
|
||||
assert.match(submit, /jioSubmitPayDownloads\.init/);
|
||||
assert.match(pay, /data-pay-download-add/);
|
||||
assert.match(pay, /data-pay-download-edit/);
|
||||
assert.match(pay, /data-pay-download-remove/);
|
||||
@@ -126,7 +126,7 @@ test('the post editor resource row mirrors the reference seven fields with icon
|
||||
// Accordion + drag reorder affordances.
|
||||
assert.match(meta, /data-group-toggle/);
|
||||
assert.match(meta, /data-group-drag/);
|
||||
assert.match(meta, /aksrt-icon-picker_assets\(|aksrt_icon_picker_field\(/);
|
||||
assert.match(meta, /jio-icon-picker_assets\(|jio_icon_picker_field\(/);
|
||||
// The swatch palette and the sanitizer share one class whitelist.
|
||||
assert.match(meta, /DomainModel::DOWNLOAD_BUTTON_CLASSES/);
|
||||
assert.match(model, /const DOWNLOAD_BUTTON_CLASSES/);
|
||||
@@ -139,24 +139,24 @@ test('the post editor resource row mirrors the reference seven fields with icon
|
||||
// Reindexing rewrites the row index for every field and is scoped to its own
|
||||
// group, so dragging a download row can never renumber attributes/episodes.
|
||||
const reindex = (group, name, index) =>
|
||||
name.replace(new RegExp(`aksrt_pay\\[${group}\\]\\[\\d+\\]`), `aksrt_pay[${group}][${index}]`);
|
||||
name.replace(new RegExp(`jio_pay\\[${group}\\]\\[\\d+\\]`), `jio_pay[${group}][${index}]`);
|
||||
['url', 'label', 'extract_code', 'copy_key', 'copy_val', 'icon', 'class'].forEach((field) => {
|
||||
assert.equal(reindex('downloads', `aksrt_pay[downloads][0][${field}]`, 3), `aksrt_pay[downloads][3][${field}]`);
|
||||
assert.equal(reindex('downloads', `jio_pay[downloads][0][${field}]`, 3), `jio_pay[downloads][3][${field}]`);
|
||||
});
|
||||
assert.equal(reindex('downloads', 'aksrt_pay[downloads][5][url]', 1), 'aksrt_pay[downloads][1][url]');
|
||||
assert.equal(reindex('downloads', 'aksrt_pay[attributes][0][key]', 9), 'aksrt_pay[attributes][0][key]');
|
||||
assert.equal(reindex('downloads', 'aksrt_pay[video_episode][1][title]', 9), 'aksrt_pay[video_episode][1][title]');
|
||||
assert.equal(reindex('downloads', 'jio_pay[downloads][5][url]', 1), 'jio_pay[downloads][1][url]');
|
||||
assert.equal(reindex('downloads', 'jio_pay[attributes][0][key]', 9), 'jio_pay[attributes][0][key]');
|
||||
assert.equal(reindex('downloads', 'jio_pay[video_episode][1][title]', 9), 'jio_pay[video_episode][1][title]');
|
||||
|
||||
assert.match(setup, /'icon' => \['type' => 'string'\]/);
|
||||
|
||||
// The icon picker registers itself as depending on the caller's handle, so the
|
||||
// pay-meta script must NOT declare the icon-picker handle back: that cycle makes
|
||||
// WP_Dependencies recurse until memory is exhausted on every post editor load.
|
||||
const picker = read('inc/helpers/aksrt-icon-picker.php');
|
||||
assert.match(picker, /array_merge\(\['jquery', 'aksrt-svg-sprite'\], \$deps\)/);
|
||||
assert.match(meta, /aksrt_icon_picker_assets\(\['aksrt-pay-meta-box'\]\)/);
|
||||
const payScript = meta.match(/wp_enqueue_script\('aksrt-pay-meta-box',[^\n]*\)/)[0];
|
||||
assert.doesNotMatch(payScript, /aksrt-icon-picker/);
|
||||
const picker = read('inc/helpers/jio-icon-picker.php');
|
||||
assert.match(picker, /array_merge\(\['jquery', 'jio-svg-sprite'\], \$deps\)/);
|
||||
assert.match(meta, /jio_icon_picker_assets\(\['jio-pay-meta-box'\]\)/);
|
||||
const payScript = meta.match(/wp_enqueue_script\('jio-pay-meta-box',[^\n]*\)/)[0];
|
||||
assert.doesNotMatch(payScript, /jio-icon-picker/);
|
||||
});
|
||||
|
||||
test('front submit paid-content prices and switch use the reference alignment rules', () => {
|
||||
@@ -175,18 +175,18 @@ test('front submit image and visibility actions live in the TinyMCE toolbar', ()
|
||||
const submit = read('assets/js/front-submit.js');
|
||||
const editor = read('assets/js/front-submit-editor.js');
|
||||
|
||||
assert.match(main, /\$toolbar1\[\]\s*=\s*'aksrt_image'/);
|
||||
assert.match(main, /\$toolbar1\[\]\s*=\s*'aksrt_hide'/);
|
||||
assert.match(main, /\$toolbar1\[\]\s*=\s*'jio_image'/);
|
||||
assert.match(main, /\$toolbar1\[\]\s*=\s*'jio_hide'/);
|
||||
assert.match(main, /mce_external_plugins/);
|
||||
assert.match(main, /aksrt_editor_tools/);
|
||||
assert.match(editor, /PluginManager\.add\('aksrt_editor_tools'/);
|
||||
assert.match(editor, /addButton\('aksrt_image'/);
|
||||
assert.match(editor, /addButton\('aksrt_hide'/);
|
||||
assert.match(main, /jio_editor_tools/);
|
||||
assert.match(editor, /PluginManager\.add\('jio_editor_tools'/);
|
||||
assert.match(editor, /addButton\('jio_image'/);
|
||||
assert.match(editor, /addButton\('jio_hide'/);
|
||||
assert.match(editor, /type:\s*'menubutton'/);
|
||||
assert.match(editor, /data-editor-media-tab="upload"/);
|
||||
assert.match(editor, /data-editor-media-tab="external"/);
|
||||
assert.match(editor, /class="aksrt-editor-media__dialog" tabindex="-1"/);
|
||||
assert.match(editor, /querySelector\('\.aksrt-editor-media__dialog'\)/);
|
||||
assert.match(editor, /class="jio-editor-media__dialog" tabindex="-1"/);
|
||||
assert.match(editor, /querySelector\('\.jio-editor-media__dialog'\)/);
|
||||
assert.doesNotMatch(editor, /focusTarget\s*=\s*root\.querySelector\('\[data-editor-media-tab\]\.is-active'\)/);
|
||||
assert.match(editor, /\(cfg\.uploads \|\| \{\}\)\.image/);
|
||||
assert.match(page, /'canUploadImage'\s*=>\s*!empty\(\$ctx\['can_upload_img'\]\)/);
|
||||
@@ -202,12 +202,12 @@ test('front submit exposes the complete reference media and formatting toolset',
|
||||
const editorCss = read('assets/css/frontend/pages/submit-editor.css');
|
||||
const submit = read('src/Core/FrontSubmit.php');
|
||||
|
||||
['aksrt_video', 'aksrt_file', 'aksrt_quote', 'aksrt_precode'].forEach((button) => {
|
||||
['jio_video', 'jio_file', 'jio_quote', 'jio_precode'].forEach((button) => {
|
||||
assert.match(main, new RegExp(`['"]${button}['"]`));
|
||||
assert.match(editor, new RegExp(`addButton\\(['"]${button}['"]`));
|
||||
});
|
||||
assert.doesNotMatch(main, /array_merge\(\$toolbar1, \['blockquote'/);
|
||||
assert.match(main, /\['aksrt_quote', 'aksrt_precode', 'fullscreen', 'wp_adv'\]/);
|
||||
assert.match(main, /\['jio_quote', 'jio_precode', 'fullscreen', 'wp_adv'\]/);
|
||||
assert.match(main, /toolbar2[\s\S]*forecolor/);
|
||||
assert.match(main, /colorpicker,textcolor/);
|
||||
|
||||
@@ -256,13 +256,13 @@ test('article paid-content metabox follows the reference field set without shop
|
||||
'pay_extra_hide',
|
||||
];
|
||||
|
||||
assert.match(meta, /add_meta_box\('aksrt_pay_meta', __\('付费功能'/);
|
||||
assert.match(meta, /add_meta_box\('jio_pay_meta', __\('付费功能'/);
|
||||
fields.forEach((field) => assert.match(meta, new RegExp(field)));
|
||||
|
||||
// video_scale_height 是参考主题轮播组件 widget 的参数,不属于付费视频字段,
|
||||
// 当初误搬到付费 metabox 上且从未被消费,已移除。
|
||||
assert.doesNotMatch(meta, /video_scale_height/);
|
||||
assert.doesNotMatch(meta, /aksrt_pay_(?:stock|purchase_limit|card_keys|free_preview|require_login|require_captcha)/);
|
||||
assert.doesNotMatch(meta, /jio_pay_(?:stock|purchase_limit|card_keys|free_preview|require_login|require_captcha)/);
|
||||
assert.doesNotMatch(meta, /电商增强|parse_json_array/);
|
||||
});
|
||||
|
||||
@@ -279,5 +279,5 @@ test('ordinary article editor chain does not import shop or forum routes', () =>
|
||||
const source = files.map(read).join('\n');
|
||||
|
||||
assert.doesNotMatch(source, /shop_product|forum_post|bbs_/i);
|
||||
assert.doesNotMatch(source, /aksrtpay_s|featured_payload|vip_1_(?:price|points)|vip_2_(?:price|points)/);
|
||||
assert.doesNotMatch(source, /jiopay_s|featured_payload|vip_1_(?:price|points)|vip_2_(?:price|points)/);
|
||||
});
|
||||
@@ -9,7 +9,7 @@ const read = file => fs.readFileSync(path.join(root, file), 'utf8')
|
||||
const list = read('src/Admin/Native/PostListEnhance.php')
|
||||
const fields = read('src/Admin/Native/PostListEditFields.php')
|
||||
const domain = read('src/Core/DomainModel.php')
|
||||
const pay = read('inc/helpers/aksrt-pay.php')
|
||||
const pay = read('inc/helpers/jio-pay.php')
|
||||
const metabox = read('src/Core/PostMetaBox.php')
|
||||
const resourceBox = read('src/Core/ResourceMetaBox.php')
|
||||
|
||||
@@ -19,7 +19,7 @@ test('quick and bulk edit boxes hook the post list with no topics column', () =>
|
||||
assert.match(list, /bulk_edit_custom_box/)
|
||||
assert.match(list, /COLUMN_STATS/)
|
||||
// 代码里不得再出现专题分类法列(类文档块的说明除外,只看 namespace 之后的部分)。
|
||||
const code = list.slice(list.indexOf('namespace AKSRT'))
|
||||
const code = list.slice(list.indexOf('namespace Jio'))
|
||||
assert.doesNotMatch(code, /taxonomy-topics/)
|
||||
assert.doesNotMatch(code, /topics/)
|
||||
})
|
||||
@@ -37,9 +37,9 @@ test('every quick-edit field group is rendered', () => {
|
||||
assert.match(fields, /function pay\(string \$currency\): array/)
|
||||
})
|
||||
|
||||
test('quick-edit inputs are namespaced under aksrt_bulk_edit', () => {
|
||||
// 字段名统一 aksrt_bulk_edit[{id}][{field}],避免与 WP 原生数据冲突。
|
||||
assert.match(list, /aksrt_bulk_edit\[' \. \$id \. '\]\[' \. \$field\['id'\] \. '\]/)
|
||||
test('quick-edit inputs are namespaced under jio_bulk_edit', () => {
|
||||
// 字段名统一 jio_bulk_edit[{id}][{field}],避免与 WP 原生数据冲突。
|
||||
assert.match(list, /jio_bulk_edit\[' \. \$id \. '\]\[' \. \$field\['id'\] \. '\]/)
|
||||
// number 类型带运算子字段。
|
||||
assert.match(list, /\[operation\]/)
|
||||
assert.match(list, /\[val\]/)
|
||||
@@ -63,7 +63,7 @@ test('field-group descriptions are emitted as pre-escaped HTML', () => {
|
||||
assert.doesNotMatch(list, /esc_html\(\$desc\)/)
|
||||
// 两个调用点都必须自带转义/HTML 包裹,不能传裸翻译串。
|
||||
assert.match(list, /'<div class="c-yellow">' \. esc_html__\('批量设置付费参数/)
|
||||
assert.match(list, /'<div class="px12 aksrt-bulk-edit-desc">' \. esc_html__\('让文章实现更加精细化/)
|
||||
assert.match(list, /'<div class="px12 jio-bulk-edit-desc">' \. esc_html__\('让文章实现更加精细化/)
|
||||
// 仍来自设置项的字段标题必须保持转义。
|
||||
assert.match(list, /esc_html\(\$title_html\)/)
|
||||
})
|
||||
@@ -84,7 +84,7 @@ test('interaction quick-edit fields write to the keys the theme reads', () => {
|
||||
assert.match(fields, /'views'/)
|
||||
assert.match(fields, /'like'/)
|
||||
assert.match(metabox, /DomainModel::POST_VIEW_COUNT_KEY, max\(0, \(int\) \(\$raw\['views'\]/)
|
||||
assert.match(metabox, /'_aksrt_like_count', max\(0, \(int\) \(\$raw\['like'\]/)
|
||||
assert.match(metabox, /'_jio_like_count', max\(0, \(int\) \(\$raw\['like'\]/)
|
||||
// 保存侧按这两个键分流到统计 meta。
|
||||
assert.match(list, /stats_meta_key/)
|
||||
})
|
||||
@@ -97,14 +97,14 @@ test('article config quick-edit fields match the extension meta box keys', () =>
|
||||
|
||||
// 布局取值白名单:metabox 的 sanitize 是裸数组,快捷编辑提供同四值的单选。
|
||||
assert.match(metabox, /\['false', 'no_sidebar', 'sidebar_left', 'sidebar_right'\]/)
|
||||
assert.match(fields, /'false'\s+=> __\('跟随主题', 'aksrt'\)/)
|
||||
assert.match(fields, /'no_sidebar'\s+=> __\('无侧边栏', 'aksrt'\)/)
|
||||
assert.match(fields, /'sidebar_left'\s+=> __\('侧边栏靠左', 'aksrt'\)/)
|
||||
assert.match(fields, /'sidebar_right'\s+=> __\('侧边栏靠右', 'aksrt'\)/)
|
||||
assert.match(fields, /'false'\s+=> __\('跟随主题', 'jio'\)/)
|
||||
assert.match(fields, /'no_sidebar'\s+=> __\('无侧边栏', 'jio'\)/)
|
||||
assert.match(fields, /'sidebar_left'\s+=> __\('侧边栏靠左', 'jio'\)/)
|
||||
assert.match(fields, /'sidebar_right'\s+=> __\('侧边栏靠右', 'jio'\)/)
|
||||
// 保存侧再用同一份白名单收敛,避免快捷编辑写入未知布局。
|
||||
assert.match(list, /\['false', 'no_sidebar', 'sidebar_left', 'sidebar_right'\]/)
|
||||
|
||||
// 落到 aksrt_post_meta 这个共享 bag 上,与 metabox 同一键(self::META_KEY)。
|
||||
// 落到 jio_post_meta 这个共享 bag 上,与 metabox 同一键(self::META_KEY)。
|
||||
assert.match(metabox, /post_meta/)
|
||||
assert.match(list, /PostMetaBox::META_KEY/)
|
||||
})
|
||||
@@ -133,16 +133,16 @@ test('pay quick-edit save sanitizes through the shared sanitizer', () => {
|
||||
assert.match(persist, /self::PAY_TYPE_KEY/)
|
||||
assert.match(persist, /self::PAY_PRICE_KEY/)
|
||||
assert.match(persist, /self::RESOURCE_MARKER_KEY/)
|
||||
// _aksrt_pay_modo / _aksrt_pay_limit 没有任何读取方,已删除,不得再写。
|
||||
assert.doesNotMatch(list, /'_aksrt_pay_modo'|'_aksrt_pay_limit'/)
|
||||
assert.doesNotMatch(resourceBox, /'_aksrt_pay_modo'|'_aksrt_pay_limit'/)
|
||||
// _jio_pay_modo / _jio_pay_limit 没有任何读取方,已删除,不得再写。
|
||||
assert.doesNotMatch(list, /'_jio_pay_modo'|'_jio_pay_limit'/)
|
||||
assert.doesNotMatch(resourceBox, /'_jio_pay_modo'|'_jio_pay_limit'/)
|
||||
})
|
||||
|
||||
test('pay prices go through the ledger, not raw floats', () => {
|
||||
// 金额字段运算在「分」上进行,避免浮点误差。
|
||||
assert.match(list, /balance_minor|price_to_minor/)
|
||||
assert.match(list, /decimal_from_minor/)
|
||||
assert.match(list, /\$pay_meta\[\$field_id\] = \\AKSRT\\Ledger\\AssetAmount::decimal_from_minor/)
|
||||
assert.match(list, /\$pay_meta\[\$field_id\] = \\Jio\\Ledger\\AssetAmount::decimal_from_minor/)
|
||||
})
|
||||
|
||||
test('quick-edit save ignores invalid operations and clamps negatives', () => {
|
||||
@@ -162,8 +162,8 @@ test('custom filter quick-edit fields reuse the settings dimensions', () => {
|
||||
// 高级筛选项从同一份设置里动态生成,键名前缀与 metabox 保存一致。
|
||||
assert.match(list, /get_post_filter_settings/)
|
||||
assert.match(list, /custom_filter/)
|
||||
assert.match(list, /'_aksrt_cf_' \. \$field_id/)
|
||||
assert.match(metabox, /'_aksrt_cf_' \. \$key/)
|
||||
assert.match(list, /'_jio_cf_' \. \$field_id/)
|
||||
assert.match(metabox, /'_jio_cf_' \. \$key/)
|
||||
})
|
||||
|
||||
test('quick-edit save is guarded by screen, autosave, revision and capability', () => {
|
||||
@@ -191,19 +191,19 @@ test('the old inline column code was fully migrated', () => {
|
||||
assert.doesNotMatch(hooks, /manage_post_posts_columns/)
|
||||
assert.doesNotMatch(hooks, /manage_post_posts_custom_column/)
|
||||
assert.doesNotMatch(hooks, /manage_edit-post_sortable_columns/)
|
||||
assert.doesNotMatch(hooks, /aksrt_enqueue_fontawesome/)
|
||||
assert.doesNotMatch(hooks, /aksrt_stats/)
|
||||
assert.doesNotMatch(hooks, /jio_enqueue_fontawesome/)
|
||||
assert.doesNotMatch(hooks, /jio_stats/)
|
||||
})
|
||||
|
||||
test('the dead tab-pane and modal css blocks are removed', () => {
|
||||
// 这些选择器无任何引用,已确认删除;文件仍保留 quick-edit 需要的轮子类。
|
||||
const css = read('assets/css/admin-main.css')
|
||||
assert.doesNotMatch(css, /aksrt-tab-pane/)
|
||||
assert.doesNotMatch(css, /\.aksrt-modal\b/)
|
||||
assert.doesNotMatch(css, /\.aksrt-modal-backdrop\b/)
|
||||
assert.doesNotMatch(css, /jio-tab-pane/)
|
||||
assert.doesNotMatch(css, /\.jio-modal\b/)
|
||||
assert.doesNotMatch(css, /\.jio-modal-backdrop\b/)
|
||||
// 快捷编辑卡片与依赖样式仍在。
|
||||
assert.match(css, /\.aksrt-bulk-edit-box/)
|
||||
assert.match(css, /\.aksrt-bulk-edit-desc/)
|
||||
assert.match(css, /\.jio-bulk-edit-box/)
|
||||
assert.match(css, /\.jio-bulk-edit-desc/)
|
||||
assert.match(css, /\.flex\s*\{/)
|
||||
})
|
||||
|
||||
@@ -211,6 +211,6 @@ test('the quick-edit dependency script exists and toggles by data-dependency', (
|
||||
assert.ok(fs.existsSync(path.join(root, 'assets/js/admin-post-list.js')))
|
||||
const js = read('assets/js/admin-post-list.js')
|
||||
assert.match(js, /data-dependency/)
|
||||
assert.match(js, /aksrt_bulk_edit\[pay\]/)
|
||||
assert.match(js, /jio_bulk_edit\[pay\]/)
|
||||
assert.match(js, /inline-edit-row/)
|
||||
})
|
||||
@@ -15,10 +15,10 @@ test('configured thumbnail ratios override body defaults for both list and card
|
||||
assert.match(main, /^body\s*\{[^}]*--posts-card-scale:\s*70%;[^}]*--posts-list-scale:\s*70%;/s);
|
||||
for (const [scenario, list, card] of [['defaults', 70, 70], ['custom', 100, 56], ['bounds', 50, 200]]) {
|
||||
const html = rendered.styles[scenario];
|
||||
const body = html.match(/<style id="aksrt-theme-color">body\{([^}]+)\}/)[1];
|
||||
const body = html.match(/<style id="jio-theme-color">body\{([^}]+)\}/)[1];
|
||||
assert.match(body, new RegExp(`--posts-list-scale:${list}%;`));
|
||||
assert.match(body, new RegExp(`--posts-card-scale:${card}%;`));
|
||||
const rootVariables = html.match(/<style id="aksrt-runtime-vars">:root\{([^}]+)\}/)[1];
|
||||
const rootVariables = html.match(/<style id="jio-runtime-vars">:root\{([^}]+)\}/)[1];
|
||||
assert.doesNotMatch(rootVariables, /--posts-(list|card)-scale/);
|
||||
assert.equal((html.match(/--posts-list-scale:/g) || []).length, 1);
|
||||
assert.equal((html.match(/--posts-card-scale:/g) || []).length, 1);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
/** Render thumbnail settings and image markup without a WordPress installation. */
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace AKSRT\Settings {
|
||||
namespace Jio\Settings {
|
||||
final class Settings {
|
||||
public static array $values = [];
|
||||
public static function get_all(): array { return self::$values; }
|
||||
@@ -23,7 +23,7 @@ namespace AKSRT\Settings {
|
||||
}
|
||||
}
|
||||
|
||||
namespace AKSRT\Core {
|
||||
namespace Jio\Core {
|
||||
final class PostMetaBox {
|
||||
public static array $meta = [];
|
||||
public static function get_meta_value(int $id, string $key, $default = '') { return self::$meta[$key] ?? $default; }
|
||||
@@ -53,11 +53,11 @@ namespace {
|
||||
function get_post_field($field, $id, $context = ''): string { return $GLOBALS['post_content']; }
|
||||
function get_the_category($id): array { return []; }
|
||||
function get_post_format($id = null) { return false; }
|
||||
function aksrt_get_lazy_thumb($size = ''): string { return 'https://example.test/placeholder.svg'; }
|
||||
function aksrt_is_lazy($key): bool { return $key === 'lazy_sider'; }
|
||||
function aksrt_get_lazy_attr($key, $src, $class = ''): string {
|
||||
if (aksrt_is_lazy($key)) {
|
||||
return ' class="lazyload ' . esc_attr($class) . '" src="' . aksrt_get_lazy_thumb() . '" data-src="' . esc_url($src) . '"';
|
||||
function jio_get_lazy_thumb($size = ''): string { return 'https://example.test/placeholder.svg'; }
|
||||
function jio_is_lazy($key): bool { return $key === 'lazy_sider'; }
|
||||
function jio_get_lazy_attr($key, $src, $class = ''): string {
|
||||
if (jio_is_lazy($key)) {
|
||||
return ' class="lazyload ' . esc_attr($class) . '" src="' . jio_get_lazy_thumb() . '" data-src="' . esc_url($src) . '"';
|
||||
}
|
||||
return ' class="' . esc_attr($class) . '" src="' . esc_url($src) . '"';
|
||||
}
|
||||
@@ -69,41 +69,41 @@ namespace {
|
||||
|
||||
$styles = [];
|
||||
foreach (['defaults' => [], 'custom' => ['list_list_option' => ['scale' => 100], 'list_card_option' => ['scale' => 56]], 'bounds' => ['list_list_option' => ['scale' => 1], 'list_card_option' => ['scale' => 900]]] as $name => $values) {
|
||||
\AKSRT\Settings\Settings::$values = $values;
|
||||
\Jio\Settings\Settings::$values = $values;
|
||||
ob_start();
|
||||
(new \AKSRT\Frontend\InlineStyleBuilder())->output();
|
||||
(new \Jio\Frontend\InlineStyleBuilder())->output();
|
||||
$styles[$name] = (string) ob_get_clean();
|
||||
}
|
||||
|
||||
$GLOBALS['featured_id'] = 42;
|
||||
$GLOBALS['post_content'] = '<p><img src="https://example.test/content.jpg"></p>';
|
||||
\AKSRT\Core\PostMetaBox::$meta = ['thumbnail_url' => 'https://example.test/external.jpg'];
|
||||
\Jio\Core\PostMetaBox::$meta = ['thumbnail_url' => 'https://example.test/external.jpg'];
|
||||
$cfg = ['thumb' => ['thumb_postfirstimg_size' => 'large', 'thumb_postfirstimg_s' => true, 'spare_thumbnail' => ['https://example.test/spare.jpg']]];
|
||||
$featured = aksrt_renderer_resolve_images(1, $cfg);
|
||||
$featured = jio_renderer_resolve_images(1, $cfg);
|
||||
$GLOBALS['featured_id'] = 999;
|
||||
$external = aksrt_renderer_resolve_images(1, $cfg);
|
||||
\AKSRT\Core\PostMetaBox::$meta = [];
|
||||
$content = aksrt_renderer_resolve_images(1, $cfg);
|
||||
$external = jio_renderer_resolve_images(1, $cfg);
|
||||
\Jio\Core\PostMetaBox::$meta = [];
|
||||
$content = jio_renderer_resolve_images(1, $cfg);
|
||||
$GLOBALS['post_content'] = '';
|
||||
$spare = aksrt_renderer_resolve_images(1, $cfg);
|
||||
\AKSRT\Core\PostMetaBox::$meta = ['featured_slide' => '999,42'];
|
||||
$slides = aksrt_renderer_curated_images(1, 'medium');
|
||||
$spare = jio_renderer_resolve_images(1, $cfg);
|
||||
\Jio\Core\PostMetaBox::$meta = ['featured_slide' => '999,42'];
|
||||
$slides = jio_renderer_curated_images(1, 'medium');
|
||||
|
||||
$data = [
|
||||
'post_id' => 1, 'primary' => $featured['primary'], 'has_real' => true,
|
||||
'permalink' => 'https://example.test/post/', 'title' => 'Featured image',
|
||||
'video_url' => '', 'slides' => [], 'gallery_count' => 0,
|
||||
];
|
||||
$cover = aksrt_renderer_thumb($data, ['thumb' => ['list_thumb_fit' => '']]);
|
||||
$contain = aksrt_renderer_thumb($data, ['thumb' => ['list_thumb_fit' => 'contain']]);
|
||||
$cover = jio_renderer_thumb($data, ['thumb' => ['list_thumb_fit' => '']]);
|
||||
$contain = jio_renderer_thumb($data, ['thumb' => ['list_thumb_fit' => 'contain']]);
|
||||
$data['is_slide'] = true;
|
||||
$data['slides'] = ['https://example.test/slide.jpg'];
|
||||
$single = aksrt_renderer_thumb($data, ['thumb' => []]);
|
||||
\AKSRT\Core\PostMetaBox::$meta = ['featured_slide' => implode(',', array_fill(0, 12, '42'))];
|
||||
$data['slides'] = aksrt_renderer_curated_images(1, 'large');
|
||||
$multiple = aksrt_renderer_thumb($data, ['thumb' => ['list_thumb_fit' => 'contain']]);
|
||||
$single = jio_renderer_thumb($data, ['thumb' => []]);
|
||||
\Jio\Core\PostMetaBox::$meta = ['featured_slide' => implode(',', array_fill(0, 12, '42'))];
|
||||
$data['slides'] = jio_renderer_curated_images(1, 'large');
|
||||
$multiple = jio_renderer_thumb($data, ['thumb' => ['list_thumb_fit' => 'contain']]);
|
||||
$data['video_url'] = 'https://example.test/movie.mp4';
|
||||
$video = aksrt_renderer_thumb($data, ['thumb' => ['list_thumb_video_s' => true, 'list_thumb_video_mute_s' => true]]);
|
||||
$video = jio_renderer_thumb($data, ['thumb' => ['list_thumb_video_s' => true, 'list_thumb_video_mute_s' => true]]);
|
||||
|
||||
echo json_encode(compact('styles', 'featured', 'external', 'content', 'spare', 'slides', 'cover', 'contain', 'single', 'multiple', 'video'), JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES);
|
||||
}
|
||||
@@ -19,8 +19,8 @@ const articleFiles = [
|
||||
'inc/helpers/posts-renderer/config.php',
|
||||
'inc/helpers/posts-renderer/post-data.php',
|
||||
'inc/helpers/posts-renderer/markup.php',
|
||||
'inc/helpers/aksrt-archive.php',
|
||||
'inc/helpers/aksrt-single-render.php',
|
||||
'inc/helpers/jio-archive.php',
|
||||
'inc/helpers/jio-single-render.php',
|
||||
];
|
||||
|
||||
test('the article stream exposes only the new typed renderer contract', () => {
|
||||
@@ -40,14 +40,14 @@ test('the article stream exposes only the new typed renderer contract', () => {
|
||||
'templates/parts/user-home/panel-favorites.php',
|
||||
]);
|
||||
|
||||
assert.match(markup, /function aksrt_render_post_list\(array \$args = \[\], \?WP_Query \$query = null, bool \$echo = true\): string/);
|
||||
assert.doesNotMatch(consumers, /function_exists\(['"]aksrt_render_post_list/);
|
||||
assert.doesNotMatch(consumers, /aksrt_render_post_list\(\s*\$query\s*,/);
|
||||
assert.doesNotMatch(consumers, /content-post-card|aksrt_homepage_render_post_card/);
|
||||
assert.match(markup, /function jio_render_post_list\(array \$args = \[\], \?WP_Query \$query = null, bool \$echo = true\): string/);
|
||||
assert.doesNotMatch(consumers, /function_exists\(['"]jio_render_post_list/);
|
||||
assert.doesNotMatch(consumers, /jio_render_post_list\(\s*\$query\s*,/);
|
||||
assert.doesNotMatch(consumers, /content-post-card|jio_homepage_render_post_card/);
|
||||
|
||||
// 搜索页的加载态必须是文章同构骨架,而不是遗留的转圈 spinner,
|
||||
// 这样 runtime main.js 的 list 型 loader 形状才与搜索结果一致。
|
||||
assert.match(read('search.php'), /post_ajax_loader search-loader"><\?php echo function_exists\('aksrt_render_placeholder'\) \? aksrt_render_placeholder\('list', 6\)/);
|
||||
assert.match(read('search.php'), /post_ajax_loader search-loader"><\?php echo function_exists\('jio_render_placeholder'\) \? jio_render_placeholder\('list', 6\)/);
|
||||
assert.doesNotMatch(read('search.php'), /class="loading mr10"/);
|
||||
});
|
||||
|
||||
@@ -103,8 +103,8 @@ test('forced mode wins and thumb_if_has ignores spare and lazy placeholders', ()
|
||||
assert.match(config, /\$is_card = \$forced_type === 'card' \|\| \(!\$is_forced/);
|
||||
|
||||
const hasReal = config.indexOf("$has_real = $primary !== '';");
|
||||
const spareCall = config.lastIndexOf('aksrt_renderer_spare_image($thumb)');
|
||||
const lazy = config.lastIndexOf('aksrt_get_lazy_thumb');
|
||||
const spareCall = config.lastIndexOf('jio_renderer_spare_image($thumb)');
|
||||
const lazy = config.lastIndexOf('jio_get_lazy_thumb');
|
||||
assert.ok(hasReal >= 0 && hasReal < spareCall && spareCall < lazy);
|
||||
assert.match(data, /'thumb_if_has' && empty\(\$images\['has_real'\]\)/);
|
||||
assert.match(data, /count\(\$gallery\) > 2/);
|
||||
@@ -112,7 +112,7 @@ test('forced mode wins and thumb_if_has ignores spare and lazy placeholders', ()
|
||||
});
|
||||
|
||||
test('category card mode carries its term id through archive and shared AJAX rendering', () => {
|
||||
const resolver = read('inc/helpers/aksrt-archive.php');
|
||||
const resolver = read('inc/helpers/jio-archive.php');
|
||||
const config = read('inc/helpers/posts-renderer/config.php');
|
||||
const admin = read('src/Admin/Admin.php');
|
||||
const global = read('src/Admin/GlobalController.php');
|
||||
@@ -125,9 +125,9 @@ test('category card mode carries its term id through archive and shared AJAX ren
|
||||
assert.doesNotMatch(config, /get_queried_object_id/);
|
||||
assert.match(admin, /\$clean\['term_id'\]\s*=\s*absint\(\$config\['term_id'\]\)/);
|
||||
assert.doesNotMatch(global, /rest_archive_posts|\/archive\/posts/);
|
||||
assert.match(read('category.php'), /aksrt_archive_get_render_config\(\['context' => 'category'\]\)/);
|
||||
assert.match(read('category.php'), /jio_archive_get_render_config\(\['context' => 'category'\]\)/);
|
||||
assert.doesNotMatch(config, /\b(?:list_scale|card_scale|mobile_meta)\b/);
|
||||
assert.doesNotMatch(resolver, /aksrt_archive_get_list_style_settings/);
|
||||
assert.doesNotMatch(resolver, /jio_archive_get_list_style_settings/);
|
||||
});
|
||||
|
||||
test('image resolution counts only content images and applies the configured WordPress size', () => {
|
||||
@@ -136,13 +136,13 @@ test('image resolution counts only content images and applies the configured Wor
|
||||
const thumbSchema = read('assets/admin/src/schema/sections/post-list/thumb.js');
|
||||
const phpSchema = read('src/Admin/Schema/SchemaPostList.php');
|
||||
|
||||
assert.match(config, /function aksrt_renderer_content_images\(int \$post_id, string \$size\)/);
|
||||
assert.match(config, /function jio_renderer_content_images\(int \$post_id, string \$size\)/);
|
||||
assert.match(config, /strip_shortcodes\(\(string\) get_post_field\('post_content'/);
|
||||
assert.match(config, /\$slides = aksrt_renderer_curated_images\(\$post_id, \$size\);\s*\$gallery = aksrt_renderer_content_images\(\$post_id, \$size\);/);
|
||||
assert.match(config, /get_post_thumbnail_id\(\$post_id\).*aksrt_renderer_sized_image\(\(string\) \$thumbnail_id, \$size\)/s);
|
||||
assert.match(config, /aksrt_renderer_category_image\(\$post_id, \$size\)/);
|
||||
assert.doesNotMatch(config, /aksrt_get_post_gallery_images|cover_images|featured_video_episode|_aksrt_featured_video|_video_url|_aksrt_thumbnail_url|_thumbnail_url/);
|
||||
assert.doesNotMatch(config, /\['aksrt_category_cover_id',/);
|
||||
assert.match(config, /\$slides = jio_renderer_curated_images\(\$post_id, \$size\);\s*\$gallery = jio_renderer_content_images\(\$post_id, \$size\);/);
|
||||
assert.match(config, /get_post_thumbnail_id\(\$post_id\).*jio_renderer_sized_image\(\(string\) \$thumbnail_id, \$size\)/s);
|
||||
assert.match(config, /jio_renderer_category_image\(\$post_id, \$size\)/);
|
||||
assert.doesNotMatch(config, /jio_get_post_gallery_images|cover_images|featured_video_episode|_jio_featured_video|_video_url|_jio_thumbnail_url|_thumbnail_url/);
|
||||
assert.doesNotMatch(config, /\['jio_category_cover_id',/);
|
||||
assert.match(data, /'gallery_count'\s*=>\s*count\(\$content_images\)/);
|
||||
assert.match(data, /\(int\) \$data\['gallery_count'\] - 4/);
|
||||
assert.match(thumbSchema, /spareThumbnailDefault = \[\{ img: `\$\{themeRoot\}\/assets\/img\/thumbnail\.svg` \}\]/);
|
||||
@@ -152,8 +152,8 @@ test('image resolution counts only content images and applies the configured Wor
|
||||
test('list, card, media, badge and Mini DOM follow dedicated legacy structures', () => {
|
||||
const markup = read('inc/helpers/posts-renderer/markup.php');
|
||||
const data = read('inc/helpers/posts-renderer/post-data.php');
|
||||
const card = markup.slice(markup.indexOf('function aksrt_renderer_card'), markup.indexOf('function aksrt_renderer_mini'));
|
||||
const mini = markup.slice(markup.indexOf('function aksrt_renderer_mini'), markup.indexOf('function aksrt_render_post'));
|
||||
const card = markup.slice(markup.indexOf('function jio_renderer_card'), markup.indexOf('function jio_renderer_mini'));
|
||||
const mini = markup.slice(markup.indexOf('function jio_renderer_mini'), markup.indexOf('function jio_render_post'));
|
||||
|
||||
assert.match(markup, /\['posts-item', 'list', 'ajax-item'\]/);
|
||||
assert.match(markup, /\$classes\[\] = .*'flex xx'.*'flex'/s);
|
||||
@@ -162,7 +162,7 @@ test('list, card, media, badge and Mini DOM follow dedicated legacy structures',
|
||||
assert.match(markup, /<div class="item-body flex xx flex1 jsb/);
|
||||
assert.doesNotMatch(card, /item-excerpt|renderer_excerpt_markup|thumb-items/);
|
||||
assert.match(data, /<div class="item-thumbnail/);
|
||||
assert.match(data, /relative aksrt-slider.*new-swiper item-thumbnail/s);
|
||||
assert.match(data, /relative jio-slider.*new-swiper item-thumbnail/s);
|
||||
assert.match(data, /swiper-slide.*gradient-bg.*data-opacity="0\.2"/s);
|
||||
assert.match(data, /\$fit = \$is_contain && !\$has_slides \? ' contain gradient-bg' : ''/);
|
||||
assert.match(data, /video-thumb-box.*video-url=/s);
|
||||
@@ -176,7 +176,7 @@ test('list, card, media, badge and Mini DOM follow dedicated legacy structures',
|
||||
assert.match(mini, /<div class="mr10"><div class="item-thumbnail"><a/);
|
||||
assert.match(mini, /posts-mini-con flex xx flex1 jsb/);
|
||||
assert.match(mini, /\['c-red', 'c-yellow', 'c-purple', 'c-blue', 'c-green'\]/);
|
||||
assert.match(mini, /aksrt_renderer_pay_badge\(\$data, true\)/);
|
||||
assert.match(mini, /jio_renderer_pay_badge\(\$data, true\)/);
|
||||
assert.match(data, /meta-pay badg badg-sm mr6/);
|
||||
assert.doesNotMatch(mini, /posts-mini-row/);
|
||||
|
||||
@@ -186,14 +186,14 @@ test('list, card, media, badge and Mini DOM follow dedicated legacy structures',
|
||||
assert.match(data, /usort\(\$tags,.*->count.*->count/s);
|
||||
assert.match(data, /查看此标签更多文章/);
|
||||
|
||||
const commentMeta = data.indexOf("aksrt_renderer_svg_icon('comment')");
|
||||
const viewMeta = data.indexOf("aksrt_renderer_svg_icon('view')", commentMeta);
|
||||
const likeMeta = data.indexOf("aksrt_renderer_svg_icon('like')", viewMeta);
|
||||
const commentMeta = data.indexOf("jio_renderer_svg_icon('comment')");
|
||||
const viewMeta = data.indexOf("jio_renderer_svg_icon('view')", commentMeta);
|
||||
const likeMeta = data.indexOf("jio_renderer_svg_icon('like')", viewMeta);
|
||||
assert.ok(commentMeta >= 0 && commentMeta < viewMeta && viewMeta < likeMeta);
|
||||
assert.match(data, /comments_open.*close_comments/s);
|
||||
assert.match(data, /show_like.*post_type.*=== 'post'/s);
|
||||
// The <use> sprite markup lives in the unified helper aksrt_renderer_svg_icon delegates to.
|
||||
assert.match(read('inc/helpers/aksrt-icons.php'), /<use xlink:href="#icon-/);
|
||||
// The <use> sprite markup lives in the unified helper jio_renderer_svg_icon delegates to.
|
||||
assert.match(read('inc/helpers/jio-icons.php'), /<use xlink:href="#icon-/);
|
||||
});
|
||||
|
||||
test('post-card CSS owns the exact desktop and 767/640 responsive matrix', () => {
|
||||
@@ -209,7 +209,7 @@ test('post-card CSS owns the exact desktop and 767/640 responsive matrix', () =>
|
||||
assert.doesNotMatch(mobile767, /\.posts-mini\s*\{[^}]*width:\s*100%/s);
|
||||
assert.match(mobile640, /\.posts-mini\s*\{[^}]*width:\s*100%/s);
|
||||
assert.match(mobile640, /\.posts-item:not\(\.no-thumb\) \.meta-right item\s*\{[^}]*display:\s*none/s);
|
||||
assert.doesNotMatch(css, /\.aksrt-(?!panel|slider)|post-card-cms|grid-cms/);
|
||||
assert.doesNotMatch(css, /\.jio-(?!panel|slider)|post-card-cms|grid-cms/);
|
||||
});
|
||||
|
||||
test('article metadata owns its flex and SVG contracts on every list route', () => {
|
||||
@@ -217,7 +217,7 @@ test('article metadata owns its flex and SVG contracts on every list route', ()
|
||||
const scripts = read('src/Core/ScriptsPart1c.php');
|
||||
const loader = read('assets/js/runtime/loader.js');
|
||||
|
||||
assert.doesNotMatch(scripts, /aksrt-svg-icon|svg-icon-helper/);
|
||||
assert.doesNotMatch(scripts, /jio-svg-icon|svg-icon-helper/);
|
||||
assert.match(loader, /'svg-icon'\s*:\s*'svg-icon'/);
|
||||
assert.match(css, /\.posts-item\.flex,[\s\S]*?\.posts-mini \.flex\s*\{[^}]*display:\s*flex;/);
|
||||
assert.match(css, /\.posts-item \.flex\.xx,[\s\S]*?\.posts-mini \.flex\.xx\s*\{[^}]*flex-direction:\s*column;/);
|
||||
@@ -228,8 +228,8 @@ test('article metadata owns its flex and SVG contracts on every list route', ()
|
||||
});
|
||||
|
||||
test('related posts, author page and widgets use their dedicated renderers', () => {
|
||||
const related = read('inc/helpers/aksrt-single-render.php');
|
||||
const relatedCard = read('inc/helpers/aksrt-single-components.php');
|
||||
const related = read('inc/helpers/jio-single-render.php');
|
||||
const relatedCard = read('inc/helpers/jio-single-components.php');
|
||||
const resourceRelated = read('templates/parts/resource-related.php');
|
||||
const author = read('templates/parts/user-home/panel-posts.php');
|
||||
const authorFavorites = read('templates/parts/user-home/panel-favorites.php');
|
||||
@@ -237,40 +237,40 @@ test('related posts, author page and widgets use their dedicated renderers', ()
|
||||
const widgets = read('src/WidgetSystem/PostRenderer.php');
|
||||
|
||||
assert.match(related, /\['img', 'list', 'text'\]/);
|
||||
assert.match(related, /aksrt_renderer_mini\(\$data, \$mini_cfg\)/);
|
||||
assert.match(related, /\<div class="swiper-slide mr10"\>' \. aksrt_render_graphic_card_post\(\$post\)/);
|
||||
assert.match(related, /jio_renderer_mini\(\$data, \$mini_cfg\)/);
|
||||
assert.match(related, /\<div class="swiper-slide mr10"\>' \. jio_render_graphic_card_post\(\$post\)/);
|
||||
assert.match(related, /<li><a class="icon-circle"/);
|
||||
assert.match(related, /\['category', 'topics', 'post_tag'\]/);
|
||||
assert.match(related, /'terms'\s+=>\s+array_map\('intval', \(array\) \$term_ids\)/);
|
||||
assert.match(related, /class=\"zib-widget\"/);
|
||||
assert.doesNotMatch(related, /post_related_source/);
|
||||
assert.doesNotMatch(related, /post__not_in/);
|
||||
assert.match(related, /\$args\['meta_key'\]\s*=\s*'aksrt_post_views'/);
|
||||
assert.match(relatedCard, /aksrt_renderer_resolve_images/);
|
||||
assert.match(relatedCard, /'text2'\s+=>\s+esc_html\(aksrt_str_cut\(\$title, 0, \(int\) \$args\['cut'\], '\.\.\.'\)\)/);
|
||||
assert.match(related, /\$args\['meta_key'\]\s*=\s*'jio_post_views'/);
|
||||
assert.match(relatedCard, /jio_renderer_resolve_images/);
|
||||
assert.match(relatedCard, /'text2'\s+=>\s+esc_html\(jio_str_cut\(\$title, 0, \(int\) \$args\['cut'\], '\.\.\.'\)\)/);
|
||||
assert.match(resourceRelated, /swiper-container swiper-scroll/);
|
||||
assert.match(resourceRelated, /aksrt_render_graphic_card_post/);
|
||||
assert.doesNotMatch(resourceRelated, /aksrt_render_post_list/);
|
||||
assert.match(author, /aksrt_render_post_list/);
|
||||
assert.match(resourceRelated, /jio_render_graphic_card_post/);
|
||||
assert.doesNotMatch(resourceRelated, /jio_render_post_list/);
|
||||
assert.match(author, /jio_render_post_list/);
|
||||
assert.doesNotMatch(author, /'type'\s*=>\s*'list'/);
|
||||
assert.match(author, /'context'\s*=>\s*'author'/);
|
||||
assert.match(author, /'no_author'\s*=>\s*true/);
|
||||
assert.match(author, /'no_margin'\s*=>\s*true/);
|
||||
assert.match(author, /'wrapper_class'\s*=>\s*'aksrt-user-home__post-list'/);
|
||||
assert.match(author, /aksrt_render_user_home_pagination\(\(int\) \$author_posts->max_num_pages, \(int\) \$posts_page\)/);
|
||||
assert.doesNotMatch(author, /aksrt_render_pagination|aksrt_render_profile_pagination|\$_GET|add_query_arg/);
|
||||
assert.match(author, /'wrapper_class'\s*=>\s*'jio-user-home__post-list'/);
|
||||
assert.match(author, /jio_render_user_home_pagination\(\(int\) \$author_posts->max_num_pages, \(int\) \$posts_page\)/);
|
||||
assert.doesNotMatch(author, /jio_render_pagination|jio_render_profile_pagination|\$_GET|add_query_arg/);
|
||||
// 面板 AJAX 翻页的骨架:与文章列表同一个 .post_ajax_loader 契约,
|
||||
// 由 author.js 的 .is-loading 切换(不能再靠 opacity 变暗反馈)。
|
||||
// 骨架形状必须跟随同一份渲染配置:写死 'list' 会让卡片模式下的占位
|
||||
// 与真实卡片不同轴。
|
||||
assert.match(author, /'append_inner_html' => \$user_home_panel_loader/);
|
||||
assert.match(author, /aksrt_render_placeholder\(\(string\) \(\$user_home_render_config\['mode'\] \?\? 'list'\), max\(4, \(int\) \$per_page\)\)/);
|
||||
assert.doesNotMatch(author, /aksrt_render_placeholder\('(list|card)'/);
|
||||
assert.match(author, /jio_render_placeholder\(\(string\) \(\$user_home_render_config\['mode'\] \?\? 'list'\), max\(4, \(int\) \$per_page\)\)/);
|
||||
assert.doesNotMatch(author, /jio_render_placeholder\('(list|card)'/);
|
||||
assert.match(authorFavorites, /'append_inner_html' => \$user_home_panel_loader/);
|
||||
assert.match(authorFavorites, /aksrt_render_placeholder\(\(string\) \(\$user_home_render_config\['mode'\] \?\? 'list'\), max\(4, \(int\) \$per_page\)\)/);
|
||||
assert.doesNotMatch(authorFavorites, /aksrt_render_placeholder\('(list|card)'/);
|
||||
assert.match(userHomeCss, /\.aksrt-user-home \.dashboard-panel \.post_ajax_loader \{/);
|
||||
assert.match(userHomeCss, /\.aksrt-user-home \.dashboard-panel\.is-loading \.post_ajax_loader \{/);
|
||||
assert.match(authorFavorites, /jio_render_placeholder\(\(string\) \(\$user_home_render_config\['mode'\] \?\? 'list'\), max\(4, \(int\) \$per_page\)\)/);
|
||||
assert.doesNotMatch(authorFavorites, /jio_render_placeholder\('(list|card)'/);
|
||||
assert.match(userHomeCss, /\.jio-user-home \.dashboard-panel \.post_ajax_loader \{/);
|
||||
assert.match(userHomeCss, /\.jio-user-home \.dashboard-panel\.is-loading \.post_ajax_loader \{/);
|
||||
assert.doesNotMatch(userHomeCss, /\.dashboard-panel\.is-loading \{[\s\S]{0,80}opacity: \.6/);
|
||||
|
||||
// 翻页 / 切 tab 是整页替换语义(author.js 最终就是 panel.innerHTML = html),
|
||||
@@ -279,29 +279,29 @@ test('related posts, author page and widgets use their dedicated renderers', ()
|
||||
// beforeSend:replace 时 $item.remove() + find(pag).remove()。
|
||||
// 刻意不按 data-view 收窄:card 模式下真实卡片同样是直接子级 .posts-item,
|
||||
// 也要一起收起,否则卡片面板旧内容不会让位。
|
||||
assert.match(userHomeCss, /\.dashboard-panel\.is-loading \.aksrt-user-home__post-list > \.posts-item \{\s*display: none;/);
|
||||
assert.match(userHomeCss, /\.dashboard-panel\.is-loading \.jio-user-home__post-list > \.posts-item \{\s*display: none;/);
|
||||
// 必须收「直接子级」而非后代:骨架内层同样带 .posts-item,
|
||||
// 写成后代选择器会连骨架自己一起隐藏掉,占位也就永远出不来了。
|
||||
assert.doesNotMatch(userHomeCss, /\.dashboard-panel\.is-loading \.aksrt-user-home__post-list \.posts-item \{/);
|
||||
assert.doesNotMatch(userHomeCss, /\.dashboard-panel\.is-loading \.jio-user-home__post-list \.posts-item \{/);
|
||||
// 旧翻页控件高亮的是旧页码,同属被替换的旧页内容。
|
||||
assert.match(userHomeCss, /\.dashboard-panel\.is-loading:has\(\.post_ajax_loader\) \.profile-pagination \{\s*display: none;/);
|
||||
assert.match(widgets, /aksrt_render_mini_list/);
|
||||
assert.match(widgets, /jio_render_mini_list/);
|
||||
assert.match(widgets, /swiper-container swiper-scroll/);
|
||||
});
|
||||
|
||||
test('after-box author card mirrors legacy user-card structure', () => {
|
||||
const render = read('inc/helpers/aksrt-single-render.php');
|
||||
const identity = read('inc/helpers/aksrt-identity.php');
|
||||
const avatar = read('inc/helpers/aksrt-avatar.php');
|
||||
const userCard = read('inc/helpers/aksrt-user-card.php');
|
||||
const render = read('inc/helpers/jio-single-render.php');
|
||||
const identity = read('inc/helpers/jio-identity.php');
|
||||
const avatar = read('inc/helpers/jio-avatar.php');
|
||||
const userCard = read('inc/helpers/jio-user-card.php');
|
||||
const css = read('assets/css/frontend/single-post/related-toc.css');
|
||||
const authorActions = read('assets/css/frontend/single-post/author-actions.css');
|
||||
const base = read('assets/css/frontend/base.css');
|
||||
const follow = read('assets/js/follow.js');
|
||||
const authorBar = read('templates/parts/single-author-bar.php');
|
||||
const single = read('inc/helpers/aksrt-single.php');
|
||||
const single = read('inc/helpers/jio-single.php');
|
||||
const settings = read('src/Settings/PostDisplay.php');
|
||||
const frontendTags = read('inc/helpers/aksrt-frontend-tags.php');
|
||||
const frontendTags = read('inc/helpers/jio-frontend-tags.php');
|
||||
|
||||
assert.match(render, /avatar-img avatar-lg/);
|
||||
assert.match(render, /'vip'\s+=>\s+true/);
|
||||
@@ -320,21 +320,21 @@ test('after-box author card mirrors legacy user-card structure', () => {
|
||||
assert.match(avatar, /class="avatar-badge"/);
|
||||
assert.match(userCard, /swiper-container more-posts swiper-scroll/);
|
||||
assert.match(userCard, /'class'\s+=>\s+'em09', 'cut'\s+=>\s+32/);
|
||||
assert.doesNotMatch(userCard, /aksrt-more-post-card/);
|
||||
assert.doesNotMatch(userCard, /jio-more-post-card/);
|
||||
assert.match(css, /^\.zib-widget \{/m);
|
||||
assert.doesNotMatch(css, /relates-thumb \.graphic\.style-3 \.graphic-img/);
|
||||
assert.doesNotMatch(authorActions, /\.user-card-author|\.user-card \.author|user-card-posts/);
|
||||
assert.match(identity, /function aksrt_get_user_follow\(/);
|
||||
assert.match(identity, /data-aksrt-follow-label/);
|
||||
assert.match(identity, /aksrt_get_user_follow\('focus-color ml10 follow flex0', \$user_id\)/);
|
||||
assert.match(authorBar, /aksrt_get_user_follow\('px12-sm ml10 follow but c-red', \$author_id\)/);
|
||||
assert.match(identity, /function jio_get_user_follow\(/);
|
||||
assert.match(identity, /data-jio-follow-label/);
|
||||
assert.match(identity, /jio_get_user_follow\('focus-color ml10 follow flex0', \$user_id\)/);
|
||||
assert.match(authorBar, /jio_get_user_follow\('px12-sm ml10 follow but c-red', \$author_id\)/);
|
||||
assert.doesNotMatch(authorBar, /Follow::get_instance|is_following|show_actions|show_bio|extra_class|meta_items/);
|
||||
assert.doesNotMatch(single, /aksrt_single_render_author_bar/);
|
||||
assert.doesNotMatch(single, /jio_single_render_author_bar/);
|
||||
assert.match(base, /\.user-card\.author \.user-name > name\s*\{[^}]*overflow:\s*visible;/s);
|
||||
assert.match(base, /\.user-card\.author \.user-name > name > \.follow\s*\{[^}]*flex-shrink:\s*0;/s);
|
||||
assert.match(follow, /querySelectorAll\('\[data-aksrt-follow="' \+ userId/);
|
||||
assert.match(follow, /querySelectorAll\('\[data-jio-follow="' \+ userId/);
|
||||
assert.match(follow, /matchingButtons\.forEach/);
|
||||
assert.match(follow, /\[data-aksrt-follow-label\]/);
|
||||
assert.match(follow, /\[data-jio-follow-label\]/);
|
||||
assert.doesNotMatch(follow, /childNodes|textNode|Fallback when markup/);
|
||||
assert.doesNotMatch(settings, /'authordesc_s'\s*=>/);
|
||||
assert.doesNotMatch(frontendTags, /array_key_exists\('authordesc_s'/);
|
||||
@@ -371,23 +371,23 @@ test('AJAX pagination can recover, caps auto loading and rehydrates appended med
|
||||
assert.match(js, /post_ajax\.ed/);
|
||||
assert.match(js, /window\.auto_fun\(\)/);
|
||||
assert.match(js, /classList\.add\("lazyload"\)/);
|
||||
assert.match(homepage, /aksrt_render_pagination\(\$query,[\s\S]*?'query_context'\s*=>\s*\$contract\['query_context'\]/);
|
||||
assert.doesNotMatch(homepage, /href="javascript:;" class="ajax-next aksrt-load-more-btn"/);
|
||||
assert.match(ajaxList, /if \(link\.closest\("\[data-aksrt-pagination\], \.aksrt-comment-pagination"\)\) return;/);
|
||||
assert.match(homepage, /jio_render_pagination\(\$query,[\s\S]*?'query_context'\s*=>\s*\$contract\['query_context'\]/);
|
||||
assert.doesNotMatch(homepage, /href="javascript:;" class="ajax-next jio-load-more-btn"/);
|
||||
assert.match(ajaxList, /if \(link\.closest\("\[data-jio-pagination\], \.jio-comment-pagination"\)\) return;/);
|
||||
assert.match(read('assets/js/runtime/main.js'), /thumb_dplayer\(_wid > 640/);
|
||||
assert.doesNotMatch(media, /new Swiper|data-post-thumb-slider/);
|
||||
assert.match(read('assets/js/runtime/main.min.js'), /new-swiper/);
|
||||
assert.doesNotMatch(media, /data-post-thumb-video|initPostThumbMedia/);
|
||||
assert.match(media, /window\.Grade\(container/);
|
||||
assert.match(media, /linear-gradient\(135deg/);
|
||||
assert.match(media, /aksrtGradientState/);
|
||||
assert.match(media, /jioGradientState/);
|
||||
assert.match(read('assets/js/runtime/main.js'), /tbquire\(\['grade'\]/);
|
||||
});
|
||||
|
||||
test('front-end carousel CSS is conditional on carousel block content', () => {
|
||||
const hooks = read('inc/core-hooks.php');
|
||||
assert.match(hooks, /has_block\('aksrt\/carousel', \$post\)/);
|
||||
assert.match(hooks, /if \(!\$has_carousel\) return;[\s\S]*?'aksrt-gutenberg-blocks-front'/);
|
||||
assert.match(hooks, /has_block\('jio\/carousel', \$post\)/);
|
||||
assert.match(hooks, /if \(!\$has_carousel\) return;[\s\S]*?'jio-gutenberg-blocks-front'/);
|
||||
});
|
||||
|
||||
test('the pinned visual baseline records all source checksums', () => {
|
||||
@@ -403,39 +403,39 @@ test('the pinned visual baseline records all source checksums', () => {
|
||||
|
||||
test('every scene reserves a sidebar column only when its widget stack has content', () => {
|
||||
const layout = read('inc/helpers/template-tags/site-layout.php');
|
||||
const single = read('inc/helpers/aksrt-single.php');
|
||||
const aside = read('inc/helpers/aksrt-single-aside.php');
|
||||
const single = read('inc/helpers/jio-single.php');
|
||||
const aside = read('inc/helpers/jio-single-aside.php');
|
||||
const homepage = read('inc/helpers/homepage/content.php');
|
||||
const sidebarTemplate = read('sidebar.php');
|
||||
const archiveHelpers = read('inc/helpers/aksrt-archive.php');
|
||||
const archiveHelpers = read('inc/helpers/jio-archive.php');
|
||||
|
||||
// Single visibility decision: an empty widget stack collapses every scene
|
||||
// (home/category/tag/search/page/archive) to no-sidebar, not just singles.
|
||||
assert.match(layout, /if \(!aksrt_has_current_sidebar_widgets\(\)\) \{\s*return false;/);
|
||||
assert.doesNotMatch(layout, /is_single\(\) && !aksrt_has_current_sidebar_widgets/);
|
||||
assert.match(layout, /if \(!jio_has_current_sidebar_widgets\(\)\) \{\s*return false;/);
|
||||
assert.doesNotMatch(layout, /is_single\(\) && !jio_has_current_sidebar_widgets/);
|
||||
|
||||
// The resolver must answer home_sidebar before the is_page() branch so a
|
||||
// static front page is checked against the zones the homepage renders.
|
||||
const resolver = layout.match(/function aksrt_resolve_sidebar_id\(\): string \{[\s\S]*?\n\}/);
|
||||
assert.ok(resolver, 'aksrt_resolve_sidebar_id should exist');
|
||||
const resolver = layout.match(/function jio_resolve_sidebar_id\(\): string \{[\s\S]*?\n\}/);
|
||||
assert.ok(resolver, 'jio_resolve_sidebar_id should exist');
|
||||
assert.ok(
|
||||
resolver[0].indexOf('is_home() || is_front_page()') < resolver[0].indexOf('is_page()'),
|
||||
'home/front_page branch must precede the is_page() branch'
|
||||
);
|
||||
|
||||
assert.match(single, /\$show_aside = get_post_type\(\$post_id\) === 'post'[\s\S]*?&& aksrt_is_show_sidebar\(\);/);
|
||||
assert.match(single, /\$show_aside = get_post_type\(\$post_id\) === 'post'[\s\S]*?&& jio_is_show_sidebar\(\);/);
|
||||
assert.match(aside, /get_post_type\(\$post_id\) !== 'post' \|\| empty\(\$ctx\['show_aside'\]\)/);
|
||||
|
||||
// Homepage aside renders under the same single decision; no dual gate.
|
||||
assert.match(homepage, /function aksrt_homepage_render_sidebar\(\): void \{\s*if \(!aksrt_is_show_sidebar\(\)\) \{\s*return;/);
|
||||
assert.doesNotMatch(homepage, /aksrt_homepage_has_sidebar/);
|
||||
assert.match(homepage, /function jio_homepage_render_sidebar\(\): void \{\s*if \(!jio_is_show_sidebar\(\)\) \{\s*return;/);
|
||||
assert.doesNotMatch(homepage, /jio_homepage_has_sidebar/);
|
||||
|
||||
// Generic sidebar template early-returns instead of rendering an empty column.
|
||||
assert.match(sidebarTemplate, /if \(!function_exists\('aksrt_is_show_sidebar'\) \|\| !aksrt_is_show_sidebar\(\)\) \{\s*return;/);
|
||||
assert.match(sidebarTemplate, /if \(!function_exists\('jio_is_show_sidebar'\) \|\| !jio_is_show_sidebar\(\)\) \{\s*return;/);
|
||||
assert.doesNotMatch(sidebarTemplate, /legacy parity/);
|
||||
|
||||
// Archive helper takes only the page-level toggle; no dead params.
|
||||
assert.match(archiveHelpers, /function aksrt_archive_has_sidebar\(bool \$enabled = true\): bool/);
|
||||
assert.match(archiveHelpers, /function jio_archive_has_sidebar\(bool \$enabled = true\): bool/);
|
||||
assert.doesNotMatch(archiveHelpers, /preferred|wp_is_mobile/);
|
||||
});
|
||||
|
||||
@@ -487,89 +487,89 @@ test('profile routes load versioned leaf styles and settings dialogs use the sha
|
||||
assert.doesNotMatch(scripts, /assets\/css\/routes\/profile\.css/);
|
||||
assert.doesNotMatch(scripts, /assets\/css\/routes\/user-center\.css/);
|
||||
|
||||
assert.match(settingsAccount, /data-aksrt-settings-modal="<\?php echo esc_attr\(\$modal\); \?>"/);
|
||||
assert.match(settingsAccount, /data-jio-settings-modal="<\?php echo esc_attr\(\$modal\); \?>"/);
|
||||
assert.match(settingsAccount, /\$render_row\([^;]*'email'/);
|
||||
assert.match(settingsAccount, /\$render_row\([^;]*'phone'/);
|
||||
assert.match(settingsAccount, /\$render_row\([^;]*'password'/);
|
||||
assert.match(settingsAccount, /template data-aksrt-settings-modal-template="password"/);
|
||||
assert.match(settingsAccount, /template data-aksrt-settings-modal-template="email"/);
|
||||
assert.match(settingsAccount, /template data-aksrt-settings-modal-template="phone"/);
|
||||
assert.match(settingsAccount, /id="pwd-form"[\s\S]*?data-aksrt-settings-submit-label/);
|
||||
assert.match(settingsAccount, /template data-jio-settings-modal-template="password"/);
|
||||
assert.match(settingsAccount, /template data-jio-settings-modal-template="email"/);
|
||||
assert.match(settingsAccount, /template data-jio-settings-modal-template="phone"/);
|
||||
assert.match(settingsAccount, /id="pwd-form"[\s\S]*?data-jio-settings-submit-label/);
|
||||
assert.match(settingsAccount, /id="email-new-form"[\s\S]*?but jb-blue padding-lg btn-block/);
|
||||
assert.match(settingsAccount, /id="aksrt-bind-phone-form"[\s\S]*?but jb-blue padding-lg btn-block/);
|
||||
assert.doesNotMatch(settingsAccount, /aksrt-settings-modal__footer|确认修改|确认更改|确认绑定/);
|
||||
assert.match(settingsAccount, /id="jio-bind-phone-form"[\s\S]*?but jb-blue padding-lg btn-block/);
|
||||
assert.doesNotMatch(settingsAccount, /jio-settings-modal__footer|确认修改|确认更改|确认绑定/);
|
||||
assert.ok(settingsAccount.indexOf('绑定邮箱') < settingsAccount.indexOf('绑定手机'));
|
||||
assert.ok(settingsAccount.indexOf('绑定手机') < settingsAccount.indexOf("require __DIR__ . '/settings-bindings.php'"));
|
||||
assert.ok(settingsAccount.indexOf("require __DIR__ . '/settings-bindings.php'") < settingsAccount.indexOf('账户密码'));
|
||||
assert.ok(settingsBindings.indexOf('绑定社交账号') < settingsBindings.indexOf('aksrt-account-list--social'));
|
||||
assert.ok(settingsBindings.indexOf('绑定社交账号') < settingsBindings.indexOf('jio-account-list--social'));
|
||||
assert.match(settingsAccount, /!empty\(\$user_settings\['bind_phone'\]\)/);
|
||||
assert.match(settingsBindings, /aksrt-account-list--social/);
|
||||
assert.match(settingsBindings, /jio-account-list--social/);
|
||||
assert.match(settingsBindings, /binding-unbind-btn/);
|
||||
assert.match(userCenterJs, /runtime\.open\(\{/);
|
||||
assert.match(userCenterJs, /className: 'aksrt-user-settings-modal modal-mini full-sm'/);
|
||||
assert.match(userCenterJs, /className: 'jio-user-settings-modal modal-mini full-sm'/);
|
||||
assert.match(userCenterJs, /mobileBottom: true/);
|
||||
assert.match(userCenterJs, /onContent: function \(_, session\)/);
|
||||
assert.match(userCenterJs, /aksrt:modal:close[\s\S]*?resetSettingsModal\(event\.detail && event\.detail\.shell\)/);
|
||||
assert.match(userCenterJs, /jio:modal:close[\s\S]*?resetSettingsModal\(event\.detail && event\.detail\.shell\)/);
|
||||
assert.match(userCenterJs, /resetCaptcha\(scene, form\)/);
|
||||
assert.match(userCenterJs, /function setSettingsSubmitLabel\(button, text\)[\s\S]*?label\.textContent = text/);
|
||||
assert.match(userCenterJs, /aksrt_change_password/);
|
||||
assert.match(userCenterJs, /aksrt_change_email/);
|
||||
assert.match(userCenterJs, /jio_change_password/);
|
||||
assert.match(userCenterJs, /jio_change_email/);
|
||||
assert.match(settingsAccount, /data-account-step="email-old"[\s\S]*?data-account-step="email-new"/);
|
||||
assert.match(settingsAccount, /\$email_verify_required/);
|
||||
assert.match(settingsAccount, /data-account-step="phone-old"[\s\S]*?data-account-step="phone-new"/);
|
||||
assert.match(settingsAccount, /data-account-recovery-open[\s\S]*?data-account-password-pane="recovery"/);
|
||||
assert.match(settingsAccount, /aksrt_render_captcha\('change_password'\)/);
|
||||
assert.match(settingsAccount, /jio_render_captcha\('change_password'\)/);
|
||||
assert.match(userCenterJs, /\/auth\/code/);
|
||||
assert.match(userCenterJs, /\/auth\/reset-password/);
|
||||
assert.match(userCenterJs, /\/user\/phone-verify/);
|
||||
assert.doesNotMatch(settingsAccount + userCenterJs + settingsCss, /pwd-strength|pwd-send-code|email-send-code|aksrt_send_email_code/);
|
||||
assert.match(settingsCss, /\.aksrt-modal-runtime\.aksrt-user-settings-modal:not\(\.aksrt-modal-runtime--bottom\)[\s\S]*?width: 400px/);
|
||||
assert.match(settingsCss, /\.aksrt-settings-modal \.modal-colorful-header[\s\S]*?height: 120px/);
|
||||
assert.doesNotMatch(settingsAccount + userCenterJs + settingsCss, /pwd-strength|pwd-send-code|email-send-code|jio_send_email_code/);
|
||||
assert.match(settingsCss, /\.jio-modal-runtime\.jio-user-settings-modal:not\(\.jio-modal-runtime--bottom\)[\s\S]*?width: 400px/);
|
||||
assert.match(settingsCss, /\.jio-settings-modal \.modal-colorful-header[\s\S]*?height: 120px/);
|
||||
assert.match(settingsAccount, /class="colorful-make"/);
|
||||
assert.doesNotMatch(userCenterJs, /function closeModal\(|\.aksrt-modal\.is-open|security-trigger|data-close-modal/);
|
||||
assert.doesNotMatch(userCenterJs, /function closeModal\(|\.jio-modal\.is-open|security-trigger|data-close-modal/);
|
||||
for (const deletedPath of [
|
||||
'inc/helpers/user-center-renderers/profile-account-modals.php',
|
||||
'inc/helpers/user-center-renderers/settings-security.php',
|
||||
'assets/css/routes/profile/modals.css',
|
||||
]) assert.equal(fs.existsSync(path.join(root, deletedPath)), false, `stale settings modal file: ${deletedPath}`);
|
||||
assert.doesNotMatch(profileListsCss, /aksrt-modal__icon|aksrt-modal__close|security-action/);
|
||||
assert.match(userHomeCss, /\.aksrt-profile-details-modal\[hidden\]\s*\{\s*display:\s*none\s*!important;\s*\}/);
|
||||
assert.match(userHomeCss, /\.aksrt-user-home \.aksrt-user-home__post-list\[data-view="list"\] \.posts-item\s*\{[^}]*background:\s*transparent;[^}]*box-shadow:\s*none;/);
|
||||
assert.match(userHomeCss, /@media \(min-width: 992px\)\s*\{[\s\S]*?\.aksrt-user-home \.aksrt-user-home__post-list\[data-view="list"\]\s*\{[^}]*display:\s*grid;[^}]*grid-template-columns:\s*repeat\(2,\s*minmax\(0,\s*1fr\)\);/);
|
||||
assert.match(userHomeCss, /@media \(min-width: 992px\)\s*\{[\s\S]*?\.aksrt-user-home \.aksrt-user-home__post-list\[data-view="list"\] > \.posts-item\s*\{[^}]*width:\s*auto;[^}]*margin:\s*0 !important;/);
|
||||
assert.match(userHomeCss, /@media \(max-width: 767px\)\s*\{[\s\S]*?\.aksrt-user-home \.aksrt-user-home__post-list\[data-view="list"\] \.posts-item\s*\{[^}]*padding:\s*10px 0;/);
|
||||
assert.doesNotMatch(profileListsCss, /jio-modal__icon|jio-modal__close|security-action/);
|
||||
assert.match(userHomeCss, /\.jio-profile-details-modal\[hidden\]\s*\{\s*display:\s*none\s*!important;\s*\}/);
|
||||
assert.match(userHomeCss, /\.jio-user-home \.jio-user-home__post-list\[data-view="list"\] \.posts-item\s*\{[^}]*background:\s*transparent;[^}]*box-shadow:\s*none;/);
|
||||
assert.match(userHomeCss, /@media \(min-width: 992px\)\s*\{[\s\S]*?\.jio-user-home \.jio-user-home__post-list\[data-view="list"\]\s*\{[^}]*display:\s*grid;[^}]*grid-template-columns:\s*repeat\(2,\s*minmax\(0,\s*1fr\)\);/);
|
||||
assert.match(userHomeCss, /@media \(min-width: 992px\)\s*\{[\s\S]*?\.jio-user-home \.jio-user-home__post-list\[data-view="list"\] > \.posts-item\s*\{[^}]*width:\s*auto;[^}]*margin:\s*0 !important;/);
|
||||
assert.match(userHomeCss, /@media \(max-width: 767px\)\s*\{[\s\S]*?\.jio-user-home \.jio-user-home__post-list\[data-view="list"\] \.posts-item\s*\{[^}]*padding:\s*10px 0;/);
|
||||
const panelFavorites = read('templates/parts/user-home/panel-favorites.php');
|
||||
assert.doesNotMatch(panelFavorites, /'type'\s*=>\s*'list'/);
|
||||
assert.match(panelFavorites, /'context'\s*=>\s*'author'/);
|
||||
assert.match(panelFavorites, /aksrt_render_user_home_pagination\(\(int\) \$favorites_query->max_num_pages, \(int\) \$favorites_page\)/);
|
||||
assert.match(panelFavorites, /array_merge\(\$user_home_render_config, \['wrapper_class' => 'aksrt-user-home__post-list', 'append_inner_html' => \$user_home_panel_loader\]\), \$favorites_query\)/);
|
||||
assert.match(panelFavorites, /aksrt_render_placeholder\(\(string\) \(\$user_home_render_config\['mode'\] \?\? 'list'\), max\(4, \(int\) \$per_page\)\)/);
|
||||
assert.match(read('templates/parts/user-home/panel-posts.php'), /array_merge\(\$user_home_render_config, \['wrapper_class' => 'aksrt-user-home__post-list', 'append_inner_html' => \$user_home_panel_loader\]\), \$author_posts\)/);
|
||||
assert.match(panelFavorites, /jio_render_user_home_pagination\(\(int\) \$favorites_query->max_num_pages, \(int\) \$favorites_page\)/);
|
||||
assert.match(panelFavorites, /array_merge\(\$user_home_render_config, \['wrapper_class' => 'jio-user-home__post-list', 'append_inner_html' => \$user_home_panel_loader\]\), \$favorites_query\)/);
|
||||
assert.match(panelFavorites, /jio_render_placeholder\(\(string\) \(\$user_home_render_config\['mode'\] \?\? 'list'\), max\(4, \(int\) \$per_page\)\)/);
|
||||
assert.match(read('templates/parts/user-home/panel-posts.php'), /array_merge\(\$user_home_render_config, \['wrapper_class' => 'jio-user-home__post-list', 'append_inner_html' => \$user_home_panel_loader\]\), \$author_posts\)/);
|
||||
assert.doesNotMatch(panelFavorites, /profile-list-item|\$_GET|add_query_arg/);
|
||||
// Only the two-column user center spans the viewport; the single-column public
|
||||
// profile keeps following the admin content max width.
|
||||
assert.match(frontendLayoutCss, /\.site-main\.content-shell\.aksrt-profile\s*\{[\s\S]*?max-width:\s*100%/);
|
||||
assert.match(frontendLayoutCss, /\.site-main\.content-shell\.aksrt-user-home\s*\{[\s\S]*?max-width:\s*var\(--container, 1440px\)[\s\S]*?padding-top:\s*0/);
|
||||
assert.match(frontendLayoutCss, /\.site-main\.content-shell\.jio-profile\s*\{[\s\S]*?max-width:\s*100%/);
|
||||
assert.match(frontendLayoutCss, /\.site-main\.content-shell\.jio-user-home\s*\{[\s\S]*?max-width:\s*var\(--container, 1440px\)[\s\S]*?padding-top:\s*0/);
|
||||
// The front end enqueues build/public.min.css, not the frontend/ sources, so a
|
||||
// source-only edit never reaches the page. The bundle must carry the contract.
|
||||
assert.match(publicBundleCss, /\.site-main\.content-shell\.aksrt-profile\s*\{[\s\S]*?max-width:\s*100%/);
|
||||
assert.match(publicBundleCss, /\.site-main\.content-shell\.aksrt-user-home\s*\{[\s\S]*?max-width:\s*var\(--container, 1440px\)/);
|
||||
// .aksrt-profile on <main> is the user center only; other routes must not carry it.
|
||||
assert.equal(read('templates/pages/page-user-home.php').includes('aksrt-profile"'), false);
|
||||
assert.doesNotMatch(profileLayoutCss, /\.aksrt-profile\.content-shell\s*,[\s\S]*?max-width:\s*var\(--container/);
|
||||
assert.doesNotMatch(userCenterShellCss, /\.aksrt-user-center \.content-shell\s*,[\s\S]*?max-width:\s*var\(--container/);
|
||||
assert.match(userCenterShellCss, /\.aksrt-profile\.content-shell\s*\{[\s\S]*?max-width:\s*none/);
|
||||
assert.match(userCenterShellCss, /\.aksrt-profile\.user-center\s*\{[\s\S]*?display:\s*block;[\s\S]*?gap:\s*0/);
|
||||
assert.match(userCenterShellCss, /\.aksrt-profile\.content-shell > \.content-wrap,[\s\S]*?\.aksrt-profile\.content-shell \.uc-header\s*\{[\s\S]*?width:\s*100%;[\s\S]*?max-width:\s*none/);
|
||||
assert.match(profileLayoutCss, /\.aksrt-cover-menu__panel\[hidden\]\s*\{\s*display:\s*none\s*!important;\s*\}/);
|
||||
assert.match(profileLayoutCss, /#aksrt-bg-file-input\[hidden\],[\s\S]*?#aksrt-avatar-file-input\[hidden\][\s\S]*?display:\s*none\s*!important;/);
|
||||
assert.match(settingsAccount, /id="aksrt-bind-phone-form"[\s\S]*?data-aksrt-sms-trigger/);
|
||||
assert.match(publicBundleCss, /\.site-main\.content-shell\.jio-profile\s*\{[\s\S]*?max-width:\s*100%/);
|
||||
assert.match(publicBundleCss, /\.site-main\.content-shell\.jio-user-home\s*\{[\s\S]*?max-width:\s*var\(--container, 1440px\)/);
|
||||
// .jio-profile on <main> is the user center only; other routes must not carry it.
|
||||
assert.equal(read('templates/pages/page-user-home.php').includes('jio-profile"'), false);
|
||||
assert.doesNotMatch(profileLayoutCss, /\.jio-profile\.content-shell\s*,[\s\S]*?max-width:\s*var\(--container/);
|
||||
assert.doesNotMatch(userCenterShellCss, /\.jio-user-center \.content-shell\s*,[\s\S]*?max-width:\s*var\(--container/);
|
||||
assert.match(userCenterShellCss, /\.jio-profile\.content-shell\s*\{[\s\S]*?max-width:\s*none/);
|
||||
assert.match(userCenterShellCss, /\.jio-profile\.user-center\s*\{[\s\S]*?display:\s*block;[\s\S]*?gap:\s*0/);
|
||||
assert.match(userCenterShellCss, /\.jio-profile\.content-shell > \.content-wrap,[\s\S]*?\.jio-profile\.content-shell \.uc-header\s*\{[\s\S]*?width:\s*100%;[\s\S]*?max-width:\s*none/);
|
||||
assert.match(profileLayoutCss, /\.jio-cover-menu__panel\[hidden\]\s*\{\s*display:\s*none\s*!important;\s*\}/);
|
||||
assert.match(profileLayoutCss, /#jio-bg-file-input\[hidden\],[\s\S]*?#jio-avatar-file-input\[hidden\][\s\S]*?display:\s*none\s*!important;/);
|
||||
assert.match(settingsAccount, /id="jio-bind-phone-form"[\s\S]*?data-jio-sms-trigger/);
|
||||
assert.match(settingsAccount, /class="modal-colorful-header colorful-bg jb-blue"/);
|
||||
assert.match(settingsAccount, /class="modal-colorful-header colorful-bg jb-yellow"/);
|
||||
assert.doesNotMatch(settingsAccount, /class="aksrt-modal"|aksrt-modal__overlay|aksrt-modal__box/);
|
||||
assert.doesNotMatch(settingsAccount, /class="jio-modal"|jio-modal__overlay|jio-modal__box/);
|
||||
assert.match(userCenterCreditsCss, /#panel-credits\.dashboard-panel\s*\{[^}]*background:\s*transparent;[^}]*box-shadow:\s*none;/);
|
||||
assert.match(userCenterCreditsCss, /\.uc-asset-tabs\s*\{[^}]*background:\s*var\(--surface-1,\s*#fff\);[^}]*border:[^;}]+;[^}]*box-shadow:[^;}]+;/);
|
||||
assert.doesNotMatch(profileCredits, /credits-signin\.php|data-aksrt-signin-calendar/);
|
||||
assert.doesNotMatch(profileCredits, /credits-signin\.php|data-jio-signin-calendar/);
|
||||
// 签到详情弹窗为参考主题 1:1 结构(src/Credits/Signin.php),日历已随
|
||||
// 旧版 lite 弹窗一并移除;用户中心里的孤儿渲染器也已整包移除。
|
||||
assert.equal(fs.existsSync(path.join(root, 'inc/helpers/user-center-renderers/credits-signin.php')), false);
|
||||
@@ -577,35 +577,35 @@ test('profile routes load versioned leaf styles and settings dialogs use the sha
|
||||
const signinModalHtml = read('src/Credits/Signin.php');
|
||||
assert.match(signinModalHtml, /modal-colorful-header colorful-bg c-blue/);
|
||||
assert.match(signinModalHtml, /checkin-mini-box/);
|
||||
assert.doesNotMatch(signinModalHtml, /data-aksrt-signin-calendar/);
|
||||
assert.match(profileOrders, /data-aksrt-orders-filter-panel hidden/);
|
||||
assert.doesNotMatch(signinModalHtml, /data-jio-signin-calendar/);
|
||||
assert.match(profileOrders, /data-jio-orders-filter-panel hidden/);
|
||||
assert.doesNotMatch(profileOrders, /\$_GET|add_query_arg|name="order_(?:status|type|number|page)"/);
|
||||
assert.match(profileOrders, /data-aksrt-orders-filter-panel hidden[\s\S]*?aksrt-orders-page__status-filters[\s\S]*?aksrt-orders-page__type-filters/);
|
||||
assert.match(profileOrders, /data-aksrt-order-filter="status"[\s\S]*?data-aksrt-order-filter="type"/);
|
||||
assert.match(profileOrders, /data-aksrt-order-page=/);
|
||||
assert.doesNotMatch(profileOrders, /aksrt-orders-page__(?:filter-chip|page-chip|clear)[^>]*href=/);
|
||||
assert.doesNotMatch(profileOrders, /aksrt-orders-page__tabs|aksrt-orders-page__tab(?:\s|<|"|')/);
|
||||
assert.match(ordersCss, /\.aksrt-orders-page__filter-panel\[hidden\]\s*\{\s*display:\s*none\s*!important;/);
|
||||
assert.match(ordersCss, /\.aksrt-orders-page__status-filters, \.aksrt-orders-page__type-filters/);
|
||||
assert.doesNotMatch(ordersCss, /\.aksrt-orders-page__tabs|\.aksrt-orders-page__tab(?:\s|\.|:|\{)/);
|
||||
assert.match(profileOrders, /data-jio-orders-filter-panel hidden[\s\S]*?jio-orders-page__status-filters[\s\S]*?jio-orders-page__type-filters/);
|
||||
assert.match(profileOrders, /data-jio-order-filter="status"[\s\S]*?data-jio-order-filter="type"/);
|
||||
assert.match(profileOrders, /data-jio-order-page=/);
|
||||
assert.doesNotMatch(profileOrders, /jio-orders-page__(?:filter-chip|page-chip|clear)[^>]*href=/);
|
||||
assert.doesNotMatch(profileOrders, /jio-orders-page__tabs|jio-orders-page__tab(?:\s|<|"|')/);
|
||||
assert.match(ordersCss, /\.jio-orders-page__filter-panel\[hidden\]\s*\{\s*display:\s*none\s*!important;/);
|
||||
assert.match(ordersCss, /\.jio-orders-page__status-filters, \.jio-orders-page__type-filters/);
|
||||
assert.doesNotMatch(ordersCss, /\.jio-orders-page__tabs|\.jio-orders-page__tab(?:\s|\.|:|\{)/);
|
||||
assert.match(orderCenterJs, /if \(panel\) panel\.hidden = expanded;/);
|
||||
assert.doesNotMatch(profileOrders, /can_open|entitlement_label|is-entitlement/);
|
||||
assert.doesNotMatch(orderCenterJs, /can_open|entitlement_label|is-entitlement/);
|
||||
assert.doesNotMatch(ordersCss, /is-entitlement/);
|
||||
assert.match(orderCenterJs, /function loadView\(state, patch\)/);
|
||||
assert.match(orderCenterJs, /data-aksrt-order-filter/);
|
||||
assert.match(orderCenterJs, /data-aksrt-order-page/);
|
||||
assert.match(orderCenterJs, /data-jio-order-filter/);
|
||||
assert.match(orderCenterJs, /data-jio-order-page/);
|
||||
assert.doesNotMatch(orderCenterJs, /new URL\(window\.location\.href\)/);
|
||||
// 订单详情弹窗滚动发生在 runtime dialog 本体:滚动条必须完全隐藏(含细滚动条),滚动行为不变。
|
||||
assert.match(ordersCss, /\.aksrt-order-detail-modal \.aksrt-modal-runtime__dialog\s*\{[^}]*scrollbar-width:\s*none/);
|
||||
assert.match(ordersCss, /\.aksrt-order-detail-modal \.aksrt-modal-runtime__dialog::-webkit-scrollbar\s*\{\s*display:\s*none;\s*\}/);
|
||||
assert.doesNotMatch(ordersCss, /aksrt-order-detail-modal[^{}]*mini-scrollbar/);
|
||||
assert.match(ordersCss, /\.jio-order-detail-modal \.jio-modal-runtime__dialog\s*\{[^}]*scrollbar-width:\s*none/);
|
||||
assert.match(ordersCss, /\.jio-order-detail-modal \.jio-modal-runtime__dialog::-webkit-scrollbar\s*\{\s*display:\s*none;\s*\}/);
|
||||
assert.doesNotMatch(ordersCss, /jio-order-detail-modal[^{}]*mini-scrollbar/);
|
||||
assert.doesNotMatch(userCenterJs, /\['order_status', 'order_type', 'order_number', 'order_page'\]/);
|
||||
assert.match(userCenterJs, /initialTab === 'orders'[\s\S]*?window\.history\.replaceState/);
|
||||
});
|
||||
|
||||
test('public user home stays param-free and swaps panels via REST fragments', () => {
|
||||
const ctx = read('inc/helpers/aksrt-user-home.php');
|
||||
const ctx = read('inc/helpers/jio-user-home.php');
|
||||
const shell = read('templates/pages/page-user-home.php');
|
||||
const follow = read('templates/parts/user-home/panel-follow.php');
|
||||
const comments = read('templates/parts/user-home/panel-comments.php');
|
||||
@@ -615,9 +615,9 @@ test('public user home stays param-free and swaps panels via REST fragments', ()
|
||||
|
||||
// Context builder owns no request state; pages default to 1.
|
||||
assert.doesNotMatch(ctx, /\$_GET/);
|
||||
assert.match(ctx, /function aksrt_render_user_home_pagination\(int \$total_pages, int \$current\): void/);
|
||||
assert.match(ctx, /function jio_render_user_home_pagination\(int \$total_pages, int \$current\): void/);
|
||||
assert.match(ctx, /data-uh-page/);
|
||||
assert.match(ctx, /function aksrt_capture_user_home_panel\(string \$tab, array \$ctx\): string/);
|
||||
assert.match(ctx, /function jio_capture_user_home_panel\(string \$tab, array \$ctx\): string/);
|
||||
|
||||
// Shell mounts panel wrappers from the tab registry with a user id hook.
|
||||
assert.match(shell, /data-user-id="<\?php echo \(int\) \$user_id; \?>">/);
|
||||
@@ -628,23 +628,23 @@ test('public user home stays param-free and swaps panels via REST fragments', ()
|
||||
// Follow panel: button-driven sub-tabs, pagination without URL building.
|
||||
assert.match(follow, /data-uh-sub="followers"/);
|
||||
assert.match(follow, /data-uh-sub="following"/);
|
||||
assert.match(follow, /aksrt_render_user_home_pagination\(\$total_pages, \$follow_page\)/);
|
||||
assert.match(ctx, /function aksrt_get_user_home_follow_placeholder\(\): string[\s\S]*?author-minicard radius8 flex ac uh-follow__placeholder[\s\S]*?placeholder k1 mb6[\s\S]*?placeholder s1/);
|
||||
assert.match(follow, /jio_render_user_home_pagination\(\$total_pages, \$follow_page\)/);
|
||||
assert.match(ctx, /function jio_get_user_home_follow_placeholder\(\): string[\s\S]*?author-minicard radius8 flex ac uh-follow__placeholder[\s\S]*?placeholder k1 mb6[\s\S]*?placeholder s1/);
|
||||
assert.match(ctx, /str_repeat\(\$item, 6\)/);
|
||||
assert.match(follow, /aksrt_get_user_home_follow_placeholder\(\)/);
|
||||
assert.match(follow, /jio_get_user_home_follow_placeholder\(\)/);
|
||||
assert.match(follow, /data-uh-retry[\s\S]*?重试/);
|
||||
assert.doesNotMatch(follow, /add_query_arg|\$_GET|paginate_links/);
|
||||
|
||||
assert.match(comments, /data-uh-comments-next=/);
|
||||
assert.match(favorites, /aksrt_render_user_home_pagination\(/);
|
||||
assert.match(favorites, /if \(!empty\(\$placeholder_only\)\)[\s\S]*?aksrt_render_placeholder\(\$placeholder_mode, 4\)/);
|
||||
assert.match(favorites, /jio_render_user_home_pagination\(/);
|
||||
assert.match(favorites, /if \(!empty\(\$placeholder_only\)\)[\s\S]*?jio_render_placeholder\(\$placeholder_mode, 4\)/);
|
||||
assert.match(favorites, /data-uh-retry[\s\S]*?重试/);
|
||||
assert.doesNotMatch(comments + favorites, /add_query_arg|\$_GET/);
|
||||
|
||||
// REST fragment endpoint serves public panel bodies.
|
||||
assert.ok(rest.includes("'/user-home/panel/(?P<user_id>\\d+)/(?P<tab>[a-z0-9_-]+)'"));
|
||||
assert.match(rest, /'permission_callback'\s*=>\s*'__return_true'/);
|
||||
assert.match(rest, /aksrt_capture_user_home_panel\(\$tab, \$context\)/);
|
||||
assert.match(rest, /jio_capture_user_home_panel\(\$tab, \$context\)/);
|
||||
|
||||
// Runtime never reads or writes URL params; fragments replace panel bodies.
|
||||
assert.doesNotMatch(authorJs, /searchParams|pushState|replaceState/);
|
||||
@@ -685,7 +685,7 @@ test('single articles enqueue only content and settings driven assets', () => {
|
||||
assert.match(builder, /'single\.css': \[[\s\S]*?single-post\/shell\.css'[\s\S]*?single-post\/content\.css'[\s\S]*?single-post\/author-actions\.css'/);
|
||||
assert.match(builder, /'comments\.css': \[[\s\S]*?comments\/shell\.css'[\s\S]*?comments\/mobile-item\.css'/);
|
||||
assert.match(singleBlock, /build\/single\.min\.css/);
|
||||
assert.match(singleBlock, /wp_register_style\([\s\S]*?'aksrt-single-post'[\s\S]*?array_merge\(\['aksrt-single-core'\], \$single_feature_handles\)/);
|
||||
assert.match(singleBlock, /wp_register_style\([\s\S]*?'jio-single-post'[\s\S]*?array_merge\(\['jio-single-core'\], \$single_feature_handles\)/);
|
||||
assert.doesNotMatch(singleBlock, /\$enqueue_module\('single-post'\)|\$enqueue_module\('comments'\)/);
|
||||
|
||||
for (const requirement of [
|
||||
@@ -707,9 +707,9 @@ test('single articles enqueue only content and settings driven assets', () => {
|
||||
assert.match(singleBlock, new RegExp(`singular_assets\\['${requirement}'\\]`));
|
||||
}
|
||||
|
||||
assert.match(runtime, /wp:\(\?:code\|preformatted\|aksrt\\\/enlighter\)/);
|
||||
assert.match(runtime, /wp:\(\?:code\|preformatted\|jio\\\/enlighter\)/);
|
||||
assert.match(runtime, /\$requirements\['has_toc'\] = \$is_article && \$article_nav_enabled/);
|
||||
assert.match(runtime, /data-aksrt-video/);
|
||||
assert.match(runtime, /data-jio-video/);
|
||||
assert.match(runtime, /post_password_required\(\$post_id\)/);
|
||||
assert.match(runtime, /comment_settings\['close_comments'\]/);
|
||||
assert.match(runtime, /comment_settings\['require_login'\]/);
|
||||
@@ -734,9 +734,9 @@ test('single articles enqueue only content and settings driven assets', () => {
|
||||
|
||||
test('single post more menu has one accessible runtime and an in-flow author anchor', () => {
|
||||
const article = read('assets/js/core/article.js');
|
||||
const components = read('inc/helpers/aksrt-single-components.php');
|
||||
const single = read('inc/helpers/aksrt-single.php');
|
||||
const singleRender = read('inc/helpers/aksrt-single-render.php');
|
||||
const components = read('inc/helpers/jio-single-components.php');
|
||||
const single = read('inc/helpers/jio-single.php');
|
||||
const singleRender = read('inc/helpers/jio-single-render.php');
|
||||
const authorBar = read('templates/parts/single-author-bar.php');
|
||||
const styles = read('assets/css/frontend/single-post/author-actions.css');
|
||||
|
||||
@@ -751,10 +751,10 @@ test('single post more menu has one accessible runtime and an in-flow author anc
|
||||
assert.match(components, /\$menu_id = 'post-drop-meta-menu-' \. \$post_id/);
|
||||
assert.match(components, /aria-controls="' \. esc_attr\(\$menu_id\)/);
|
||||
assert.match(components, /aria-haspopup="true"/);
|
||||
assert.match(single, /aksrt_get_single_meta_box\(\$post_id\)/);
|
||||
assert.match(single, /jio_get_single_meta_box\(\$post_id\)/);
|
||||
assert.doesNotMatch(components, /\$direction/);
|
||||
assert.match(components, /\$count_label = function_exists\('aksrt_format_number'\) \? aksrt_format_number\(\$count\) : number_format_i18n\(\$count\)/);
|
||||
assert.match(components, /\$likes_label = function_exists\('aksrt_format_number'\) \? aksrt_format_number\(\$likes\) : number_format_i18n\(\$likes\)/);
|
||||
assert.match(components, /\$count_label = function_exists\('jio_format_number'\) \? jio_format_number\(\$count\) : number_format_i18n\(\$count\)/);
|
||||
assert.match(components, /\$likes_label = function_exists\('jio_format_number'\) \? jio_format_number\(\$likes\) : number_format_i18n\(\$likes\)/);
|
||||
assert.match(components, /esc_html\(\(string\) \$count_label\)/);
|
||||
assert.match(components, /esc_html\(\(string\) \$likes_label\)/);
|
||||
assert.match(authorBar, /class="single-meta-row"/);
|
||||
@@ -767,18 +767,18 @@ test('single post more menu has one accessible runtime and an in-flow author anc
|
||||
});
|
||||
|
||||
test('single post like, favorite and comment counts abbreviate everywhere', () => {
|
||||
const actionsRender = read('inc/helpers/aksrt-single-render.php');
|
||||
const actionsRender = read('inc/helpers/jio-single-render.php');
|
||||
const tabbar = read('inc/helpers/template-tags/pagination-permissions.php');
|
||||
const article = read('assets/js/core/article.js');
|
||||
|
||||
assert.match(actionsRender, /\$like_label = function_exists\('aksrt_format_number'\) \? aksrt_format_number\(\$like_count\) : number_format_i18n\(\$like_count\)/);
|
||||
assert.match(actionsRender, /\$favorite_label = function_exists\('aksrt_format_number'\) \? aksrt_format_number\(\$favorite_count\) : number_format_i18n\(\$favorite_count\)/);
|
||||
assert.match(actionsRender, /\$like_label = function_exists\('jio_format_number'\) \? jio_format_number\(\$like_count\) : number_format_i18n\(\$like_count\)/);
|
||||
assert.match(actionsRender, /\$favorite_label = function_exists\('jio_format_number'\) \? jio_format_number\(\$favorite_count\) : number_format_i18n\(\$favorite_count\)/);
|
||||
assert.match(actionsRender, /data-stat-count="likes" data-base-count="<\?php echo esc_attr\(\(string\) \$like_count\); \?>"><\?php echo esc_html\(\(string\) \$like_label\);/);
|
||||
assert.match(actionsRender, /data-stat-count="favorites" data-base-count="<\?php echo esc_attr\(\(string\) \$favorite_count\); \?>"><\?php echo esc_html\(\(string\) \$favorite_label\);/);
|
||||
|
||||
assert.match(tabbar, /\$like_label = function_exists\('aksrt_format_number'\) \? aksrt_format_number\(\$like_count\) : number_format_i18n\(\$like_count\)/);
|
||||
assert.match(tabbar, /\$favorite_label = function_exists\('aksrt_format_number'\) \? aksrt_format_number\(\$favorite_count\) : number_format_i18n\(\$favorite_count\)/);
|
||||
assert.match(tabbar, /\$comment_label = function_exists\('aksrt_format_number'\) \? aksrt_format_number\(\$comment_count\) : number_format_i18n\(\$comment_count\)/);
|
||||
assert.match(tabbar, /\$like_label = function_exists\('jio_format_number'\) \? jio_format_number\(\$like_count\) : number_format_i18n\(\$like_count\)/);
|
||||
assert.match(tabbar, /\$favorite_label = function_exists\('jio_format_number'\) \? jio_format_number\(\$favorite_count\) : number_format_i18n\(\$favorite_count\)/);
|
||||
assert.match(tabbar, /\$comment_label = function_exists\('jio_format_number'\) \? jio_format_number\(\$comment_count\) : number_format_i18n\(\$comment_count\)/);
|
||||
assert.match(tabbar, /echo esc_html\(\(string\) \$like_label\)/);
|
||||
assert.match(tabbar, /echo esc_html\(\(string\) \$favorite_label\)/);
|
||||
assert.match(tabbar, /echo esc_html\(\(string\) \$comment_label\)/);
|
||||
@@ -790,11 +790,11 @@ test('single post like, favorite and comment counts abbreviate everywhere', () =
|
||||
});
|
||||
|
||||
test('abbreviated counts use uppercase unit suffixes everywhere', () => {
|
||||
const frontendTags = read('inc/helpers/aksrt-frontend-tags.php');
|
||||
const frontendTags = read('inc/helpers/jio-frontend-tags.php');
|
||||
const article = read('assets/js/core/article.js');
|
||||
const commentAjax = read('assets/js/comment-ajax.js');
|
||||
|
||||
const formatNumber = frontendTags.match(/function aksrt_format_number\([\s\S]*?\nendif;/)[0];
|
||||
const formatNumber = frontendTags.match(/function jio_format_number\([\s\S]*?\nendif;/)[0];
|
||||
assert.match(formatNumber, /round\(\$num \/ 10000, 1\) \. 'W\+'/);
|
||||
assert.match(formatNumber, /round\(\$num \/ 1000, 1\) \. 'K'/);
|
||||
assert.doesNotMatch(formatNumber, /\. 'k'/);
|
||||
@@ -808,29 +808,29 @@ test('abbreviated counts use uppercase unit suffixes everywhere', () => {
|
||||
});
|
||||
|
||||
test('article page sales counts and comment likes abbreviate', () => {
|
||||
const pay = read('inc/helpers/aksrt-pay.php');
|
||||
const pay = read('inc/helpers/jio-pay.php');
|
||||
const downloadBox = read('templates/parts/resource-download-box.php');
|
||||
const commentItem = read('inc/helpers/comments/item.php');
|
||||
|
||||
// 付费盒(文章页 + 侧栏小工具)「已售」徽章。
|
||||
assert.match(pay, /\$sales_label = function_exists\('aksrt_format_number'\) \? aksrt_format_number\(\$sales\) : number_format_i18n\(\$sales\)/);
|
||||
assert.doesNotMatch(pay, /sprintf\(__\('已售 %s', 'aksrt'\), \$sales\)/);
|
||||
assert.match(pay, /\$sales_label = function_exists\('jio_format_number'\) \? jio_format_number\(\$sales\) : number_format_i18n\(\$sales\)/);
|
||||
assert.doesNotMatch(pay, /sprintf\(__\('已售 %s', 'jio'\), \$sales\)/);
|
||||
|
||||
// 资源下载盒「已售」徽章。
|
||||
assert.match(downloadBox, /\$sales_label = function_exists\('aksrt_format_number'\) \? aksrt_format_number\(\$display_count\) : number_format_i18n\(\$display_count\)/);
|
||||
assert.match(downloadBox, /printf\(esc_html__\('已售 %s', 'aksrt'\), \$sales_label\)/);
|
||||
assert.match(downloadBox, /\$sales_label = function_exists\('jio_format_number'\) \? jio_format_number\(\$display_count\) : number_format_i18n\(\$display_count\)/);
|
||||
assert.match(downloadBox, /printf\(esc_html__\('已售 %s', 'jio'\), \$sales_label\)/);
|
||||
assert.doesNotMatch(downloadBox, /number_format_i18n\(\$display_count\)\);/);
|
||||
|
||||
// 评论区点赞数。
|
||||
assert.match(commentItem, /\$like_label = function_exists\('aksrt_format_number'\) \? aksrt_format_number\(\$like_count\) : number_format_i18n\(\$like_count\)/);
|
||||
assert.match(commentItem, /\$like_label = function_exists\('jio_format_number'\) \? jio_format_number\(\$like_count\) : number_format_i18n\(\$like_count\)/);
|
||||
assert.match(commentItem, /esc_html\(\(string\) \$like_label\)/);
|
||||
});
|
||||
|
||||
test('single post has no word-count or estimated-reading-time chain', () => {
|
||||
const chain = join([
|
||||
'inc/helpers/aksrt-single.php',
|
||||
'inc/helpers/aksrt-single-render.php',
|
||||
'inc/helpers/aksrt-frontend-tags.php',
|
||||
'inc/helpers/jio-single.php',
|
||||
'inc/helpers/jio-single-render.php',
|
||||
'inc/helpers/jio-frontend-tags.php',
|
||||
'src/Settings/PostDisplay.php',
|
||||
'src/Settings/FacadeBulk.php',
|
||||
'src/Admin/Schema/SchemaPostList.php',
|
||||
@@ -838,7 +838,7 @@ test('single post has no word-count or estimated-reading-time chain', () => {
|
||||
'assets/css/frontend/single-post/shell.css',
|
||||
]);
|
||||
|
||||
assert.doesNotMatch(chain, /aksrt_(?:post_word_count|reading_minutes)/);
|
||||
assert.doesNotMatch(chain, /jio_(?:post_word_count|reading_minutes)/);
|
||||
assert.doesNotMatch(chain, /(?:feature_|post_)?word_count(?:_enabled)?/);
|
||||
assert.doesNotMatch(chain, /(?:feature_|post_)?reading_time(?:_enabled)?/);
|
||||
assert.doesNotMatch(chain, /meta_items_secondary|article-reading-meta/);
|
||||
|
||||
@@ -13,7 +13,7 @@ for (const style of ['list', 'card', 'mini']) {
|
||||
test(`article ${style} widget renders its saved layout directly`, () => {
|
||||
const html = cases[`posts_${style}_detail`];
|
||||
assert.match(html, new RegExp(`class="widget-main-post mb20 style-${style}"`));
|
||||
assert.match(html, /data-widget="posts" data-instance="aksrt_widget_posts-3"/);
|
||||
assert.match(html, /data-widget="posts" data-instance="jio_widget_posts-3"/);
|
||||
assert.match(html, /data-loaded="1" aria-live="polite" aria-busy="false"/);
|
||||
assert.equal((html.match(/<h2 class="item-heading\b/g) || []).length, 4);
|
||||
assert.match(html, /data-widget-page="2"/);
|
||||
@@ -26,7 +26,7 @@ for (const style of ['list', 'card', 'mini']) {
|
||||
const html = cases[`posts_${style}_ajax`];
|
||||
assert.match(html, new RegExp(`class="widget-main-post mb20 style-${style}"`));
|
||||
assert.match(html, /class="widget-ajaxpager lazyload" data-widget-lazy/);
|
||||
assert.match(html, /data-widget="posts" data-instance="aksrt_widget_posts-3"/);
|
||||
assert.match(html, /data-widget="posts" data-instance="jio_widget_posts-3"/);
|
||||
assert.match(html, /aria-live="polite" aria-busy="true"/);
|
||||
assert.match(html, /class="post_ajax_loader is-loading" role="status"/);
|
||||
assert.match(html, new RegExp(`data-placeholder="${style}:4"`));
|
||||
|
||||
@@ -10,7 +10,7 @@ test('article TOC widget uses the shared reference tree styles, not posts-navs p
|
||||
const routeCss = read('assets/css/routes/posts-navs.css');
|
||||
const mainCss = read('assets/css/main.css');
|
||||
|
||||
assert.doesNotMatch(routeCss, /\.posts-nav-box|\.posts-nav-lists|\.aksrt-section-nav/);
|
||||
assert.doesNotMatch(routeCss, /\.posts-nav-box|\.posts-nav-lists|\.jio-section-nav/);
|
||||
for (const selector of [
|
||||
'.posts-nav-lists .bl',
|
||||
'.posts-nav-lists li.n-H1 a',
|
||||
@@ -35,6 +35,6 @@ test('mobile TOC panel and its collapse control remain styled and accessible', (
|
||||
assert.match(mainCss, /\.posts-nav-lists \.nav-toggle-collapse\s*\{[^}]*border:\s*0;[^}]*background:\s*transparent;/s);
|
||||
assert.match(mainCss, /\.posts-nav-box \.posts-nav-lists :where\(a, \.nav-toggle-collapse\):focus-visible/);
|
||||
assert.match(mainCss, /@media \(prefers-reduced-motion: reduce\)[\s\S]*?\.posts-nav-lists \.nav-toggle-collapse \{\s*transition:\s*none;/);
|
||||
assert.doesNotMatch(mainCss, /\.mobile-navbar \.posts-nav-box \.aksrt-panel/);
|
||||
assert.doesNotMatch(mainCss, /\.mobile-navbar \.posts-nav-box \.jio-panel/);
|
||||
assert.doesNotMatch(read('assets/widgets/frontend.css'), /\.posts-nav-box|\.posts-nav-lists/);
|
||||
});
|
||||
@@ -20,7 +20,7 @@ test('TOC widget renders one escaped title and asset eligibility follows saved a
|
||||
assert.match(fixture.widget, /^<div class="widget-container"><div data-affix="true" class="posts-nav-box"/);
|
||||
assert.equal((fixture.widget.match(/data-title=/g) || []).length, 1);
|
||||
assert.match(fixture.widget, /&amp; 标题/);
|
||||
assert.doesNotMatch(fixture.widget, /<aside>|class="title-theme"|aksrt-widget-shell/);
|
||||
assert.doesNotMatch(fixture.widget, /<aside>|class="title-theme"|jio-widget-shell/);
|
||||
});
|
||||
|
||||
test('article TOC renderer owns its reference container and title without the generic title wrapper', () => {
|
||||
@@ -45,12 +45,12 @@ test('TOC script enqueue follows article visibility settings and per-post overri
|
||||
assert.match(scripts, /empty\(\$extension_meta\['no_article-navs'\]\)/);
|
||||
assert.match(scripts, /\$requirements\['has_toc'\] = \$is_article && \$article_nav_enabled/);
|
||||
assert.doesNotMatch(scripts, /preg_match_all\('\/<h\[1-4\]/);
|
||||
assert.match(scripts, /\$singular_assets\['has_toc'\][\s\S]*?'aksrt-section-navs'/);
|
||||
assert.match(scripts, /\$singular_assets\['has_toc'\][\s\S]*?'jio-section-navs'/);
|
||||
|
||||
const pageScripts = read('src/Core/ScriptsPart1b.php');
|
||||
const pageBlockStart = pageScripts.indexOf("if (is_page_template('templates/pages/page-posts-navs.php'))");
|
||||
const pageBlockEnd = pageScripts.indexOf('// 文章归档页', pageBlockStart);
|
||||
const pageBlock = pageScripts.slice(pageBlockStart, pageBlockEnd);
|
||||
assert.match(pageBlock, /aksrt-posts-navs-page/);
|
||||
assert.doesNotMatch(pageBlock, /aksrt-section-navs|section-navs\.js/);
|
||||
assert.match(pageBlock, /jio-posts-navs-page/);
|
||||
assert.doesNotMatch(pageBlock, /jio-section-navs|section-navs\.js/);
|
||||
});
|
||||
@@ -2,13 +2,13 @@
|
||||
/** Exercise the production TOC widget and article asset resolver. */
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace AKSRT\Settings {
|
||||
namespace Jio\Settings {
|
||||
final class Settings {
|
||||
public static function get_comment_display_settings(): array { return []; }
|
||||
}
|
||||
}
|
||||
|
||||
namespace AKSRT\WidgetSystem {
|
||||
namespace Jio\WidgetSystem {
|
||||
final class AssetManager {
|
||||
public static function enqueue_for(WidgetDefinition $definition): void {}
|
||||
}
|
||||
@@ -21,32 +21,32 @@ namespace {
|
||||
function post_password_required(int $id): bool { return false; }
|
||||
function comments_open(int $id): bool { return false; }
|
||||
function get_comments_number(int $id): int { return 0; }
|
||||
function aksrt_resolve_single_post_display_settings(int $id): array { return $GLOBALS['toc_settings']; }
|
||||
function jio_resolve_single_post_display_settings(int $id): array { return $GLOBALS['toc_settings']; }
|
||||
class TocPost extends WP_Post { public string $post_content = '[dynamic_headings]'; }
|
||||
$GLOBALS['aksrt_test_state']['singular'] = true;
|
||||
$GLOBALS['aksrt_test_state']['current_id'] = 8;
|
||||
$GLOBALS['aksrt_test_state']['current_post'] = new TocPost(8);
|
||||
$GLOBALS['jio_test_state']['singular'] = true;
|
||||
$GLOBALS['jio_test_state']['current_id'] = 8;
|
||||
$GLOBALS['jio_test_state']['current_post'] = new TocPost(8);
|
||||
$GLOBALS['toc_settings'] = [];
|
||||
$GLOBALS['toc_meta'] = [];
|
||||
$resolver = new class {
|
||||
use \AKSRT\Core\ScriptsPart1c { resolve_singular_asset_requirements as public requirements; }
|
||||
use \Jio\Core\ScriptsPart1c { resolve_singular_asset_requirements as public requirements; }
|
||||
};
|
||||
$cases = ['dynamic_headings' => $resolver->requirements(true, false, false)['has_toc']];
|
||||
$GLOBALS['toc_settings'] = ['article_nav' => false];
|
||||
$cases['global_disabled'] = $resolver->requirements(true, false, false)['has_toc'];
|
||||
$GLOBALS['toc_settings'] = ['article_nav' => true];
|
||||
$GLOBALS['toc_meta'][\AKSRT\Core\DomainModel::POST_META_KEY] = ['no_article-navs' => true];
|
||||
$GLOBALS['toc_meta'][\Jio\Core\DomainModel::POST_META_KEY] = ['no_article-navs' => true];
|
||||
$cases['post_disabled'] = $resolver->requirements(true, false, false)['has_toc'];
|
||||
$GLOBALS['toc_meta'] = [];
|
||||
$cases['other_page'] = $resolver->requirements(false, false, false)['has_toc'];
|
||||
$definition = \AKSRT\WidgetSystem\WidgetRegistry::get('posts_toc');
|
||||
$definition = \Jio\WidgetSystem\WidgetRegistry::get('posts_toc');
|
||||
$cases['widget_fields'] = array_column($definition->fields(), 'id');
|
||||
$widget = new \AKSRT\WidgetSystem\SchemaWidget($definition);
|
||||
$widget = new \Jio\WidgetSystem\SchemaWidget($definition);
|
||||
$widget->_set(2);
|
||||
ob_start();
|
||||
$widget->widget(['before_widget' => '<aside>', 'after_widget' => '</aside>'], ['title' => '目录 & 标题', 'mini_title' => '副标题', 'in_affix' => true]);
|
||||
$cases['widget'] = ob_get_clean();
|
||||
$GLOBALS['aksrt_test_state']['singular'] = false;
|
||||
$GLOBALS['jio_test_state']['singular'] = false;
|
||||
ob_start();
|
||||
$widget->widget([], []);
|
||||
$cases['non_article_widget'] = ob_get_clean();
|
||||
|
||||
@@ -12,7 +12,7 @@ test('article list uses the reference widget container and keeps widget behavior
|
||||
const renderer = read('src/WidgetSystem/FrontendRenderer.php');
|
||||
const catalog = read('src/WidgetSystem/WidgetCatalog.php');
|
||||
assert.match(renderer, /\['user_card', 'author_card', 'tab_posts', 'posts'\]/);
|
||||
assert.match(renderer, /\$classes = \['zib-widget-wrap', 'aksrt-' \. str_replace/);
|
||||
assert.match(renderer, /\$classes = \['zib-widget-wrap', 'jio-' \. str_replace/);
|
||||
assert.match(renderer, /echo '<div class="widget-container"><div>'/);
|
||||
assert.match(renderer, /WidgetVisibility::device_class\(\$instance\)/);
|
||||
assert.match(renderer, /!empty\(\$instance\['sidebar_affix'\]\)/);
|
||||
@@ -32,6 +32,6 @@ test('article list pagination shares the reference widget rules and obsolete tri
|
||||
'.widget-tab-post .posts-mini, .widget-main-post .posts-mini',
|
||||
'.widget-tab-post .posts-mini-con, .widget-main-post .posts-mini-con',
|
||||
'.widget-tab-post .item-meta .meta-pay, .widget-main-post .item-meta .meta-pay',
|
||||
'.aksrt-posts-wrap :where(a, button, input, select, textarea, [tabindex]):focus-visible',
|
||||
'.jio-posts-wrap :where(a, button, input, select, textarea, [tabindex]):focus-visible',
|
||||
]) assert.ok(css.includes(token), `missing shared widget rule: ${token}`);
|
||||
});
|
||||
@@ -11,7 +11,7 @@ const read = (file) => fs.readFileSync(path.join(root, file), 'utf8');
|
||||
test('profile comments follow the author-page row and moderation contract', () => {
|
||||
const panel = read('templates/parts/user-home/panel-comments.php');
|
||||
const item = read('templates/parts/content-profile-comment-item.php');
|
||||
const dashboard = read('inc/helpers/aksrt-user-dashboard.php');
|
||||
const dashboard = read('inc/helpers/jio-user-dashboard.php');
|
||||
const userCenter = read('inc/helpers/user-center-renderers/profile-comments.php');
|
||||
const styles = read('assets/css/routes/profile/lists.css');
|
||||
|
||||
@@ -20,8 +20,8 @@ test('profile comments follow the author-page row and moderation contract', () =
|
||||
assert.match(panel, /'post_type'\s*=>\s*\['post'\]/);
|
||||
assert.match(panel, /'count'\s*=>\s*true/);
|
||||
assert.match(panel, /data-uh-comments-next=/);
|
||||
assert.match(panel, /<\?php else : \?>\s*<\?php aksrt_render_profile_empty\(/);
|
||||
assert.doesNotMatch(panel, /aksrt_render_user_home_pagination/);
|
||||
assert.match(panel, /<\?php else : \?>\s*<\?php jio_render_profile_empty\(/);
|
||||
assert.doesNotMatch(panel, /jio_render_user_home_pagination/);
|
||||
assert.match(dashboard, /'type'\s*=>\s*'comment'[\s\S]*?'post_type'\s*=>\s*\['post'\]/);
|
||||
assert.match(userCenter, /'type'\s*=>\s*'comment'[\s\S]*?'post_type'\s*=>\s*\['post'\]/);
|
||||
|
||||
|
||||
@@ -72,12 +72,12 @@ test('commission is credited immediately with no settlement freeze', () => {
|
||||
assert.doesNotMatch(attribution, /->stats\(/)
|
||||
|
||||
// 即时入账模型:不再有结算冻结期 / 结算 cron / settle_due 方法。
|
||||
assert.doesNotMatch(ledger, /aksrt_affiliate_settle_due'?, \[\$this, 'settle_due'\]/)
|
||||
assert.doesNotMatch(ledger, /wp_schedule_event\([^)]*aksrt_affiliate_settle_due/)
|
||||
assert.doesNotMatch(ledger, /jio_affiliate_settle_due'?, \[\$this, 'settle_due'\]/)
|
||||
assert.doesNotMatch(ledger, /wp_schedule_event\([^)]*jio_affiliate_settle_due/)
|
||||
assert.doesNotMatch(ledger, /function settle_due|function settle_one/)
|
||||
assert.doesNotMatch(ledger, /available_at|settled_at/)
|
||||
// 遗留结算 cron 需在 init() 主动清理。
|
||||
assert.match(ledger, /wp_unschedule_event\([^)]*aksrt_affiliate_settle_due/)
|
||||
assert.match(ledger, /wp_unschedule_event\([^)]*jio_affiliate_settle_due/)
|
||||
|
||||
// 下单支付即把佣金 credit 到 COMMISSION 资金池。
|
||||
assert.match(ledger, /AssetType::COMMISSION/)
|
||||
@@ -96,7 +96,7 @@ test('referral codes are self-serve: no admin ops page, granted at registration'
|
||||
|
||||
// 用户菜单不再挂"推广码运营"页,trait 文件已删除。
|
||||
const menus = read('src', 'Admin', 'Native', 'UsersMenus.php')
|
||||
assert.doesNotMatch(menus, /UsersMenusReferral|aksrt_referral_codes|render_referral_codes_page/)
|
||||
assert.doesNotMatch(menus, /UsersMenusReferral|jio_referral_codes|render_referral_codes_page/)
|
||||
assert.equal(fs.existsSync(path.join(root, 'src', 'Admin', 'Native', 'UsersMenusReferral.php')), false)
|
||||
|
||||
// admin SPA 不再保留跳转路由。
|
||||
|
||||
@@ -202,17 +202,17 @@ test('poster modal shell is hidden at rest so it cannot block the whole page', (
|
||||
|
||||
// 标记、JS 查询与 CSS 钩子必须共用同一个属性,缺一即失效:
|
||||
// 少了标记上的属性,JS 拿不到弹窗、CSS 也隐藏不了它。
|
||||
assert.match(modal, /data-aksrt-affiliate-poster-modal/)
|
||||
assert.match(script, /querySelector\('\[data-aksrt-affiliate-poster-modal\]'\)/)
|
||||
assert.match(modal, /data-jio-affiliate-poster-modal/)
|
||||
assert.match(script, /querySelector\('\[data-jio-affiliate-poster-modal\]'\)/)
|
||||
|
||||
// 服务端静态输出的 .modal.flex 必须显式隐藏。main.css 的 .flex{display:flex}
|
||||
// 后加载且与 bootstrap 的 .modal{display:none} 同特异性,不在此回位就会让
|
||||
// 静止态弹窗铺满全屏,叠加 .fade 的 opacity:0 形成隐形遮罩、拦截整页点击。
|
||||
assert.match(css, /\.modal\[data-aksrt-affiliate-poster-modal\]\s*\{\s*display:\s*none;/)
|
||||
assert.match(css, /\.modal\[data-aksrt-affiliate-poster-modal\]\.in\s*\{\s*display:\s*flex;/)
|
||||
assert.match(css, /\.modal\[data-jio-affiliate-poster-modal\]\s*\{\s*display:\s*none;/)
|
||||
assert.match(css, /\.modal\[data-jio-affiliate-poster-modal\]\.in\s*\{\s*display:\s*flex;/)
|
||||
// 圆形按钮必须回位成 pill。credits.css 的 .uc-content .but.jb-blue 是 (0,3,0)
|
||||
// 且后加载,会盖掉 main.css 的 .poster-share .but.cir,把蓝色下载钮压成方角。
|
||||
assert.match(css, /\.modal\[data-aksrt-affiliate-poster-modal\] \.but\.cir\s*\{[^}]*border-radius:\s*5em/s)
|
||||
assert.match(css, /\.modal\[data-jio-affiliate-poster-modal\] \.but\.cir\s*\{[^}]*border-radius:\s*5em/s)
|
||||
// 36×36 与投影仍由 main.css 的 .poster-share .but.cir 提供。
|
||||
const mainCss = read('assets', 'css', 'main.css')
|
||||
assert.match(mainCss, /\.poster-share \.but\.cir\s*\{[^}]*width:\s*36px/s)
|
||||
@@ -227,26 +227,26 @@ test('invite-code input verifies live against the public REST endpoint', () => {
|
||||
assert.match(script, /function initInviteVerify\(root\)/)
|
||||
assert.match(script, /input\[data-invite-verify\]/)
|
||||
assert.match(script, /initInviteVerify\(root\)/)
|
||||
assert.match(script, /\/wp-json\/aksrt\/v1\/invite-code\/verify\?code=/)
|
||||
assert.match(script, /\/wp-json\/jio\/v1\/invite-code\/verify\?code=/)
|
||||
})
|
||||
|
||||
test('affiliate posters declare the local QR dependency independently of withdrawal history', () => {
|
||||
const scripts = read('src', 'Core', 'ScriptsPart1b.php')
|
||||
assert.match(scripts, /assets\/js\/vendor\/qrcode\.min\.js/)
|
||||
assert.match(scripts, /'aksrt-qrcode'/)
|
||||
assert.match(scripts, /'jio-qrcode'/)
|
||||
// 海报模块显式依赖本地 QR;提现历史无需 QR。
|
||||
// credits.js 经收银台间接使用 QR,不能把这条支付依赖误认成海报依赖。
|
||||
assert.match(scripts, /\['credits-affiliate' => \['loader_js', 'aksrt-qrcode'\]\]/)
|
||||
assert.match(scripts, /'aksrt-credits'[\s\S]*?\['loader_js', 'aksrt-vip-coordinator', 'aksrt-pay-cashier'\]/)
|
||||
assert.doesNotMatch(scripts, /'credits-withdraw-history' => \['loader_js', 'aksrt-qrcode'\]/)
|
||||
assert.match(scripts, /\['credits-affiliate' => \['loader_js', 'jio-qrcode'\]\]/)
|
||||
assert.match(scripts, /'jio-credits'[\s\S]*?\['loader_js', 'jio-vip-coordinator', 'jio-pay-cashier'\]/)
|
||||
assert.doesNotMatch(scripts, /'credits-withdraw-history' => \['loader_js', 'jio-qrcode'\]/)
|
||||
})
|
||||
|
||||
test('client IP honours the trusted-proxy filter before CDN headers', () => {
|
||||
const captcha = read('inc', 'modules', 'captcha.php')
|
||||
assert.match(captcha, /apply_filters\('aksrt_trusted_proxies', \[\]\)/)
|
||||
assert.match(captcha, /function aksrt_ip_in_list\(string \$ip, array \$list\): bool/)
|
||||
assert.match(captcha, /apply_filters\('jio_trusted_proxies', \[\]\)/)
|
||||
assert.match(captcha, /function jio_ip_in_list\(string \$ip, array \$list\): bool/)
|
||||
// CIDR 比较必须先检查同族地址长度,避免跨族误判。
|
||||
assert.match(captcha, /strlen\(\$ip_binary\) !== strlen\(\$subnet_binary\)/)
|
||||
// 过滤命中失败时直接回落 REMOTE_ADDR,不读转发头。
|
||||
assert.match(captcha, /!aksrt_ip_in_list\(\$remote, \$trusted\)/)
|
||||
assert.match(captcha, /!jio_ip_in_list\(\$remote, \$trusted\)/)
|
||||
})
|
||||
@@ -1,10 +1,10 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* AksField 的 group / repeater 行身份稳定性契约。
|
||||
* JioField 的 group / repeater 行身份稳定性契约。
|
||||
*
|
||||
* 背景:inline 展开模式用 vuedraggable 渲染行,vuedraggable 的 computeNodes 会把
|
||||
* `node.key = getKey(element)`,而行 key 由 AksField 的 repeaterDragKey 提供。服务端
|
||||
* `node.key = getKey(element)`,而行 key 由 JioField 的 repeaterDragKey 提供。服务端
|
||||
* 历史行没有 _id,key 来自以「对象身份」为键的 WeakMap。
|
||||
*
|
||||
* 关键坑:行数据是 reactive 的。updateRepeaterItem 一旦把行展开成一个新对象,渲染时
|
||||
@@ -20,7 +20,7 @@ const test = require('node:test');
|
||||
|
||||
const root = path.resolve(__dirname, '..', '..');
|
||||
const fieldVue = fs.readFileSync(
|
||||
path.join(root, 'assets', 'admin', 'src', 'components', 'form', 'AksField.vue'),
|
||||
path.join(root, 'assets', 'admin', 'src', 'components', 'form', 'JioField.vue'),
|
||||
'utf8'
|
||||
);
|
||||
|
||||
@@ -47,7 +47,7 @@ function reactiveOf(value) {
|
||||
/** 取一整行声明(WeakMap / 种子计数器都没有花括号体)。 */
|
||||
function declaration(source, marker) {
|
||||
const start = source.indexOf(marker);
|
||||
assert.notStrictEqual(start, -1, `AksField.vue 缺少声明: ${marker}`);
|
||||
assert.notStrictEqual(start, -1, `JioField.vue 缺少声明: ${marker}`);
|
||||
const end = source.indexOf('\n', start);
|
||||
return source.slice(start, end === -1 ? undefined : end);
|
||||
}
|
||||
@@ -55,7 +55,7 @@ function declaration(source, marker) {
|
||||
/** 按花括号配平切出一个顶层函数(这些函数体内没有字符串/注释里的孤立花括号)。 */
|
||||
function functionBlock(source, marker) {
|
||||
const start = source.indexOf(marker);
|
||||
assert.notStrictEqual(start, -1, `AksField.vue 缺少函数: ${marker}`);
|
||||
assert.notStrictEqual(start, -1, `JioField.vue 缺少函数: ${marker}`);
|
||||
const open = source.indexOf('{', start);
|
||||
assert.notStrictEqual(open, -1, `函数没有函数体: ${marker}`);
|
||||
let depth = 0;
|
||||
|
||||
@@ -22,10 +22,10 @@ const userBanJs = read('assets/js/user-ban.js')
|
||||
const authorHero = read('templates/parts/user/author-hero.php')
|
||||
const banBlock = read('templates/parts/user/ban-block.php')
|
||||
const ucHeader = read('inc/helpers/user-center-renderers/profile-header.php')
|
||||
const commentsHelper = read('inc/helpers/aksrt-comments.php')
|
||||
const identityHelper = read('inc/helpers/aksrt-identity.php')
|
||||
const avatarHelper = read('inc/helpers/aksrt-avatar.php')
|
||||
const singleComponents = read('inc/helpers/aksrt-single-components.php')
|
||||
const commentsHelper = read('inc/helpers/jio-comments.php')
|
||||
const identityHelper = read('inc/helpers/jio-identity.php')
|
||||
const avatarHelper = read('inc/helpers/jio-avatar.php')
|
||||
const singleComponents = read('inc/helpers/jio-single-components.php')
|
||||
const banAppeal = read('src/User/BanAppeal.php')
|
||||
const mainCss = read('assets/css/main.css')
|
||||
const mainMinCss = read('assets/css/main.min.css')
|
||||
@@ -68,7 +68,7 @@ test('report center has no legacy ajax handlers or option migration', () => {
|
||||
assert.ok(!reportCenter.includes('ajax_counts'))
|
||||
assert.ok(!reportCenter.includes('migrate_from_option'))
|
||||
assert.ok(!reportCenter.includes('maybe_migrate'))
|
||||
assert.ok(!reportCenter.includes("get_option('aksrt_reports'"))
|
||||
assert.ok(!reportCenter.includes("get_option('jio_reports'"))
|
||||
assert.ok(!functions.includes('ReportCenter::get_instance()->init()'), 'ReportCenter init() is gone')
|
||||
assert.match(reportCenter, /public function has_pending_report/)
|
||||
assert.match(reportOps, /has_pending_report\(\$reporter_id, \$target_type, \$target_id\)/)
|
||||
@@ -79,8 +79,8 @@ test('report processing delegates actions to the shared ReportCenter entry', ()
|
||||
assert.match(reportCenter, /public function apply_action\(/)
|
||||
assert.match(reportCenter, /->restrict\(\s*\$target_id,\s*\$action_taken,/)
|
||||
assert.match(reportCenter, /lift_user_restriction\(\$target_id\)/)
|
||||
assert.match(reportCenter, /in_array\(\$action_taken, \\AKSRT\\User\\Restriction::TYPES, true\)/)
|
||||
assert.ok(!reportCenter.includes('aksrt_user_ban_state'), 'dual-track ban state meta must not be written or read')
|
||||
assert.match(reportCenter, /in_array\(\$action_taken, \\Jio\\User\\Restriction::TYPES, true\)/)
|
||||
assert.ok(!reportCenter.includes('jio_user_ban_state'), 'dual-track ban state meta must not be written or read')
|
||||
for (const deadAction of ["'value' => 'warned'", "'value' => 'muted'", "'value' => 'restricted'", "'value' => 'banned'"]) {
|
||||
assert.ok(!reportOps.includes(deadAction), `action value must map to Restriction types: ${deadAction}`)
|
||||
}
|
||||
@@ -96,7 +96,7 @@ test('restriction class supports lifting and kicks sessions on ban', () => {
|
||||
assert.match(restriction, /public function lift_user_restriction\(int \$user_id, array \$types = \[self::TYPE_BAN, self::TYPE_SUSPEND, self::TYPE_MUTE\]\): int/)
|
||||
assert.match(restriction, /WP_Session_Tokens::get_instance\(\$user_id\)->destroy_all\(\)/)
|
||||
assert.ok(!restriction.includes('sync_user_restriction_meta'), 'write-only meta sync is dead code')
|
||||
assert.ok(!restriction.includes('_aksrt_is_banned'))
|
||||
assert.ok(!restriction.includes('_jio_is_banned'))
|
||||
})
|
||||
|
||||
test('ban admin REST endpoints stay in sync with the SPA client', () => {
|
||||
@@ -162,22 +162,22 @@ test('ban appeal approval actually lifts restrictions', () => {
|
||||
test('ban identity badge occupies the public profile meta row', () => {
|
||||
// 公开主页:禁封状态占据身份行元信息位(参考主题 author_header_identity),
|
||||
// 不再是资料卡下方提示框(notice 链已移除)。
|
||||
assert.match(userBanHelper, /function aksrt_get_user_ban_identity_badge/)
|
||||
assert.match(authorHero, /aksrt_get_user_ban_identity_badge\(\$user_id\)/)
|
||||
assert.match(authorHero, /aksrt-author-hero__identity/)
|
||||
assert.ok(!authorHero.includes('aksrt-author-hero__notice'), 'profile notice box must stay removed')
|
||||
assert.match(userBanHelper, /function jio_get_user_ban_identity_badge/)
|
||||
assert.match(authorHero, /jio_get_user_ban_identity_badge\(\$user_id\)/)
|
||||
assert.match(authorHero, /jio-author-hero__identity/)
|
||||
assert.ok(!authorHero.includes('jio-author-hero__notice'), 'profile notice box must stay removed')
|
||||
assert.ok(!authorHero.includes('profile_notice'), 'dashboard must not carry profile_notice anymore')
|
||||
assert.ok(!home.includes('get_profile_notice'), 'Home::get_profile_notice must stay removed')
|
||||
assert.ok(!mainCss.includes('aksrt-author-hero__notice'), 'notice box CSS must stay removed')
|
||||
assert.ok(!mainCss.includes('jio-author-hero__notice'), 'notice box CSS must stay removed')
|
||||
// 徽章字段(文案/颜色/图标)由 Restriction::state_badges() 统一管理。
|
||||
assert.match(userBanHelper, /state_badges\(\)/)
|
||||
// 死配置清理:公开提示开关与提示文案键不再进入运行时。
|
||||
assert.ok(!userRuntime.includes('public_notice'), 'dead public_notice setting must stay removed')
|
||||
assert.ok(!userRuntime.includes('ban_notice_text'), 'dead ban_notice_text setting must stay removed')
|
||||
// 用户中心元信息入口:but 按钮(徽章 + 箭头)→ 查看禁封状态弹窗。
|
||||
assert.match(userBanHelper, /function aksrt_get_user_ban_info_link/)
|
||||
assert.match(userBanHelper, /data-aksrt-ban-info=/)
|
||||
assert.match(ucHeader, /aksrt_get_user_ban_info_link/)
|
||||
assert.match(userBanHelper, /function jio_get_user_ban_info_link/)
|
||||
assert.match(userBanHelper, /data-jio-ban-info=/)
|
||||
assert.match(ucHeader, /jio_get_user_ban_info_link/)
|
||||
})
|
||||
|
||||
test('governance display state is a single source shared by every prompt surface', () => {
|
||||
@@ -188,9 +188,9 @@ test('governance display state is a single source shared by every prompt surface
|
||||
assert.match(restriction, /public function get_display_state\(int \$user_id\): string/)
|
||||
assert.match(restriction, /public static function state_badges\(\): array/)
|
||||
// 颜色统一管理:封号=红、小黑屋=黄、禁言=灰。
|
||||
assert.match(restriction, /'label' => __\('违规已封号', 'aksrt'\),\s*'tone' => 'c-red'/)
|
||||
assert.match(restriction, /'label' => __\('小黑屋禁封中', 'aksrt'\),\s*'tone' => 'c-yellow'/)
|
||||
assert.match(restriction, /'label' => __\('禁言中', 'aksrt'\),\s*'tone' => 'c-gray'/)
|
||||
assert.match(restriction, /'label' => __\('违规已封号', 'jio'\),\s*'tone' => 'c-red'/)
|
||||
assert.match(restriction, /'label' => __\('小黑屋禁封中', 'jio'\),\s*'tone' => 'c-yellow'/)
|
||||
assert.match(restriction, /'label' => __\('禁言中', 'jio'\),\s*'tone' => 'c-gray'/)
|
||||
// 禁封信息行单一数据源(查看禁封状态弹窗/申诉弹窗/禁封页/管理端弹窗共用)。
|
||||
assert.match(restriction, /public function get_ban_info\(int \$user_id\): array/)
|
||||
assert.match(restriction, /'status'\s*=>/)
|
||||
@@ -202,16 +202,16 @@ test('governance display state is a single source shared by every prompt surface
|
||||
|
||||
test('ban prompt surface mirrors the reference theme modals and block page', () => {
|
||||
// 查看禁封状态弹窗(1:1 zib_get_ban_info_modal:多彩头 + 信息行 + 申诉按钮)。
|
||||
assert.match(userBanHelper, /function aksrt_get_user_ban_info_modal/)
|
||||
assert.match(userBanHelper, /aksrt-user-ban-info-modal/)
|
||||
assert.match(userBanHelper, /function jio_get_user_ban_info_modal/)
|
||||
assert.match(userBanHelper, /jio-user-ban-info-modal/)
|
||||
assert.match(userBanHelper, /您已解封,请刷新页面/)
|
||||
assert.match(userBanHelper, /您当前已无法进行申诉/)
|
||||
// 禁封申诉弹窗(1:1 zib_get_user_ban_appeal_modal:edit-header + 信息框 +
|
||||
// 资料项 inputs + 审核中态 + 禁止申诉态;仅本人)。
|
||||
assert.match(userBanHelper, /function aksrt_get_user_ban_appeal_modal/)
|
||||
assert.match(userBanHelper, /aksrt-user-ban-appeal-modal/)
|
||||
assert.match(userBanHelper, /function aksrt_get_user_ban_appeal_link/)
|
||||
assert.match(userBanHelper, /data-aksrt-ban-appeal=/)
|
||||
assert.match(userBanHelper, /function jio_get_user_ban_appeal_modal/)
|
||||
assert.match(userBanHelper, /jio-user-ban-appeal-modal/)
|
||||
assert.match(userBanHelper, /function jio_get_user_ban_appeal_link/)
|
||||
assert.match(userBanHelper, /data-jio-ban-appeal=/)
|
||||
assert.match(userBanHelper, /抱歉!您当前已无法进行申诉/)
|
||||
assert.match(userBanHelper, /您的申诉正在审核中,请耐心等待/)
|
||||
assert.match(userBanHelper, /name="data\[/)
|
||||
@@ -220,7 +220,7 @@ test('ban prompt surface mirrors the reference theme modals and block page', ()
|
||||
// 说明框保持 rows=3 自然高度(禁封弹窗留言框才有 autoHeight/maxHeight)。
|
||||
assert.match(userBanHelper, /scroll-y no-scrollbar max-vh5/)
|
||||
assert.ok(!userBanHelper.includes('mini-scrollbar'), 'ban modals must not show any scrollbar (thin included)')
|
||||
const appealModalFn = userBanHelper.slice(userBanHelper.indexOf('function aksrt_get_user_ban_appeal_modal'))
|
||||
const appealModalFn = userBanHelper.slice(userBanHelper.indexOf('function jio_get_user_ban_appeal_modal'))
|
||||
const appealModalBody = appealModalFn.slice(0, appealModalFn.indexOf('\n}'))
|
||||
const appealTextarea = (appealModalBody.match(/<textarea[^>]*>/) || [''])[0]
|
||||
assert.ok(appealTextarea !== '' && appealTextarea.includes('rows="3"'), 'appeal textarea keeps natural rows=3 height')
|
||||
@@ -229,7 +229,7 @@ test('ban prompt surface mirrors the reference theme modals and block page', ()
|
||||
// JS:autoHeight 仅对显式声明的文本框生效。
|
||||
assert.match(userBanJs, /querySelectorAll\('textarea\[autoHeight\], textarea\[maxHeight\]'\)/)
|
||||
// 用户中心元信息按钮:外层 but 不重复状态色,颜色仅由内层 badg 承载。
|
||||
const infoLinkFn = userBanHelper.slice(userBanHelper.indexOf('function aksrt_get_user_ban_info_link'))
|
||||
const infoLinkFn = userBanHelper.slice(userBanHelper.indexOf('function jio_get_user_ban_info_link'))
|
||||
const infoLinkBody = infoLinkFn.slice(0, infoLinkFn.indexOf('\n}'))
|
||||
assert.match(infoLinkBody, /\$classes = trim\('but p2-10'\s*\./)
|
||||
assert.ok(!infoLinkBody.includes("'but p2-10 ' . (string) \$state['tone']"), 'outer but must not repeat the badge tone')
|
||||
@@ -237,49 +237,49 @@ test('ban prompt surface mirrors the reference theme modals and block page', ()
|
||||
assert.match(banAppeal, /is_array\(\$_POST\['data'\]\)/)
|
||||
assert.match(banAppeal, /申诉说明/)
|
||||
// 禁封记录弹窗 + 记录链接(1:1 zib_get_ban_log_modal / zib_get_user_ban_log_link)。
|
||||
assert.match(userBanHelper, /function aksrt_get_user_ban_log_modal/)
|
||||
assert.match(userBanHelper, /function aksrt_get_user_ban_log_link/)
|
||||
assert.match(userBanHelper, /data-aksrt-ban-log=/)
|
||||
assert.match(userBanHelper, /function jio_get_user_ban_log_modal/)
|
||||
assert.match(userBanHelper, /function jio_get_user_ban_log_link/)
|
||||
assert.match(userBanHelper, /data-jio-ban-log=/)
|
||||
assert.match(userBanHelper, /当前用户已有%d次禁封记录,请谨慎操作/)
|
||||
assert.match(userBanHelper, /该用户共有%d次禁封记录/)
|
||||
// 登录后禁封状态页(1:1 zib_user_ban_restrict_login / zib_die_page)。
|
||||
assert.match(userBanHelper, /function aksrt_render_user_ban_block_page/)
|
||||
assert.match(userBanHelper, /function jio_render_user_ban_block_page/)
|
||||
assert.match(banBlock, /抱歉,您的账号当前处于禁封状态,暂时无法登录/)
|
||||
assert.match(banBlock, /退出登录/)
|
||||
assert.match(banBlock, /aksrt_get_user_ban_appeal_link/)
|
||||
assert.match(banBlock, /jio_get_user_ban_appeal_link/)
|
||||
for (const row of ['用户名', '状态', '开始时间', '结束时间', '原因', '说明', '申诉权限']) {
|
||||
assert.ok(banBlock.includes(`__('${row}', 'aksrt')`), `ban block page missing row: ${row}`)
|
||||
assert.ok(banBlock.includes(`__('${row}', 'jio')`), `ban block page missing row: ${row}`)
|
||||
}
|
||||
// Restriction 以 template_redirect 收口(不禁登录)。
|
||||
assert.match(restriction, /add_action\('template_redirect', \[\$this, 'restrict_frontend_access'\], 1\)/)
|
||||
assert.match(restriction, /public function restrict_frontend_access\(\): void/)
|
||||
assert.ok(!restriction.includes('enforce_login_ban'), 'login-time ban block must stay removed')
|
||||
// 评论拒绝态(参考主题 zib_get_user_ban_nocan_info)。
|
||||
assert.match(userBanHelper, /function aksrt_get_user_ban_nocan_html/)
|
||||
assert.match(commentsHelper, /aksrt_get_user_ban_nocan_html/)
|
||||
assert.match(userBanHelper, /function jio_get_user_ban_nocan_html/)
|
||||
assert.match(commentsHelper, /jio_get_user_ban_nocan_html/)
|
||||
assert.match(commentsHelper, /暂时无法发表评论/)
|
||||
// 昵称徽章 + 头像角标(参考主题 user_name_badge / user_avatar_badge)。
|
||||
assert.match(userBanHelper, /function aksrt_get_user_ban_name_badge/)
|
||||
assert.match(userBanHelper, /function aksrt_get_user_ban_avatar_badge/)
|
||||
assert.match(identityHelper, /aksrt_get_user_ban_name_badge\(\$user_id\)/)
|
||||
assert.match(avatarHelper, /aksrt_get_user_ban_avatar_badge\(\$user_id\)/)
|
||||
assert.match(userBanHelper, /function jio_get_user_ban_name_badge/)
|
||||
assert.match(userBanHelper, /function jio_get_user_ban_avatar_badge/)
|
||||
assert.match(identityHelper, /jio_get_user_ban_name_badge\(\$user_id\)/)
|
||||
assert.match(avatarHelper, /jio_get_user_ban_avatar_badge\(\$user_id\)/)
|
||||
// 三个弹窗 REST 路由(modal-mini 几何 / 每次刷新)。
|
||||
for (const route of ['/user-ban/info-modal', '/user-ban/appeal-modal', '/user-ban/log-modal']) {
|
||||
assert.ok(userController.includes(`'${route}'`), `missing REST route: ${route}`)
|
||||
}
|
||||
// 前端委托与 admin-ajax 提交。
|
||||
for (const attr of ['data-aksrt-ban-info', 'data-aksrt-ban-appeal', 'data-aksrt-ban-log']) {
|
||||
for (const attr of ['data-jio-ban-info', 'data-jio-ban-appeal', 'data-jio-ban-log']) {
|
||||
assert.ok(userBanJs.includes(attr), `missing JS delegation: ${attr}`)
|
||||
}
|
||||
assert.match(userBanJs, /aksrt_ban_appeal_submit/)
|
||||
assert.match(userBanJs, /jio_ban_appeal_submit/)
|
||||
assert.match(userBanJs, /new FormData\(form\)/)
|
||||
// modal-mini 几何(400px)与头部样式覆盖三个新弹窗类。
|
||||
for (const cls of ['aksrt-user-ban-info-modal', 'aksrt-user-ban-appeal-modal', 'aksrt-user-ban-log-modal']) {
|
||||
for (const cls of ['jio-user-ban-info-modal', 'jio-user-ban-appeal-modal', 'jio-user-ban-log-modal']) {
|
||||
assert.ok(modalRuntimeCss.includes(cls), `missing modal geometry for ${cls}`)
|
||||
}
|
||||
// 头像角标样式:源文件与入队的 min 产物同步。
|
||||
for (const css of [mainCss, mainMinCss]) {
|
||||
assert.ok(css.includes('aksrt-avatar-ban-badge'), 'ban avatar badge CSS must exist in both main.css and main.min.css')
|
||||
assert.ok(css.includes('jio-avatar-ban-badge'), 'ban avatar badge CSS must exist in both main.css and main.min.css')
|
||||
}
|
||||
// 申诉页面模板与专属资产已移除(死代码清理)。
|
||||
assert.ok(!templatesMap.includes('page-ban-appeal'), 'ban appeal page template registration must stay removed')
|
||||
@@ -288,10 +288,10 @@ test('ban prompt surface mirrors the reference theme modals and block page', ()
|
||||
|
||||
test('report entry hides targets already banned or suspended', () => {
|
||||
// 参考主题 zib_get_report_link:已封禁/小黑屋用户不展示举报入口(禁言不隐藏)。
|
||||
assert.match(userBanHelper, /function aksrt_is_user_ban_restricted/)
|
||||
assert.match(commentItem, /aksrt_is_user_ban_restricted\(\$author_id\)/)
|
||||
assert.match(singleComponents, /aksrt_is_user_ban_restricted\(\$post_author_id\)/)
|
||||
assert.match(authorHero, /aksrt_is_user_ban_restricted\(\$user_id\)/)
|
||||
assert.match(userBanHelper, /function jio_is_user_ban_restricted/)
|
||||
assert.match(commentItem, /jio_is_user_ban_restricted\(\$author_id\)/)
|
||||
assert.match(singleComponents, /jio_is_user_ban_restricted\(\$post_author_id\)/)
|
||||
assert.match(authorHero, /jio_is_user_ban_restricted\(\$user_id\)/)
|
||||
})
|
||||
|
||||
test('banned users may log in and meet the frontend block page', () => {
|
||||
@@ -313,18 +313,18 @@ test('banned users may log in and meet the frontend block page', () => {
|
||||
// Toast 透传(一般性修复,保留):REST 层不得把失败原因硬编码为凭证错误。
|
||||
assert.match(phoneOps, /\$result\['error'\]/)
|
||||
assert.ok(
|
||||
phoneOps.includes("$login_error !== '' ? $login_error : __('账号或密码错误', 'aksrt')"),
|
||||
phoneOps.includes("$login_error !== '' ? $login_error : __('账号或密码错误', 'jio')"),
|
||||
'auth_login must pass through the real failure reason'
|
||||
)
|
||||
})
|
||||
|
||||
test('admin report notification links to the real page', () => {
|
||||
assert.match(mailNotifications, /users\.php\?page=aksrt_user_ban'/)
|
||||
assert.ok(!mailNotifications.includes('page=aksrt_reports&'), 'dead admin page link')
|
||||
assert.match(mailNotifications, /users\.php\?page=jio_user_ban'/)
|
||||
assert.ok(!mailNotifications.includes('page=jio_reports&'), 'dead admin page link')
|
||||
assert.ok(!mailNotifications.includes('tab=reports'), 'merged ticket page has no reports tab')
|
||||
// 处理说明必须随 aksrt_report_status_changed 传入邮件载荷(此前硬编码空串,
|
||||
// 处理说明必须随 jio_report_status_changed 传入邮件载荷(此前硬编码空串,
|
||||
// 举报人收到的“说明:”永远为空)。
|
||||
assert.match(reportCenter, /do_action\('aksrt_report_status_changed', \$report_id[\s\S]*?new_status, \$note_clean\)/)
|
||||
assert.match(reportCenter, /do_action\('jio_report_status_changed', \$report_id[\s\S]*?new_status, \$note_clean\)/)
|
||||
assert.match(mailNotifications, /function report_status_changed\(int \$report_id, int \$reporter_id, string \$status, string \$note = ''\)/)
|
||||
assert.match(mailNotifications, /'note'\s*=>\s*\$note !== '' \? \$note :/)
|
||||
// 举报通知必须入站内消息系统:提交→管理员(直达工单列表)、
|
||||
@@ -332,7 +332,7 @@ test('admin report notification links to the real page', () => {
|
||||
assert.match(reportCenter, /notify_admins_new_report\(/)
|
||||
assert.match(reportCenter, /notify_reporter_status_changed\(/)
|
||||
assert.match(reportCenter, /Message::get_instance\(\)->send\(/)
|
||||
assert.match(reportCenter, /users\.php\?page=aksrt_user_ban&type=report&status=pending/)
|
||||
assert.match(reportCenter, /users\.php\?page=jio_user_ban&type=report&status=pending/)
|
||||
})
|
||||
|
||||
test('native ban page uses the scoped template-and-dialog admin flow', () => {
|
||||
@@ -344,8 +344,8 @@ test('native ban page uses the scoped template-and-dialog admin flow', () => {
|
||||
const banCss = read('assets/css/admin/user-ban.css');
|
||||
|
||||
assert.match(usersMenus, /use UsersMenusBan;/);
|
||||
assert.match(usersMenus, /admin_post_aksrt_users_update_report/);
|
||||
assert.match(usersMenus, /admin_post_aksrt_users_process_appeal/);
|
||||
assert.match(usersMenus, /admin_post_jio_users_update_report/);
|
||||
assert.match(usersMenus, /admin_post_jio_users_process_appeal/);
|
||||
assert.doesNotMatch(usersMenus, /use UsersMenusTickets;/, 'ban review must not use the deleted ticket renderer');
|
||||
assert.ok(!fs.existsSync(path.join(root, 'src/Admin/Native/UsersMenusTickets.php')));
|
||||
|
||||
@@ -357,17 +357,17 @@ test('native ban page uses the scoped template-and-dialog admin flow', () => {
|
||||
assert.match(banQuery, /array_slice\(\$filtered, \(\$page - 1\) \* self::BAN_PER_PAGE, self::BAN_PER_PAGE\)/);
|
||||
assert.match(banQuery, /'counts'\s*=>\s*\$counts/);
|
||||
|
||||
assert.match(banPage, /if \(\$hook !== 'users_page_aksrt_user_ban'\) return/);
|
||||
assert.match(banPage, /wp_enqueue_style\('aksrt-admin-user-ban'.*NativeUi::STYLE_HANDLE/);
|
||||
assert.match(banPage, /wp_enqueue_script\('aksrt-admin-user-ban'/);
|
||||
assert.match(banPage, /<dialog id="aksrt-ban-dialog"/);
|
||||
assert.match(banDialogs, /<template id="aksrt-ban-detail-/);
|
||||
assert.match(banPage, /data-aksrt-ban-view=/);
|
||||
assert.match(banPage, /if \(\$hook !== 'users_page_jio_user_ban'\) return/);
|
||||
assert.match(banPage, /wp_enqueue_style\('jio-admin-user-ban'.*NativeUi::STYLE_HANDLE/);
|
||||
assert.match(banPage, /wp_enqueue_script\('jio-admin-user-ban'/);
|
||||
assert.match(banPage, /<dialog id="jio-ban-dialog"/);
|
||||
assert.match(banDialogs, /<template id="jio-ban-detail-/);
|
||||
assert.match(banPage, /data-jio-ban-view=/);
|
||||
assert.doesNotMatch(banPage, /update_user_meta|update_status|do_action\(/);
|
||||
assert.doesNotMatch(banPage, /data-aksrt-ticket-toggle|data-aksrt-ticket-cancel|data-aksrt-ban-toggle/);
|
||||
assert.doesNotMatch(banPage, /data-jio-ticket-toggle|data-jio-ticket-cancel|data-jio-ban-toggle/);
|
||||
|
||||
assert.match(banDialogs, /if \(\$pending\) \{[\s\S]*?<form method="post"/);
|
||||
assert.match(banDialogs, /else \{[\s\S]*?<div class="aksrt-ban-dialog__frame">/);
|
||||
assert.match(banDialogs, /else \{[\s\S]*?<div class="jio-ban-dialog__frame">/);
|
||||
assert.match(banDialogs, /'action'\s*=>\s*\$action/);
|
||||
assert.match(banDialogs, /record_version/);
|
||||
assert.match(banDialogs, /wp_nonce_field\(\$action \. '_' \. \(int\) \$row\['id'\]\)/);
|
||||
@@ -382,8 +382,8 @@ test('native ban page uses the scoped template-and-dialog admin flow', () => {
|
||||
for (const method of ['handle_update_report', 'handle_process_appeal']) {
|
||||
assert.match(banActions, new RegExp(`public function ${method}\\(\\): void`));
|
||||
}
|
||||
assert.match(banActions, /check_admin_referer\('aksrt_users_update_report_'/);
|
||||
assert.match(banActions, /check_admin_referer\('aksrt_users_process_appeal_'/);
|
||||
assert.match(banActions, /check_admin_referer\('jio_users_update_report_'/);
|
||||
assert.match(banActions, /check_admin_referer\('jio_users_process_appeal_'/);
|
||||
assert.match(banActions, /hash_equals\(\$this->ban_record_version\(\$row\), \$submitted\)/);
|
||||
assert.match(banActions, /\$this->ban_request_is_current\(\$row, 'pending'\)/);
|
||||
assert.match(banActions, /\$new_status === 'dismissed' && \$note === ''/);
|
||||
@@ -394,7 +394,7 @@ test('native ban page uses the scoped template-and-dialog admin flow', () => {
|
||||
assert.match(banJs, /window\.addEventListener\('pageshow'/);
|
||||
assert.match(banJs, /previousOpener\.focus\(\)/);
|
||||
assert.match(banJs, /if \(!submitting\) dialog\.close\(\)/);
|
||||
assert.match(banCss, /html\.aksrt-ban-modal-open \{ overflow: hidden/);
|
||||
assert.match(banCss, /html\.jio-ban-modal-open \{ overflow: hidden/);
|
||||
});
|
||||
test('report modal uses accessible reasons, automatic target association and optional evidence', () => {
|
||||
const scripts = read('src/Core/ScriptsPart1c.php')
|
||||
@@ -404,10 +404,10 @@ test('report modal uses accessible reasons, automatic target association and opt
|
||||
assert.match(scripts, /report_settings\(\)/)
|
||||
|
||||
const reportJs = read('assets/js/report.js')
|
||||
for (const part of ['aksrt-report-target', 'name="report_reason"', 'type="radio"', '其他原因', '补充说明', '图片举证', '提交举报', '关联对象:']) {
|
||||
for (const part of ['jio-report-target', 'name="report_reason"', 'type="radio"', '其他原因', '补充说明', '图片举证', '提交举报', '关联对象:']) {
|
||||
assert.ok(reportJs.includes(part), `report.js missing form contract: ${part}`)
|
||||
}
|
||||
assert.doesNotMatch(reportJs, /report_url|违规链接|data-aksrt-report-reason=|bindAutoHeight/)
|
||||
assert.doesNotMatch(reportJs, /report_url|违规链接|data-jio-report-reason=|bindAutoHeight/)
|
||||
for (const source of [reportJs, commentItem, authorHero]) assert.doesNotMatch(source, /target_avatar/)
|
||||
assert.match(reportJs, /description\) content\.push\(description\)/)
|
||||
assert.doesNotMatch(reportJs, /category\.slug|reason = category/)
|
||||
@@ -415,37 +415,37 @@ test('report modal uses accessible reasons, automatic target association and opt
|
||||
assert.match(reportCenter, /'category'\s*=>\s*\$category/)
|
||||
assert.doesNotMatch(reportCenter, /sanitize_key\(\$category\)/)
|
||||
assert.match(reportJs, /report\/upload-image/)
|
||||
assert.match(reportJs, /window\.AksrtModalRuntime\.getActive\(\) === shell/)
|
||||
assert.match(reportJs, /window\.JioModalRuntime\.getActive\(\) === shell/)
|
||||
assert.match(userController, /'\/report\/upload-image'/)
|
||||
|
||||
const css = read('assets/css/frontend/report.css')
|
||||
assert.match(css, /\.aksrt-report-modal:not\(\.aksrt-modal-runtime--bottom\) \.aksrt-modal-runtime__dialog/)
|
||||
assert.match(css, /\.jio-report-modal:not\(\.jio-modal-runtime--bottom\) \.jio-modal-runtime__dialog/)
|
||||
assert.match(css, /600px/)
|
||||
assert.match(css, /:focus-visible/)
|
||||
assert.match(css, /\.aksrt-report-reason:has\(input:checked\)/)
|
||||
assert.match(css, /\.jio-report-reason:has\(input:checked\)/)
|
||||
assert.match(css, /clip-path: inset\(50%\)/)
|
||||
assert.match(css, /text-overflow: ellipsis/)
|
||||
assert.doesNotMatch(reportJs, /aksrt-report-target__icon|无需填写链接|正在提交举报,请稍候/)
|
||||
assert.doesNotMatch(reportJs, /jio-report-target__icon|无需填写链接|正在提交举报,请稍候/)
|
||||
assert.match(css, /safe-area-inset-bottom/)
|
||||
assert.match(css, /\[hidden\] \{ display: none !important;/)
|
||||
assert.doesNotMatch(modalRuntimeCss, /aksrt-report-modal/, 'retired report styles must not remain in the shared stylesheet')
|
||||
assert.doesNotMatch(modalRuntimeCss, /jio-report-modal/, 'retired report styles must not remain in the shared stylesheet')
|
||||
assert.match(scriptsPart1b, /assets\/css\/frontend\/report\.css/)
|
||||
})
|
||||
|
||||
test('comments expose a report entry for other users when reporting is enabled', () => {
|
||||
// 举报位于“回复”右侧三点下拉菜单内(参考主题同位置),载荷走 wp_json_encode。
|
||||
assert.match(commentItem, /data-aksrt-report="/)
|
||||
assert.match(commentItem, /function aksrt_get_comment_report_html/)
|
||||
assert.match(commentItem, /data-jio-report="/)
|
||||
assert.match(commentItem, /function jio_get_comment_report_html/)
|
||||
assert.match(commentItem, /comment-report-link/)
|
||||
assert.match(commentItem, /report'\]\['enabled'\]/)
|
||||
assert.match(commentItem, /\$author_id === \$reporter_id/)
|
||||
// 下拉菜单(置顶/编辑/批准/删除 + 禁封或举报)末项走禁封优先、否则举报。
|
||||
assert.match(commentItem, /function aksrt_get_comments_action_dropdown_html[\s\S]*?\$lists \.= aksrt_get_comment_ban_or_report_item/)
|
||||
assert.match(commentItem, /function jio_get_comments_action_dropdown_html[\s\S]*?\$lists \.= jio_get_comment_ban_or_report_item/)
|
||||
// 禁封项(管理员可见):参考主题 comments_action_lists 钩子语义,渲染于 user-ban.php。
|
||||
assert.match(userBanHelper, /comment-ban-link/)
|
||||
assert.match(userBanHelper, /data-aksrt-ban="/)
|
||||
assert.match(userBanHelper, /data-jio-ban="/)
|
||||
// 举报不作为 comt-meta 独立按钮直接渲染。
|
||||
assert.doesNotMatch(commentItem, /\$report\s*=\s*aksrt_get_comment_report_html\(\$comment\)/)
|
||||
assert.doesNotMatch(commentItem, /\$report\s*=\s*jio_get_comment_report_html\(\$comment\)/)
|
||||
})
|
||||
|
||||
test('frontend ban modal mirrors the reference theme zib_get_set_user_ban_modal 1:1', () => {
|
||||
@@ -459,21 +459,21 @@ test('frontend ban modal mirrors the reference theme zib_get_set_user_ban_modal
|
||||
assert.ok(restriction.includes(label), `ban status label missing from Restriction: ${label}`)
|
||||
}
|
||||
// 时长选项 1/7/14/30/100/永久(默认 7 天);类型映射:小黑屋→suspend,禁封→ban。
|
||||
assert.match(userBanHelper, /1 => __\('1天', 'aksrt'\)/)
|
||||
assert.match(userBanHelper, /7 => __\('7天', 'aksrt'\)/)
|
||||
assert.match(userBanHelper, /100 => __\('100天', 'aksrt'\)/)
|
||||
assert.match(userBanHelper, /1 => __\('1天', 'jio'\)/)
|
||||
assert.match(userBanHelper, /7 => __\('7天', 'jio'\)/)
|
||||
assert.match(userBanHelper, /100 => __\('100天', 'jio'\)/)
|
||||
assert.match(userBanHelper, /7 === \$days \? ' checked="checked"' : ''/)
|
||||
// 预置原因来自 UserRuntime::ban_settings(与后台同一数据源)。
|
||||
assert.match(userBanHelper, /UserRuntime::ban_settings\(\)\['reasons'\]/)
|
||||
// 勾选禁止申诉必须填写说明(参考主题同校验),落用户 meta 供申诉入口拦截。
|
||||
assert.match(userBanHelper, /_aksrt_no_appeal|no_appeal/)
|
||||
assert.match(userBanHelper, /_jio_no_appeal|no_appeal/)
|
||||
const appeal = read('src/User/BanAppeal.php')
|
||||
assert.match(appeal, /_aksrt_no_appeal/)
|
||||
// 弹窗入口按钮契约:[data-aksrt-ban] 委托(封面按钮 / 评论菜单项 / 文章作者菜单项)。
|
||||
assert.match(userBanHelper, /data-aksrt-ban="/)
|
||||
assert.match(userBanHelper, /function aksrt_get_user_cover_menu_action_html/)
|
||||
assert.match(userBanHelper, /function aksrt_get_comment_ban_or_report_item/)
|
||||
assert.match(userBanHelper, /function aksrt_get_post_author_ban_item/)
|
||||
assert.match(appeal, /_jio_no_appeal/)
|
||||
// 弹窗入口按钮契约:[data-jio-ban] 委托(封面按钮 / 评论菜单项 / 文章作者菜单项)。
|
||||
assert.match(userBanHelper, /data-jio-ban="/)
|
||||
assert.match(userBanHelper, /function jio_get_user_cover_menu_action_html/)
|
||||
assert.match(userBanHelper, /function jio_get_comment_ban_or_report_item/)
|
||||
assert.match(userBanHelper, /function jio_get_post_author_ban_item/)
|
||||
// 权限模型(参考主题 zib_current_user_can('set_user_ban') 语义):
|
||||
// manage_options + 非本人 + 非超级管理员 + 功能开关。
|
||||
assert.match(userBanHelper, /current_user_can\('manage_options'\)/)
|
||||
@@ -487,31 +487,31 @@ test('frontend ban modal mirrors the reference theme zib_get_set_user_ban_modal
|
||||
assert.match(userBanHelper, /modal-colorful-header colorful-bg c-yellow/)
|
||||
// 提交后整页刷新(禁封 reload:true)/ 仅关闭弹窗(解封 hide_modal:true)。
|
||||
assert.match(userBanJs, /window\.location\.reload\(\)/)
|
||||
assert.match(userBanJs, /AksrtModalRuntime\.close\('submit'\)/)
|
||||
assert.match(userBanJs, /data-aksrt-ban-submit/)
|
||||
assert.match(userBanJs, /data-aksrt-unban-submit/)
|
||||
assert.match(userBanJs, /JioModalRuntime\.close\('submit'\)/)
|
||||
assert.match(userBanJs, /data-jio-ban-submit/)
|
||||
assert.match(userBanJs, /data-jio-unban-submit/)
|
||||
// 桌面 600px(参考主题未传 data-class → Bootstrap .modal-dialog 默认宽)
|
||||
// + min-height 360px(data-height=360);移动端底部抽屉。
|
||||
assert.match(userBanJs, /MODAL_HEIGHT = 360/)
|
||||
assert.match(userBanJs, /mobileBottom: true/)
|
||||
const css = read('assets/css/frontend/modal-runtime.css')
|
||||
assert.match(css, /\.aksrt-user-ban-modal:not\(\.aksrt-modal-runtime--bottom\) \.aksrt-modal-runtime__dialog/)
|
||||
assert.match(css, /\.jio-user-ban-modal:not\(\.jio-modal-runtime--bottom\) \.jio-modal-runtime__dialog/)
|
||||
assert.match(css, /600px/)
|
||||
// textarea autoHeight 1:1(参考主题 maxHeight 锁高 + 边框补偿)。
|
||||
assert.match(userBanJs, /getAttribute\('maxHeight'\)/)
|
||||
// 三个入口模板接线:封面三点菜单 / 评论区三点菜单 / 文章作者 metabox 三点菜单。
|
||||
const authorHero = read('templates/parts/user/author-hero.php')
|
||||
assert.match(authorHero, /aksrt_get_user_cover_menu_action_html\(\$user_id\)/)
|
||||
assert.match(commentItem, /aksrt_get_comment_ban_or_report_item\(\$comment\)/)
|
||||
const singleComponents = read('inc/helpers/aksrt-single-components.php')
|
||||
assert.match(singleComponents, /aksrt_get_post_author_ban_item\(/)
|
||||
assert.match(authorHero, /jio_get_user_cover_menu_action_html\(\$user_id\)/)
|
||||
assert.match(commentItem, /jio_get_comment_ban_or_report_item\(\$comment\)/)
|
||||
const singleComponents = read('inc/helpers/jio-single-components.php')
|
||||
assert.match(singleComponents, /jio_get_post_author_ban_item\(/)
|
||||
// 脚本入队(singular + profile,与 report.js 同组)。
|
||||
const scripts = read('src/Core/ScriptsPart1b.php')
|
||||
assert.match(scripts, /'aksrt-user-ban'/)
|
||||
assert.match(scripts, /'jio-user-ban'/)
|
||||
})
|
||||
|
||||
test('uninstall cleans up every ban/report meta key written by the theme', () => {
|
||||
for (const key of ['_aksrt_active_restrictions', '_aksrt_is_banned', '_aksrt_no_appeal', 'aksrt_user_ban_state']) {
|
||||
for (const key of ['_jio_active_restrictions', '_jio_is_banned', '_jio_no_appeal', 'jio_user_ban_state']) {
|
||||
assert.ok(uninstall.includes(`'${key}'`), `missing uninstall cleanup: ${key}`)
|
||||
}
|
||||
})
|
||||
@@ -539,8 +539,8 @@ test('report modal delegates its labelled title, close button and focus return t
|
||||
const reportJs = read('assets/js/report.js')
|
||||
assert.match(reportJs, /title: TARGETS\[data\.target_type\]\.title/)
|
||||
assert.match(reportJs, /mobileBottom: true, focusReturn: opener/)
|
||||
assert.match(reportJs, /<label class="aksrt-report-label" for=/)
|
||||
assert.match(reportJs, /data-aksrt-report-reason-error role="alert"/)
|
||||
assert.match(reportJs, /<label class="jio-report-label" for=/)
|
||||
assert.match(reportJs, /data-jio-report-reason-error role="alert"/)
|
||||
assert.doesNotMatch(reportJs, /class="close"|style="display:|setTimeout/)
|
||||
})
|
||||
|
||||
|
||||
@@ -112,24 +112,24 @@ function createRuntime({ imgEnabled = true, categories = [
|
||||
if (!shell) return;
|
||||
if (activeModal === shell) activeModal = null;
|
||||
shell.remove();
|
||||
document.dispatch('aksrt:modal:close', { detail: { shell } });
|
||||
document.dispatch('jio:modal:close', { detail: { shell } });
|
||||
},
|
||||
};
|
||||
|
||||
const window = {
|
||||
aksrtData: {
|
||||
apiUrl: '/wp-json/aksrt/v1',
|
||||
jioData: {
|
||||
apiUrl: '/wp-json/jio/v1',
|
||||
restNonce: 'test-nonce',
|
||||
reportCategories: categories,
|
||||
report: { imgEnabled },
|
||||
},
|
||||
AksrtModalRuntime: modalRuntime,
|
||||
JioModalRuntime: modalRuntime,
|
||||
fetch,
|
||||
};
|
||||
const jquery = () => ({
|
||||
off() { return this; },
|
||||
on(event, callback) {
|
||||
if (event === 'auto_fun.aksrt-report') autoInit = callback;
|
||||
if (event === 'auto_fun.jio-report') autoInit = callback;
|
||||
return this;
|
||||
},
|
||||
});
|
||||
@@ -157,7 +157,7 @@ function createRuntime({ imgEnabled = true, categories = [
|
||||
get activeModal() { return activeModal; },
|
||||
openReport(target = { target_type: 'post', target_id: 42, target_name: 'Article' }) {
|
||||
const opener = document.createElement('button');
|
||||
opener.setAttribute('data-aksrt-report', JSON.stringify(target));
|
||||
opener.setAttribute('data-jio-report', JSON.stringify(target));
|
||||
document.body.appendChild(opener);
|
||||
const event = opener.dispatch('click');
|
||||
return { ...modals.at(-1), opener, event };
|
||||
@@ -175,7 +175,7 @@ function selectReason(modal, value) {
|
||||
}
|
||||
|
||||
function submit(modal) {
|
||||
return modal.shell.querySelector('.aksrt-report-form').dispatch('submit');
|
||||
return modal.shell.querySelector('.jio-report-form').dispatch('submit');
|
||||
}
|
||||
|
||||
function callsFor(runtime, endpoint) {
|
||||
@@ -195,8 +195,8 @@ test('opens from an article button, associates the target, omits URL input, and
|
||||
const runtime = createRuntime({ categories: [{ name: '<svg onload="run()">reason</svg>' }] });
|
||||
const targetName = '<img src=x onerror="run()"> &';
|
||||
const modal = runtime.openReport({ target_type: 'post', target_id: 73, target_name: targetName });
|
||||
const target = modal.shell.querySelector('.aksrt-report-target__name');
|
||||
const reason = modal.shell.querySelector('.aksrt-report-reason span');
|
||||
const target = modal.shell.querySelector('.jio-report-target__name');
|
||||
const reason = modal.shell.querySelector('.jio-report-reason span');
|
||||
|
||||
assert.equal(modal.event.defaultPrevented, true);
|
||||
assert.equal(modal.options.title, '举报文章');
|
||||
@@ -207,18 +207,18 @@ test('opens from an article button, associates the target, omits URL input, and
|
||||
assert.equal(reason.querySelector('svg'), null);
|
||||
assert.equal(modal.shell.querySelector('[name="report_url"]'), null);
|
||||
assert.equal(modal.shell.querySelector('input[type="url"]'), null);
|
||||
assert.ok(modal.shell.querySelector('.aksrt-report-target'));
|
||||
assert.equal(modal.shell.querySelector('.aksrt-report-target__label').textContent, '关联对象:');
|
||||
assert.equal(modal.shell.querySelector('.aksrt-report-target__icon'), null);
|
||||
assert.ok(modal.shell.querySelector('.jio-report-target'));
|
||||
assert.equal(modal.shell.querySelector('.jio-report-target__label').textContent, '关联对象:');
|
||||
assert.equal(modal.shell.querySelector('.jio-report-target__icon'), null);
|
||||
assert.equal(target.getAttribute('title'), target.textContent);
|
||||
});
|
||||
|
||||
test('requires a radio reason and other text, then sends canonical fields with description', async () => {
|
||||
const runtime = createRuntime();
|
||||
const modal = runtime.openReport();
|
||||
const form = modal.shell.querySelector('.aksrt-report-form');
|
||||
const reasonError = form.querySelector('[data-aksrt-report-reason-error]');
|
||||
const otherBox = form.querySelector('.aksrt-report-other');
|
||||
const form = modal.shell.querySelector('.jio-report-form');
|
||||
const reasonError = form.querySelector('[data-jio-report-reason-error]');
|
||||
const otherBox = form.querySelector('.jio-report-other');
|
||||
const otherInput = form.querySelector('[name="reason_other"]');
|
||||
const description = form.querySelector('[name="report_desc"]');
|
||||
|
||||
@@ -265,7 +265,7 @@ test('preset reason ignores hidden custom text while including description in re
|
||||
description.value = 'More context';
|
||||
selectReason(modal, 1);
|
||||
|
||||
assert.equal(modal.shell.querySelector('.aksrt-report-other').hidden, true);
|
||||
assert.equal(modal.shell.querySelector('.jio-report-other').hidden, true);
|
||||
assert.equal(otherInput.disabled, true);
|
||||
submit(modal);
|
||||
const payload = payloadFor(callsFor(runtime, '/report/submit')[0]);
|
||||
@@ -308,24 +308,24 @@ test('omits all image controls when image evidence is disabled', () => {
|
||||
const runtime = createRuntime({ imgEnabled: false });
|
||||
const modal = runtime.openReport();
|
||||
|
||||
assert.equal(modal.shell.querySelector('[data-aksrt-report-file]'), null);
|
||||
assert.equal(modal.shell.querySelector('[data-aksrt-report-add]'), null);
|
||||
assert.equal(modal.shell.querySelector('[data-aksrt-report-images]'), null);
|
||||
assert.equal(modal.shell.querySelector('[data-aksrt-report-image-count]'), null);
|
||||
assert.equal(modal.shell.querySelector('[data-jio-report-file]'), null);
|
||||
assert.equal(modal.shell.querySelector('[data-jio-report-add]'), null);
|
||||
assert.equal(modal.shell.querySelector('[data-jio-report-images]'), null);
|
||||
assert.equal(modal.shell.querySelector('[data-jio-report-image-count]'), null);
|
||||
});
|
||||
|
||||
test('counts in-flight images once, accepts exactly three, and blocks submit until ready', async () => {
|
||||
const runtime = createRuntime();
|
||||
const modal = runtime.openReport();
|
||||
selectReason(modal, 0);
|
||||
const fileInput = modal.shell.querySelector('[data-aksrt-report-file]');
|
||||
const fileInput = modal.shell.querySelector('[data-jio-report-file]');
|
||||
fileInput.files = [1, 2, 3].map((index) => ({ name: `evidence-${index}.png`, type: 'image/png' }));
|
||||
fileInput.dispatch('change');
|
||||
|
||||
assert.equal(callsFor(runtime, '/report/upload-image').length, 3);
|
||||
assert.equal(modal.shell.querySelector('[data-aksrt-report-image-count]').textContent, '3 / 3');
|
||||
assert.equal(modal.shell.querySelector('[data-aksrt-report-submit]').disabled, true);
|
||||
assert.equal(modal.shell.querySelector('[data-aksrt-report-status]').textContent, '图片正在上传,完成后即可提交');
|
||||
assert.equal(modal.shell.querySelector('[data-jio-report-image-count]').textContent, '3 / 3');
|
||||
assert.equal(modal.shell.querySelector('[data-jio-report-submit]').disabled, true);
|
||||
assert.equal(modal.shell.querySelector('[data-jio-report-status]').textContent, '图片正在上传,完成后即可提交');
|
||||
submit(modal);
|
||||
assert.equal(callsFor(runtime, '/report/submit').length, 0);
|
||||
|
||||
@@ -334,9 +334,9 @@ test('counts in-flight images once, accepts exactly three, and blocks submit unt
|
||||
data: { id: 800 + index, url: `https://evidence.test/${index + 1}.png` },
|
||||
}));
|
||||
await flushPromises();
|
||||
assert.equal(modal.shell.querySelectorAll('.aksrt-report-image').length, 3);
|
||||
assert.equal(modal.shell.querySelector('[data-aksrt-report-image-count]').textContent, '3 / 3');
|
||||
assert.equal(modal.shell.querySelector('[data-aksrt-report-add]').disabled, true);
|
||||
assert.equal(modal.shell.querySelectorAll('.jio-report-image').length, 3);
|
||||
assert.equal(modal.shell.querySelector('[data-jio-report-image-count]').textContent, '3 / 3');
|
||||
assert.equal(modal.shell.querySelector('[data-jio-report-add]').disabled, true);
|
||||
|
||||
fileInput.files = [{ name: 'fourth.png', type: 'image/png' }];
|
||||
fileInput.dispatch('change');
|
||||
@@ -353,39 +353,39 @@ test('counts in-flight images once, accepts exactly three, and blocks submit unt
|
||||
test('retries a failed upload and deletes its pending attachment when removed', async () => {
|
||||
const runtime = createRuntime();
|
||||
const modal = runtime.openReport();
|
||||
const fileInput = modal.shell.querySelector('[data-aksrt-report-file]');
|
||||
const fileInput = modal.shell.querySelector('[data-jio-report-file]');
|
||||
fileInput.files = [{ name: 'retry.png', type: 'image/png' }];
|
||||
fileInput.dispatch('change');
|
||||
|
||||
callsFor(runtime, '/report/upload-image')[0].resolve({ success: false, message: 'Upload failed' });
|
||||
await flushPromises();
|
||||
const image = modal.shell.querySelector('.aksrt-report-image');
|
||||
const image = modal.shell.querySelector('.jio-report-image');
|
||||
assert.equal(image.getAttribute('data-upload-state'), 'error');
|
||||
assert.match(modal.shell.querySelector('[data-aksrt-report-error]').textContent, /Upload failed/);
|
||||
assert.match(modal.shell.querySelector('[data-jio-report-error]').textContent, /Upload failed/);
|
||||
|
||||
image.querySelector('[data-aksrt-report-retry]').dispatch('click');
|
||||
image.querySelector('[data-jio-report-retry]').dispatch('click');
|
||||
const retry = callsFor(runtime, '/report/upload-image')[1];
|
||||
assert.ok(retry);
|
||||
retry.resolve({ success: true, data: { id: 901, url: 'https://evidence.test/retry.png' } });
|
||||
await flushPromises();
|
||||
assert.equal(image.getAttribute('data-upload-state'), 'ready');
|
||||
|
||||
image.querySelector('[data-aksrt-report-remove]').dispatch('click');
|
||||
image.querySelector('[data-jio-report-remove]').dispatch('click');
|
||||
const deletion = callsFor(runtime, '/upload/pending/901')[0];
|
||||
assert.ok(deletion);
|
||||
assert.equal(deletion.options.method, 'DELETE');
|
||||
assert.equal(modal.shell.querySelectorAll('.aksrt-report-image').length, 0);
|
||||
assert.equal(modal.shell.querySelector('[data-aksrt-report-image-count]').textContent, '0 / 3');
|
||||
assert.equal(modal.shell.querySelectorAll('.jio-report-image').length, 0);
|
||||
assert.equal(modal.shell.querySelector('[data-jio-report-image-count]').textContent, '0 / 3');
|
||||
});
|
||||
|
||||
test('server failure preserves values, reenables controls, and allows a retry', async () => {
|
||||
const runtime = createRuntime();
|
||||
const modal = runtime.openReport();
|
||||
selectReason(modal, 'other');
|
||||
const form = modal.shell.querySelector('.aksrt-report-form');
|
||||
const form = modal.shell.querySelector('.jio-report-form');
|
||||
const otherInput = form.querySelector('[name="reason_other"]');
|
||||
const description = form.querySelector('[name="report_desc"]');
|
||||
const submitButton = form.querySelector('[data-aksrt-report-submit]');
|
||||
const submitButton = form.querySelector('[data-jio-report-submit]');
|
||||
otherInput.value = 'A custom report reason';
|
||||
description.value = 'Additional context';
|
||||
|
||||
@@ -402,7 +402,7 @@ test('server failure preserves values, reenables controls, and allows a retry',
|
||||
assert.equal(otherInput.disabled, false);
|
||||
assert.equal(description.disabled, false);
|
||||
assert.equal(submitButton.disabled, false);
|
||||
assert.equal(form.querySelector('[data-aksrt-report-error]').hidden, true);
|
||||
assert.equal(form.querySelector('[data-jio-report-error]').hidden, true);
|
||||
assert.deepEqual(runtime.notyfCalls.at(-1).slice(0, 3), ['Server rejected report', 'danger', 5]);
|
||||
|
||||
submit(modal);
|
||||
@@ -416,9 +416,9 @@ test('uses a persistent shared toast per submit attempt without inline submittin
|
||||
const runtime = createRuntime();
|
||||
const modal = runtime.openReport();
|
||||
selectReason(modal, 0);
|
||||
const form = modal.shell.querySelector('.aksrt-report-form');
|
||||
const button = form.querySelector('[data-aksrt-report-submit]');
|
||||
const status = form.querySelector('[data-aksrt-report-status]');
|
||||
const form = modal.shell.querySelector('.jio-report-form');
|
||||
const button = form.querySelector('[data-jio-report-submit]');
|
||||
const status = form.querySelector('[data-jio-report-status]');
|
||||
|
||||
submit(modal);
|
||||
const firstToast = runtime.notyfCalls.at(-1);
|
||||
@@ -467,7 +467,7 @@ test('blocks duplicate submits and closing an old submitting dialog cannot close
|
||||
test('cleans pending attachments on close and when an upload completes after close', async () => {
|
||||
const runtime = createRuntime();
|
||||
const readyModal = runtime.openReport();
|
||||
const readyFile = readyModal.shell.querySelector('[data-aksrt-report-file]');
|
||||
const readyFile = readyModal.shell.querySelector('[data-jio-report-file]');
|
||||
readyFile.files = [{ name: 'ready.png', type: 'image/png' }];
|
||||
readyFile.dispatch('change');
|
||||
callsFor(runtime, '/report/upload-image')[0].resolve({
|
||||
@@ -482,7 +482,7 @@ test('cleans pending attachments on close and when an upload completes after clo
|
||||
assert.equal(readyDeletion.options.method, 'DELETE');
|
||||
|
||||
const pendingModal = runtime.openReport();
|
||||
const pendingFile = pendingModal.shell.querySelector('[data-aksrt-report-file]');
|
||||
const pendingFile = pendingModal.shell.querySelector('[data-jio-report-file]');
|
||||
pendingFile.files = [{ name: 'pending.png', type: 'image/png' }];
|
||||
pendingFile.dispatch('change');
|
||||
const pendingUpload = callsFor(runtime, '/report/upload-image')[1];
|
||||
@@ -505,16 +505,16 @@ test('keeps a live child modal open and marks the finished report after success'
|
||||
selectReason(report, 0);
|
||||
submit(report);
|
||||
const request = callsFor(runtime, '/report/submit')[0];
|
||||
const child = runtime.modalRuntime.open({ className: 'aksrt-child-modal', html: '<p>Child dialog</p>' });
|
||||
const child = runtime.modalRuntime.open({ className: 'jio-child-modal', html: '<p>Child dialog</p>' });
|
||||
request.resolve({ success: true });
|
||||
await flushPromises();
|
||||
|
||||
const form = report.shell.querySelector('.aksrt-report-form');
|
||||
const form = report.shell.querySelector('.jio-report-form');
|
||||
assert.equal(runtime.activeModal, child);
|
||||
assert.equal(runtime.closeCalls.length, 0);
|
||||
assert.equal(form.getAttribute('aria-busy'), 'false');
|
||||
assert.equal(form.querySelector('[data-aksrt-report-submit]').textContent, '举报已提交');
|
||||
assert.ok(report.shell.querySelector('[data-aksrt-report-status]'));
|
||||
assert.equal(form.querySelector('[data-jio-report-submit]').textContent, '举报已提交');
|
||||
assert.ok(report.shell.querySelector('[data-jio-report-status]'));
|
||||
});
|
||||
|
||||
test('retains claimed evidence on post-close success and deletes it on post-close failure', async () => {
|
||||
@@ -522,7 +522,7 @@ test('retains claimed evidence on post-close success and deletes it on post-clos
|
||||
async function submitClosedReport(id, result) {
|
||||
const modal = runtime.openReport();
|
||||
selectReason(modal, 0);
|
||||
const fileInput = modal.shell.querySelector('[data-aksrt-report-file]');
|
||||
const fileInput = modal.shell.querySelector('[data-jio-report-file]');
|
||||
fileInput.files = [{ name: `${id}.png`, type: 'image/png' }];
|
||||
fileInput.dispatch('change');
|
||||
callsFor(runtime, '/report/upload-image').at(-1).resolve({
|
||||
|
||||
@@ -2,16 +2,16 @@
|
||||
/**
|
||||
* Exercise report submission, storage, notifications, and admin filtering without WordPress.
|
||||
*
|
||||
* @package AKSRT
|
||||
* @package Jio
|
||||
*/
|
||||
|
||||
namespace AKSRT\Core {
|
||||
namespace Jio\Core {
|
||||
final class DBMigrator {
|
||||
public static function table(string $name): string { return 'wp_aksrt_' . $name; }
|
||||
public static function table(string $name): string { return 'wp_jio_' . $name; }
|
||||
}
|
||||
}
|
||||
|
||||
namespace AKSRT\Settings {
|
||||
namespace Jio\Settings {
|
||||
final class Settings {
|
||||
public static array $values = [];
|
||||
public static function get_all(): array { return self::$values; }
|
||||
@@ -22,24 +22,24 @@ namespace AKSRT\Settings {
|
||||
}
|
||||
}
|
||||
|
||||
namespace AKSRT\Messages {
|
||||
namespace Jio\Messages {
|
||||
final class Message {
|
||||
public static function get_instance(): self { return new self(); }
|
||||
public function send(int $user_id, string $type, string $content, array $args = []): void {
|
||||
$GLOBALS['aksrt_test_messages'][] = compact('user_id', 'type', 'content', 'args');
|
||||
$GLOBALS['jio_test_messages'][] = compact('user_id', 'type', 'content', 'args');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace AKSRT\Attachment {
|
||||
namespace Jio\Attachment {
|
||||
final class PendingUploads {
|
||||
public static function claim_text(string $text, int $user_id): void {
|
||||
$GLOBALS['aksrt_test_claims'][] = [$text, $user_id];
|
||||
$GLOBALS['jio_test_claims'][] = [$text, $user_id];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace AKSRT\User {
|
||||
namespace Jio\User {
|
||||
final class Restriction {
|
||||
public const TYPE_WARN = 'warn';
|
||||
public const TYPE_MUTE = 'mute';
|
||||
@@ -193,7 +193,7 @@ namespace {
|
||||
}
|
||||
|
||||
final class ReportFixtureController {
|
||||
use \AKSRT\Admin\UserControllerReportOperations;
|
||||
use \Jio\Admin\UserControllerReportOperations;
|
||||
|
||||
protected function success($data = [], int $status = 200): WP_REST_Response {
|
||||
return new WP_REST_Response(array_merge(['success' => true], $data), $status);
|
||||
@@ -226,7 +226,7 @@ namespace {
|
||||
function wp_kses_post($value): string { return strip_tags((string) $value); }
|
||||
function current_time(string $type): string { unset($type); return '2026-10-30 12:00:00'; }
|
||||
function wp_json_encode($value): string { return json_encode($value, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) ?: '[]'; }
|
||||
function get_current_user_id(): int { return (int) ($GLOBALS['aksrt_test_user_id'] ?? 0); }
|
||||
function get_current_user_id(): int { return (int) ($GLOBALS['jio_test_user_id'] ?? 0); }
|
||||
function get_userdata(int $user_id) { return $user_id > 0 ? new WP_User($user_id, 'Reporter ' . $user_id) : false; }
|
||||
function get_users(array $args = []): array { unset($args); return [901]; }
|
||||
function admin_url(string $path = ''): string { return 'https://example.test/wp-admin/' . ltrim($path, '/'); }
|
||||
@@ -234,8 +234,8 @@ namespace {
|
||||
function get_the_title($post): string { return 'Article #' . (is_object($post) ? $post->ID : (int) $post); }
|
||||
function get_permalink($post): string { return 'https://example.test/post/' . (is_object($post) ? $post->ID : (int) $post); }
|
||||
function do_action(string $hook, ...$args): void {
|
||||
if ($hook === 'aksrt_report_submitted') {
|
||||
$GLOBALS['aksrt_test_actions'][] = $args;
|
||||
if ($hook === 'jio_report_submitted') {
|
||||
$GLOBALS['jio_test_actions'][] = $args;
|
||||
}
|
||||
}
|
||||
if (!function_exists('mb_substr')) {
|
||||
@@ -246,12 +246,12 @@ namespace {
|
||||
|
||||
function reset_report_fixture(array $preset_names = ['垃圾信息', '人身攻击']): void {
|
||||
$GLOBALS['wpdb'] = new TestWpdb();
|
||||
$GLOBALS['aksrt_test_user_id'] = 42;
|
||||
$GLOBALS['aksrt_test_actions'] = [];
|
||||
$GLOBALS['aksrt_test_messages'] = [];
|
||||
$GLOBALS['aksrt_test_claims'] = [];
|
||||
\AKSRT\User\Restriction::$calls = [];
|
||||
\AKSRT\Settings\Settings::$values = [
|
||||
$GLOBALS['jio_test_user_id'] = 42;
|
||||
$GLOBALS['jio_test_actions'] = [];
|
||||
$GLOBALS['jio_test_messages'] = [];
|
||||
$GLOBALS['jio_test_claims'] = [];
|
||||
\Jio\User\Restriction::$calls = [];
|
||||
\Jio\Settings\Settings::$values = [
|
||||
'ban_preset_reason' => array_map(static fn (string $name): array => ['t' => $name], $preset_names),
|
||||
'user_report_img_s' => true,
|
||||
];
|
||||
@@ -268,16 +268,16 @@ namespace {
|
||||
'status' => $response->get_status(),
|
||||
'rows' => $wpdb->rows,
|
||||
'insert_calls' => $wpdb->insert_calls,
|
||||
'actions' => $GLOBALS['aksrt_test_actions'],
|
||||
'messages' => $GLOBALS['aksrt_test_messages'],
|
||||
'claims' => $GLOBALS['aksrt_test_claims'],
|
||||
'actions' => $GLOBALS['jio_test_actions'],
|
||||
'messages' => $GLOBALS['jio_test_messages'],
|
||||
'claims' => $GLOBALS['jio_test_claims'],
|
||||
];
|
||||
}
|
||||
|
||||
$results = [];
|
||||
|
||||
reset_report_fixture(['垃圾信息', '人身攻击']);
|
||||
$results['runtime_reasons'] = \AKSRT\Settings\UserRuntime::ban_settings()['reasons'];
|
||||
$results['runtime_reasons'] = \Jio\Settings\UserRuntime::ban_settings()['reasons'];
|
||||
|
||||
reset_report_fixture();
|
||||
$results['preset_with_evidence'] = snapshot_response(submit_report_case([
|
||||
@@ -349,7 +349,7 @@ namespace {
|
||||
]));
|
||||
|
||||
reset_report_fixture();
|
||||
$GLOBALS['aksrt_test_user_id'] = 0;
|
||||
$GLOBALS['jio_test_user_id'] = 0;
|
||||
$results['unauthenticated'] = snapshot_response(submit_report_case([
|
||||
'target_type' => 'post', 'target_id' => 5, 'category' => '垃圾信息', 'reason' => '',
|
||||
]));
|
||||
@@ -400,7 +400,7 @@ namespace {
|
||||
];
|
||||
|
||||
reset_report_fixture();
|
||||
$service_id = \AKSRT\Messages\ReportCenter::get_instance()->submit(42, 'post', 6, '详情', '');
|
||||
$service_id = \Jio\Messages\ReportCenter::get_instance()->submit(42, 'post', 6, '详情', '');
|
||||
$results['service_blank_category'] = [
|
||||
'id' => $service_id,
|
||||
'rows' => $GLOBALS['wpdb']->rows,
|
||||
@@ -415,9 +415,9 @@ namespace {
|
||||
'target_type' => 'post', 'target_id' => 62, 'category' => '待删除的分类', 'reason' => '',
|
||||
]);
|
||||
$admin_controller = new ReportFixtureController();
|
||||
\AKSRT\Settings\Settings::$values['ban_preset_reason'] = [['t' => '当前新分类']];
|
||||
\Jio\Settings\Settings::$values['ban_preset_reason'] = [['t' => '当前新分类']];
|
||||
$changed_list = $admin_controller->get_report_list(new WP_REST_Request())->get_data()['data'] ?? [];
|
||||
\AKSRT\Settings\Settings::$values['ban_preset_reason'] = [];
|
||||
\Jio\Settings\Settings::$values['ban_preset_reason'] = [];
|
||||
$removed_list = $admin_controller->get_report_list(new WP_REST_Request())->get_data()['data'] ?? [];
|
||||
$first_detail = $admin_controller->get_report_detail(new WP_REST_Request(['id' => 1]))->get_data()['data']['report'] ?? [];
|
||||
$second_detail = $admin_controller->get_report_detail(new WP_REST_Request(['id' => 2]))->get_data()['data']['report'] ?? [];
|
||||
@@ -444,7 +444,7 @@ namespace {
|
||||
$preset_row = $GLOBALS['wpdb']->rows[0];
|
||||
$results['process_preset_only'] = [
|
||||
'success' => !empty($preset_process->get_data()['success']),
|
||||
'restriction' => \AKSRT\User\Restriction::$calls[0] ?? [],
|
||||
'restriction' => \Jio\User\Restriction::$calls[0] ?? [],
|
||||
'report_reason' => $preset_row['reason'],
|
||||
'report_category' => $preset_row['category'],
|
||||
];
|
||||
@@ -462,7 +462,7 @@ namespace {
|
||||
$other_row = $GLOBALS['wpdb']->rows[0];
|
||||
$results['process_other_with_details'] = [
|
||||
'success' => !empty($other_process->get_data()['success']),
|
||||
'restriction' => \AKSRT\User\Restriction::$calls[0] ?? [],
|
||||
'restriction' => \Jio\User\Restriction::$calls[0] ?? [],
|
||||
'report_reason' => $other_row['reason'],
|
||||
'report_category' => $other_row['category'],
|
||||
];
|
||||
|
||||
@@ -134,12 +134,12 @@ test('preserves readable labels through the admin category filter, list, and det
|
||||
assert.equal(result.detail_report.category, '涉嫌侵权');
|
||||
});
|
||||
|
||||
test('stores report categories as text and uses schema version 3.2.0', () => {
|
||||
test('stores report categories as text and uses schema version 3.3.0', () => {
|
||||
const migrator = fs.readFileSync(path.join(root, 'src/Core/DBMigrator.php'), 'utf8');
|
||||
const tableDefinitions = fs.readFileSync(path.join(root, 'src/Core/DBMigratorPart2.php'), 'utf8');
|
||||
const reportTable = tableDefinitions.match(/\$sql_report\s*=\s*"([\s\S]*?)";/);
|
||||
|
||||
assert.match(migrator, /const SCHEMA_VERSION\s*=\s*'3\.2\.0';/);
|
||||
assert.match(migrator, /const SCHEMA_VERSION\s*=\s*'3\.3\.0';/);
|
||||
assert.ok(reportTable, 'report table definition should exist');
|
||||
assert.match(reportTable[1], /\bcategory\s+TEXT\s+NOT NULL\b/);
|
||||
});
|
||||
|
||||
+51
-51
@@ -4,19 +4,19 @@ declare(strict_types=1);
|
||||
|
||||
require __DIR__ . '/bootstrap.php';
|
||||
|
||||
use AKSRT\WidgetSystem\FieldSchema;
|
||||
use AKSRT\WidgetSystem\FormRenderer;
|
||||
use AKSRT\WidgetSystem\PostQuery;
|
||||
use AKSRT\WidgetSystem\RestController;
|
||||
use AKSRT\WidgetSystem\SchemaWidget;
|
||||
use AKSRT\WidgetSystem\WidgetDefinition;
|
||||
use AKSRT\WidgetSystem\WidgetRegistry;
|
||||
use AKSRT\WidgetSystem\WidgetTransfer;
|
||||
use AKSRT\WidgetSystem\WidgetVisibility;
|
||||
use AKSRT\WidgetSystem\WidgetSchemas;
|
||||
use AKSRT\WidgetSystem\Renderers\ContentRenderer;
|
||||
use AKSRT\WidgetSystem\Renderers\GeneralRenderer;
|
||||
use AKSRT\WidgetSystem\Renderers\SliderRenderer;
|
||||
use Jio\WidgetSystem\FieldSchema;
|
||||
use Jio\WidgetSystem\FormRenderer;
|
||||
use Jio\WidgetSystem\PostQuery;
|
||||
use Jio\WidgetSystem\RestController;
|
||||
use Jio\WidgetSystem\SchemaWidget;
|
||||
use Jio\WidgetSystem\WidgetDefinition;
|
||||
use Jio\WidgetSystem\WidgetRegistry;
|
||||
use Jio\WidgetSystem\WidgetTransfer;
|
||||
use Jio\WidgetSystem\WidgetVisibility;
|
||||
use Jio\WidgetSystem\WidgetSchemas;
|
||||
use Jio\WidgetSystem\Renderers\ContentRenderer;
|
||||
use Jio\WidgetSystem\Renderers\GeneralRenderer;
|
||||
use Jio\WidgetSystem\Renderers\SliderRenderer;
|
||||
|
||||
if (!function_exists('current_datetime')) {
|
||||
function current_datetime(): DateTimeImmutable {
|
||||
@@ -114,7 +114,7 @@ final class WidgetSystemTest {
|
||||
$this->same('1,-2,3', $reference['cat'], 'reference cat preserves cleaned signed IDs for WP_Query descendant semantics');
|
||||
$this->false(isset($reference['category__in']) || isset($reference['category__not_in']), 'reference cat does not narrow matches to direct categories');
|
||||
$this->false(isset($reference['tax_query']), 'topic filters are removed with the topics feature');
|
||||
$this->same('aksrt_post_views', $reference['meta_key'], 'reference view ordering uses numeric view meta');
|
||||
$this->same('jio_post_views', $reference['meta_key'], 'reference view ordering uses numeric view meta');
|
||||
$this->false(isset($reference['meta_query']), 'modern reference queries do not apply commerce filters by default');
|
||||
$this->same(1, $reference['paged'], 'no-pagination queries stay on first page');
|
||||
$this->same('2026-06-08 12:30:00', $reference['date_query'][0]['after'], 'limit_day subtracts days in the WordPress site timezone');
|
||||
@@ -260,31 +260,31 @@ final class WidgetSystemTest {
|
||||
$this->false(WidgetVisibility::should_render(['show_id_type' => 'show', 'show_ids' => '3,8'], 0, false), 'show list rejects archives');
|
||||
$this->false(WidgetVisibility::should_render(['show_id_type' => 'hide', 'show_ids' => '3,8'], 3, true), 'hide list suppresses matching ID');
|
||||
$this->true(WidgetVisibility::should_render(['show_id_type' => 'hide', 'show_ids' => '3,8'], 0, false), 'hide list keeps archives');
|
||||
$this->same('aksrt-widget--only-pc', WidgetVisibility::device_class(['show_type' => 'only_pc']), 'PC device class is stable');
|
||||
$this->same('aksrt-widget--only-mobile', WidgetVisibility::device_class(['show_type' => 'only_sm']), 'mobile device class is stable');
|
||||
$this->same('jio-widget--only-pc', WidgetVisibility::device_class(['show_type' => 'only_pc']), 'PC device class is stable');
|
||||
$this->same('jio-widget--only-mobile', WidgetVisibility::device_class(['show_type' => 'only_sm']), 'mobile device class is stable');
|
||||
$this->same('', WidgetVisibility::device_class(['show_type' => 'invalid']), 'invalid device rule adds no class');
|
||||
}
|
||||
|
||||
private function transfer_round_trip(): void {
|
||||
aksrt_test_reset_state();
|
||||
jio_test_reset_state();
|
||||
$GLOBALS['wp_registered_sidebars'] = ['home_sidebar' => [], 'single_sidebar' => []];
|
||||
update_option('widget_aksrt_widget_posts', [2 => ['title' => '<b>课程列表</b>', 'count' => 999, 'style' => 'mini'], '_multiwidget' => 1]);
|
||||
$GLOBALS['aksrt_test_state']['sidebars'] = ['home_sidebar' => ['text-1', 'aksrt_widget_posts-2']];
|
||||
update_option('widget_jio_widget_posts', [2 => ['title' => '<b>课程列表</b>', 'count' => 999, 'style' => 'mini'], '_multiwidget' => 1]);
|
||||
$GLOBALS['jio_test_state']['sidebars'] = ['home_sidebar' => ['text-1', 'jio_widget_posts-2']];
|
||||
$payload = WidgetTransfer::export('home_sidebar');
|
||||
$this->same(WidgetTransfer::FORMAT, $payload['format'], 'export format is versioned');
|
||||
$this->same(1, count($payload['sidebars']['home_sidebar']), 'export ignores non-schema widgets');
|
||||
$this->same(20, $payload['sidebars']['home_sidebar'][0]['settings']['count'], 'export normalizes saved settings');
|
||||
update_option('widget_aksrt_widget_posts', []);
|
||||
$GLOBALS['aksrt_test_state']['sidebars']['single_sidebar'] = ['text-9'];
|
||||
update_option('widget_jio_widget_posts', []);
|
||||
$GLOBALS['jio_test_state']['sidebars']['single_sidebar'] = ['text-9'];
|
||||
$result = WidgetTransfer::import($payload, 'append', 'single_sidebar');
|
||||
$this->same(['sidebars' => 1, 'widgets' => 1], $result, 'append import reports changes');
|
||||
$this->same(['text-9', 'aksrt_widget_posts-1'], $GLOBALS['aksrt_test_state']['sidebars']['single_sidebar'], 'append import preserves existing widgets');
|
||||
$this->same(['text-9', 'jio_widget_posts-1'], $GLOBALS['jio_test_state']['sidebars']['single_sidebar'], 'append import preserves existing widgets');
|
||||
$round_trip = WidgetTransfer::export('single_sidebar');
|
||||
$this->same($payload['sidebars']['home_sidebar'][0], $round_trip['sidebars']['single_sidebar'][0], 'export/import round trip preserves normalized module');
|
||||
$replacement = WidgetTransfer::template_payload('links_navigation');
|
||||
$result = WidgetTransfer::import($replacement, 'replace', 'single_sidebar');
|
||||
$this->same(3, $result['widgets'], 'targeted template import combines template areas');
|
||||
$this->false(in_array('text-9', $GLOBALS['aksrt_test_state']['sidebars']['single_sidebar'], true), 'replace import removes previous assignment');
|
||||
$this->false(in_array('text-9', $GLOBALS['jio_test_state']['sidebars']['single_sidebar'], true), 'replace import removes previous assignment');
|
||||
try {
|
||||
WidgetTransfer::import(['format' => 'legacy.widgets', 'sidebars' => []]);
|
||||
$this->failures[] = 'transfer rejects legacy formats: no exception thrown';
|
||||
@@ -294,45 +294,45 @@ final class WidgetSystemTest {
|
||||
}
|
||||
|
||||
private function rest_contract(): void {
|
||||
aksrt_test_reset_state();
|
||||
jio_test_reset_state();
|
||||
$controller = new RestController();
|
||||
$controller->register_routes();
|
||||
$this->same(1, count($GLOBALS['aksrt_test_state']['routes']), 'REST registers only the widget items route');
|
||||
$items_route = 'aksrt/v1/widgets/(?P<widget>[a-z][a-z0-9_]*)/items';
|
||||
$route = $GLOBALS['aksrt_test_state']['routes'][$items_route] ?? [];
|
||||
$this->true(isset($GLOBALS['aksrt_test_state']['routes'][$items_route]), 'items route remains registered');
|
||||
$this->false(isset($GLOBALS['aksrt_test_state']['routes']['aksrt/v1/widgets/quote']), 'obsolete quote REST route is removed');
|
||||
$this->same(1, count($GLOBALS['jio_test_state']['routes']), 'REST registers only the widget items route');
|
||||
$items_route = 'jio/v1/widgets/(?P<widget>[a-z][a-z0-9_]*)/items';
|
||||
$route = $GLOBALS['jio_test_state']['routes'][$items_route] ?? [];
|
||||
$this->true(isset($GLOBALS['jio_test_state']['routes'][$items_route]), 'items route remains registered');
|
||||
$this->false(isset($GLOBALS['jio_test_state']['routes']['jio/v1/widgets/quote']), 'obsolete quote REST route is removed');
|
||||
$this->false(method_exists($controller, 'get_quote'), 'obsolete quote REST callback is removed');
|
||||
$this->same('__return_true', $route['permission_callback'], 'public items route is read-only accessible');
|
||||
$this->true($controller->validate_widget('posts'), 'known widget validates');
|
||||
$this->false($controller->validate_widget('shop_products'), 'unknown/shop widget is rejected');
|
||||
$error = $controller->get_items(new WP_REST_Request(['widget' => 'posts', 'instance' => 'aksrt_widget_posts-1', 'page' => 1, 'query' => 'arbitrary']));
|
||||
$this->error('aksrt_widget_unknown_parameter', $error, 'REST rejects arbitrary query parameters');
|
||||
$error = $controller->get_items(new WP_REST_Request(['widget' => 'search', 'instance' => 'aksrt_widget_search-1', 'page' => 1]));
|
||||
$this->error('aksrt_widget_items_unsupported', $error, 'REST rejects non-dynamic widgets');
|
||||
$error = $controller->get_items(new WP_REST_Request(['widget' => 'posts', 'instance' => 'aksrt_widget_posts-1', 'page' => 1]));
|
||||
$this->error('aksrt_widget_instance_not_registered', $error, 'REST requires an assigned instance');
|
||||
$GLOBALS['aksrt_test_state']['sidebars'] = ['home_top_content' => ['aksrt_widget_posts-1']];
|
||||
update_option('widget_aksrt_widget_posts', [1 => ['title' => 'Saved only', 'count' => 2, 'style' => 'list', 'paginate' => 'ajax'], '_multiwidget' => 1]);
|
||||
$response = $controller->get_items(new WP_REST_Request(['widget' => 'posts', 'instance' => 'aksrt_widget_posts-1', 'page' => 2, 'segment' => '']));
|
||||
$error = $controller->get_items(new WP_REST_Request(['widget' => 'posts', 'instance' => 'jio_widget_posts-1', 'page' => 1, 'query' => 'arbitrary']));
|
||||
$this->error('jio_widget_unknown_parameter', $error, 'REST rejects arbitrary query parameters');
|
||||
$error = $controller->get_items(new WP_REST_Request(['widget' => 'search', 'instance' => 'jio_widget_search-1', 'page' => 1]));
|
||||
$this->error('jio_widget_items_unsupported', $error, 'REST rejects non-dynamic widgets');
|
||||
$error = $controller->get_items(new WP_REST_Request(['widget' => 'posts', 'instance' => 'jio_widget_posts-1', 'page' => 1]));
|
||||
$this->error('jio_widget_instance_not_registered', $error, 'REST requires an assigned instance');
|
||||
$GLOBALS['jio_test_state']['sidebars'] = ['home_top_content' => ['jio_widget_posts-1']];
|
||||
update_option('widget_jio_widget_posts', [1 => ['title' => 'Saved only', 'count' => 2, 'style' => 'list', 'paginate' => 'ajax'], '_multiwidget' => 1]);
|
||||
$response = $controller->get_items(new WP_REST_Request(['widget' => 'posts', 'instance' => 'jio_widget_posts-1', 'page' => 2, 'segment' => '']));
|
||||
$this->true($response instanceof WP_REST_Response, 'registered instance returns REST response');
|
||||
$data = $response->get_data();
|
||||
$this->true($data['success'], 'REST success envelope is stable');
|
||||
$this->same(2, $data['page'], 'REST page reaches saved-query pipeline');
|
||||
$this->contains('暂无内容', $data['html'], 'REST returns deterministic empty state');
|
||||
$this->same(4, end($GLOBALS['aksrt_test_state']['query_args'])['posts_per_page'], 'REST reloads saved normalized configuration');
|
||||
$response = $controller->get_items(new WP_REST_Request(['widget' => 'posts', 'instance' => 'aksrt_widget_posts-1', 'page' => 1, 'rest_route' => '/aksrt/v1/widgets/posts/items']));
|
||||
$this->same(4, end($GLOBALS['jio_test_state']['query_args'])['posts_per_page'], 'REST reloads saved normalized configuration');
|
||||
$response = $controller->get_items(new WP_REST_Request(['widget' => 'posts', 'instance' => 'jio_widget_posts-1', 'page' => 1, 'rest_route' => '/jio/v1/widgets/posts/items']));
|
||||
$this->true($response instanceof WP_REST_Response, 'plain permalink transport parameter is accepted');
|
||||
$error = $controller->get_items(new WP_REST_Request(['widget' => 'posts', 'instance' => 'aksrt_widget_hot_posts-1', 'page' => 1]));
|
||||
$this->error('aksrt_widget_invalid_instance', $error, 'REST binds instance ID to requested widget');
|
||||
$error = $controller->get_items(new WP_REST_Request(['widget' => 'posts', 'instance' => 'jio_widget_hot_posts-1', 'page' => 1]));
|
||||
$this->error('jio_widget_invalid_instance', $error, 'REST binds instance ID to requested widget');
|
||||
}
|
||||
|
||||
private function payment_states(): void {
|
||||
aksrt_test_reset_state();
|
||||
$GLOBALS['aksrt_test_state']['current_post'] = new WP_Post(77, 5);
|
||||
jio_test_reset_state();
|
||||
$GLOBALS['jio_test_state']['current_post'] = new WP_Post(77, 5);
|
||||
$method = new ReflectionMethod(ContentRenderer::class, 'post_pay');
|
||||
$render = static fn(): string => (string) $method->invoke(null, ['theme' => 'jb-red']);
|
||||
$GLOBALS['aksrt_test_state']['pay_meta'] = ['pay_type' => 'no', 'price' => 0];
|
||||
$GLOBALS['jio_test_state']['pay_meta'] = ['pay_type' => 'no', 'price' => 0];
|
||||
$this->same('', $render(), 'unconfigured article renders nothing and aborts the widget');
|
||||
|
||||
$authorized = [
|
||||
@@ -341,15 +341,15 @@ final class WidgetSystemTest {
|
||||
'vip' => ['pay_type' => 'vip', 'price' => 0],
|
||||
];
|
||||
foreach ($authorized as $label => $meta) {
|
||||
$GLOBALS['aksrt_test_state']['pay_meta'] = $meta;
|
||||
$GLOBALS['aksrt_test_state']['paid_access'] = true;
|
||||
$GLOBALS['jio_test_state']['pay_meta'] = $meta;
|
||||
$GLOBALS['jio_test_state']['paid_access'] = true;
|
||||
$this->contains('内容已解锁', $render(), $label . ' authorization delegates to existing access service');
|
||||
}
|
||||
$GLOBALS['aksrt_test_state']['pay_meta'] = ['pay_type' => 'video', 'price' => 49];
|
||||
$GLOBALS['aksrt_test_state']['paid_access'] = false;
|
||||
$GLOBALS['aksrt_test_state']['logged_in'] = false;
|
||||
$GLOBALS['jio_test_state']['pay_meta'] = ['pay_type' => 'video', 'price' => 49];
|
||||
$GLOBALS['jio_test_state']['paid_access'] = false;
|
||||
$GLOBALS['jio_test_state']['logged_in'] = false;
|
||||
$this->contains('guest-locked', $render(), 'guest unauthorized state delegates to paywall');
|
||||
$GLOBALS['aksrt_test_state']['logged_in'] = true;
|
||||
$GLOBALS['jio_test_state']['logged_in'] = true;
|
||||
$this->contains('member-locked', $render(), 'logged-in unauthorized state delegates to paywall');
|
||||
}
|
||||
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
declare(strict_types=1);
|
||||
|
||||
define('ABSPATH', __DIR__ . DIRECTORY_SEPARATOR);
|
||||
define('AKSRT_VERSION', 'theme-fallback');
|
||||
define('AKSRT_URI', 'https://example.test/theme');
|
||||
define('JIO_VERSION', 'theme-fallback');
|
||||
define('JIO_URI', 'https://example.test/theme');
|
||||
|
||||
$GLOBALS['runtime_asset_fixture_root'] = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'aksrt-runtime-assets-' . bin2hex(random_bytes(6));
|
||||
$GLOBALS['runtime_asset_fixture_root'] = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'jio-runtime-assets-' . bin2hex(random_bytes(6));
|
||||
$GLOBALS['runtime_asset_localized'] = [];
|
||||
mkdir($GLOBALS['runtime_asset_fixture_root'] . '/assets/js/runtime', 0777, true);
|
||||
|
||||
@@ -36,17 +36,17 @@ function is_user_logged_in(): bool { return false; }
|
||||
function get_current_user_id(): int { return 0; }
|
||||
function wp_max_upload_size(): int { return 1048576; }
|
||||
function is_page_template(string $template = ''): bool { return false; }
|
||||
function aksrt_is_lazy(string $scope): bool { return false; }
|
||||
function aksrt_get_lazy_thumb(string $size = ''): string { return 'https://example.test/lazy.svg'; }
|
||||
function aksrt_get_default_avatar_url(): string { return 'https://example.test/avatar.png'; }
|
||||
function jio_is_lazy(string $scope): bool { return false; }
|
||||
function jio_get_lazy_thumb(string $size = ''): string { return 'https://example.test/lazy.svg'; }
|
||||
function jio_get_default_avatar_url(): string { return 'https://example.test/avatar.png'; }
|
||||
|
||||
require dirname(__DIR__, 2) . '/src/Core/ScriptsPart1c.php';
|
||||
|
||||
class RuntimeAssetBehaviorHarness {
|
||||
use \AKSRT\Core\ScriptsPart1c;
|
||||
use \Jio\Core\ScriptsPart1c;
|
||||
|
||||
public function enqueue(): void {
|
||||
$this->enqueue_public_fonts_js_and_data('https://example.test/theme', 'aksrt-public', [], false, false, false, false, AKSRT_VERSION, []);
|
||||
$this->enqueue_public_fonts_js_and_data('https://example.test/theme', 'jio-public', [], false, false, false, false, JIO_VERSION, []);
|
||||
}
|
||||
|
||||
private function resolve_frontend_routes(): array {
|
||||
@@ -93,5 +93,5 @@ rmdir(get_template_directory());
|
||||
|
||||
echo json_encode([
|
||||
'versions' => $versions,
|
||||
'expected' => [(string) ($baseTime + 30), (string) ($baseTime + 40), (string) ($baseTime + 50), AKSRT_VERSION],
|
||||
'expected' => [(string) ($baseTime + 30), (string) ($baseTime + 40), (string) ($baseTime + 50), JIO_VERSION],
|
||||
], JSON_THROW_ON_ERROR);
|
||||
@@ -6,15 +6,15 @@ const path = require('node:path')
|
||||
const root = path.resolve(__dirname, '../..')
|
||||
const read = file => fs.readFileSync(path.join(root, file), 'utf8')
|
||||
|
||||
const pay = read('inc/helpers/aksrt-pay.php')
|
||||
const resource = read('inc/helpers/aksrt-resource.php')
|
||||
const pay = read('inc/helpers/jio-pay.php')
|
||||
const resource = read('inc/helpers/jio-resource.php')
|
||||
const list = read('src/Admin/Native/PostListEnhance.php')
|
||||
const domain = read('src/Core/DomainModel.php')
|
||||
|
||||
test('sales count has a single read path that adds the 销量浮动', () => {
|
||||
// 展示销量只有一个口径:真实订单 + 后台「销量浮动」(pay_count)。
|
||||
// 此前各调用点各自拼 virtual_sales / sales_volume,pay_count 写进去却没人读。
|
||||
assert.match(pay, /function aksrt_get_content_sales_count\(/)
|
||||
assert.match(pay, /function jio_get_content_sales_count\(/)
|
||||
assert.match(pay, /RESOURCE_SALES_VOLUME_KEY/)
|
||||
assert.match(pay, /\$pay_meta\['pay_count'\]/)
|
||||
// 两部分都必须下限为 0,避免负的浮动把销量拉成负数。
|
||||
@@ -23,9 +23,9 @@ test('sales count has a single read path that adds the 销量浮动', () => {
|
||||
|
||||
test('every sales_count producer goes through that read path', () => {
|
||||
// 内容帖、资源帖、付费墙、付费盒四处不得再各自拼装销量。
|
||||
assert.match(pay, /'sales_count'\s*=>\s*\$show_sales \? aksrt_get_content_sales_count\(\$p, \$pay_meta\) : 0/)
|
||||
assert.match(resource, /'sales_count'\s*=>\s*\$show_sales \? aksrt_get_content_sales_count\(\$p, \$pay_meta\) : 0/)
|
||||
assert.match(pay, /\$sales = aksrt_get_content_sales_count\(\$post, \$meta\)/)
|
||||
assert.match(pay, /'sales_count'\s*=>\s*\$show_sales \? jio_get_content_sales_count\(\$p, \$pay_meta\) : 0/)
|
||||
assert.match(resource, /'sales_count'\s*=>\s*\$show_sales \? jio_get_content_sales_count\(\$p, \$pay_meta\) : 0/)
|
||||
assert.match(pay, /\$sales = jio_get_content_sales_count\(\$post, \$meta\)/)
|
||||
|
||||
// 不得再出现按 virtual_sales 现拼销量的旧写法。
|
||||
assert.doesNotMatch(pay, /\$sales\s*=\s*max\(0, \(int\) \(\$meta\['virtual_sales'\] \?\? 0\)\);/)
|
||||
|
||||
@@ -73,31 +73,31 @@ class wpdb {
|
||||
$GLOBALS['wpdb'] = new wpdb();
|
||||
|
||||
// Fake posts the stub WP_Query will serve, keyed by expected cat.
|
||||
$GLOBALS['aksrt_harness_posts'] = [
|
||||
$GLOBALS['jio_harness_posts'] = [
|
||||
['id' => 101, 'title' => '第一篇文章', 'cat' => 5],
|
||||
['id' => 102, 'title' => '第二篇文章', 'cat' => 5],
|
||||
['id' => 103, 'title' => '第三篇文章', 'cat' => 5],
|
||||
['id' => 104, 'title' => '第四篇文章', 'cat' => 5],
|
||||
];
|
||||
$GLOBALS['aksrt_harness_cursor'] = 0;
|
||||
$GLOBALS['jio_harness_cursor'] = 0;
|
||||
|
||||
class WP_Query {
|
||||
public array $posts = [];
|
||||
public int $found_posts = 0;
|
||||
public function __construct(public array $query_args = []) {
|
||||
$cat_in = (array) ($query_args['category__in'] ?? []);
|
||||
$matched = array_values(array_filter($GLOBALS['aksrt_harness_posts'], static fn(array $p): bool => in_array($p['cat'], $cat_in, true)));
|
||||
$matched = array_values(array_filter($GLOBALS['jio_harness_posts'], static fn(array $p): bool => in_array($p['cat'], $cat_in, true)));
|
||||
$this->found_posts = count($matched);
|
||||
foreach ($matched as $post) {
|
||||
$this->posts[] = new WP_Post($post['id']);
|
||||
$GLOBALS['aksrt_harness_lookup'][$post['id']] = $post;
|
||||
$GLOBALS['jio_harness_lookup'][$post['id']] = $post;
|
||||
}
|
||||
}
|
||||
public function have_posts(): bool { return $GLOBALS['aksrt_harness_cursor'] < count($this->posts); }
|
||||
public function have_posts(): bool { return $GLOBALS['jio_harness_cursor'] < count($this->posts); }
|
||||
public function the_post(): void {
|
||||
$post = $this->posts[$GLOBALS['aksrt_harness_cursor']];
|
||||
$GLOBALS['aksrt_harness_cursor']++;
|
||||
$GLOBALS['aksrt_harness_current_post'] = $GLOBALS['aksrt_harness_lookup'][$post->ID] ?? ['id' => $post->ID, 'title' => '文章' . $post->ID];
|
||||
$post = $this->posts[$GLOBALS['jio_harness_cursor']];
|
||||
$GLOBALS['jio_harness_cursor']++;
|
||||
$GLOBALS['jio_harness_current_post'] = $GLOBALS['jio_harness_lookup'][$post->ID] ?? ['id' => $post->ID, 'title' => '文章' . $post->ID];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,7 +141,7 @@ function human_time_diff(int $from, int $to = 0): string {
|
||||
function get_avatar_url($id, array $args = []): string { return 'https://example.test/avatar-' . (int) $id . '.jpg'; }
|
||||
function get_post_meta($id, $key = '', $single = false) { unset($id, $key, $single); return ''; }
|
||||
function get_term_meta(int $term_id, string $key, bool $single = false) {
|
||||
if ($key === 'aksrt_category_cover_id') {
|
||||
if ($key === 'jio_category_cover_id') {
|
||||
return $term_id === 5 ? 55 : 0;
|
||||
}
|
||||
return '';
|
||||
@@ -170,10 +170,10 @@ function get_term(int $term_id, string $taxonomy = '') {
|
||||
function get_term_link(WP_Term $term) {
|
||||
return 'https://example.test/cat-' . $term->term_id;
|
||||
}
|
||||
function get_the_ID(): int { return (int) ($GLOBALS['aksrt_harness_current_post']['id'] ?? 0); }
|
||||
function get_the_ID(): int { return (int) ($GLOBALS['jio_harness_current_post']['id'] ?? 0); }
|
||||
function get_the_title($id = 0): string {
|
||||
unset($id);
|
||||
return (string) ($GLOBALS['aksrt_harness_current_post']['title'] ?? '');
|
||||
return (string) ($GLOBALS['jio_harness_current_post']['title'] ?? '');
|
||||
}
|
||||
function get_permalink($id = 0): string { return 'https://example.test/post-' . (int) ($id ?: get_the_ID()); }
|
||||
function get_the_excerpt($id = 0): string { unset($id); return '这是摘要'; }
|
||||
@@ -191,19 +191,19 @@ function get_comments_link($id = 0): string { return get_permalink($id) . '#comm
|
||||
function comments_open($id = 0): bool { unset($id); return true; }
|
||||
function get_post_type($id = 0): string { unset($id); return 'post'; }
|
||||
function has_post_format($formats, $id = 0): bool { unset($formats, $id); return false; }
|
||||
function aksrt_get_pay_meta($post = null): array { unset($post); return ['pay_type' => 'no', 'price' => 0]; }
|
||||
function aksrt_get_post_views($id = 0): int { return (int) ($id % 90) + 10; }
|
||||
function aksrt_get_post_likes($id = 0): int { return (int) ($id % 7); }
|
||||
function aksrt_get_post_favorites($id = 0): int { return (int) ($id % 5); }
|
||||
function aksrt_resource_card_badge_text($id = 0): string { unset($id); return ''; }
|
||||
function aksrt_format_number($num): string { return (string) $num; }
|
||||
function aksrt_is_lazy(string $key, bool $default = false): bool { unset($key); return $default; }
|
||||
function aksrt_get_lazy_thumb(string $size = ''): string { unset($size); return 'https://example.test/lazy.svg'; }
|
||||
function aksrt_get_lazy_attr(string $type, string $src, string $class = '', string $lazy_src = ''): string {
|
||||
function jio_get_pay_meta($post = null): array { unset($post); return ['pay_type' => 'no', 'price' => 0]; }
|
||||
function jio_get_post_views($id = 0): int { return (int) ($id % 90) + 10; }
|
||||
function jio_get_post_likes($id = 0): int { return (int) ($id % 7); }
|
||||
function jio_get_post_favorites($id = 0): int { return (int) ($id % 5); }
|
||||
function jio_resource_card_badge_text($id = 0): string { unset($id); return ''; }
|
||||
function jio_format_number($num): string { return (string) $num; }
|
||||
function jio_is_lazy(string $key, bool $default = false): bool { unset($key); return $default; }
|
||||
function jio_get_lazy_thumb(string $size = ''): string { unset($size); return 'https://example.test/lazy.svg'; }
|
||||
function jio_get_lazy_attr(string $type, string $src, string $class = '', string $lazy_src = ''): string {
|
||||
$url = $lazy_src !== '' ? $lazy_src : $src;
|
||||
return ' class="' . esc_attr($class) . '" src="' . esc_url($url) . '" data-src="' . esc_url($src) . '" data-lazy-type="' . esc_attr($type) . '"';
|
||||
}
|
||||
function aksrt_renderer_resolve_images($post_id, array $cfg): array {
|
||||
function jio_renderer_resolve_images($post_id, array $cfg): array {
|
||||
unset($post_id, $cfg);
|
||||
return ['primary' => '', 'has_real' => false, 'gallery' => [], 'slides' => [], 'is_slide' => false, 'video_url' => ''];
|
||||
}
|
||||
@@ -241,11 +241,11 @@ class WP_Widget {
|
||||
}
|
||||
|
||||
require dirname(__DIR__, 2) . '/vendor/autoload.php';
|
||||
require dirname(__DIR__, 2) . '/inc/helpers/aksrt-graphic-card.php';
|
||||
require dirname(__DIR__, 2) . '/inc/helpers/aksrt-widgets-markup.php';
|
||||
require dirname(__DIR__, 2) . '/inc/helpers/jio-graphic-card.php';
|
||||
require dirname(__DIR__, 2) . '/inc/helpers/jio-widgets-markup.php';
|
||||
require dirname(__DIR__, 2) . '/inc/helpers/posts-renderer/post-data.php';
|
||||
|
||||
use AKSRT\WidgetSystem\Renderers\ContentRenderer;
|
||||
use Jio\WidgetSystem\Renderers\ContentRenderer;
|
||||
|
||||
$instance = [
|
||||
'term_id' => [5, 8],
|
||||
@@ -270,7 +270,7 @@ try {
|
||||
|
||||
// Single-term variant (is_row=false) + animation variant.
|
||||
try {
|
||||
$GLOBALS['aksrt_harness_cursor'] = 0;
|
||||
$GLOBALS['jio_harness_cursor'] = 0;
|
||||
$single = ContentRenderer::render('term_aggregation', array_merge($instance, ['term_id' => [5], 'obs_animation' => 'fade']));
|
||||
echo "=== SINGLE OK (len=" . strlen($single) . ") ===\n";
|
||||
echo $single . "\n";
|
||||
@@ -280,16 +280,16 @@ try {
|
||||
|
||||
// Full pipeline: registry -> definition -> normalize(raw form input '5,8') -> FrontendRenderer.
|
||||
try {
|
||||
$definitions = \AKSRT\WidgetSystem\WidgetRegistry::all();
|
||||
$definitions = \Jio\WidgetSystem\WidgetRegistry::all();
|
||||
echo "=== REGISTRY: term_aggregation present = " . (isset($definitions['term_aggregation']) ? 'yes' : 'NO') . " ===\n";
|
||||
$definition = $definitions['term_aggregation'];
|
||||
$widget = new \AKSRT\WidgetSystem\SchemaWidget($definition);
|
||||
$widget = new \Jio\WidgetSystem\SchemaWidget($definition);
|
||||
$widget->_set(2);
|
||||
$normalized = $definition->normalize(['term_id' => '5,8']);
|
||||
echo "=== NORMALIZED term_id = " . json_encode($normalized['term_id']) . " ===\n";
|
||||
$GLOBALS['aksrt_harness_cursor'] = 0;
|
||||
$GLOBALS['jio_harness_cursor'] = 0;
|
||||
ob_start();
|
||||
\AKSRT\WidgetSystem\FrontendRenderer::render(
|
||||
\Jio\WidgetSystem\FrontendRenderer::render(
|
||||
$definition,
|
||||
['before_widget' => '<section class="w">', 'after_widget' => '</section>'],
|
||||
$normalized,
|
||||
@@ -301,7 +301,7 @@ try {
|
||||
|
||||
// Empty selection must render nothing at all.
|
||||
ob_start();
|
||||
\AKSRT\WidgetSystem\FrontendRenderer::render($definition, ['before_widget' => '<section>', 'after_widget' => '</section>'], $definition->normalize([]), $widget);
|
||||
\Jio\WidgetSystem\FrontendRenderer::render($definition, ['before_widget' => '<section>', 'after_widget' => '</section>'], $definition->normalize([]), $widget);
|
||||
$empty = (string) ob_get_clean();
|
||||
echo "=== EMPTY SELECTION OUTPUT len=" . strlen($empty) . " ===\n";
|
||||
|
||||
@@ -309,12 +309,12 @@ try {
|
||||
ob_start();
|
||||
$widget->render_full_form($definition->normalize(['term_id' => '5,8']));
|
||||
$form = (string) ob_get_clean();
|
||||
if (preg_match('/<div class="aksrt-widget-field aksrt-widget-field--terms".*?<\/div>\s*<\/div>\s*<\/div>/s', $form, $m)) {
|
||||
if (preg_match('/<div class="jio-widget-field jio-widget-field--terms".*?<\/div>\s*<\/div>\s*<\/div>/s', $form, $m)) {
|
||||
echo "=== TERMS FIELD HTML ===\n" . $m[0] . "\n";
|
||||
}
|
||||
echo "=== FORM contains hidden terms value name = " . (strpos($form, 'name="widget-aksrt_widget_term_aggregation[2][term_id]"') !== false ? 'yes' : 'NO') . " ===\n";
|
||||
echo "=== FORM contains hidden terms value name = " . (strpos($form, 'name="widget-jio_widget_term_aggregation[2][term_id]"') !== false ? 'yes' : 'NO') . " ===\n";
|
||||
echo "=== FORM hidden value = ";
|
||||
preg_match('/class="aksrt-widget-terms__value"[^>]*value="([^"]*)"/', $form, $v);
|
||||
preg_match('/class="jio-widget-terms__value"[^>]*value="([^"]*)"/', $form, $v);
|
||||
echo ($v[1] ?? '(not found)') . " ===\n";
|
||||
} catch (Throwable $e) {
|
||||
echo '=== FULL PATH FATAL: ' . get_class($e) . ': ' . $e->getMessage() . ' @ ' . $e->getFile() . ':' . $e->getLine() . "\n";
|
||||
|
||||
@@ -14,7 +14,7 @@ test('search route keeps the reference Zibll results and empty-state structure',
|
||||
for (const token of ['search-input', 'search-page-ajaxpager', 'search-tab-header', 'search-filter-item', 'post_ajax_loader']) {
|
||||
assert.match(template + styles, new RegExp(token.replace(/[.*+?^${}()|[\\]\\]/g, '\\$&')));
|
||||
}
|
||||
assert.match(template, /aksrt_get_ajax_null\(__\('请输入搜索关键词'/);
|
||||
assert.match(template, /jio_get_ajax_null\(__\('请输入搜索关键词'/);
|
||||
assert.match(template, /'null-search\.svg'/);
|
||||
assert.match(template, /user_query instanceof WP_User_Query/);
|
||||
assert.equal(fs.existsSync(path.join(root, 'assets/css/routes/search.css')), false);
|
||||
|
||||
@@ -37,7 +37,7 @@ function add_query_arg($key, $value = null, $url = ''): string {
|
||||
}
|
||||
return rtrim($url, '/') . '/?' . http_build_query($args);
|
||||
}
|
||||
function get_bloginfo($key): string { return $key === 'name' ? 'AKSRT' : ''; }
|
||||
function get_bloginfo($key): string { return $key === 'name' ? 'Jio' : ''; }
|
||||
function number_format_i18n($number): string { return number_format((float) $number); }
|
||||
function wp_kses_post($html): string { return strip_tags((string) $html, '<a><b><span><i><div><p><ul><li>'); }
|
||||
function is_wp_error($value): bool { return false; }
|
||||
@@ -46,20 +46,20 @@ function dynamic_sidebar($id): void {}
|
||||
function get_header(): void {}
|
||||
function get_sidebar(): void {}
|
||||
function get_footer(): void {}
|
||||
function aksrt_save_history_search(string $value): void {}
|
||||
function aksrt_update_search_keywords(string $value): void {}
|
||||
function aksrt_search_history_enabled(): bool { return false; }
|
||||
function aksrt_get_search_hot_keywords(): array { return []; }
|
||||
function aksrt_get_search_type_options(): array { return ['post' => '文章', 'user' => '用户']; }
|
||||
function aksrt_get_search_form_type(): string { return (string) ($_REQUEST['type'] ?? 'post'); }
|
||||
function aksrt_get_search_form_state(): array {
|
||||
return ['type' => aksrt_get_search_form_type(), 'types' => aksrt_get_search_type_options(), 'category_id' => 0, 'category_enabled' => false, 'categories' => []];
|
||||
function jio_save_history_search(string $value): void {}
|
||||
function jio_update_search_keywords(string $value): void {}
|
||||
function jio_search_history_enabled(): bool { return false; }
|
||||
function jio_get_search_hot_keywords(): array { return []; }
|
||||
function jio_get_search_type_options(): array { return ['post' => '文章', 'user' => '用户']; }
|
||||
function jio_get_search_form_type(): string { return (string) ($_REQUEST['type'] ?? 'post'); }
|
||||
function jio_get_search_form_state(): array {
|
||||
return ['type' => jio_get_search_form_type(), 'types' => jio_get_search_type_options(), 'category_id' => 0, 'category_enabled' => false, 'categories' => []];
|
||||
}
|
||||
|
||||
class WP_Term {
|
||||
public function __construct(public int $term_id, public string $name, public string $taxonomy = 'category') {}
|
||||
}
|
||||
function aksrt_get_search_categories(?array $ids = null): array {
|
||||
function jio_get_search_categories(?array $ids = null): array {
|
||||
return array_map(static fn($id) => new WP_Term((int) $id, (int) $id === 7 ? 'Tech' : 'News'), $ids ?? []);
|
||||
}
|
||||
function get_term($id) { return new WP_Term((int) $id, 'Tech'); }
|
||||
@@ -78,40 +78,40 @@ class WP_User_Query {
|
||||
public function get_total(): int { return $GLOBALS['user_total']; }
|
||||
}
|
||||
function get_userdata($id) { return $GLOBALS['user_data'][(int) $id] ?? false; }
|
||||
function aksrt_get_user_home_url($id): string { return 'https://example.test/user/' . (int) $id; }
|
||||
function get_author_posts_url($id): string { return aksrt_get_user_home_url($id); }
|
||||
function aksrt_get_avatar_url($id, $args = []): string { return 'https://example.test/avatar/' . (int) $id . '.png'; }
|
||||
function aksrt_get_lazy_attr($mode, $url, $class = '', $placeholder = ''): string {
|
||||
function jio_get_user_home_url($id): string { return 'https://example.test/user/' . (int) $id; }
|
||||
function get_author_posts_url($id): string { return jio_get_user_home_url($id); }
|
||||
function jio_get_avatar_url($id, $args = []): string { return 'https://example.test/avatar/' . (int) $id . '.png'; }
|
||||
function jio_get_lazy_attr($mode, $url, $class = '', $placeholder = ''): string {
|
||||
return ' class="' . esc_attr($class) . '" src="' . esc_attr($url) . '"';
|
||||
}
|
||||
function aksrt_get_lazy_thumb($size): string { return ''; }
|
||||
function aksrt_get_user_name($args): string {
|
||||
function jio_get_lazy_thumb($size): string { return ''; }
|
||||
function jio_get_user_name($args): string {
|
||||
$user = get_userdata((int) $args['id']);
|
||||
$name = function_exists('aksrt_renderer_highlight_text')
|
||||
? aksrt_renderer_highlight_text((string) $user->display_name, (string) ($args['highlight_query'] ?? ''))
|
||||
$name = function_exists('jio_renderer_highlight_text')
|
||||
? jio_renderer_highlight_text((string) $user->display_name, (string) ($args['highlight_query'] ?? ''))
|
||||
: esc_html($user->display_name);
|
||||
return '<name class="flex ac flex1"><a class="display-name" href="' . esc_url(aksrt_get_user_home_url($user->ID)) . '">' . $name . '</a></name>';
|
||||
return '<name class="flex ac flex1"><a class="display-name" href="' . esc_url(jio_get_user_home_url($user->ID)) . '">' . $name . '</a></name>';
|
||||
}
|
||||
function aksrt_get_user_follow($class, $id): string { return '<a class="follow flex0" data-follow="' . (int) $id . '">Follow</a>'; }
|
||||
function aksrt_get_user_description($id): string { return (string) (get_userdata($id)->description ?? ''); }
|
||||
function jio_get_user_follow($class, $id): string { return '<a class="follow flex0" data-follow="' . (int) $id . '">Follow</a>'; }
|
||||
function jio_get_user_description($id): string { return (string) (get_userdata($id)->description ?? ''); }
|
||||
function get_user_meta($id, $key, $single = true): string { return ''; }
|
||||
function aksrt_get_ajax_null(?string $text = null, $margin = 60, string $img = 'null.svg', int $width = 280): string {
|
||||
return '<div class="ajax-item aksrt-ajax-null" data-img="' . esc_attr($img) . '" data-width="' . $width . '">' . esc_html((string) $text) . '</div>';
|
||||
function jio_get_ajax_null(?string $text = null, $margin = 60, string $img = 'null.svg', int $width = 280): string {
|
||||
return '<div class="ajax-item jio-ajax-null" data-img="' . esc_attr($img) . '" data-width="' . $width . '">' . esc_html((string) $text) . '</div>';
|
||||
}
|
||||
function aksrt_get_null($text = null, $margin = 60, string $img = 'null.svg', string $class = '', int $width = 280): string { return ''; }
|
||||
function aksrt_get_search_tab_nav_filter(string $type): string { return ''; }
|
||||
function aksrt_archive_get_render_config(array $args): array { return $args + ['mode' => 'list']; }
|
||||
function aksrt_render_post_list(array $args, WP_Query $query): void {
|
||||
function jio_get_null($text = null, $margin = 60, string $img = 'null.svg', string $class = '', int $width = 280): string { return ''; }
|
||||
function jio_get_search_tab_nav_filter(string $type): string { return ''; }
|
||||
function jio_archive_get_render_config(array $args): array { return $args + ['mode' => 'list']; }
|
||||
function jio_render_post_list(array $args, WP_Query $query): void {
|
||||
$title = $query->posts[0]->post_title ?? '';
|
||||
echo '<div class="posts-item list ajax-item">' . aksrt_renderer_highlight_text((string) $title, (string) ($args['highlight_query'] ?? '')) . '</div>';
|
||||
echo '<div class="posts-item list ajax-item">' . jio_renderer_highlight_text((string) $title, (string) ($args['highlight_query'] ?? '')) . '</div>';
|
||||
}
|
||||
function aksrt_render_pagination($query, array $args): void {}
|
||||
function aksrt_render_placeholder($kind, $count): string { return ''; }
|
||||
function jio_render_pagination($query, array $args): void {}
|
||||
function jio_render_placeholder($kind, $count): string { return ''; }
|
||||
|
||||
require ABSPATH . 'inc/helpers/posts-renderer/config.php';
|
||||
require ABSPATH . 'inc/helpers/aksrt-graphic-card.php';
|
||||
require ABSPATH . 'inc/helpers/aksrt-ajax-list.php';
|
||||
require ABSPATH . 'inc/helpers/search/aksrt-search-box.php';
|
||||
require ABSPATH . 'inc/helpers/jio-graphic-card.php';
|
||||
require ABSPATH . 'inc/helpers/jio-ajax-list.php';
|
||||
require ABSPATH . 'inc/helpers/search/jio-search-box.php';
|
||||
|
||||
function render_search_case(string $query, string $type, array $users = [], int $user_total = 0, array $posts = []): string {
|
||||
$GLOBALS['search_text'] = $query;
|
||||
@@ -152,13 +152,13 @@ if (($GLOBALS['last_user_query_args']['orderby'] ?? '') !== 'display_name') {
|
||||
throw new RuntimeException('User name ordering is not mapped to WP_User_Query.');
|
||||
}
|
||||
|
||||
$highlight = aksrt_renderer_highlight_text('<b>A&B</b> "Z" <script>x</script>', $query);
|
||||
$highlight = jio_renderer_highlight_text('<b>A&B</b> "Z" <script>x</script>', $query);
|
||||
$plain = html_entity_decode(strip_tags($highlight), ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
||||
if ($plain !== 'A&B "Z" x' || !str_contains($highlight, '<b class="focus-color">')) {
|
||||
throw new RuntimeException('Keyword highlighting does not preserve safe text.');
|
||||
}
|
||||
|
||||
$form_with_category = aksrt_get_search_box([
|
||||
$form_with_category = jio_get_search_box([
|
||||
'class' => 'main-search page-search-box',
|
||||
's' => '',
|
||||
'show_keywords' => false,
|
||||
@@ -169,7 +169,7 @@ $form_with_category = aksrt_get_search_box([
|
||||
'show_type' => true,
|
||||
'in_type' => 'post',
|
||||
], false);
|
||||
$form_without_category = aksrt_get_search_box([
|
||||
$form_without_category = jio_get_search_box([
|
||||
'class' => 'main-search page-search-box',
|
||||
's' => '',
|
||||
'show_keywords' => false,
|
||||
@@ -192,7 +192,7 @@ if (!str_contains((string) $form_with_category, 'class="line-form-input" tabinde
|
||||
|
||||
foreach (['post' => 'null-post.svg', 'user' => 'null-user.svg'] as $type => $image) {
|
||||
$no_results = render_search_case('No matches', $type);
|
||||
if (!str_contains($no_results, 'class="ajax-item aksrt-ajax-null"') || !str_contains($no_results, $image)) {
|
||||
if (!str_contains($no_results, 'class="ajax-item jio-ajax-null"') || !str_contains($no_results, $image)) {
|
||||
throw new RuntimeException('Empty results must remain an AJAX replaceable illustrated item.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ test('clearing history expires the saved cookie at its original WordPress path a
|
||||
};
|
||||
jQuery.cookie = (name, value, options) => cookies.push({ name, value, ...options });
|
||||
const handler = handlerBetween('//清空搜索关键词', '//用户签到', {
|
||||
jQuery, confirm: () => confirmed, aksrt__: (key) => key,
|
||||
jQuery, confirm: () => confirmed, jio__: (key) => key,
|
||||
});
|
||||
handler.call(control);
|
||||
assert.deepEqual(cookies, [{ name: 'history_search', value: null, path: '/blog/', domain: '.example.test' }]);
|
||||
@@ -65,7 +65,7 @@ test('clearing history expires the saved cookie at its original WordPress path a
|
||||
});
|
||||
|
||||
test('removed search markup has no orphaned JavaScript owner', () => {
|
||||
assert.doesNotMatch(read('assets/js/core/article.js'), /initSearchClear|search-clear-btn|search-page-input|search-category-filter|aksrt_search_type|aksrt_category/);
|
||||
assert.doesNotMatch(read('assets/js/core/article.js'), /initSearchClear|search-clear-btn|search-page-input|search-category-filter|jio_search_type|jio_category/);
|
||||
assert.doesNotMatch(runtime, /eval\('\/' \+ val|search-tab \.item-heading/);
|
||||
assert.doesNotMatch(runtime, /css\/dplayer\.min\.css/);
|
||||
});
|
||||
|
||||
@@ -145,18 +145,18 @@ test('short or missing headings preserve pre-existing box content but clear this
|
||||
assert.equal(box.firstChild, external);
|
||||
assert.equal(box.querySelector('.other-widget-content').textContent, 'keep me');
|
||||
assert.equal(box.querySelector('.posts-nav-lists'), null);
|
||||
assert.equal(box.dataset.aksrtSectionNav, undefined);
|
||||
assert.equal(box.dataset.jioSectionNav, undefined);
|
||||
|
||||
const third = addHeading(runtime.document, rootNode, 'h2', 'Three');
|
||||
runtime.autoFun();
|
||||
assert.equal(box.dataset.aksrtSectionNav, '1');
|
||||
assert.equal(box.dataset.jioSectionNav, '1');
|
||||
assert.ok(box.querySelector('.posts-nav-lists'));
|
||||
|
||||
third.remove();
|
||||
runtime.autoFun();
|
||||
assert.equal(box.querySelector('.posts-nav-lists'), null);
|
||||
assert.equal(box.children.length, 0);
|
||||
assert.equal(box.dataset.aksrtSectionNav, undefined);
|
||||
assert.equal(box.dataset.jioSectionNav, undefined);
|
||||
assert.ok(first.parentNode && second.parentNode);
|
||||
});
|
||||
|
||||
|
||||
@@ -84,12 +84,12 @@ function setup({ buttons = [makeButton('btn uc-header__signin-btn', '签到')],
|
||||
};
|
||||
const jq = { off() { return this; }, on(event, callback) { listeners[event] = callback; return this; } };
|
||||
const window = {
|
||||
aksrtData: { captchaSignin, signinDetailsUrl: '/wp-json/aksrt/v1/signin/details', currencyName },
|
||||
AksrtModalRuntime: { open: (...args) => modals.push(args), openRemote: (...args) => modals.push(args) },
|
||||
AksrtRequest: { request(endpoint, options) { calls.push({ endpoint, options }); return endpoint === '/signin/today' ? today : signin; } },
|
||||
jioData: { captchaSignin, signinDetailsUrl: '/wp-json/jio/v1/signin/details', currencyName },
|
||||
JioModalRuntime: { open: (...args) => modals.push(args), openRemote: (...args) => modals.push(args) },
|
||||
JioRequest: { request(endpoint, options) { calls.push({ endpoint, options }); return endpoint === '/signin/today' ? today : signin; } },
|
||||
};
|
||||
const captchaCalls = [];
|
||||
if (captchaRun) window.AKSRTCaptcha = {
|
||||
if (captchaRun) window.JioCaptcha = {
|
||||
run(scene, scope) { captchaCalls.push(['run', scene, scope]); return captchaRun; },
|
||||
reset(scene, scope) { captchaCalls.push(['reset', scene, scope]); },
|
||||
};
|
||||
@@ -147,8 +147,8 @@ test('uses reference loading, success toast, and replaces all entry points with
|
||||
await settle();
|
||||
assert.deepEqual(h.notices.find((notice) => notice[1] === 'success')?.slice(0, 2), ['签到成功,积分 +2', 'success']);
|
||||
for (const original of [first, second]) {
|
||||
assert.equal(original.replaced?.getAttribute('data-aksrt-modal'), 'remote');
|
||||
assert.equal(original.replaced?.getAttribute('data-aksrt-modal-url'), '/wp-json/aksrt/v1/signin/details');
|
||||
assert.equal(original.replaced?.getAttribute('data-jio-modal'), 'remote');
|
||||
assert.equal(original.replaced?.getAttribute('data-jio-modal-url'), '/wp-json/jio/v1/signin/details');
|
||||
assert.match(original.replaced.innerHTML, /已签到/);
|
||||
assert.doesNotMatch(original.replaced.innerHTML, /data-signin-label/);
|
||||
assert.match(original.replaced.className, /checkin-details-link/);
|
||||
@@ -271,7 +271,7 @@ test('leaves a disabled sign-in feature disabled without turning it into a signe
|
||||
test('does not send the request when captcha is absent or cancelled and keeps original markup', async () => {
|
||||
const missingButton = makeButton('btn', '签到');
|
||||
const missing = setup({ buttons: [missingButton], captchaSignin: true });
|
||||
missing.window.AKSRTCaptcha = undefined;
|
||||
missing.window.JioCaptcha = undefined;
|
||||
// No mount is needed in the harness to verify the missing provider path.
|
||||
missingButton.click();
|
||||
await settle();
|
||||
@@ -292,7 +292,7 @@ test('does not bind the same button twice through repeated auto_fun scans', asyn
|
||||
const button = makeButton('btn', '签到');
|
||||
const h = setup({ buttons: [button] });
|
||||
await settle();
|
||||
h.listeners['auto_fun.aksrt-signin']();
|
||||
h.listeners['auto_fun.jio-signin']();
|
||||
assert.equal(button.listeners.click.length, 1);
|
||||
button.click();
|
||||
await settle();
|
||||
|
||||
@@ -13,8 +13,8 @@ const test = require('node:test');
|
||||
const root = path.resolve(__dirname, '..', '..');
|
||||
const read = (file) => fs.readFileSync(path.join(root, file), 'utf8');
|
||||
|
||||
const singleRender = read('inc/helpers/aksrt-single-render.php');
|
||||
const sliderHelper = read('inc/helpers/aksrt-slider.php');
|
||||
const singleRender = read('inc/helpers/jio-single-render.php');
|
||||
const sliderHelper = read('inc/helpers/jio-slider.php');
|
||||
|
||||
test('single cover: image mode keeps the calc(scale% + 50px) height', () => {
|
||||
assert.match(
|
||||
@@ -51,7 +51,7 @@ test('single cover: slide mode reserves the same +50px via the slider helper', (
|
||||
assert.match(
|
||||
singleRender,
|
||||
/\$slider_args\['scale_extra'\] = '50px';/,
|
||||
'比例高度分支必须向 aksrt_render_slider 传 scale_extra=50px'
|
||||
'比例高度分支必须向 jio_render_slider 传 scale_extra=50px'
|
||||
);
|
||||
assert.match(
|
||||
singleRender,
|
||||
@@ -74,8 +74,8 @@ test('single cover: slide mode reserves the same +50px via the slider helper', (
|
||||
test('single cover: the dead static slide fallback is gone', () => {
|
||||
assert.doesNotMatch(
|
||||
singleRender,
|
||||
/function_exists\('aksrt_render_slider'\)/,
|
||||
'aksrt_render_slider 由 bootstrap 无条件加载,不得保留 function_exists 双路判断'
|
||||
/function_exists\('jio_render_slider'\)/,
|
||||
'jio_render_slider 由 bootstrap 无条件加载,不得保留 function_exists 双路判断'
|
||||
);
|
||||
assert.doesNotMatch(
|
||||
singleRender,
|
||||
@@ -107,7 +107,7 @@ test('video cover: the player video fills and center-crops like the image cover'
|
||||
const player = read('assets/js/video-player.js');
|
||||
assert.match(
|
||||
player,
|
||||
/\.aksrt-video video\{display:block;width:100%;height:100%;object-fit:cover;\}/,
|
||||
/\.jio-video video\{display:block;width:100%;height:100%;object-fit:cover;\}/,
|
||||
'视频必须铺满封面比例盒并居中裁切'
|
||||
);
|
||||
assert.doesNotMatch(
|
||||
@@ -180,7 +180,7 @@ test('resource singles get the cover leaf versioned, never via the aggregate @im
|
||||
);
|
||||
assert.match(
|
||||
enqueue,
|
||||
/'aksrt-single-post-cover'/,
|
||||
/'jio-single-post-cover'/,
|
||||
'cover leaf 句柄与文章分支命名保持一致'
|
||||
);
|
||||
});
|
||||
|
||||
@@ -48,7 +48,7 @@ test('security settings are consumed at runtime and initialized on boot', () =>
|
||||
const bootstrap = read('functions.php');
|
||||
|
||||
assert.match(runtime, /public static function get_security_settings\(\): array/);
|
||||
assert.match(bootstrap, /\\AKSRT\\Extensions\\SecurityHardening::get_instance\(\)->init\(\)/);
|
||||
assert.match(bootstrap, /\\Jio\\Extensions\\SecurityHardening::get_instance\(\)->init\(\)/);
|
||||
});
|
||||
|
||||
test('hardening switches wire the documented WordPress hooks', () => {
|
||||
@@ -74,7 +74,7 @@ test('verification-code sends are funneled through the configurable send limiter
|
||||
|
||||
assert.match(sms, /SendLimit::check\(\$phone\)/);
|
||||
assert.match(sms, /SendLimit::record\(\$phone\)/);
|
||||
assert.doesNotMatch(sms, /RATE_LIMIT|DAILY_LIMIT|aksrt_sms_rate_|aksrt_sms_daily_/);
|
||||
assert.doesNotMatch(sms, /RATE_LIMIT|DAILY_LIMIT|jio_sms_rate_|jio_sms_daily_/);
|
||||
assert.match(sendLimit, /security_captcha_send_cooldown/);
|
||||
assert.match(sendLimit, /security_captcha_to_hour/);
|
||||
assert.match(sendLimit, /security_captcha_to_day/);
|
||||
@@ -104,17 +104,17 @@ test('content brush limits are wired into post, comment, upload, register, and o
|
||||
});
|
||||
|
||||
test('phone masking is shared by the account UI and adapts to international numbers', () => {
|
||||
const helper = read('inc/helpers/aksrt-phone.php');
|
||||
const helper = read('inc/helpers/jio-phone.php');
|
||||
const bootstrap = read('inc/bootstrap.php');
|
||||
const template = read('inc/helpers/user-center-renderers/settings-account.php');
|
||||
|
||||
assert.match(bootstrap, /inc\/helpers\/aksrt-phone\.php/);
|
||||
assert.match(helper, /function aksrt_phone_mask_parts\(string \$phone\): \?array/);
|
||||
assert.match(helper, /function aksrt_phone_mask_display\(string \$phone\): string/);
|
||||
assert.match(bootstrap, /inc\/helpers\/jio-phone\.php/);
|
||||
assert.match(helper, /function jio_phone_mask_parts\(string \$phone\): \?array/);
|
||||
assert.match(helper, /function jio_phone_mask_display\(string \$phone\): string/);
|
||||
assert.match(helper, /min\(11, max\(4, \$length - 7\)\)/);
|
||||
assert.doesNotMatch(template, /substr_replace\(\$account_phone, '\*\*\*\*', 3, 4\)/);
|
||||
assert.match(template, /aksrt_phone_mask_parts\(\$account_phone\)/);
|
||||
assert.match(template, /aksrt_phone_mask_display\(\$account_phone\)/);
|
||||
assert.match(template, /jio_phone_mask_parts\(\$account_phone\)/);
|
||||
assert.match(template, /jio_phone_mask_display\(\$account_phone\)/);
|
||||
});
|
||||
|
||||
test('site security runtime contract harness passes', () => {
|
||||
|
||||
@@ -8,33 +8,33 @@ namespace {
|
||||
define('MINUTE_IN_SECONDS', 60);
|
||||
define('HOUR_IN_SECONDS', 3600);
|
||||
define('DAY_IN_SECONDS', 86400);
|
||||
$GLOBALS['aksrt_security_settings'] = [];
|
||||
$GLOBALS['aksrt_security_transients'] = [];
|
||||
$GLOBALS['aksrt_security_user_id'] = 7;
|
||||
$GLOBALS['jio_security_settings'] = [];
|
||||
$GLOBALS['jio_security_transients'] = [];
|
||||
$GLOBALS['jio_security_user_id'] = 7;
|
||||
$_SERVER['REMOTE_ADDR'] = '203.0.113.9';
|
||||
|
||||
function __(string $text, string $domain = ''): string { return $text; }
|
||||
function sanitize_key(string $key): string { return preg_replace('/[^a-z0-9_\-]/', '', strtolower($key)) ?? ''; }
|
||||
function get_current_user_id(): int { return (int) $GLOBALS['aksrt_security_user_id']; }
|
||||
function get_current_user_id(): int { return (int) $GLOBALS['jio_security_user_id']; }
|
||||
function current_user_can(string $capability): bool { return false; }
|
||||
function wp_date(string $format): string { return date($format); }
|
||||
function add_filter(string $hook, callable $callback, int $priority = 10, int $args = 1): void {}
|
||||
function add_action(string $hook, callable $callback, int $priority = 10, int $args = 1): void {}
|
||||
function set_transient(string $key, mixed $value, int $expiration = 0): bool {
|
||||
$GLOBALS['aksrt_security_transients'][$key] = ['value' => $value, 'expires' => $expiration > 0 ? time() + $expiration : 0];
|
||||
$GLOBALS['jio_security_transients'][$key] = ['value' => $value, 'expires' => $expiration > 0 ? time() + $expiration : 0];
|
||||
return true;
|
||||
}
|
||||
function get_transient(string $key): mixed {
|
||||
$row = $GLOBALS['aksrt_security_transients'][$key] ?? null;
|
||||
$row = $GLOBALS['jio_security_transients'][$key] ?? null;
|
||||
if (!is_array($row)) return false;
|
||||
if (!empty($row['expires']) && $row['expires'] <= time()) {
|
||||
unset($GLOBALS['aksrt_security_transients'][$key]);
|
||||
unset($GLOBALS['jio_security_transients'][$key]);
|
||||
return false;
|
||||
}
|
||||
return $row['value'] ?? false;
|
||||
}
|
||||
function delete_transient(string $key): bool {
|
||||
unset($GLOBALS['aksrt_security_transients'][$key]);
|
||||
unset($GLOBALS['jio_security_transients'][$key]);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -43,17 +43,17 @@ namespace {
|
||||
}
|
||||
|
||||
function site_security_brush_minute_key(string $scope, string $subject): string {
|
||||
return sprintf('aksrt_brush_%s_m_%s_%s%d', $scope, md5($subject), wp_date('YmdH'), intdiv((int) wp_date('i'), 10));
|
||||
return sprintf('jio_brush_%s_m_%s_%s%d', $scope, md5($subject), wp_date('YmdH'), intdiv((int) wp_date('i'), 10));
|
||||
}
|
||||
|
||||
function site_security_brush_day_key(string $scope, string $subject): string {
|
||||
return sprintf('aksrt_brush_%s_d_%s_%s', $scope, md5($subject), wp_date('Ymd'));
|
||||
return sprintf('jio_brush_%s_d_%s_%s', $scope, md5($subject), wp_date('Ymd'));
|
||||
}
|
||||
|
||||
require dirname(__DIR__, 2) . '/src/Settings/ExtensionRuntime.php';
|
||||
require dirname(__DIR__, 2) . '/src/Extensions/SecurityHardening.php';
|
||||
require dirname(__DIR__, 2) . '/src/Extensions/SendLimit.php';
|
||||
require dirname(__DIR__, 2) . '/inc/helpers/aksrt-phone.php';
|
||||
require dirname(__DIR__, 2) . '/inc/helpers/jio-phone.php';
|
||||
require dirname(__DIR__, 2) . '/src/Admin/Schema/SchemaExtensionsIP.php';
|
||||
require dirname(__DIR__, 2) . '/src/Admin/Schema/SchemaExtensionsAudit.php';
|
||||
require dirname(__DIR__, 2) . '/src/Admin/Schema/SchemaExtensionsPush.php';
|
||||
@@ -62,37 +62,37 @@ namespace {
|
||||
require dirname(__DIR__, 2) . '/src/Admin/Schema/SchemaExtensions.php';
|
||||
}
|
||||
|
||||
namespace AKSRT\Settings {
|
||||
namespace Jio\Settings {
|
||||
final class Settings {
|
||||
public static function get_all(): array { return $GLOBALS['aksrt_security_settings']; }
|
||||
public static function get_all(): array { return $GLOBALS['jio_security_settings']; }
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
use AKSRT\Admin\Schema\SchemaExtensions;
|
||||
use AKSRT\Extensions\SecurityHardening;
|
||||
use AKSRT\Extensions\SendLimit;
|
||||
use AKSRT\Settings\ExtensionRuntime;
|
||||
use Jio\Admin\Schema\SchemaExtensions;
|
||||
use Jio\Extensions\SecurityHardening;
|
||||
use Jio\Extensions\SendLimit;
|
||||
use Jio\Settings\ExtensionRuntime;
|
||||
|
||||
// 1) Phone masking: domestic hides 4 digits, international hides length-7 (min 4), short numbers are unmaskable.
|
||||
$domestic = aksrt_phone_mask_parts('13812341234');
|
||||
$domestic = jio_phone_mask_parts('13812341234');
|
||||
site_security_check($domestic !== null, 'domestic number should be maskable');
|
||||
site_security_check($domestic['prefix'] === '138' && $domestic['hidden'] === '1234' && $domestic['suffix'] === '1234', 'domestic mask split is wrong');
|
||||
site_security_check($domestic['hidden_length'] === 4, 'domestic mask should hide 4 digits');
|
||||
site_security_check(aksrt_phone_mask_display('13812341234') === '138****1234', 'domestic masked display is wrong');
|
||||
site_security_check(jio_phone_mask_display('13812341234') === '138****1234', 'domestic masked display is wrong');
|
||||
|
||||
$intl = aksrt_phone_mask_parts('+8613812341234');
|
||||
$intl = jio_phone_mask_parts('+8613812341234');
|
||||
site_security_check($intl !== null, 'international number should be maskable');
|
||||
site_security_check($intl['hidden_length'] === 6, 'international mask should hide length-7 digits');
|
||||
site_security_check($intl['hidden'] === '381234', 'international hidden segment is wrong');
|
||||
site_security_check($intl['leading_plus'] === true, 'leading plus flag is wrong');
|
||||
site_security_check(aksrt_phone_mask_display('+8613812341234') === '+861******1234', 'international masked display is wrong');
|
||||
site_security_check(jio_phone_mask_display('+8613812341234') === '+861******1234', 'international masked display is wrong');
|
||||
|
||||
$eight = aksrt_phone_mask_parts('12345678');
|
||||
$eight = jio_phone_mask_parts('12345678');
|
||||
site_security_check($eight !== null && $eight['hidden_length'] === 4 && $eight['suffix'] === '8', 'short mask should still hide 4 digits with a 1-digit suffix');
|
||||
site_security_check(aksrt_phone_mask_parts('1234567') === null, 'numbers with fewer than 8 digits must be unmaskable');
|
||||
site_security_check(aksrt_phone_mask_parts('') === null, 'empty number must be unmaskable');
|
||||
site_security_check(aksrt_phone_mask_display('1234567') === '1234567', 'unmaskable numbers must display raw');
|
||||
site_security_check(jio_phone_mask_parts('1234567') === null, 'numbers with fewer than 8 digits must be unmaskable');
|
||||
site_security_check(jio_phone_mask_parts('') === null, 'empty number must be unmaskable');
|
||||
site_security_check(jio_phone_mask_display('1234567') === '1234567', 'unmaskable numbers must display raw');
|
||||
|
||||
// 2) Brush limits: 10-minute and daily caps, 0 = unlimited, admin exempt handled by callers.
|
||||
$allowed = SecurityHardening::brush_allow('post', '12', 2, 0);
|
||||
@@ -101,12 +101,12 @@ namespace {
|
||||
site_security_check((int) get_transient(site_security_brush_minute_key('post', '12')) === 2, 'post attempts were not counted');
|
||||
site_security_check(SecurityHardening::brush_allow('post', '12', 2, 0) !== true, 'post 10-minute cap was not enforced');
|
||||
|
||||
$GLOBALS['aksrt_security_transients'] = [];
|
||||
$GLOBALS['jio_security_transients'] = [];
|
||||
site_security_check(SecurityHardening::brush_allow('post', '12', 0, 2) === true, 'first daily post was blocked');
|
||||
site_security_check(SecurityHardening::brush_allow('post', '12', 0, 2) === true, 'second daily post was blocked');
|
||||
site_security_check(SecurityHardening::brush_allow('post', '12', 0, 2) !== true, 'post daily cap was not enforced');
|
||||
|
||||
$GLOBALS['aksrt_security_transients'] = [];
|
||||
$GLOBALS['jio_security_transients'] = [];
|
||||
site_security_check(SecurityHardening::brush_allow('order', '203.0.113.9', 0, 0) === true, 'disabled caps should never block');
|
||||
site_security_check(get_transient(site_security_brush_minute_key('order', '203.0.113.9')) === false, 'disabled caps should not count attempts');
|
||||
|
||||
@@ -128,13 +128,13 @@ namespace {
|
||||
site_security_check(SecurityHardening::error_frequency_wait() === 0, 'error frequency clear did not reset the counter');
|
||||
|
||||
// Disabling the switch must silence the limit.
|
||||
$GLOBALS['aksrt_security_settings']['security_error_frequency_limit'] = false;
|
||||
$GLOBALS['jio_security_settings']['security_error_frequency_limit'] = false;
|
||||
SecurityHardening::error_frequency_record();
|
||||
site_security_check(SecurityHardening::error_frequency_wait() === 0, 'disabling security_error_frequency_limit did not silence the limiter');
|
||||
$GLOBALS['aksrt_security_settings'] = [];
|
||||
$GLOBALS['jio_security_settings'] = [];
|
||||
|
||||
// 4) Send limit: cooldown, recipient hourly/daily, IP 10-minute/daily caps; 0 = unlimited.
|
||||
$GLOBALS['aksrt_security_transients'] = [];
|
||||
$GLOBALS['jio_security_transients'] = [];
|
||||
$first = SendLimit::check('user@example.test');
|
||||
site_security_check($first['ok'] === true, 'first verification send was blocked');
|
||||
site_security_check(SendLimit::record('user@example.test') === 60, 'send limit did not return the cooldown');
|
||||
@@ -142,30 +142,30 @@ namespace {
|
||||
site_security_check($second['ok'] === false && $second['remaining'] > 0, 'send cooldown was not enforced');
|
||||
site_security_check(SendLimit::check('other@example.test')['ok'] === true, 'send cooldown leaked across recipients');
|
||||
|
||||
$GLOBALS['aksrt_security_transients'] = [];
|
||||
$GLOBALS['jio_security_transients'] = [];
|
||||
for ($send = 0; $send < 15; $send++) {
|
||||
site_security_check(SendLimit::record('user@example.test') === 60, 'send record failed');
|
||||
}
|
||||
site_security_check(SendLimit::check('user@example.test')['ok'] === false, 'recipient hourly cap was not enforced');
|
||||
site_security_check(SendLimit::check('user2@example.test')['ok'] === true, 'recipient hourly cap leaked across recipients');
|
||||
|
||||
$GLOBALS['aksrt_security_transients'] = [];
|
||||
$GLOBALS['jio_security_transients'] = [];
|
||||
for ($send = 0; $send < 20; $send++) {
|
||||
SendLimit::record('user@example.test');
|
||||
}
|
||||
site_security_check(SendLimit::check('user2@example.test')['ok'] === false, 'IP 10-minute cap was not enforced');
|
||||
|
||||
$GLOBALS['aksrt_security_transients'] = [];
|
||||
$GLOBALS['aksrt_security_settings']['security_captcha_send_cooldown'] = 0;
|
||||
$GLOBALS['aksrt_security_settings']['security_captcha_to_hour'] = 0;
|
||||
$GLOBALS['aksrt_security_settings']['security_captcha_to_day'] = 0;
|
||||
$GLOBALS['aksrt_security_settings']['security_captcha_ip_10min'] = 0;
|
||||
$GLOBALS['aksrt_security_settings']['security_captcha_ip_day'] = 0;
|
||||
$GLOBALS['jio_security_transients'] = [];
|
||||
$GLOBALS['jio_security_settings']['security_captcha_send_cooldown'] = 0;
|
||||
$GLOBALS['jio_security_settings']['security_captcha_to_hour'] = 0;
|
||||
$GLOBALS['jio_security_settings']['security_captcha_to_day'] = 0;
|
||||
$GLOBALS['jio_security_settings']['security_captcha_ip_10min'] = 0;
|
||||
$GLOBALS['jio_security_settings']['security_captcha_ip_day'] = 0;
|
||||
for ($send = 0; $send < 100; $send++) {
|
||||
site_security_check(SendLimit::record('user@example.test') === 0, 'disabled send caps should not cooldown');
|
||||
}
|
||||
site_security_check(SendLimit::check('user@example.test')['ok'] === true, 'disabled send caps still blocked sends');
|
||||
$GLOBALS['aksrt_security_settings'] = [];
|
||||
$GLOBALS['jio_security_settings'] = [];
|
||||
|
||||
// 5) Runtime defaults mirror the schema defaults.
|
||||
$security = ExtensionRuntime::get_security_settings();
|
||||
@@ -181,12 +181,12 @@ namespace {
|
||||
site_security_check($security['security_disabled_pingback'] === true && $security['security_disabled_xmlrpc'] === true, 'hardening switch defaults are wrong');
|
||||
site_security_check($security['security_user_disable_admin'] === true && $security['close_attachment_page'] === true, 'access switch defaults are wrong');
|
||||
|
||||
$GLOBALS['aksrt_security_settings']['security_brush_post_10min'] = '35';
|
||||
$GLOBALS['aksrt_security_settings']['close_attachment_page'] = 0;
|
||||
$GLOBALS['jio_security_settings']['security_brush_post_10min'] = '35';
|
||||
$GLOBALS['jio_security_settings']['close_attachment_page'] = 0;
|
||||
$stored = ExtensionRuntime::get_security_settings();
|
||||
site_security_check($stored['security_brush_post_10min'] === 35, 'stored cap override was not honored');
|
||||
site_security_check($stored['close_attachment_page'] === false, 'stored switch override was not honored');
|
||||
$GLOBALS['aksrt_security_settings'] = [];
|
||||
$GLOBALS['jio_security_settings'] = [];
|
||||
|
||||
// 6) Schema: security section is registered, defaults match, and no 人机验证 fields exist.
|
||||
$extensions = SchemaExtensions::build();
|
||||
|
||||
@@ -6,7 +6,7 @@ declare(strict_types=1);
|
||||
namespace {
|
||||
define('ABSPATH', __DIR__ . '/');
|
||||
define('HOUR_IN_SECONDS', 3600);
|
||||
define('WP_CONTENT_DIR', sys_get_temp_dir() . '/aksrt-split-test-' . getmypid());
|
||||
define('WP_CONTENT_DIR', sys_get_temp_dir() . '/jio-split-test-' . getmypid());
|
||||
|
||||
function sanitize_file_name(string $name): string { return preg_replace('/[^A-Za-z0-9._\-]/', '', $name) ?? ''; }
|
||||
function __(string $text, string $domain = ''): string { return $text; }
|
||||
@@ -63,9 +63,9 @@ namespace {
|
||||
require dirname(__DIR__, 2) . '/src/Core/FrontSubmitChunk.php';
|
||||
}
|
||||
|
||||
namespace AKSRT\Core {
|
||||
namespace Jio\Core {
|
||||
final class FakeFrontSubmit {
|
||||
use \AKSRT\Core\FrontSubmitChunk;
|
||||
use \Jio\Core\FrontSubmitChunk;
|
||||
|
||||
public static int $limit_bytes = 10 * 1048576;
|
||||
public static array $persisted = [];
|
||||
@@ -83,10 +83,10 @@ namespace AKSRT\Core {
|
||||
}
|
||||
|
||||
namespace {
|
||||
use AKSRT\Core\FakeFrontSubmit;
|
||||
use Jio\Core\FakeFrontSubmit;
|
||||
|
||||
$submit = new FakeFrontSubmit();
|
||||
$temp_dir = WP_CONTENT_DIR . '/aksrt-temp';
|
||||
$temp_dir = WP_CONTENT_DIR . '/jio-temp';
|
||||
|
||||
// 1) Chunk parameter validation.
|
||||
$err = $submit->rest_upload_chunk(new FakeRestRequest(['name' => '', 'chunks' => 3, 'chunk' => 0, 'size' => 10]));
|
||||
@@ -124,12 +124,12 @@ namespace {
|
||||
|
||||
// 4) Merge refuses when a chunk is missing.
|
||||
$err = $submit->rest_upload_chunk_merge(new FakeRestRequest(['kind' => 'video', 'name' => 'movie.mp4', 'chunks' => 3, 'size' => 9]));
|
||||
split_upload_check($err instanceof \WP_Error && $err->get_error_code() === 'aksrt_chunk_missing', 'merge should refuse with missing chunks');
|
||||
split_upload_check($err instanceof \WP_Error && $err->get_error_code() === 'jio_chunk_missing', 'merge should refuse with missing chunks');
|
||||
|
||||
// 5) Merge with a size mismatch is refused.
|
||||
$submit->rest_upload_chunk(new FakeRestRequest($params(1, 3, 6), ['file' => ['tmp_name' => $make_file('BBB'), 'error' => 0]]));
|
||||
$err = $submit->rest_upload_chunk_merge(new FakeRestRequest(['kind' => 'video', 'name' => 'movie.mp4', 'chunks' => 3, 'size' => 999]));
|
||||
split_upload_check($err instanceof \WP_Error && $err->get_error_code() === 'aksrt_chunk_size_mismatch', 'size mismatch should refuse the merge');
|
||||
split_upload_check($err instanceof \WP_Error && $err->get_error_code() === 'jio_chunk_size_mismatch', 'size mismatch should refuse the merge');
|
||||
|
||||
// 6) Happy-path merge concatenates in order, flags sideload, and cleans up.
|
||||
$response = $submit->rest_upload_chunk_merge(new FakeRestRequest(['kind' => 'video', 'name' => 'movie.mp4', 'chunks' => 3, 'size' => 9]));
|
||||
@@ -147,20 +147,20 @@ namespace {
|
||||
// 8) Chunk size cap refuses oversized chunk blobs.
|
||||
$big = $make_file(str_repeat('x', 3 * 1048576));
|
||||
$err = $submit->rest_upload_chunk(new FakeRestRequest($params(0, 1, 3 * 1048576), ['file' => ['tmp_name' => $big, 'error' => 0]]));
|
||||
split_upload_check($err instanceof \WP_Error && $err->get_error_code() === 'aksrt_chunk_invalid_size', 'oversized chunk blobs should be refused');
|
||||
split_upload_check($err instanceof \WP_Error && $err->get_error_code() === 'jio_chunk_invalid_size', 'oversized chunk blobs should be refused');
|
||||
|
||||
// 9) Kind size limit caps cumulative uploads.
|
||||
FakeFrontSubmit::$limit_bytes = 4;
|
||||
$err = $submit->rest_upload_chunk(new FakeRestRequest($params(0, 2, 9), ['file' => ['tmp_name' => $make_file('AAAAA'), 'error' => 0]]));
|
||||
split_upload_check($err instanceof \WP_Error && $err->get_error_code() === 'aksrt_upload_too_large', 'cumulative size over the kind limit should be refused');
|
||||
split_upload_check($err instanceof \WP_Error && $err->get_error_code() === 'jio_upload_too_large', 'cumulative size over the kind limit should be refused');
|
||||
|
||||
// Cleanup.
|
||||
FakeFrontSubmit::$persisted = [];
|
||||
foreach ((array) glob(WP_CONTENT_DIR . '/*.part') as $leftover) {
|
||||
@unlink((string) $leftover);
|
||||
}
|
||||
@unlink(WP_CONTENT_DIR . '/aksrt-temp/index.php');
|
||||
@rmdir(WP_CONTENT_DIR . '/aksrt-temp');
|
||||
@unlink(WP_CONTENT_DIR . '/jio-temp/index.php');
|
||||
@rmdir(WP_CONTENT_DIR . '/jio-temp');
|
||||
@rmdir(WP_CONTENT_DIR);
|
||||
|
||||
echo "Split upload harness passed\n";
|
||||
|
||||
@@ -56,8 +56,8 @@ test('system tools settings are consumed at runtime and initialized on boot', ()
|
||||
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\(\)/);
|
||||
assert.match(bootstrap, /\\Jio\\Extensions\\SystemTools::get_instance\(\)->init\(\)/);
|
||||
assert.match(bootstrap, /\\Jio\\Frontend\\PageAdminSet::get_instance\(\)->init\(\)/);
|
||||
});
|
||||
|
||||
test('system tools switches wire the reference theme hook suite', () => {
|
||||
@@ -121,14 +121,14 @@ test('split upload ships a full chunk protocol: routes, merge pipeline, and clie
|
||||
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, /apply_filters\('jio_temp_dir', WP_CONTENT_DIR \. '\/jio-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\)/);
|
||||
assert.match(frontSubmit, /media_handle_upload\('jio_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\)/);
|
||||
@@ -139,7 +139,7 @@ test('split upload ships a full chunk protocol: routes, merge pipeline, and clie
|
||||
assert.match(editorJs, /chunkUrl\('upload-merge'\)/);
|
||||
assert.match(editorJs, /'\/front-submit\/' \+ action \+ '\/' \+ uploadKey/);
|
||||
|
||||
// Config flows from the settings page into aksrtSubmitData.
|
||||
// Config flows from the settings page into jioSubmitData.
|
||||
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\)/);
|
||||
@@ -152,11 +152,11 @@ test('split upload ships a full chunk protocol: routes, merge pipeline, and clie
|
||||
|
||||
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 floatButtons = read('inc/helpers/jio-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(bootstrap, /\\Jio\\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'\)/);
|
||||
@@ -164,15 +164,15 @@ test('frontend page edit ships gate, footer modal, and legacy ajax-save contract
|
||||
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(floatButtons, /apply_filters\('jio_float_right_buttons', \$btn, \$is_mobile\)/);
|
||||
assert.match(pageAdminSet, /add_filter\('jio_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\)/);
|
||||
assert.match(pageAdminSet, /wp_nonce_field\('jio_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="action" value="jio_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\)/);
|
||||
@@ -181,25 +181,25 @@ test('frontend page edit ships gate, footer modal, and legacy ajax-save contract
|
||||
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, /esc_html__\('模块布局', 'jio'\)/);
|
||||
assert.match(pageAdminSet, /esc_html__\('后台编辑', 'jio'\)/);
|
||||
assert.match(pageAdminSet, /esc_html__\('确认修改', 'jio'\)/);
|
||||
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.
|
||||
// Fields replicate the reference input construct and map to Jio'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, /\\Jio\\Core\\PostMetaBox::META_KEY/);
|
||||
assert.match(pageAdminSet, /\\Jio\\Core\\DomainModel::POST_VIEW_COUNT_KEY/);
|
||||
assert.match(pageAdminSet, /update_post_meta\(\$post_id, '_jio_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.match(pageAdminSet, /return \[\s*'standard' => __\('标准', 'jio'\),\s*'image' => __\('图像', 'jio'\),\s*'gallery' => __\('画廊', 'jio'\),\s*'video' => __\('视频', 'jio'\),\s*\];/s);
|
||||
assert.doesNotMatch(pageAdminSet, /get_theme_support\('post-formats'\)\s*\?/);
|
||||
assert.doesNotMatch(pageAdminSet, /get_post_format_string\(/);
|
||||
// Page branch: 标题样式 + 模块布局(widgets_register) + 容器位置.
|
||||
@@ -213,10 +213,10 @@ test('frontend page edit ships gate, footer modal, and legacy ajax-save contract
|
||||
// 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\]\)/);
|
||||
// Save runs under the vendored main.js jio_ajax contract ({msg,reload}).
|
||||
assert.match(pageAdminSet, /add_action\('wp_ajax_jio_frontend_set_save', \[\$this, 'ajax_save'\]\)/);
|
||||
assert.match(pageAdminSet, /check_ajax_referer\('jio_frontend_set', '_wpnonce'\)/);
|
||||
assert.match(pageAdminSet, /wp_send_json\(\['msg' => __\('保存成功,正在刷新页面', 'jio'\), '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.
|
||||
@@ -230,7 +230,7 @@ test('frontend page edit icons follow the FA5 convention and the hint bar fades
|
||||
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-*`:
|
||||
// Site-wide icon convention is jio_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>/);
|
||||
@@ -256,14 +256,14 @@ test('frontend page edit icons follow the FA5 convention and the hint bar fades
|
||||
|
||||
// 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(pageAdminSet, /wp_enqueue_script\(\s*'jio-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.
|
||||
// Jio'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);
|
||||
@@ -306,16 +306,16 @@ test('float toolbar config reaches the tinymce plugin with reference-context but
|
||||
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, /editor\.addButton\('jio_h2'/);
|
||||
assert.match(editorJs, /editor\.addButton\('jio_h3'/);
|
||||
assert.match(editorJs, /editor\.addButton\('jio_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/);
|
||||
assert.match(editorTemplate, /jio_editor_tools/);
|
||||
});
|
||||
|
||||
test('countdown display switch gates aksrt_time_ago with the custom format fallback', () => {
|
||||
test('countdown display switch gates jio_time_ago with the custom format fallback', () => {
|
||||
const helper = read('inc/helpers/template-tags/search-meta.php');
|
||||
const coreHooks = read('inc/core-hooks.php');
|
||||
|
||||
|
||||
@@ -6,31 +6,31 @@ declare(strict_types=1);
|
||||
namespace {
|
||||
define('ABSPATH', __DIR__ . '/');
|
||||
|
||||
$GLOBALS['aksrt_system_settings'] = [];
|
||||
$GLOBALS['aksrt_system_added'] = [];
|
||||
$GLOBALS['aksrt_system_removed'] = [];
|
||||
$GLOBALS['jio_system_settings'] = [];
|
||||
$GLOBALS['jio_system_added'] = [];
|
||||
$GLOBALS['jio_system_removed'] = [];
|
||||
|
||||
function __(string $text, string $domain = ''): string { return $text; }
|
||||
function sanitize_key(string $key): string { return preg_replace('/[^a-z0-9_\-]/', '', strtolower($key)) ?? ''; }
|
||||
function is_super_admin(): bool { return (bool) $GLOBALS['aksrt_system_is_super_admin']; }
|
||||
function is_super_admin(): bool { return (bool) $GLOBALS['jio_system_is_super_admin']; }
|
||||
function current_time(string $type): string { return date('Y-m-d H:i:s'); }
|
||||
function add_filter(string $hook, $callback, int $priority = 10, int $args = 1): void {
|
||||
$GLOBALS['aksrt_system_added'][] = ['filter', $hook, $callback, $priority];
|
||||
$GLOBALS['jio_system_added'][] = ['filter', $hook, $callback, $priority];
|
||||
}
|
||||
function add_action(string $hook, $callback, int $priority = 10, int $args = 1): void {
|
||||
$GLOBALS['aksrt_system_added'][] = ['action', $hook, $callback, $priority];
|
||||
$GLOBALS['jio_system_added'][] = ['action', $hook, $callback, $priority];
|
||||
}
|
||||
function remove_filter(string $hook, $callback, int $priority = 10): void {
|
||||
$GLOBALS['aksrt_system_removed'][] = ['filter', $hook, $callback, $priority];
|
||||
$GLOBALS['jio_system_removed'][] = ['filter', $hook, $callback, $priority];
|
||||
}
|
||||
function remove_action(string $hook, $callback, int $priority = 10): void {
|
||||
$GLOBALS['aksrt_system_removed'][] = ['action', $hook, $callback, $priority];
|
||||
$GLOBALS['jio_system_removed'][] = ['action', $hook, $callback, $priority];
|
||||
}
|
||||
function wp_deregister_style(string $handle): void {}
|
||||
function wp_register_style(string $handle, $src): void {}
|
||||
function wp_enqueue_style(string $handle, $src = ''): void {}
|
||||
function remove_image_size(string $name): void {
|
||||
$GLOBALS['aksrt_system_added'][] = ['remove_image_size', $name, null, 10];
|
||||
$GLOBALS['jio_system_added'][] = ['remove_image_size', $name, null, 10];
|
||||
}
|
||||
|
||||
function system_tools_check(bool $condition, string $message): void {
|
||||
@@ -38,14 +38,14 @@ namespace {
|
||||
}
|
||||
|
||||
function system_tools_added(string $type, string $hook): bool {
|
||||
foreach ($GLOBALS['aksrt_system_added'] as $row) {
|
||||
foreach ($GLOBALS['jio_system_added'] as $row) {
|
||||
if ($row[0] === $type && $row[1] === $hook) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function system_tools_removed(string $type, string $hook, string $needle = ''): bool {
|
||||
foreach ($GLOBALS['aksrt_system_removed'] as $row) {
|
||||
foreach ($GLOBALS['jio_system_removed'] as $row) {
|
||||
if ($row[0] !== $type || $row[1] !== $hook) continue;
|
||||
if ($needle === '') return true;
|
||||
if (is_array($row[2])) {
|
||||
@@ -65,8 +65,8 @@ namespace {
|
||||
}
|
||||
|
||||
function system_tools_reset(): void {
|
||||
$GLOBALS['aksrt_system_added'] = [];
|
||||
$GLOBALS['aksrt_system_removed'] = [];
|
||||
$GLOBALS['jio_system_added'] = [];
|
||||
$GLOBALS['jio_system_removed'] = [];
|
||||
}
|
||||
|
||||
require dirname(__DIR__, 2) . '/src/Settings/ExtensionRuntime.php';
|
||||
@@ -80,13 +80,13 @@ namespace {
|
||||
require dirname(__DIR__, 2) . '/inc/helpers/template-tags/search-meta.php';
|
||||
}
|
||||
|
||||
namespace AKSRT\Settings {
|
||||
namespace Jio\Settings {
|
||||
final class Settings {
|
||||
public static function get_all(): array { return $GLOBALS['aksrt_system_settings']; }
|
||||
public static function get_all(): array { return $GLOBALS['jio_system_settings']; }
|
||||
}
|
||||
}
|
||||
|
||||
namespace AKSRT\SystemToolsHarness {
|
||||
namespace Jio\SystemToolsHarness {
|
||||
final class WpEmbedStub {
|
||||
public function autoembed($content) { return $content; }
|
||||
public function maybe_run_ajax_cache(): void {}
|
||||
@@ -94,13 +94,13 @@ namespace AKSRT\SystemToolsHarness {
|
||||
}
|
||||
|
||||
namespace {
|
||||
use AKSRT\Admin\Schema\SchemaExtensions;
|
||||
use AKSRT\Admin\Schema\SchemaExtensionsSystem;
|
||||
use AKSRT\Extensions\SystemTools;
|
||||
use AKSRT\Settings\ExtensionRuntime;
|
||||
use Jio\Admin\Schema\SchemaExtensions;
|
||||
use Jio\Admin\Schema\SchemaExtensionsSystem;
|
||||
use Jio\Extensions\SystemTools;
|
||||
use Jio\Settings\ExtensionRuntime;
|
||||
|
||||
$GLOBALS['aksrt_system_is_super_admin'] = false;
|
||||
$GLOBALS['wp_embed'] = new \AKSRT\SystemToolsHarness\WpEmbedStub();
|
||||
$GLOBALS['jio_system_is_super_admin'] = false;
|
||||
$GLOBALS['wp_embed'] = new \Jio\SystemToolsHarness\WpEmbedStub();
|
||||
|
||||
// 1) Defaults match the reference theme's 系统工具 defaults.
|
||||
$defaults = ExtensionRuntime::get_system_tools_settings();
|
||||
@@ -130,7 +130,7 @@ namespace {
|
||||
}
|
||||
|
||||
// 2) Stored values sanitize: rename type whitelist, switch coercion, passthrough format.
|
||||
$GLOBALS['aksrt_system_settings'] = [
|
||||
$GLOBALS['jio_system_settings'] = [
|
||||
'newfilename_type' => 'bogus',
|
||||
'newfilename' => 1,
|
||||
'time_ago_s' => 0,
|
||||
@@ -149,18 +149,18 @@ namespace {
|
||||
system_tools_check($clean['mce_float_toolbar'] === ['m_s'], 'float toolbar scenes should whitelist and dedupe');
|
||||
|
||||
// 2b) Unsaved float toolbar key keeps the both-scenes default; explicit empty stays empty.
|
||||
$GLOBALS['aksrt_system_settings'] = ['hide_admin_bar' => 0];
|
||||
$GLOBALS['jio_system_settings'] = ['hide_admin_bar' => 0];
|
||||
$clean = ExtensionRuntime::get_system_tools_settings();
|
||||
system_tools_check($clean['mce_float_toolbar'] === ['pc_s', 'm_s'], 'float toolbar should default to pc+mobile');
|
||||
system_tools_check($clean['hide_admin_bar'] === false, 'stored falsy value should not fall back to default');
|
||||
$GLOBALS['aksrt_system_settings'] = ['mce_float_toolbar' => []];
|
||||
$GLOBALS['jio_system_settings'] = ['mce_float_toolbar' => []];
|
||||
$clean = ExtensionRuntime::get_system_tools_settings();
|
||||
system_tools_check($clean['mce_float_toolbar'] === [], 'explicitly empty float toolbar should stay empty');
|
||||
|
||||
// 3) Hook wiring with every switch enabled.
|
||||
$GLOBALS['aksrt_system_settings'] = array_fill_keys(array_keys($expected), true);
|
||||
$GLOBALS['aksrt_system_settings']['time_format'] = 'n月j日 H:i';
|
||||
$GLOBALS['aksrt_system_is_super_admin'] = false;
|
||||
$GLOBALS['jio_system_settings'] = array_fill_keys(array_keys($expected), true);
|
||||
$GLOBALS['jio_system_settings']['time_format'] = 'n月j日 H:i';
|
||||
$GLOBALS['jio_system_is_super_admin'] = false;
|
||||
system_tools_reset();
|
||||
SystemTools::get_instance()->init();
|
||||
|
||||
@@ -181,8 +181,8 @@ namespace {
|
||||
system_tools_check(system_tools_removed('action', 'init', 'wp_schedule_update_checks'), 'update cron schedule should be removed');
|
||||
|
||||
// 4) hide_admin_bar off keeps the admin bar for super admins.
|
||||
$GLOBALS['aksrt_system_settings']['hide_admin_bar'] = false;
|
||||
$GLOBALS['aksrt_system_is_super_admin'] = true;
|
||||
$GLOBALS['jio_system_settings']['hide_admin_bar'] = false;
|
||||
$GLOBALS['jio_system_is_super_admin'] = true;
|
||||
system_tools_reset();
|
||||
SystemTools::get_instance()->init();
|
||||
system_tools_check(!system_tools_added('filter', 'show_admin_bar'), 'super admin should keep the admin bar when hide_admin_bar is off');
|
||||
@@ -225,10 +225,10 @@ namespace {
|
||||
system_tools_check($passed === 'passthrough', 'update checks should pass through the blocker');
|
||||
|
||||
// 8) Large thumbnail pruning removes the reference theme's size list.
|
||||
$GLOBALS['aksrt_system_added'] = [];
|
||||
$GLOBALS['jio_system_added'] = [];
|
||||
$tools->disable_large_thumbnail();
|
||||
$sizes_filter = null;
|
||||
foreach ($GLOBALS['aksrt_system_added'] as $row) {
|
||||
foreach ($GLOBALS['jio_system_added'] as $row) {
|
||||
if ($row[0] === 'filter' && $row[1] === 'intermediate_image_sizes') {
|
||||
$sizes_filter = $row[2];
|
||||
}
|
||||
@@ -252,17 +252,17 @@ namespace {
|
||||
$client_defaults = array_keys($expected);
|
||||
system_tools_check(count(SchemaExtensionsSystem::build()['groups']) === 3, 'system schema should keep three groups');
|
||||
|
||||
// 10) aksrt_time_ago honors the 倒计时显示 switch and custom format.
|
||||
// 10) jio_time_ago honors the 倒计时显示 switch and custom format.
|
||||
$past = date('Y-m-d H:i:s', strtotime('-3 days'));
|
||||
$GLOBALS['aksrt_system_settings'] = [];
|
||||
system_tools_check(strpos(aksrt_time_ago($past), '天前') !== false, 'relative time should be used by default');
|
||||
$GLOBALS['aksrt_system_settings'] = ['time_ago_s' => 0, 'time_format' => 'Y年n月j日'];
|
||||
$GLOBALS['jio_system_settings'] = [];
|
||||
system_tools_check(strpos(jio_time_ago($past), '天前') !== false, 'relative time should be used by default');
|
||||
$GLOBALS['jio_system_settings'] = ['time_ago_s' => 0, 'time_format' => 'Y年n月j日'];
|
||||
$stamp = strtotime('-3 days');
|
||||
system_tools_check(aksrt_time_ago(date('Y-m-d H:i:s', $stamp)) === date('Y年n月j日', $stamp), 'custom time format should apply when countdown display is off');
|
||||
$GLOBALS['aksrt_system_settings'] = ['time_ago_s' => 0, 'time_format' => ''];
|
||||
system_tools_check(strpos(aksrt_time_ago($past), '天前') !== false, 'empty custom format should fall back to relative time');
|
||||
$GLOBALS['aksrt_system_settings'] = ['time_ago_s' => 1];
|
||||
system_tools_check(strpos(aksrt_time_ago($past), '天前') !== false, 'countdown display should stay relative when enabled');
|
||||
system_tools_check(jio_time_ago(date('Y-m-d H:i:s', $stamp)) === date('Y年n月j日', $stamp), 'custom time format should apply when countdown display is off');
|
||||
$GLOBALS['jio_system_settings'] = ['time_ago_s' => 0, 'time_format' => ''];
|
||||
system_tools_check(strpos(jio_time_ago($past), '天前') !== false, 'empty custom format should fall back to relative time');
|
||||
$GLOBALS['jio_system_settings'] = ['time_ago_s' => 1];
|
||||
system_tools_check(strpos(jio_time_ago($past), '天前') !== false, 'countdown display should stay relative when enabled');
|
||||
|
||||
echo "System tools harness passed\n";
|
||||
}
|
||||
@@ -140,16 +140,16 @@ test('list margin defaults inherit the site setting while an explicit widget fal
|
||||
|
||||
test('multi-column frontend output uses the reference widget wrapper and carries layout variables', () => {
|
||||
const wrapper = render().frontend_wrapper;
|
||||
assert.match(wrapper, /class="zib-widget-wrap aksrt-tab-posts-wrap"/);
|
||||
assert.match(wrapper, /data-aksrt-widget="tab_posts" data-widget-id="aksrt_widget_tab_posts-4"/);
|
||||
assert.match(wrapper, /--aksrt-widget-space-top:8px/);
|
||||
assert.match(wrapper, /--aksrt-widget-space-m-bottom:10px/);
|
||||
assert.match(wrapper, /--aksrt-widget-bg-light:#ffffff/);
|
||||
assert.match(wrapper, /--aksrt-widget-bg-dark:#111111/);
|
||||
assert.match(wrapper, /class="zib-widget-wrap jio-tab-posts-wrap"/);
|
||||
assert.match(wrapper, /data-jio-widget="tab_posts" data-widget-id="jio_widget_tab_posts-4"/);
|
||||
assert.match(wrapper, /--jio-widget-space-top:8px/);
|
||||
assert.match(wrapper, /--jio-widget-space-m-bottom:10px/);
|
||||
assert.match(wrapper, /--jio-widget-bg-light:#ffffff/);
|
||||
assert.match(wrapper, /--jio-widget-bg-dark:#111111/);
|
||||
assert.match(wrapper, /widget-container/);
|
||||
assert.match(wrapper, /class="widget-content"/);
|
||||
assert.match(wrapper, /class="widget-tab-post style-mini/);
|
||||
assert.doesNotMatch(wrapper, /aksrt-widget__inner container|<aside>|<\/aside>/);
|
||||
assert.doesNotMatch(wrapper, /jio-widget__inner container|<aside>|<\/aside>/);
|
||||
|
||||
const frontend = read('src/WidgetSystem/FrontendRenderer.php');
|
||||
const routeStart = frontend.indexOf("if (in_array($definition->slug(), ['user_card', 'author_card', 'tab_posts', 'posts']");
|
||||
|
||||
@@ -59,7 +59,7 @@ class WP_Query {
|
||||
}
|
||||
}
|
||||
|
||||
namespace AKSRT\Settings {
|
||||
namespace Jio\Settings {
|
||||
final class Settings {
|
||||
public static function get_post_list_style_settings(): array {
|
||||
return $GLOBALS['tab_fixture_global_style'] ?? [];
|
||||
@@ -110,7 +110,7 @@ namespace {
|
||||
function wp_strip_all_tags($value): string { return trim(strip_tags((string) $value)); }
|
||||
function wp_trim_words($value, int $num_words = 55, string $more = '...'): string { return implode(' ', array_slice(preg_split('/\s+/', trim((string) $value)) ?: [], 0, $num_words)) . $more; }
|
||||
function wp_is_mobile(): bool { return (bool) $GLOBALS['tab_fixture_mobile']; }
|
||||
function aksrt_is_show_sidebar(): bool { return true; }
|
||||
function jio_is_show_sidebar(): bool { return true; }
|
||||
function is_category(): bool { return false; }
|
||||
function is_search(): bool { return false; }
|
||||
function is_home(): bool { return false; }
|
||||
@@ -130,7 +130,7 @@ namespace {
|
||||
}
|
||||
function get_userdata(int $id): WP_User { return new WP_User($id); }
|
||||
function get_avatar_url(int $id, array $args = []): string { unset($args); return 'https://example.test/avatar/' . $id . '.png'; }
|
||||
function aksrt_get_avatar_box($subject, string $class = 'avatar-img', bool $link = true, bool $vip = true): string {
|
||||
function jio_get_avatar_box($subject, string $class = 'avatar-img', bool $link = true, bool $vip = true): string {
|
||||
$user_id = is_numeric($subject) ? (int) $subject : 0;
|
||||
$GLOBALS['tab_fixture_avatar_calls'][] = ['user_id' => $user_id, 'class' => $class, 'link' => $link, 'vip' => $vip];
|
||||
$inner = '<span class="' . esc_attr($class) . '"><img class="avatar" src="https://example.test/avatar/' . $user_id . '.png" alt=""><img class="avatar-badge" src="https://example.test/vip/' . $user_id . '.svg" alt="VIP"></span>';
|
||||
@@ -153,24 +153,24 @@ namespace {
|
||||
function get_comments_link(int $id = 0): string { return 'https://example.test/post/' . $id . '#comments'; }
|
||||
function get_post_type(int $id = 0): string { unset($id); return 'post'; }
|
||||
function is_sticky(int $id = 0): bool { unset($id); return false; }
|
||||
function aksrt_resource_card_badge_text(int $id): string { return isset($GLOBALS['tab_fixture_pay_meta'][$id]) ? '旧版资源标签' : ''; }
|
||||
function aksrt_get_pay_meta($post = null): array {
|
||||
function jio_resource_card_badge_text(int $id): string { return isset($GLOBALS['tab_fixture_pay_meta'][$id]) ? '旧版资源标签' : ''; }
|
||||
function jio_get_pay_meta($post = null): array {
|
||||
$post_id = $post instanceof WP_Post ? $post->ID : (int) $post;
|
||||
return $GLOBALS['tab_fixture_pay_meta'][$post_id] ?? ['pay_type' => 'no'];
|
||||
}
|
||||
function aksrt_get_pay_types(): array { return ['reading' => '付费阅读', 'download' => '付费下载', 'gallery' => '付费图片', 'video' => '付费视频']; }
|
||||
function aksrt_format_price($price): string {
|
||||
function jio_get_pay_types(): array { return ['reading' => '付费阅读', 'download' => '付费下载', 'gallery' => '付费图片', 'video' => '付费视频']; }
|
||||
function jio_format_price($price): string {
|
||||
$value = number_format((float) $price, 2, '.', '');
|
||||
return rtrim(rtrim($value, '0'), '.') ?: '0';
|
||||
}
|
||||
function aksrt_get_post_views(int $id): int { return $id * 10; }
|
||||
function aksrt_get_post_likes(int $id): int { return $id * 2; }
|
||||
function aksrt_format_number(int $number): string { return (string) $number; }
|
||||
function aksrt_get_lazy_thumb(string $size = ''): string { unset($size); return 'https://example.test/placeholder.jpg'; }
|
||||
function aksrt_get_lazy_attr(string $key, string $url, string $class = '', string $lazy = ''): string { unset($key, $lazy); return ' class="' . esc_attr($class) . '" src="' . esc_url($url) . '"'; }
|
||||
function jio_get_post_views(int $id): int { return $id * 10; }
|
||||
function jio_get_post_likes(int $id): int { return $id * 2; }
|
||||
function jio_format_number(int $number): string { return (string) $number; }
|
||||
function jio_get_lazy_thumb(string $size = ''): string { unset($size); return 'https://example.test/placeholder.jpg'; }
|
||||
function jio_get_lazy_attr(string $key, string $url, string $class = '', string $lazy = ''): string { unset($key, $lazy); return ' class="' . esc_attr($class) . '" src="' . esc_url($url) . '"'; }
|
||||
function wp_reset_postdata(): void { $GLOBALS['tab_fixture_current_post'] = null; $GLOBALS['post'] = null; }
|
||||
function aksrt_widget_ajax_null(string $message = ''): string { return '<div class="aksrt-widget__empty">' . esc_html($message) . '</div>'; }
|
||||
function aksrt_render_placeholder(string $style, int $count): string { return '<div data-placeholder="' . esc_attr($style) . ':' . $count . '"></div>'; }
|
||||
function jio_widget_ajax_null(string $message = ''): string { return '<div class="jio-widget__empty">' . esc_html($message) . '</div>'; }
|
||||
function jio_render_placeholder(string $style, int $count): string { return '<div data-placeholder="' . esc_attr($style) . ':' . $count . '"></div>'; }
|
||||
function get_template_directory(): string { return dirname(__DIR__, 2); }
|
||||
function get_template_directory_uri(): string { return 'https://example.test/theme'; }
|
||||
function rest_url(string $path): string { return 'https://example.test/wp-json/' . $path; }
|
||||
@@ -182,7 +182,7 @@ namespace {
|
||||
function wp_json_encode($value): string { return json_encode($value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR); }
|
||||
|
||||
require dirname(__DIR__, 2) . '/vendor/autoload.php';
|
||||
require dirname(__DIR__, 2) . '/inc/helpers/aksrt-icons.php';
|
||||
require dirname(__DIR__, 2) . '/inc/helpers/jio-icons.php';
|
||||
require dirname(__DIR__, 2) . '/inc/helpers/posts-renderer/config.php';
|
||||
require dirname(__DIR__, 2) . '/inc/helpers/posts-renderer/post-data.php';
|
||||
require dirname(__DIR__, 2) . '/inc/helpers/posts-renderer/markup.php';
|
||||
@@ -224,70 +224,70 @@ namespace {
|
||||
if ($name === 'invalid') $instance['style'] = 'unknown';
|
||||
if ($name === 'default' || $name === 'invalid') {
|
||||
$cases['raw_' . $name] = [
|
||||
'tab_markup' => \AKSRT\WidgetSystem\Renderers\ContentRenderer::render('tab_posts', $instance, ['widget_id' => 'aksrt_widget_tab_posts-4']),
|
||||
'first' => \AKSRT\WidgetSystem\PostRenderer::payload($instance, 'tab_posts', 1, '0')['html'],
|
||||
'tab_markup' => \Jio\WidgetSystem\Renderers\ContentRenderer::render('tab_posts', $instance, ['widget_id' => 'jio_widget_tab_posts-4']),
|
||||
'first' => \Jio\WidgetSystem\PostRenderer::payload($instance, 'tab_posts', 1, '0')['html'],
|
||||
];
|
||||
}
|
||||
$instance = \AKSRT\WidgetSystem\FieldSchema::normalize(\AKSRT\WidgetSystem\WidgetSchemas::for('tab_posts'), $instance);
|
||||
$instance = \Jio\WidgetSystem\FieldSchema::normalize(\Jio\WidgetSystem\WidgetSchemas::for('tab_posts'), $instance);
|
||||
if ($name === 'list') $list_instance = $instance;
|
||||
$avatar_start = count($GLOBALS['tab_fixture_avatar_calls']);
|
||||
$first_page = \AKSRT\WidgetSystem\PostRenderer::payload($instance, 'tab_posts', 1, '0');
|
||||
$second_page = \AKSRT\WidgetSystem\PostRenderer::payload($instance, 'tab_posts', 2, '0');
|
||||
$first_page = \Jio\WidgetSystem\PostRenderer::payload($instance, 'tab_posts', 1, '0');
|
||||
$second_page = \Jio\WidgetSystem\PostRenderer::payload($instance, 'tab_posts', 2, '0');
|
||||
$cases[$name] = [
|
||||
'instance_style' => $instance['style'],
|
||||
'tab_markup' => \AKSRT\WidgetSystem\Renderers\ContentRenderer::render('tab_posts', $instance, ['widget_id' => 'aksrt_widget_tab_posts-4']),
|
||||
'tab_markup' => \Jio\WidgetSystem\Renderers\ContentRenderer::render('tab_posts', $instance, ['widget_id' => 'jio_widget_tab_posts-4']),
|
||||
'first' => $first_page['html'],
|
||||
'second' => $second_page['html'],
|
||||
'pagination' => $first_page['paginate_html'],
|
||||
'last_pagination' => \AKSRT\WidgetSystem\PostRenderer::payload($instance, 'tab_posts', 9, '0')['paginate_html'],
|
||||
'last_pagination' => \Jio\WidgetSystem\PostRenderer::payload($instance, 'tab_posts', 9, '0')['paginate_html'],
|
||||
'avatar_calls' => array_slice($GLOBALS['tab_fixture_avatar_calls'], $avatar_start),
|
||||
];
|
||||
}
|
||||
$mini_instance = ['style' => 'mini', 'mini_opt' => ['show_thumb', 'show_number', 'show_meta'], 'count' => 4, 'paginate' => 'number', 'tabs' => $tabs];
|
||||
$mini_instance = \AKSRT\WidgetSystem\FieldSchema::normalize(\AKSRT\WidgetSystem\WidgetSchemas::for('tab_posts'), $mini_instance);
|
||||
$mini_instance = \Jio\WidgetSystem\FieldSchema::normalize(\Jio\WidgetSystem\WidgetSchemas::for('tab_posts'), $mini_instance);
|
||||
$avatar_start = count($GLOBALS['tab_fixture_avatar_calls']);
|
||||
$mini_first = \AKSRT\WidgetSystem\PostRenderer::payload($mini_instance, 'tab_posts', 1, '0')['html'];
|
||||
$mini_second = \AKSRT\WidgetSystem\PostRenderer::payload($mini_instance, 'tab_posts', 2, '0')['html'];
|
||||
$mini_first = \Jio\WidgetSystem\PostRenderer::payload($mini_instance, 'tab_posts', 1, '0')['html'];
|
||||
$mini_second = \Jio\WidgetSystem\PostRenderer::payload($mini_instance, 'tab_posts', 2, '0')['html'];
|
||||
$cases['mini_all_options'] = [
|
||||
'first' => $mini_first,
|
||||
'second' => $mini_second,
|
||||
'avatar_calls' => array_slice($GLOBALS['tab_fixture_avatar_calls'], $avatar_start),
|
||||
];
|
||||
$cases['no_pagination_page'] = \AKSRT\WidgetSystem\PostRenderer::payload(array_merge($mini_instance, ['paginate' => '']), 'tab_posts', 2, '0');
|
||||
$cases['no_pagination_page'] = \Jio\WidgetSystem\PostRenderer::payload(array_merge($mini_instance, ['paginate' => '']), 'tab_posts', 2, '0');
|
||||
foreach (['list', 'card', 'mini'] as $posts_style) {
|
||||
$posts_definition = \AKSRT\WidgetSystem\WidgetRegistry::get('posts');
|
||||
$posts_definition = \Jio\WidgetSystem\WidgetRegistry::get('posts');
|
||||
foreach (['detail', 'ajax'] as $load_mode) {
|
||||
$posts_instance = $posts_definition->normalize(['style' => $posts_style, 'load_mode' => $load_mode, 'count' => 4, 'paginate' => 'number', 'title' => '文章列表']);
|
||||
$cases['posts_' . $posts_style . '_' . $load_mode] = \AKSRT\WidgetSystem\Renderers\ContentRenderer::render('posts', $posts_instance, ['widget_id' => 'aksrt_widget_posts-3']);
|
||||
$cases['posts_' . $posts_style . '_' . $load_mode] = \Jio\WidgetSystem\Renderers\ContentRenderer::render('posts', $posts_instance, ['widget_id' => 'jio_widget_posts-3']);
|
||||
}
|
||||
}
|
||||
$GLOBALS['tab_fixture_global_style']['post_list_author'] = false;
|
||||
$mini_time_instance = ['style' => 'mini', 'mini_opt' => ['show_meta'], 'count' => 4, 'paginate' => '', 'tabs' => $tabs];
|
||||
$mini_time_instance = \AKSRT\WidgetSystem\FieldSchema::normalize(\AKSRT\WidgetSystem\WidgetSchemas::for('tab_posts'), $mini_time_instance);
|
||||
$cases['mini_time_source'] = ['html' => \AKSRT\WidgetSystem\PostRenderer::payload($mini_time_instance, 'tab_posts', 1, '0')['html']];
|
||||
$mini_time_instance = \Jio\WidgetSystem\FieldSchema::normalize(\Jio\WidgetSystem\WidgetSchemas::for('tab_posts'), $mini_time_instance);
|
||||
$cases['mini_time_source'] = ['html' => \Jio\WidgetSystem\PostRenderer::payload($mini_time_instance, 'tab_posts', 1, '0')['html']];
|
||||
$GLOBALS['tab_fixture_global_style']['post_list_author'] = true;
|
||||
$ajax_instance = $instance;
|
||||
$ajax_instance['style'] = 'mini';
|
||||
$ajax_instance['paginate'] = 'ajax';
|
||||
$ajax_instance = \AKSRT\WidgetSystem\FieldSchema::normalize(\AKSRT\WidgetSystem\WidgetSchemas::for('tab_posts'), $ajax_instance);
|
||||
$ajax_instance = \Jio\WidgetSystem\FieldSchema::normalize(\Jio\WidgetSystem\WidgetSchemas::for('tab_posts'), $ajax_instance);
|
||||
$cases['ajax_pagination'] = [
|
||||
'first' => \AKSRT\WidgetSystem\PostRenderer::payload($ajax_instance, 'tab_posts', 1, '0')['paginate_html'],
|
||||
'last' => \AKSRT\WidgetSystem\PostRenderer::payload($ajax_instance, 'tab_posts', 9, '0')['paginate_html'],
|
||||
'first' => \Jio\WidgetSystem\PostRenderer::payload($ajax_instance, 'tab_posts', 1, '0')['paginate_html'],
|
||||
'last' => \Jio\WidgetSystem\PostRenderer::payload($ajax_instance, 'tab_posts', 9, '0')['paginate_html'],
|
||||
];
|
||||
$GLOBALS['tab_fixture_mobile'] = true;
|
||||
$cases['mobile_pagination'] = [
|
||||
'middle' => \AKSRT\WidgetSystem\PostRenderer::payload($list_instance, 'tab_posts', 5, '0')['paginate_html'],
|
||||
'last' => \AKSRT\WidgetSystem\PostRenderer::payload($list_instance, 'tab_posts', 9, '0')['paginate_html'],
|
||||
'middle' => \Jio\WidgetSystem\PostRenderer::payload($list_instance, 'tab_posts', 5, '0')['paginate_html'],
|
||||
'last' => \Jio\WidgetSystem\PostRenderer::payload($list_instance, 'tab_posts', 9, '0')['paginate_html'],
|
||||
];
|
||||
$GLOBALS['tab_fixture_mobile'] = false;
|
||||
$cases['context_margins'] = [
|
||||
'implicit' => \aksrt_renderer_config(['type' => 'list'])['no_margin'],
|
||||
'explicit_false' => \aksrt_renderer_config(['type' => 'list', 'no_margin' => false])['no_margin'],
|
||||
'implicit' => \jio_renderer_config(['type' => 'list'])['no_margin'],
|
||||
'explicit_false' => \jio_renderer_config(['type' => 'list', 'no_margin' => false])['no_margin'],
|
||||
];
|
||||
$definition = \AKSRT\WidgetSystem\WidgetRegistry::get('tab_posts');
|
||||
$widget = new \AKSRT\WidgetSystem\SchemaWidget($definition);
|
||||
$widget->id = 'aksrt_widget_tab_posts-4';
|
||||
$wrapper_instance = \AKSRT\WidgetSystem\FieldSchema::normalize($definition->fields(), [
|
||||
$definition = \Jio\WidgetSystem\WidgetRegistry::get('tab_posts');
|
||||
$widget = new \Jio\WidgetSystem\SchemaWidget($definition);
|
||||
$widget->id = 'jio_widget_tab_posts-4';
|
||||
$wrapper_instance = \Jio\WidgetSystem\FieldSchema::normalize($definition->fields(), [
|
||||
'title' => '参考标题',
|
||||
'layout_pin_pc' => ['top' => 8, 'right' => 6, 'bottom' => 18, 'left' => 4],
|
||||
'layout_pin_m' => ['top' => 3, 'right' => 2, 'bottom' => 10, 'left' => 2],
|
||||
@@ -298,25 +298,25 @@ namespace {
|
||||
'tabs' => $tabs,
|
||||
]);
|
||||
ob_start();
|
||||
\AKSRT\WidgetSystem\FrontendRenderer::render($definition, ['before_widget' => '<aside>', 'after_widget' => '</aside>'], $wrapper_instance, $widget);
|
||||
\Jio\WidgetSystem\FrontendRenderer::render($definition, ['before_widget' => '<aside>', 'after_widget' => '</aside>'], $wrapper_instance, $widget);
|
||||
$cases['frontend_wrapper'] = (string) ob_get_clean();
|
||||
$paid_instance = \AKSRT\WidgetSystem\FieldSchema::normalize(\AKSRT\WidgetSystem\WidgetSchemas::for('tab_posts'), [
|
||||
$paid_instance = \Jio\WidgetSystem\FieldSchema::normalize(\Jio\WidgetSystem\WidgetSchemas::for('tab_posts'), [
|
||||
'style' => 'mini', 'mini_opt' => ['show_meta'], 'count' => 20, 'paginate' => '',
|
||||
'tabs' => [['title' => '付费徽标用例', 'cat' => '9']],
|
||||
]);
|
||||
$cases['pay_meta_right'] = \AKSRT\WidgetSystem\PostRenderer::payload($paid_instance, 'tab_posts', 1, '0')['html'];
|
||||
$cases['pay_meta_right'] = \Jio\WidgetSystem\PostRenderer::payload($paid_instance, 'tab_posts', 1, '0')['html'];
|
||||
$paid_instance['mini_opt'] = [];
|
||||
$cases['pay_meta_hidden'] = \AKSRT\WidgetSystem\PostRenderer::payload($paid_instance, 'tab_posts', 1, '0')['html'];
|
||||
$cases['pay_meta_hidden'] = \Jio\WidgetSystem\PostRenderer::payload($paid_instance, 'tab_posts', 1, '0')['html'];
|
||||
$paid_instance['mini_opt'] = ['show_meta'];
|
||||
|
||||
$GLOBALS['tab_fixture_global_style']['post_list_author'] = false;
|
||||
$GLOBALS['tab_fixture_global_style']['post_list_time'] = false;
|
||||
$cases['pay_meta_left'] = \AKSRT\WidgetSystem\PostRenderer::payload($paid_instance, 'tab_posts', 1, '0')['html'];
|
||||
$cases['pay_meta_left'] = \Jio\WidgetSystem\PostRenderer::payload($paid_instance, 'tab_posts', 1, '0')['html'];
|
||||
$GLOBALS['tab_fixture_global_style']['post_list_author'] = true;
|
||||
$GLOBALS['tab_fixture_global_style']['post_list_time'] = true;
|
||||
|
||||
$GLOBALS['tab_fixture_paid_settings']['display']['currency_symbol'] = '€';
|
||||
$cases['pay_meta_custom_currency'] = \AKSRT\WidgetSystem\PostRenderer::payload($paid_instance, 'tab_posts', 1, '0')['html'];
|
||||
$cases['pay_meta_custom_currency'] = \Jio\WidgetSystem\PostRenderer::payload($paid_instance, 'tab_posts', 1, '0')['html'];
|
||||
$GLOBALS['tab_fixture_paid_settings']['display']['currency_symbol'] = '¥';
|
||||
|
||||
$GLOBALS['tab_fixture_vip_settings'] = [
|
||||
@@ -326,7 +326,7 @@ namespace {
|
||||
2 => ['enabled' => false, 'name' => '钻石会员', 'img_icon' => 'https://example.test/vip-2.svg'],
|
||||
],
|
||||
];
|
||||
$cases['pay_vip1_only'] = \AKSRT\WidgetSystem\PostRenderer::payload($paid_instance, 'tab_posts', 1, '0')['html'];
|
||||
$cases['pay_vip1_only'] = \Jio\WidgetSystem\PostRenderer::payload($paid_instance, 'tab_posts', 1, '0')['html'];
|
||||
$GLOBALS['tab_fixture_vip_settings'] = [
|
||||
'enabled' => true,
|
||||
'levels' => [
|
||||
@@ -334,7 +334,7 @@ namespace {
|
||||
2 => ['enabled' => true, 'name' => '钻石会员', 'img_icon' => 'https://example.test/vip-2.svg'],
|
||||
],
|
||||
];
|
||||
$cases['pay_vip2_only'] = \AKSRT\WidgetSystem\PostRenderer::payload($paid_instance, 'tab_posts', 1, '0')['html'];
|
||||
$cases['pay_vip2_only'] = \Jio\WidgetSystem\PostRenderer::payload($paid_instance, 'tab_posts', 1, '0')['html'];
|
||||
$GLOBALS['tab_fixture_vip_settings'] = [
|
||||
'enabled' => false,
|
||||
'levels' => [
|
||||
@@ -342,7 +342,7 @@ namespace {
|
||||
2 => ['enabled' => false, 'name' => '钻石会员', 'img_icon' => 'https://example.test/vip-2.svg'],
|
||||
],
|
||||
];
|
||||
$cases['pay_vip_disabled'] = \AKSRT\WidgetSystem\PostRenderer::payload($paid_instance, 'tab_posts', 1, '0')['html'];
|
||||
$cases['pay_empty'] = aksrt_renderer_compact_pay_badge([]);
|
||||
$cases['pay_vip_disabled'] = \Jio\WidgetSystem\PostRenderer::payload($paid_instance, 'tab_posts', 1, '0')['html'];
|
||||
$cases['pay_empty'] = jio_renderer_compact_pay_badge([]);
|
||||
echo json_encode(['cases' => $cases], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR);
|
||||
}
|
||||
@@ -113,7 +113,7 @@ function postsMarkup(document, unveiled = false) {
|
||||
pager.setAttribute('data-widget-items', '');
|
||||
pager.setAttribute('data-widget-lazy', '');
|
||||
pager.setAttribute('data-widget', 'posts');
|
||||
pager.setAttribute('data-instance', 'aksrt_widget_posts-2');
|
||||
pager.setAttribute('data-instance', 'jio_widget_posts-2');
|
||||
pager.setAttribute('data-page', '1');
|
||||
pager.setAttribute('aria-busy', 'true');
|
||||
pager.innerHTML = '<div class="post_ajax_loader is-loading"><div class="posts-item placeholder">Loading</div></div>';
|
||||
@@ -121,7 +121,7 @@ function postsMarkup(document, unveiled = false) {
|
||||
return { root };
|
||||
}
|
||||
|
||||
function createRuntime({ fetch, pages, posts = false, unveiled = false, restUrl = '/wp-json/aksrt/v1/widgets/', observe = false } = {}) {
|
||||
function createRuntime({ fetch, pages, posts = false, unveiled = false, restUrl = '/wp-json/jio/v1/widgets/', observe = false } = {}) {
|
||||
const document = new MiniDocument();
|
||||
const { root: widget, nav, tabOne, tabTwo } = posts ? postsMarkup(document, unveiled) : tabMarkup(document);
|
||||
document.body.appendChild(widget);
|
||||
@@ -152,7 +152,7 @@ function createRuntime({ fetch, pages, posts = false, unveiled = false, restUrl
|
||||
observe() {}
|
||||
} : undefined;
|
||||
const window = {
|
||||
aksrtWidgets: config,
|
||||
jioWidgets: config,
|
||||
location: { href: 'https://example.test/' },
|
||||
matchMedia: () => ({ matches: false }),
|
||||
jQuery,
|
||||
@@ -487,8 +487,8 @@ test('article widget waits for its own lazy reveal and fetches the first page on
|
||||
pager.dispatch('lazybeforeunveil');
|
||||
assert.deepEqual(pageRequests(runtime.requests), [1]);
|
||||
const url = new URL(runtime.requests[0]);
|
||||
assert.equal(url.pathname, '/wp-json/aksrt/v1/widgets/posts/items');
|
||||
assert.equal(url.searchParams.get('instance'), 'aksrt_widget_posts-2');
|
||||
assert.equal(url.pathname, '/wp-json/jio/v1/widgets/posts/items');
|
||||
assert.equal(url.searchParams.get('instance'), 'jio_widget_posts-2');
|
||||
transport.pending[0].resolve({ html: '<posts class="posts-item list ajax-item" data-post-id="first">First article</posts>', page: 1, paginate_html: '' });
|
||||
await tick();
|
||||
assert.ok(pager.querySelector('[data-post-id="first"]'));
|
||||
@@ -526,11 +526,11 @@ test('article widget failure exposes a retry that replaces the initial skeleton'
|
||||
});
|
||||
|
||||
test('article REST addresses preserve the route for plain WordPress permalinks', async () => {
|
||||
const runtime = createRuntime({ posts: true, unveiled: true, restUrl: 'https://example.test/blog/?rest_route=/aksrt/v1/widgets/' });
|
||||
const runtime = createRuntime({ posts: true, unveiled: true, restUrl: 'https://example.test/blog/?rest_route=/jio/v1/widgets/' });
|
||||
const url = new URL(runtime.requests[0]);
|
||||
assert.equal(url.pathname, '/blog/');
|
||||
assert.equal(url.searchParams.get('rest_route'), '/aksrt/v1/widgets/posts/items');
|
||||
assert.equal(url.searchParams.get('instance'), 'aksrt_widget_posts-2');
|
||||
assert.equal(url.searchParams.get('rest_route'), '/jio/v1/widgets/posts/items');
|
||||
assert.equal(url.searchParams.get('instance'), 'jio_widget_posts-2');
|
||||
assert.equal(url.searchParams.get('page'), '1');
|
||||
await tick();
|
||||
assert.equal(runtime.widget.querySelector('[data-widget-items]').dataset.loaded, '1');
|
||||
|
||||
@@ -30,7 +30,7 @@ test('tab and single post widget titles restore reference weight while preservin
|
||||
assert.equal(declarations('.widget-tab-post .item-heading.font-bold')['font-weight'], '700');
|
||||
assert.equal(declarations('.widget-main-post .item-heading')['font-weight'], '500');
|
||||
assert.equal(declarations('.widget-main-post .item-heading.font-bold')['font-weight'], '700');
|
||||
const shell = declarations('.aksrt-tab-posts-wrap');
|
||||
const shell = declarations('.jio-tab-posts-wrap');
|
||||
assert.equal(shell['font-size'], '14px');
|
||||
assert.equal(shell['line-height'], '1.42857143');
|
||||
assert.equal(shell.color, 'var(--main-color,#4e5358)');
|
||||
@@ -40,7 +40,7 @@ test('tab and single post widget titles restore reference weight while preservin
|
||||
assert.equal(declarations('.widget-main-post .posts-mini-con').flex, 'auto');
|
||||
assert.equal(declarations('.widget-tab-post .item-meta .meta-pay').cursor, 'auto');
|
||||
assert.equal(declarations('.widget-main-post .item-meta .meta-pay').cursor, 'auto');
|
||||
assert.match(css, /\.aksrt-posts-wrap :where\(a, button, input, select, textarea, \[tabindex\]\):focus-visible/);
|
||||
assert.match(css, /\.jio-posts-wrap :where\(a, button, input, select, textarea, \[tabindex\]\):focus-visible/);
|
||||
});
|
||||
|
||||
test('widget pagination restores compact reference metrics over public pagination overrides', () => {
|
||||
@@ -88,7 +88,7 @@ test('modern tabs have one interaction owner and no retired tab selector or unus
|
||||
const runtime = read('assets/widgets/frontend.js');
|
||||
const renderer = read('src/WidgetSystem/PostRenderer.php');
|
||||
assert.doesNotMatch(content, /data-toggle="tab"|data-ajax/);
|
||||
assert.doesNotMatch(runtime, /aksrt-widget-tabs/);
|
||||
assert.doesNotMatch(runtime, /jio-widget-tabs/);
|
||||
assert.doesNotMatch(renderer, /widget_style/);
|
||||
assert.match(runtime, /scope\.querySelectorAll\('\.widget-tab-post'\)\.forEach\(initTabs\)/);
|
||||
});
|
||||
@@ -25,7 +25,7 @@ test('ticket captcha scenes are registered in all three places', () => {
|
||||
const adminSchema = read('assets/admin/src/schema/sections/permissions/index.js');
|
||||
|
||||
for (const scene of SCENES) {
|
||||
assert.match(captcha, new RegExp(`'${scene.key}'\\s*=> __\\('${scene.label}', 'aksrt'\\)`),
|
||||
assert.match(captcha, new RegExp(`'${scene.key}'\\s*=> __\\('${scene.label}', 'jio'\\)`),
|
||||
`captcha scene ${scene.key} missing`);
|
||||
assert.ok(permissions.includes(`'label' => '${scene.label}', 'value' => '${scene.key}'`),
|
||||
`PHP permissions schema missing ${scene.key}`);
|
||||
@@ -54,14 +54,14 @@ test('the ticket REST layer consumes both scenes exactly once', () => {
|
||||
assert.match(rest, /const CAPTCHA_SCENE_REPLY = 'ticket_reply';/);
|
||||
assert.equal((rest.match(/require_captcha\(\$request, self::CAPTCHA_SCENE_/g) || []).length, 2,
|
||||
'create and reply must each verify captcha exactly once');
|
||||
assert.equal((rest.match(/aksrt_require_captcha\(/g) || []).length, 1,
|
||||
assert.equal((rest.match(/jio_require_captcha\(/g) || []).length, 1,
|
||||
'captcha verification must funnel through one helper');
|
||||
});
|
||||
|
||||
test('the ticket panel mounts both captcha scenes', () => {
|
||||
const renderer = read('inc/helpers/user-center-renderers/profile-tickets.php');
|
||||
assert.ok(renderer.includes("aksrt_render_captcha('ticket_create')"), 'create mount missing');
|
||||
assert.ok(renderer.includes("aksrt_render_captcha('ticket_reply')"), 'reply mount missing');
|
||||
assert.ok(renderer.includes("jio_render_captcha('ticket_create')"), 'create mount missing');
|
||||
assert.ok(renderer.includes("jio_render_captcha('ticket_reply')"), 'reply mount missing');
|
||||
assert.ok(!renderer.includes('after_sale'), 'renderer must not mount after-sale scenes');
|
||||
|
||||
const js = read('assets/js/ticket-center.js');
|
||||
|
||||
@@ -68,7 +68,7 @@ test('default status cannot be edited or deleted in the admin surface', () => {
|
||||
const config = read('src/Ticket/TicketConfigRest.php');
|
||||
assert.equal((config.match(/!empty\(\$row\['is_default'\]\)/g) || []).length, 2,
|
||||
'both update and delete must honour is_default');
|
||||
assert.match(config, /'aksrt_ticket_status_default'/);
|
||||
assert.match(config, /'jio_ticket_status_default'/);
|
||||
|
||||
// 原生后台不再维护状态定义,因此不再出现 is_default 判断。
|
||||
const native = read('src/Admin/Native/UsersMenusTicketsCenter.php');
|
||||
@@ -78,8 +78,8 @@ test('default status cannot be edited or deleted in the admin surface', () => {
|
||||
// 删除前还要确认没有工单挂在该状态/部门下(守卫在 TicketConfigRest trait 中)。
|
||||
assert.match(config, /count_by_status\(\(string\) \$row\['status_key'\]\) > 0/);
|
||||
assert.match(config, /count_by_type\(\$id\) > 0/);
|
||||
assert.match(config, /'aksrt_ticket_status_in_use'/);
|
||||
assert.match(config, /'aksrt_ticket_type_in_use'/);
|
||||
assert.match(config, /'jio_ticket_status_in_use'/);
|
||||
assert.match(config, /'jio_ticket_type_in_use'/);
|
||||
|
||||
const repo = read('src/Ticket/TicketRepository.php');
|
||||
assert.match(repo, /public function count_by_status\(string \$status_key\): int/);
|
||||
@@ -113,13 +113,13 @@ test('ticket numbers are unique and settings-driven', () => {
|
||||
|
||||
test('notifications go through the message inbox with deep links', () => {
|
||||
const notifier = read('src/Ticket/TicketNotifier.php');
|
||||
// 所有通知都走 Message::send(),持久化到 aksrt_notification。
|
||||
assert.match(notifier, /\\AKSRT\\Messages\\Message::get_instance\(\)->send\(/);
|
||||
assert.match(notifier, /class_exists\('\\AKSRT\\Messages\\Message'\)/);
|
||||
// 所有通知都走 Message::send(),持久化到 jio_notification。
|
||||
assert.match(notifier, /\\Jio\\Messages\\Message::get_instance\(\)->send\(/);
|
||||
assert.match(notifier, /class_exists\('\\Jio\\Messages\\Message'\)/);
|
||||
// 新建工单通知全体管理员,催单只通知跟进人。
|
||||
assert.match(notifier, /get_users\(\['role' => 'administrator', 'fields' => 'ID', 'number' => 20\]\)/);
|
||||
assert.match(notifier, /\$admin_id = \(int\) \(\$ticket\['admin_id'\] \?\? 0\);/);
|
||||
// 深链分别指向后台工单页与用户中心工单位。
|
||||
assert.match(notifier, /admin_url\('users\.php\?page=aksrt_tickets'\)/);
|
||||
assert.match(notifier, /aksrt_get_user_center_url\('tickets'\)/);
|
||||
assert.match(notifier, /admin_url\('users\.php\?page=jio_tickets'\)/);
|
||||
assert.match(notifier, /jio_get_user_center_url\('tickets'\)/);
|
||||
});
|
||||
@@ -17,12 +17,12 @@ const css = read('assets/css/admin/tickets.css');
|
||||
|
||||
test('ticket native page registers no AJAX read; writes reuse REST', () => {
|
||||
assert.match(menus, /use UsersMenusTicketsCenter;/);
|
||||
assert.match(menus, /'users.php',[\s\S]*?'aksrt_tickets'/);
|
||||
assert.doesNotMatch(menus + center, /wp_ajax_aksrt_ticket_view|ajax_ticket_view|aksrt_ticket_view/);
|
||||
assert.match(menus, /'users.php',[\s\S]*?'jio_tickets'/);
|
||||
assert.doesNotMatch(menus + center, /wp_ajax_jio_ticket_view|ajax_ticket_view|jio_ticket_view/);
|
||||
assert.match(center, /wp_create_nonce\('wp_rest'\)/);
|
||||
assert.match(center, /rest_url\('aksrt\/v1\/admin\/tickets' \./);
|
||||
assert.match(center, /rest_url\('jio\/v1\/admin\/tickets' \./);
|
||||
assert.match(js, /'X-WP-Nonce': config.restNonce/);
|
||||
assert.doesNotMatch(menus, /admin_post_aksrt_ticket_update/);
|
||||
assert.doesNotMatch(menus, /admin_post_jio_ticket_update/);
|
||||
assert.doesNotMatch(center, /function (handle_ticket_update|ticket_action_\w+|ticket_redirect)/);
|
||||
assert.match(menus, /count_pending_tickets\(\)/);
|
||||
assert.match(menus, /TicketSettings::enabled\(\)/);
|
||||
@@ -44,8 +44,8 @@ test('list and workspace are page views; only creation is a dialog on the list p
|
||||
// 处理工作区本身不是弹窗;只有内部备注的输入在弹窗内。
|
||||
assert.equal((detail.match(/<dialog /g) || []).length, 1);
|
||||
assert.match(detail, /<dialog id="' \. esc_attr\(self::TICKET_NOTE_DIALOG\)/);
|
||||
assert.match(detail, /ticket_form_open\('note', \$id, 'aksrt-ticket-dialog__frame'\)/);
|
||||
assert.doesNotMatch(js + css, /aksrt-ticket-detail-dialog|aksrt-ticket-modal-open|data-aksrt-ticket-panel/);
|
||||
assert.match(detail, /ticket_form_open\('note', \$id, 'jio-ticket-dialog__frame'\)/);
|
||||
assert.doesNotMatch(js + css, /jio-ticket-detail-dialog|jio-ticket-modal-open|data-jio-ticket-panel/);
|
||||
});
|
||||
|
||||
test('detail lookup is independent of the filtered list; paging clamps and preserves filters', () => {
|
||||
@@ -64,25 +64,25 @@ test('detail lookup is independent of the filtered list; paging clamps and prese
|
||||
test('conversation, private notes and final-state confirmation remain distinct', () => {
|
||||
assert.match(detail, /TicketNoteService::for_ticket\(\$id\)/);
|
||||
assert.match(detail, /TicketRepository::get_instance\(\)->replies\(\$id\)/);
|
||||
assert.match(detail, /!empty\(\$quote\['is_deleted'\]\) \? __\('原回复已删除', 'aksrt'\)/);
|
||||
assert.match(detail, /!empty\(\$quote\['is_deleted'\]\) \? __\('原回复已删除', 'jio'\)/);
|
||||
// 回复框是会话面板的底部,而不是独立卡片。
|
||||
assert.match(detail, /return \$html \. '<\/ol><\/div>' \. \$footer \. '<\/section>';/);
|
||||
assert.ok(detail.includes("ticket_form_open('reply', $id, 'aksrt-ticket-composer')"));
|
||||
assert.match(detail, /data-aksrt-ticket-send-shortcut/);
|
||||
assert.ok(detail.includes("ticket_form_open('reply', $id, 'jio-ticket-composer')"));
|
||||
assert.match(detail, /data-jio-ticket-send-shortcut/);
|
||||
assert.match(js, /event\.ctrlKey \|\| event\.metaKey/);
|
||||
assert.ok(detail.includes("ticket_form_open('status'"));
|
||||
assert.doesNotMatch(detail, /function ticket_composer\(/);
|
||||
assert.match(detail, /aksrt-ticket-detail__closed/);
|
||||
assert.match(detail, /jio-ticket-detail__closed/);
|
||||
assert.match(detail, /TicketStatusService::can_transition\(\$from, \$key\)/);
|
||||
assert.match(detail, /data-aksrt-ticket-close-confirm/);
|
||||
assert.match(detail, /data-aksrt-ticket-assign/);
|
||||
assert.match(detail, /data-jio-ticket-close-confirm/);
|
||||
assert.match(detail, /data-jio-ticket-assign/);
|
||||
assert.match(detail, /仅管理员可见,不会发送给用户/);
|
||||
});
|
||||
|
||||
test('page safety: unsaved-change guard, drafts survive the post-save reload, uncertain writes lock forms', () => {
|
||||
assert.match(js, /window.addEventListener\('beforeunload'/);
|
||||
assert.match(js, /sessionStorage/);
|
||||
assert.match(js, /aksrt_ticket_msg/);
|
||||
assert.match(js, /jio_ticket_msg/);
|
||||
assert.match(js, /next.length > field.maxLength/);
|
||||
assert.match(js, /strings.uncertain/);
|
||||
assert.match(js, /\[401, 403, 404, 409\]/);
|
||||
@@ -94,19 +94,19 @@ test('page safety: unsaved-change guard, drafts survive the post-save reload, un
|
||||
});
|
||||
|
||||
test('configuration remains in the SPA; native ops has no old config tabs', () => {
|
||||
assert.match(list, /admin_url\('admin\.php\?page=aksrt-settings#\/users\/ticket'\)/);
|
||||
assert.match(list, /admin_url\('admin\.php\?page=jio-settings#\/users\/ticket'\)/);
|
||||
assert.doesNotMatch(center + list + detail, /nav-tab-wrapper|ticket_tab|UsersMenusTicketsConfig/);
|
||||
assert.ok(!fs.existsSync(path.join(root, 'src/Admin/Native/UsersMenusTicketsConfig.php')));
|
||||
assert.ok(read('assets/admin/src/router/index.js').includes("{ path: '/users/tickets', title: '工单管理', nativeAdminPath: 'users.php?page=aksrt_tickets' }"));
|
||||
assert.ok(read('assets/admin/src/router/index.js').includes("{ path: '/users/tickets', title: '工单管理', nativeAdminPath: 'users.php?page=jio_tickets' }"));
|
||||
});
|
||||
|
||||
test('frontend hooks remain aligned with their independent REST consumer', () => {
|
||||
const renderer = read('inc/helpers/user-center-renderers/profile-tickets.php');
|
||||
const frontend = read('assets/js/ticket-center.js') + '\n' + read('assets/js/ticket-center-core.js');
|
||||
for (const hook of ['data-aksrt-ticket-center', 'data-aksrt-ticket-create', 'data-aksrt-ticket-stats', 'data-aksrt-ticket-stat=', 'data-aksrt-ticket-list', 'data-aksrt-ticket-pagination', 'data-aksrt-tickets-search', 'data-aksrt-ticket-filter=']) {
|
||||
for (const hook of ['data-jio-ticket-center', 'data-jio-ticket-create', 'data-jio-ticket-stats', 'data-jio-ticket-stat=', 'data-jio-ticket-list', 'data-jio-ticket-pagination', 'data-jio-tickets-search', 'data-jio-ticket-filter=']) {
|
||||
assert.ok(renderer.includes(hook), `renderer missing ${hook}`);
|
||||
}
|
||||
for (const hook of ['data-aksrt-ticket-center', 'data-aksrt-ticket-create', 'data-aksrt-ticket-list', 'data-aksrt-ticket-view=', 'data-aksrt-ticket-page=', 'data-aksrt-ticket-quote=', 'data-aksrt-ticket-images']) {
|
||||
for (const hook of ['data-jio-ticket-center', 'data-jio-ticket-create', 'data-jio-ticket-list', 'data-jio-ticket-view=', 'data-jio-ticket-page=', 'data-jio-ticket-quote=', 'data-jio-ticket-images']) {
|
||||
assert.ok(frontend.includes(hook), `frontend missing ${hook}`);
|
||||
}
|
||||
for (const endpoint of ["'/tickets'", "'/tickets/statistic'", "'/tickets/meta'", "'/tickets/' + ticketId", "'/tickets/' + ticket.id + '/reply'", "'/ticket/upload-attachment'"]) {
|
||||
@@ -115,5 +115,5 @@ test('frontend hooks remain aligned with their independent REST consumer', () =>
|
||||
assert.match(frontend, /runCaptcha\('ticket_create'\)/);
|
||||
assert.match(frontend, /runCaptcha\('ticket_reply'\)/);
|
||||
const routeCss = read('assets/css/routes/tickets.css');
|
||||
for (const hook of ['.aksrt-tickets-page', '.aksrt-ticket-badge', '.aksrt-ticket-detail', '--aksrt-ticket-color']) assert.ok(routeCss.includes(hook));
|
||||
for (const hook of ['.jio-tickets-page', '.jio-ticket-badge', '.jio-ticket-detail', '--jio-ticket-color']) assert.ok(routeCss.includes(hook));
|
||||
});
|
||||
@@ -88,7 +88,7 @@ test('linked orders must belong to the caller and be paid', () => {
|
||||
assert.match(body, /get_user_order\(get_current_user_id\(\), \$order_number\)/);
|
||||
assert.match(body, /ORDER_STATUS_PAID/);
|
||||
// 新建时关联订单非法必须拒掉。
|
||||
assert.match(rest, /if \(\$order_number !== '' && !\$this->owns_paid_order\(\$order_number\)\) \{\s*\n\s*return new \\WP_Error\('aksrt_ticket_order'/);
|
||||
assert.match(rest, /if \(\$order_number !== '' && !\$this->owns_paid_order\(\$order_number\)\) \{\s*\n\s*return new \\WP_Error\('jio_ticket_order'/);
|
||||
});
|
||||
|
||||
test('admin routes all require manage_options', () => {
|
||||
@@ -104,7 +104,7 @@ test('admin routes all require manage_options', () => {
|
||||
|
||||
assert.match(admin, /current_user_can\('manage_options'\)/);
|
||||
// 管理侧命名空间与用户侧隔离。
|
||||
assert.match(body, /\$namespace = 'aksrt\/v1\/admin';/);
|
||||
assert.match(body, /\$namespace = 'jio\/v1\/admin';/);
|
||||
});
|
||||
|
||||
test('closed tickets refuse replies and cannot be re-closed', () => {
|
||||
@@ -112,10 +112,10 @@ test('closed tickets refuse replies and cannot be re-closed', () => {
|
||||
const admin = read('src/Ticket/TicketRestAdmin.php');
|
||||
|
||||
// 用户侧
|
||||
assert.match(rest, /if \(!empty\(\$ticket\['is_closed'\]\)\) \{\s*\n\s*return new \\WP_Error\('aksrt_ticket_closed', __\('工单已关闭,无法回复'/);
|
||||
assert.match(rest, /if \(!empty\(\$ticket\['is_closed'\]\)\) \{\s*\n\s*return new \\WP_Error\('aksrt_ticket_closed', __\('工单已关闭,无法催单'/);
|
||||
assert.match(rest, /if \(!empty\(\$ticket\['is_closed'\]\)\) \{\s*\n\s*return new \\WP_Error\('jio_ticket_closed', __\('工单已关闭,无法回复'/);
|
||||
assert.match(rest, /if \(!empty\(\$ticket\['is_closed'\]\)\) \{\s*\n\s*return new \\WP_Error\('jio_ticket_closed', __\('工单已关闭,无法催单'/);
|
||||
// 管理侧
|
||||
assert.match(admin, /if \(!empty\(\$ticket\['is_closed'\]\)\) \{\s*\n\s*return new \\WP_Error\('aksrt_ticket_closed'/);
|
||||
assert.match(admin, /if \(!empty\(\$ticket\['is_closed'\]\)\) \{\s*\n\s*return new \\WP_Error\('jio_ticket_closed'/);
|
||||
|
||||
// close_ticket 自身也挡已完结(双保险,避免前端绕过 UI 直接 POST)。
|
||||
const status = read('src/Ticket/TicketStatus.php');
|
||||
@@ -127,9 +127,9 @@ test('urge has a cooldown driven by settings', () => {
|
||||
const body = rest.slice(rest.indexOf('public function urge('));
|
||||
|
||||
// 冷却以工单行 last_urge_time 为准(条件 UPDATE 防并发),不再依赖 transient。
|
||||
assert.ok(!/_transient\('aksrt_ticket_urge_/.test(rest), 'urge cooldown must not use transients');
|
||||
assert.ok(!/_transient\('jio_ticket_urge_/.test(rest), 'urge cooldown must not use transients');
|
||||
assert.match(body, /\$count = \$repo->record_urge\(\$id, TicketSettings::urge_interval\(\) \* MINUTE_IN_SECONDS\);/);
|
||||
assert.match(body, /if \(\$count <= 0\) \{\s*\n\s*return new \\WP_Error\('aksrt_ticket_urge_limit'/);
|
||||
assert.match(body, /if \(\$count <= 0\) \{\s*\n\s*return new \\WP_Error\('jio_ticket_urge_limit'/);
|
||||
|
||||
const repo = read('src/Ticket/TicketRepository.php');
|
||||
assert.match(repo, /SET urge_count = urge_count \+ 1, last_urge_time = %s/);
|
||||
@@ -142,7 +142,7 @@ test('urge writes a system record, notifies admins and fires the mail hook', ()
|
||||
|
||||
assert.match(body, /\$repo->create_reply\(\$id, 'system', get_current_user_id\(\)/);
|
||||
assert.match(body, /TicketNotifier::admin_activity\(\$ticket, 'urge'\);/);
|
||||
assert.match(body, /do_action\('aksrt_ticket_urged', \$ticket\);/);
|
||||
assert.match(body, /do_action\('jio_ticket_urged', \$ticket\);/);
|
||||
|
||||
const repo = read('src/Ticket/TicketRepository.php');
|
||||
assert.match(repo, /if \(!in_array\(\$author_type, \['admin', 'system'\], true\)\) \{/);
|
||||
|
||||
Loaded 100 of 997 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user