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
@@ -21,7 +21,7 @@ const submitContext = read('templates/parts/submit/context.php');
|
||||
const submitPage = read('templates/pages/page-submit.php');
|
||||
|
||||
const removed = [
|
||||
'aksrt_ai_settings',
|
||||
'jio_ai_settings',
|
||||
'AI 连接配置',
|
||||
'AI连接配置',
|
||||
'ExtensionAI',
|
||||
|
||||
@@ -45,7 +45,7 @@ namespace {
|
||||
function wp_nonce_field($action, $name) { echo '<input type="hidden" name="' . $name . '" value="nonce" />'; }
|
||||
function wp_unslash($s) { return $s; }
|
||||
function sanitize_text_field($s) { return trim(strip_tags((string) $s)); }
|
||||
function wp_verify_nonce($nonce, $action) { return $nonce === 'valid-nonce' && $action === 'aksrt_push_meta_save'; }
|
||||
function wp_verify_nonce($nonce, $action) { return $nonce === 'valid-nonce' && $action === 'jio_push_meta_save'; }
|
||||
function is_wp_error($t) { return $t instanceof WP_Error; }
|
||||
function home_url($path = '/') { return 'https://example.test' . $path; }
|
||||
function current_time($type = 'mysql') { return '2026-10-13 12:00:00'; }
|
||||
@@ -56,7 +56,7 @@ namespace {
|
||||
}
|
||||
function wp_is_post_revision($id) { return false; }
|
||||
function wp_is_post_autosave($id) { return false; }
|
||||
function aksrt_searchable_post_types(): array { return ['post']; }
|
||||
function jio_searchable_post_types(): array { return ['post']; }
|
||||
function get_object_taxonomies($types, $output = 'names') { return ['post_tag', 'category', 'post_format']; }
|
||||
|
||||
// Outgoing HTTP: every call is captured so channel/URL/body can be asserted.
|
||||
@@ -79,7 +79,7 @@ namespace {
|
||||
function delete_term_meta($id, $key) { unset($GLOBALS['push_term_meta'][$id][$key]); return true; }
|
||||
}
|
||||
|
||||
namespace AKSRT\Settings {
|
||||
namespace Jio\Settings {
|
||||
class Settings {
|
||||
public static array $config = [];
|
||||
public static function get_search_push_settings(): array { return self::$config; }
|
||||
@@ -90,8 +90,8 @@ namespace {
|
||||
require_once dirname(__DIR__, 2) . '/src/Extensions/SearchPush.php';
|
||||
require_once dirname(__DIR__, 2) . '/src/Extensions/SearchPushMetaBox.php';
|
||||
|
||||
use AKSRT\Extensions\SearchPush;
|
||||
use AKSRT\Extensions\SearchPushMetaBox;
|
||||
use Jio\Extensions\SearchPush;
|
||||
use Jio\Extensions\SearchPushMetaBox;
|
||||
|
||||
$failures = [];
|
||||
$check = static function (string $label, bool $ok) use (&$failures): void {
|
||||
@@ -101,7 +101,7 @@ namespace {
|
||||
};
|
||||
|
||||
$reset = static function (array $config): void {
|
||||
\AKSRT\Settings\Settings::$config = $config;
|
||||
\Jio\Settings\Settings::$config = $config;
|
||||
$GLOBALS['push_requests'] = [];
|
||||
$GLOBALS['push_post_meta'] = [];
|
||||
$GLOBALS['push_term_meta'] = [];
|
||||
@@ -236,8 +236,8 @@ namespace {
|
||||
$check('面板含普通收录成功', str_contains($html, '普通收录:成功'));
|
||||
$check('面板含快速收录失败', str_contains($html, '快速收录:失败'));
|
||||
$check('面板含更新时间', str_contains($html, '2026-10-13 09:00:00'));
|
||||
$check('面板含重新提交勾选', str_contains($html, 'name="aksrt_push_resubmit"'));
|
||||
$check('面板含 nonce 字段', str_contains($html, 'name="aksrt_push_meta_nonce"'));
|
||||
$check('面板含重新提交勾选', str_contains($html, 'name="jio_push_resubmit"'));
|
||||
$check('面板含 nonce 字段', str_contains($html, 'name="jio_push_meta_nonce"'));
|
||||
$check('面板转义接口 JSON', str_contains($html, '{"success":1}'));
|
||||
|
||||
ob_start();
|
||||
@@ -251,7 +251,7 @@ namespace {
|
||||
$box->render_term_panel($term);
|
||||
$termHtml = (string) ob_get_clean();
|
||||
$check('术语编辑表单为表单行', str_contains($termHtml, '<tr class="form-field">'));
|
||||
$check('术语编辑表单含重新提交', str_contains($termHtml, 'name="aksrt_push_resubmit"'));
|
||||
$check('术语编辑表单含重新提交', str_contains($termHtml, 'name="jio_push_resubmit"'));
|
||||
|
||||
if ($failures) {
|
||||
echo "Baidu push harness FAILED\n";
|
||||
|
||||
@@ -27,7 +27,7 @@ const displayGroups = fs.readFileSync(path.join(root, 'src/Settings/DisplayPageG
|
||||
const metaBox = fs.readFileSync(path.join(root, 'src/Extensions/SearchPushMetaBox.php'), 'utf8');
|
||||
const functions = fs.readFileSync(path.join(root, 'functions.php'), 'utf8');
|
||||
|
||||
// 参考主题(zibll)百度推送字段全集,AKSRT 单一 canonical 配置面(扩展&增强 → 百度推送)。
|
||||
// 参考主题(zibll)百度推送字段全集,Jio 单一 canonical 配置面(扩展&增强 → 百度推送)。
|
||||
const REFERENCE_KEYS = [
|
||||
'baidu_push_enabled',
|
||||
'baidu_push_token',
|
||||
@@ -136,7 +136,7 @@ test('前台自动推送 JS 与参考主题一致', () => {
|
||||
|
||||
test('发布/更新触发沿用 transition_post_status 且仅限可检索内容类型', () => {
|
||||
assert.match(runtime, /transition_post_status/);
|
||||
assert.match(runtime, /aksrt_searchable_post_types/);
|
||||
assert.match(runtime, /jio_searchable_post_types/);
|
||||
assert.match(runtime, /wp_is_post_revision/);
|
||||
assert.match(runtime, /wp_is_post_autosave/);
|
||||
});
|
||||
@@ -156,12 +156,12 @@ test('分类法推送接入 saved_term 且排除文章形式', () => {
|
||||
assert.match(runtime, /add_action\('saved_term'/);
|
||||
assert.match(runtime, /public static function pushable_taxonomies/);
|
||||
assert.match(runtime, /array_diff\(\(array\) \$taxonomies, \['post_format', 'nav_menu'\]\)/);
|
||||
assert.match(runtime, /aksrt_baidu_push_taxonomies/);
|
||||
assert.match(runtime, /jio_baidu_push_taxonomies/);
|
||||
assert.match(runtime, /get_term_link\(\$term_id, \$taxonomy\)/);
|
||||
});
|
||||
|
||||
test('推送结果写入 meta 并按记录去重', () => {
|
||||
assert.match(runtime, /META_KEY\s*=\s*'_aksrt_baidu_push_back'/);
|
||||
assert.match(runtime, /META_KEY\s*=\s*'_jio_baidu_push_back'/);
|
||||
assert.match(runtime, /private function record_is_complete/);
|
||||
assert.match(runtime, /private function build_record/);
|
||||
assert.match(runtime, /'normal_push'/);
|
||||
@@ -172,14 +172,14 @@ test('推送结果写入 meta 并按记录去重', () => {
|
||||
});
|
||||
|
||||
test('「重新提交」勾选需通过 metabox nonce 校验', () => {
|
||||
assert.match(runtime, /NONCE_ACTION\s*=\s*'aksrt_push_meta_save'/);
|
||||
assert.match(runtime, /NONCE_FIELD\s*=\s*'aksrt_push_meta_nonce'/);
|
||||
assert.match(runtime, /RESUBMIT_FIELD\s*=\s*'aksrt_push_resubmit'/);
|
||||
assert.match(runtime, /NONCE_ACTION\s*=\s*'jio_push_meta_save'/);
|
||||
assert.match(runtime, /NONCE_FIELD\s*=\s*'jio_push_meta_nonce'/);
|
||||
assert.match(runtime, /RESUBMIT_FIELD\s*=\s*'jio_push_resubmit'/);
|
||||
assert.match(runtime, /wp_verify_nonce\(\$nonce, self::NONCE_ACTION\)/);
|
||||
});
|
||||
|
||||
test('编辑页面板与参考主题「百度资源提交」Metabox 对齐', () => {
|
||||
assert.match(metaBox, /add_meta_box\(\s*'aksrt_baidu_push',\s*__\('百度资源提交', 'aksrt'\)/);
|
||||
assert.match(metaBox, /add_meta_box\(\s*'jio_baidu_push',\s*__\('百度资源提交', 'jio'\)/);
|
||||
assert.match(metaBox, /'side',\s*'high'/);
|
||||
assert.match(metaBox, /\{\$taxonomy\}_edit_form_fields/);
|
||||
assert.match(metaBox, /\{\$taxonomy\}_add_form_fields/);
|
||||
|
||||
@@ -84,10 +84,10 @@ function extractCalls(source, marker) {
|
||||
/** 收集一行渲染调用携带的依赖规则与显示标签。 */
|
||||
function rowsWithDeps(source) {
|
||||
const markers = [
|
||||
{ marker: '->field_row(', label: (t) => (t.match(/__\(\s*'([^']*)'\s*,\s*'aksrt'\s*\)/) || [])[1] },
|
||||
{ marker: '->field_row(', label: (t) => (t.match(/__\(\s*'([^']*)'\s*,\s*'jio'\s*\)/) || [])[1] },
|
||||
{
|
||||
marker: '->submessage_row(',
|
||||
label: (t) => (t.match(/<b>'\s*\.\s*esc_html__\(\s*'([^']*)'\s*,\s*'aksrt'\s*\)/) || [])[1],
|
||||
label: (t) => (t.match(/<b>'\s*\.\s*esc_html__\(\s*'([^']*)'\s*,\s*'jio'\s*\)/) || [])[1],
|
||||
},
|
||||
];
|
||||
const calls = [];
|
||||
@@ -147,7 +147,7 @@ function createForm() {
|
||||
const document = {
|
||||
readyState: 'loading',
|
||||
querySelectorAll(sel) {
|
||||
if (sel === '.aksrt-cardpass-form [name]') return inputs;
|
||||
if (sel === '.jio-cardpass-form [name]') return inputs;
|
||||
if (sel === '[data-dep]') return rows.map((row) => row.el);
|
||||
return [];
|
||||
},
|
||||
@@ -220,10 +220,10 @@ test('两种行渲染都接入服务端预隐藏,避免首帧铺开', () => {
|
||||
// wp-admin/js/common.js 会把页面上的 notice 搬到页头,见下一条用例),
|
||||
// 它仍能被求值并保持隐藏。
|
||||
assert.match(page, /<td colspan="2"><div'\s*\.\s*\$dep_attr/, '提示块必须自带 data-dep');
|
||||
assert.match(page, /class="aksrt-submsg"'\s*\.\s*\$hidden/, '提示块必须自带初始隐藏状态');
|
||||
assert.match(page, /class="jio-submsg"'\s*\.\s*\$hidden/, '提示块必须自带初始隐藏状态');
|
||||
// 求值范围不限域:提示块被移出表格/表单子树后仍必须被求值。
|
||||
assert.match(script, /querySelectorAll\('\[data-dep\]'\)/, '求值必须全文档查找 data-dep');
|
||||
assert.doesNotMatch(script, /querySelectorAll\('\.aksrt-cardpass-form \[data-dep\]'\)/, '求值不得限域在表单内');
|
||||
assert.doesNotMatch(script, /querySelectorAll\('\.jio-cardpass-form \[data-dep\]'\)/, '求值不得限域在表单内');
|
||||
});
|
||||
|
||||
test('导入提示不得使用 WP 的 .notice 类(否则被 common.js 搬到页面顶部)', () => {
|
||||
@@ -234,19 +234,19 @@ test('导入提示不得使用 WP 的 .notice 类(否则被 common.js 搬到
|
||||
// 且搬迁发生在 evalDeps 之后,行内显隐逻辑无法纠正(历史故障:提示一直显示在页面最上方)。
|
||||
assert.match(
|
||||
page,
|
||||
/private function submessage_row\([\s\S]*?class="aksrt-submsg"/,
|
||||
'提示块只应带自有类名 aksrt-submsg'
|
||||
/private function submessage_row\([\s\S]*?class="jio-submsg"/,
|
||||
'提示块只应带自有类名 jio-submsg'
|
||||
);
|
||||
assert.doesNotMatch(
|
||||
page,
|
||||
/class="aksrt-submsg[^"]*\bnotice\b/,
|
||||
/class="jio-submsg[^"]*\bnotice\b/,
|
||||
'提示块不得带 notice 类:会被 common.js 搬到页面顶部'
|
||||
);
|
||||
// 外观改由自有类名承担,删掉样式规则会让提示失去底色与左侧色条。
|
||||
assert.match(
|
||||
page,
|
||||
/\.aksrt-submsg \{[^}]*background:[^}]*border-left:/,
|
||||
'.aksrt-submsg 必须自带 background 与 border-left 外观'
|
||||
/\.jio-submsg \{[^}]*background:[^}]*border-left:/,
|
||||
'.jio-submsg 必须自带 background 与 border-left 外观'
|
||||
);
|
||||
// 允许(且要求)真正需要置顶的提示保留 notice:确认本用例没有误伤其它通知输出。
|
||||
assert.match(page, /class="notice notice-error"/, '页面级错误通知仍应使用 notice');
|
||||
@@ -266,17 +266,17 @@ test('导入提示的条目列表由 <ul> 承载,圆点不越出文字边界',
|
||||
checked++;
|
||||
assert.match(
|
||||
call.text,
|
||||
/<ul class="aksrt-submsg-list">/,
|
||||
'提示内的 <li> 必须包在 <ul class="aksrt-submsg-list"> 中'
|
||||
/<ul class="jio-submsg-list">/,
|
||||
'提示内的 <li> 必须包在 <ul class="jio-submsg-list"> 中'
|
||||
);
|
||||
assert.equal([...call.text.matchAll(/<ul class="aksrt-submsg-list">/g)].length, 1, '每条提示只应有一个条目列表');
|
||||
assert.equal([...call.text.matchAll(/<ul class="jio-submsg-list">/g)].length, 1, '每条提示只应有一个条目列表');
|
||||
assert.equal(opens, [...call.text.matchAll(/<\/li>/g)].length, '<li> 标签必须成对');
|
||||
assert.equal([...call.text.matchAll(/<\/ul>/g)].length, 1, '</ul> 必须成对');
|
||||
}
|
||||
assert.equal(checked, 4, '四条导入提示都应带条目列表');
|
||||
|
||||
assert.match(page, /\.aksrt-submsg-list \{[^}]*padding-left:/, '.aksrt-submsg-list 必须给出 padding-left,圆点才有落点');
|
||||
assert.match(page, /\.aksrt-submsg-list \{[^}]*list-style:\s*disc/, '.aksrt-submsg-list 应显式声明圆点样式,不依赖 UA 默认值');
|
||||
assert.match(page, /\.jio-submsg-list \{[^}]*padding-left:/, '.jio-submsg-list 必须给出 padding-left,圆点才有落点');
|
||||
assert.match(page, /\.jio-submsg-list \{[^}]*list-style:\s*disc/, '.jio-submsg-list 应显式声明圆点样式,不依赖 UA 默认值');
|
||||
});
|
||||
|
||||
test('行内脚本不再保留解析期的空操作求值(死代码)', () => {
|
||||
|
||||
@@ -156,7 +156,7 @@ test('整个后台页源码内没有任何输入携带原生约束属性', () =>
|
||||
|
||||
test('表单不用 novalidate 掩盖问题,且提交按钮仍是原生 submit', () => {
|
||||
assert.doesNotMatch(addForm, /novalidate/i, '不得通过关闭校验来绕过,应去掉约束本身');
|
||||
assert.match(addForm, /submit_button\(\s*__\('确认提交',\s*'aksrt'\)/);
|
||||
assert.match(addForm, /submit_button\(\s*__\('确认提交',\s*'jio'\)/);
|
||||
assert.match(addForm, /wp_nonce_field\(self::NONCE\)/);
|
||||
});
|
||||
|
||||
|
||||
@@ -48,8 +48,8 @@ test('标题右侧按钮只在页面头部输出一次', () => {
|
||||
// 列表页的两个入口按钮各自只有一处定义。
|
||||
assert.equal(count(page, /add_query_arg\('tab', 'add'/g), 1);
|
||||
assert.equal(count(page, /add_query_arg\('tab', 'export'/g), 1);
|
||||
assert.match(header, /esc_html__\('添加卡密', 'aksrt'\)/);
|
||||
assert.match(header, /esc_html__\('导出卡密', 'aksrt'\)/);
|
||||
assert.match(header, /esc_html__\('添加卡密', 'jio'\)/);
|
||||
assert.match(header, /esc_html__\('导出卡密', 'jio'\)/);
|
||||
});
|
||||
|
||||
test('搜索提示仍由页面头部输出且列表不再自建头部', () => {
|
||||
|
||||
@@ -77,7 +77,7 @@ test('ice_perpage 仍作为 URL 参数被尊重,默认每页 20', () => {
|
||||
// 明确约定:不在列表工具栏增加「每页显示」下拉/按钮。
|
||||
assert.doesNotMatch(page, /per_page_selector/);
|
||||
assert.doesNotMatch(page, /PER_PAGE_CHOICES/);
|
||||
assert.doesNotMatch(page, /aksrt-perpage-form/);
|
||||
assert.doesNotMatch(page, /jio-perpage-form/);
|
||||
assert.doesNotMatch(page, /name="ice_perpage"/);
|
||||
assert.doesNotMatch(page, /order-header-right/);
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
* 3) 插入表情(comment_smilie)、评论地理位置(comment_city_s/comment_city_type,
|
||||
* 由扩展&增强/IP归属地实现)不在本页;
|
||||
* 4) 快捷回复按评论区硬切换契约保持移除(comment_quick_* 不存在);
|
||||
* 5) 旧 AKSRT 私有字段(comment_order / comment_per_page / comment_pagination_type /
|
||||
* 5) 旧 Jio 私有字段(comment_order / comment_per_page / comment_pagination_type /
|
||||
* comment_nested_depth / comment_avatar_size / comment_close_comments /
|
||||
* comment_edit_window / comment_hot_threshold / comment_empty_text /
|
||||
* post_close_comments)已彻底移除,无双路兼容;
|
||||
@@ -29,7 +29,7 @@ const root = path.resolve(__dirname, '..', '..');
|
||||
const php = fs.readFileSync(path.join(root, 'src/Admin/Schema/SchemaPostList.php'), 'utf8');
|
||||
const js = fs.readFileSync(path.join(root, 'assets/admin/src/schema/sections/post-list/comment.js'), 'utf8');
|
||||
const runtime = fs.readFileSync(path.join(root, 'src/Settings/PostDisplay.php'), 'utf8');
|
||||
const commentsHelper = fs.readFileSync(path.join(root, 'inc/helpers/aksrt-comments.php'), 'utf8');
|
||||
const commentsHelper = fs.readFileSync(path.join(root, 'inc/helpers/jio-comments.php'), 'utf8');
|
||||
const geoHelper = fs.readFileSync(path.join(root, 'inc/helpers/comments/geo.php'), 'utf8');
|
||||
const actionsHelper = fs.readFileSync(path.join(root, 'inc/helpers/comments/actions.php'), 'utf8');
|
||||
|
||||
@@ -62,7 +62,7 @@ const EXCLUDED_KEYS = [
|
||||
'comment_quick_often',
|
||||
];
|
||||
|
||||
// 旧 AKSRT 私有字段(含已并入 WP 讨论设置与死字段 post_close_comments)。
|
||||
// 旧 Jio 私有字段(含已并入 WP 讨论设置与死字段 post_close_comments)。
|
||||
const LEGACY_KEYS = [
|
||||
'comment_order',
|
||||
'comment_per_page',
|
||||
@@ -204,7 +204,7 @@ test('旧字段在 schema 与运行时中均已移除(无双路)', () => {
|
||||
assert.ok(!php.includes(legacy), `PHP schema 残留旧字段: ${legacy}`);
|
||||
// 运行时按主题设置读取的形态 $settings['<legacy>'] 必须消失
|
||||
// (comment_order 允许以 WP 讨论选项 get_option('comment_order') 形态存在)。
|
||||
for (const source of [['PostDisplay', runtime], ['aksrt-comments.php', commentsHelper], ['comments/geo.php', geoHelper], ['comments/actions.php', actionsHelper]]) {
|
||||
for (const source of [['PostDisplay', runtime], ['jio-comments.php', commentsHelper], ['comments/geo.php', geoHelper], ['comments/actions.php', actionsHelper]]) {
|
||||
assert.ok(!source[1].includes(`$settings['${legacy}']`), `${source[0]} 残留旧字段读取: ${legacy}`);
|
||||
}
|
||||
}
|
||||
@@ -217,8 +217,8 @@ test('旧字段在 schema 与运行时中均已移除(无双路)', () => {
|
||||
// 排序回退到 WP 讨论设置(参考主题行为)。
|
||||
assert.match(runtime, /get_option\('comment_order', 'desc'\)/);
|
||||
// 评论区位置显示交由扩展 IP 归属地控制。
|
||||
assert.match(geoHelper, /aksrt_ip_geo_display_enabled\('comment'\)/);
|
||||
assert.match(geoHelper, /aksrt_ip_geo_format_key/);
|
||||
assert.match(geoHelper, /jio_ip_geo_display_enabled\('comment'\)/);
|
||||
assert.match(geoHelper, /jio_ip_geo_format_key/);
|
||||
// 编辑权限使用 user_edit 总开关。
|
||||
assert.match(actionsHelper, /\$display\['user_edit'\]/);
|
||||
});
|
||||
@@ -8,7 +8,7 @@
|
||||
* imagelightbox / imagelightbox_type / imagelightbox_thumbs_s /
|
||||
* imagelightbox_play_s / imagelightbox_zoom_s / imagelightbox_full_s /
|
||||
* imagelightbox_down_s / post_like_s / post_default_mate(views/like);
|
||||
* 3) 旧 AKSRT 私有/异名字段(feature_print / feature_pdf_export /
|
||||
* 3) 旧 Jio 私有/异名字段(feature_print / feature_pdf_export /
|
||||
* post_lightbox* / post_like_enabled / post_default_*_min/max)已彻底移除,
|
||||
* 文章页面(page)不再承载这些字段,无双路兼容;
|
||||
* 4) 运行时(PostDisplay::get_post_page_settings)只读取新键,灯箱复选框
|
||||
@@ -27,10 +27,10 @@ const pageJs = fs.readFileSync(path.join(root, 'assets/admin/src/schema/sections
|
||||
const runtime = fs.readFileSync(path.join(root, 'src/Settings/PostDisplay.php'), 'utf8');
|
||||
const scripts1c = fs.readFileSync(path.join(root, 'src/Core/ScriptsPart1c.php'), 'utf8');
|
||||
const scripts1b = fs.readFileSync(path.join(root, 'src/Core/ScriptsPart1b.php'), 'utf8');
|
||||
const frontendTags = fs.readFileSync(path.join(root, 'inc/helpers/aksrt-frontend-tags.php'), 'utf8');
|
||||
const frontendTags = fs.readFileSync(path.join(root, 'inc/helpers/jio-frontend-tags.php'), 'utf8');
|
||||
const coreHooks = fs.readFileSync(path.join(root, 'inc/core-hooks.php'), 'utf8');
|
||||
const singleRender = fs.readFileSync(path.join(root, 'inc/helpers/aksrt-single-render.php'), 'utf8');
|
||||
const mobileHeader = fs.readFileSync(path.join(root, 'inc/helpers/header/aksrt-header-mobile.php'), 'utf8');
|
||||
const singleRender = fs.readFileSync(path.join(root, 'inc/helpers/jio-single-render.php'), 'utf8');
|
||||
const mobileHeader = fs.readFileSync(path.join(root, 'inc/helpers/header/jio-header-mobile.php'), 'utf8');
|
||||
|
||||
const REFERENCE_KEYS = [
|
||||
'article_nav',
|
||||
@@ -46,7 +46,7 @@ const REFERENCE_KEYS = [
|
||||
'post_default_mate',
|
||||
];
|
||||
|
||||
// 旧 AKSRT 私有 / 异名字段(打印与 PDF 导出、旧灯箱六开关、旧点赞与默认数值四键)。
|
||||
// 旧 Jio 私有 / 异名字段(打印与 PDF 导出、旧灯箱六开关、旧点赞与默认数值四键)。
|
||||
const LEGACY_KEYS = [
|
||||
'feature_print',
|
||||
'feature_pdf_export',
|
||||
@@ -194,7 +194,7 @@ test('旧字段在 schema 与运行时中均已移除(无双路)', () => {
|
||||
assert.ok(!runtime.includes(legacy), `PostDisplay 残留旧字段: ${legacy}`);
|
||||
assert.ok(!js.includes(legacy), `feature.js 残留旧字段: ${legacy}`);
|
||||
assert.ok(!pageJs.includes(legacy), `page.js 残留旧字段: ${legacy}`);
|
||||
assert.ok(!frontendTags.includes(legacy), `aksrt-frontend-tags 残留旧字段: ${legacy}`);
|
||||
assert.ok(!frontendTags.includes(legacy), `jio-frontend-tags 残留旧字段: ${legacy}`);
|
||||
}
|
||||
assert.ok(!scripts1b.includes('needs_print'), 'ScriptsPart1b 残留 needs_print 分支');
|
||||
assert.ok(!scripts1c.includes('needs_print'), 'ScriptsPart1c 残留 needs_print 需求');
|
||||
@@ -228,5 +228,5 @@ test('运行时只读取新键并接入灯箱/目录树/预置参数', () => {
|
||||
// 预置参数:新建文章随机填入。
|
||||
assert.match(coreHooks, /save_post/);
|
||||
assert.match(coreHooks, /POST_VIEW_COUNT_KEY/);
|
||||
assert.match(coreHooks, /_aksrt_like_count/);
|
||||
assert.match(coreHooks, /_jio_like_count/);
|
||||
});
|
||||
@@ -18,7 +18,7 @@ const php = read('src/Admin/Schema/SchemaPostList.php');
|
||||
const postDisplay = read('src/Settings/PostDisplay.php');
|
||||
const settings = read('src/Settings/Settings.php');
|
||||
const termMeta = read('src/Core/TermMeta.php');
|
||||
const filtersHelper = read('inc/helpers/aksrt-archive-filters.php');
|
||||
const filtersHelper = read('inc/helpers/jio-archive-filters.php');
|
||||
const categoryTpl = read('category.php');
|
||||
const tagTpl = read('tag.php');
|
||||
const listJs = read('assets/js/runtime/main.js');
|
||||
@@ -26,7 +26,7 @@ const globalController = read('src/Admin/GlobalController.php');
|
||||
|
||||
// 参考主题字段键(顺序即参考主题定义顺序)。
|
||||
const REFERENCE_KEYS = ['custom_filter_show', 'custom_filter', 'cat_custom_filter'];
|
||||
// AKSRT 私有旧字段(参考主题无此配置,须移除避免双路)。
|
||||
// Jio 私有旧字段(参考主题无此配置,须移除避免双路)。
|
||||
const LEGACY_KEYS = [
|
||||
'filter_enabled',
|
||||
'filter_categories',
|
||||
@@ -149,17 +149,17 @@ test('前台链路:覆盖与门禁(参考主题 zib-category.php 语义)',
|
||||
|
||||
test('前台链路:维度筛选渲染与查询过滤', () => {
|
||||
// 渲染:维度 chip 组(全部 chip 参考主题 focus 语义)。
|
||||
assert.match(filtersHelper, /function aksrt_archive_build_custom_filter_groups\(/);
|
||||
assert.match(filtersHelper, /function jio_archive_build_custom_filter_groups\(/);
|
||||
assert.match(filtersHelper, /ajax-replace="true" class="ajax-next/);
|
||||
assert.match(filtersHelper, /\$keys !== 'all' && !in_array\(\$dim_key, \(array\) \$keys, true\)/);
|
||||
assert.match(categoryTpl, /aksrt_archive_build_custom_filter_groups\(/);
|
||||
assert.match(tagTpl, /aksrt_archive_build_custom_filter_groups\(/);
|
||||
// 查询:_aksrt_cf_{key} meta_query LIKE(主查询 + AJAX 端点)。
|
||||
assert.match(termMeta, /'_aksrt_cf_' \. \$dim_key/);
|
||||
assert.match(categoryTpl, /jio_archive_build_custom_filter_groups\(/);
|
||||
assert.match(tagTpl, /jio_archive_build_custom_filter_groups\(/);
|
||||
// 查询:_jio_cf_{key} meta_query LIKE(主查询 + AJAX 端点)。
|
||||
assert.match(termMeta, /'_jio_cf_' \. \$dim_key/);
|
||||
assert.match(termMeta, /'compare'\s*=>\s*'LIKE'/);
|
||||
// Category filtering uses the same HTML AJAX pipeline as the homepage.
|
||||
assert.doesNotMatch(globalController, /rest_archive_posts|\/archive\/posts/);
|
||||
assert.match(listJs, /win-ajax-replace/);
|
||||
assert.match(listJs, /post_ajax/);
|
||||
assert.match(filtersHelper, /'key'\s*=>\s*'_aksrt_cf_|\$dim_key/);
|
||||
assert.match(filtersHelper, /'key'\s*=>\s*'_jio_cf_|\$dim_key/);
|
||||
});
|
||||
@@ -110,7 +110,7 @@ test('每页条数默认 20 来自共享常量,且无「每页显示」控件'
|
||||
for (const [key, src] of Object.entries(pages)) {
|
||||
assert.doesNotMatch(src, /per_page_selector/, `${labels[key]} 出现 per_page 选择器`);
|
||||
assert.doesNotMatch(src, /PER_PAGE_CHOICES/, `${labels[key]} 出现 PER_PAGE_CHOICES`);
|
||||
assert.doesNotMatch(src, /aksrt-perpage-form/, `${labels[key]} 出现每页条数表单`);
|
||||
assert.doesNotMatch(src, /jio-perpage-form/, `${labels[key]} 出现每页条数表单`);
|
||||
assert.doesNotMatch(src, /name="ice_perpage"/, `${labels[key]} 出现 ice_perpage 输入框`);
|
||||
assert.doesNotMatch(src, /order-header-right/, `${labels[key]} 出现工具栏右侧每页控件`);
|
||||
}
|
||||
@@ -191,7 +191,7 @@ test('提现审核不把一次性动作参数带进翻页链接', () => {
|
||||
});
|
||||
|
||||
test('共享助手自身:页码夹取、四按钮标记与显式基准 URL', () => {
|
||||
assert.match(helper, /namespace AKSRT\\Admin\\Native;/);
|
||||
assert.match(helper, /namespace Jio\\Admin\\Native;/);
|
||||
assert.match(helper, /defined\('ABSPATH'\) \|\| exit;/);
|
||||
|
||||
// 总数为 0 或每页条数非法时不渲染;单页照样渲染(与原版 render_pagenavi 一致)。
|
||||
|
||||
@@ -12,10 +12,10 @@
|
||||
* post_front_content/after_content、post_related_*;
|
||||
* 3) 幻灯片 fieldset 全字段及嵌套依赖(direction/scale_height/auto_height 组合)
|
||||
* 对齐参考主题 CFS_Module::slide;
|
||||
* 4) 旧 AKSRT 私有/异名字段(阅读进度条、面包屑末项文本、作者卡片配置、收藏开关、
|
||||
* 4) 旧 Jio 私有/异名字段(阅读进度条、面包屑末项文本、作者卡片配置、收藏开关、
|
||||
* share_s 与 post_rewards_*、旧封面键)已彻底移除,无双路兼容;
|
||||
* 5) 运行时(PostDisplay::get_post_page_settings)只读取新键:幻灯片配置归一化并
|
||||
* 接线 aksrt_render_slider,p_indent 接线容器类,breadcrumbs_home_text 为
|
||||
* 接线 jio_render_slider,p_indent 接线容器类,breadcrumbs_home_text 为
|
||||
* switcher 布尔语义(true = 用「首页」替代网站名称)。
|
||||
*/
|
||||
|
||||
@@ -30,8 +30,8 @@ const js = fs.readFileSync(path.join(root, 'assets/admin/src/schema/sections/pos
|
||||
const runtime = fs.readFileSync(path.join(root, 'src/Settings/PostDisplay.php'), 'utf8');
|
||||
const facadeBulk = fs.readFileSync(path.join(root, 'src/Settings/FacadeBulk.php'), 'utf8');
|
||||
const metaBox = fs.readFileSync(path.join(root, 'src/Core/PostMetaBox.php'), 'utf8');
|
||||
const frontendTags = fs.readFileSync(path.join(root, 'inc/helpers/aksrt-frontend-tags.php'), 'utf8');
|
||||
const singleRender = fs.readFileSync(path.join(root, 'inc/helpers/aksrt-single-render.php'), 'utf8');
|
||||
const frontendTags = fs.readFileSync(path.join(root, 'inc/helpers/jio-frontend-tags.php'), 'utf8');
|
||||
const singleRender = fs.readFileSync(path.join(root, 'inc/helpers/jio-single-render.php'), 'utf8');
|
||||
const contentCss = fs.readFileSync(path.join(root, 'assets/css/frontend/single-post/content.css'), 'utf8');
|
||||
const shareJs = fs.readFileSync(path.join(root, 'assets/admin/src/schema/sections/global/share.js'), 'utf8');
|
||||
const sharePhp = fs.readFileSync(path.join(root, 'src/Admin/Schema/GlobalSchema.php'), 'utf8');
|
||||
@@ -104,7 +104,7 @@ const PAGE_FIELD_SEQUENCE = [
|
||||
'post_related_orderby',
|
||||
];
|
||||
|
||||
// 旧 AKSRT 私有/异名字段:已迁移(share_s / post_rewards_*)或无参考对应项被移除。
|
||||
// 旧 Jio 私有/异名字段:已迁移(share_s / post_rewards_*)或无参考对应项被移除。
|
||||
const LEGACY_KEYS = [
|
||||
'post_reading_progress',
|
||||
'post_author_card_limit',
|
||||
@@ -271,7 +271,7 @@ test('旧字段在 schema 与运行时中均已移除(无双路)', () => {
|
||||
assert.ok(!pagePhp.includes(`'breadcrumbs_single_text'`), 'page section 残留 breadcrumbs_single_text');
|
||||
assert.ok(!js.includes(`key: 'breadcrumbs_single_text'`), 'page.js 残留 breadcrumbs_single_text');
|
||||
assert.match(runtime, /'breadcrumbs_single_text'\s*=>\s*true/);
|
||||
// aksrt-frontend-tags 不再有 breadcrumbs_home_text 文本兼容(switcher 布尔语义)。
|
||||
// jio-frontend-tags 不再有 breadcrumbs_home_text 文本兼容(switcher 布尔语义)。
|
||||
assert.ok(!frontendTags.includes("breadcrumbs_home'] = (string) $settings['breadcrumbs_home_text'"), 'frontend-tags 残留 breadcrumbs_home_text 文本兼容');
|
||||
});
|
||||
|
||||
@@ -318,7 +318,7 @@ test('运行时只读取新键并接线封面/缩进/版权/一言', () => {
|
||||
});
|
||||
|
||||
test('前台链路:幻灯片配置接线渲染器,p_indent 接线容器与 CSS', () => {
|
||||
// 幻灯片配置接入 aksrt_render_slider(effect/direction/spaceBetween/speed/interval)。
|
||||
// 幻灯片配置接入 jio_render_slider(effect/direction/spaceBetween/speed/interval)。
|
||||
assert.match(singleRender, /article_slide_cover_option/);
|
||||
assert.match(singleRender, /'effect'\s*=>\s*\(string\) \(\$slide_option\['effect'\] \?\? 'slide'\)/);
|
||||
assert.match(singleRender, /'direction'\s*=>\s*\(string\) \(\$slide_option\['direction'\] \?\? 'horizontal'\)/);
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
* post_article_pay_vip_1_discount / post_article_pay_vip_2_discount /
|
||||
* post_t_placeholder / post_c_placeholder;
|
||||
* 3) 专题(topics)相关字段不存在;
|
||||
* 4) 旧 AKSRT 私有字段(front_submit_*、post_article_title_strlen_limit_min/max、
|
||||
* 4) 旧 Jio 私有字段(front_submit_*、post_article_title_strlen_limit_min/max、
|
||||
* post_article_pay_price_min/max)已彻底移除,无双路兼容;
|
||||
* 5) 运行时(PostDisplay::get_submit_settings)只读取新键。
|
||||
*/
|
||||
|
||||
@@ -42,7 +42,7 @@ namespace {
|
||||
}
|
||||
}
|
||||
|
||||
namespace AKSRT\Ticket {
|
||||
namespace Jio\Ticket {
|
||||
final class TicketSettings {
|
||||
public static bool $order_link_enabled = false;
|
||||
public static function order_link_enabled(): bool { return self::$order_link_enabled; }
|
||||
@@ -131,7 +131,7 @@ namespace AKSRT\Ticket {
|
||||
}
|
||||
}
|
||||
|
||||
namespace AKSRT\Attachment {
|
||||
namespace Jio\Attachment {
|
||||
final class PendingUploads {
|
||||
public static function claim_urls(array $attachment, int $user_id): void {}
|
||||
}
|
||||
@@ -148,45 +148,45 @@ namespace {
|
||||
}
|
||||
};
|
||||
$reset = static function (): void {
|
||||
\AKSRT\Ticket\TicketRepository::$tickets = [
|
||||
\Jio\Ticket\TicketRepository::$tickets = [
|
||||
1 => ['id' => 1, 'status' => 'handling', 'ticket_type_id' => 3, 'admin_id' => 11],
|
||||
2 => ['id' => 2, 'status' => 'closed', 'ticket_type_id' => 3, 'admin_id' => 11],
|
||||
3 => ['id' => 3, 'status' => 'handling', 'ticket_type_id' => 3, 'admin_id' => 11],
|
||||
];
|
||||
\AKSRT\Ticket\TicketRepository::$creates = [];
|
||||
\AKSRT\Ticket\TicketRepository::$updates = [];
|
||||
\AKSRT\Ticket\TicketNotifier::$closed = [];
|
||||
\AKSRT\Ticket\TicketNotifier::$user_replied = [];
|
||||
\AKSRT\Ticket\TicketNoteService::$created = [];
|
||||
\AKSRT\Ticket\TicketSettings::$order_link_enabled = false;
|
||||
\Jio\Ticket\TicketRepository::$creates = [];
|
||||
\Jio\Ticket\TicketRepository::$updates = [];
|
||||
\Jio\Ticket\TicketNotifier::$closed = [];
|
||||
\Jio\Ticket\TicketNotifier::$user_replied = [];
|
||||
\Jio\Ticket\TicketNoteService::$created = [];
|
||||
\Jio\Ticket\TicketSettings::$order_link_enabled = false;
|
||||
$GLOBALS['ticket_rest_user_can_calls'] = [];
|
||||
};
|
||||
$admin = new \AKSRT\Ticket\TicketRestAdmin();
|
||||
$admin = new \Jio\Ticket\TicketRestAdmin();
|
||||
$error_code = static fn($value): string => $value instanceof \WP_Error ? $value->code : '';
|
||||
|
||||
// Explicit admin_id=0 clears assignment and status updates always touch updated_at.
|
||||
$reset();
|
||||
$response = $admin->set_status(new \WP_REST_Request(['id' => 1, 'status' => 'handling', 'admin_id' => 0]));
|
||||
$saved = \AKSRT\Ticket\TicketRepository::$tickets[1] ?? [];
|
||||
$saved = \Jio\Ticket\TicketRepository::$tickets[1] ?? [];
|
||||
$check('解除指派接受 admin_id=0', !\is_wp_error($response) && ($saved['admin_id'] ?? null) === 0);
|
||||
$check('状态更新写入 updated_at', ($saved['updated_at'] ?? '') === '2026-10-31 12:34:56');
|
||||
|
||||
// Invalid department returns an explicit error and performs no write.
|
||||
$reset();
|
||||
$response = $admin->set_status(new \WP_REST_Request(['id' => 1, 'status' => 'handling', 'ticket_type_id' => 999]));
|
||||
$check('非法部门返回明确错误', $error_code($response) === 'aksrt_ticket_type');
|
||||
$check('非法部门不写入任何字段', \AKSRT\Ticket\TicketRepository::$updates === []);
|
||||
$check('非法部门返回明确错误', $error_code($response) === 'jio_ticket_type');
|
||||
$check('非法部门不写入任何字段', \Jio\Ticket\TicketRepository::$updates === []);
|
||||
|
||||
// Existing assignees may lose admin capability; retaining the same ID isn't a new assignment.
|
||||
$reset();
|
||||
\AKSRT\Ticket\TicketRepository::$tickets[1]['admin_id'] = 12;
|
||||
\Jio\Ticket\TicketRepository::$tickets[1]['admin_id'] = 12;
|
||||
$response = $admin->set_status(new \WP_REST_Request([
|
||||
'id' => 1,
|
||||
'status' => 'admin_replied',
|
||||
'ticket_type_id' => 4,
|
||||
'admin_id' => 12,
|
||||
]));
|
||||
$saved = \AKSRT\Ticket\TicketRepository::$tickets[1] ?? [];
|
||||
$saved = \Jio\Ticket\TicketRepository::$tickets[1] ?? [];
|
||||
$check('保留已失去管理员权限的原跟进人仍可更新状态和部门', !\is_wp_error($response)
|
||||
&& ($saved['status'] ?? null) === 'admin_replied'
|
||||
&& ($saved['ticket_type_id'] ?? null) === 4
|
||||
@@ -201,8 +201,8 @@ namespace {
|
||||
'ticket_type_id' => 4,
|
||||
'admin_id' => 12,
|
||||
]));
|
||||
$check('无 manage_options 的用户不能被指派', $error_code($response) === 'aksrt_ticket_admin');
|
||||
$check('管理员校验失败时部门也不被部分写入', \AKSRT\Ticket\TicketRepository::$updates === []);
|
||||
$check('无 manage_options 的用户不能被指派', $error_code($response) === 'jio_ticket_admin');
|
||||
$check('管理员校验失败时部门也不被部分写入', \Jio\Ticket\TicketRepository::$updates === []);
|
||||
$check('目标管理员必须通过 manage_options 校验', in_array([12, 'manage_options'], $GLOBALS['ticket_rest_user_can_calls'], true));
|
||||
|
||||
$reset();
|
||||
@@ -211,13 +211,13 @@ namespace {
|
||||
'status' => 'handling',
|
||||
'admin_id' => 777,
|
||||
]));
|
||||
$check('不存在的管理员返回明确错误', $error_code($response) === 'aksrt_ticket_admin');
|
||||
$check('不存在的管理员返回明确错误', $error_code($response) === 'jio_ticket_admin');
|
||||
|
||||
|
||||
$check('不存在的管理员不会触发写入', \AKSRT\Ticket\TicketRepository::$updates === []);
|
||||
$check('不存在的管理员不会触发写入', \Jio\Ticket\TicketRepository::$updates === []);
|
||||
|
||||
$reset();
|
||||
\AKSRT\Ticket\TicketRepository::$tickets[1]['admin_id'] = 12;
|
||||
\Jio\Ticket\TicketRepository::$tickets[1]['admin_id'] = 12;
|
||||
$response = $admin->set_status(new \WP_REST_Request([
|
||||
'id' => 1,
|
||||
'status' => 'handling',
|
||||
@@ -225,18 +225,18 @@ namespace {
|
||||
]));
|
||||
$check('具备 manage_options 的管理员可被指派', !\is_wp_error($response)
|
||||
&& in_array([11, 'manage_options'], $GLOBALS['ticket_rest_user_can_calls'], true)
|
||||
&& (\AKSRT\Ticket\TicketRepository::$tickets[1]['admin_id'] ?? null) === 11);
|
||||
&& (\Jio\Ticket\TicketRepository::$tickets[1]['admin_id'] ?? null) === 11);
|
||||
|
||||
// Only the first open -> closed transition sends a close notification.
|
||||
$reset();
|
||||
$first_close = $admin->set_status(new \WP_REST_Request(['id' => 3, 'status' => 'closed']));
|
||||
$second_close = $admin->set_status(new \WP_REST_Request(['id' => 3, 'status' => 'closed']));
|
||||
$check('首次关闭成功', !\is_wp_error($first_close));
|
||||
$check('同状态再次提交不重发关闭通知', count(\AKSRT\Ticket\TicketNotifier::$closed) === 1);
|
||||
$check('同状态再次提交不重发关闭通知', count(\Jio\Ticket\TicketNotifier::$closed) === 1);
|
||||
|
||||
// Order association follows the feature switch.
|
||||
$reset();
|
||||
\AKSRT\Ticket\TicketSettings::$order_link_enabled = false;
|
||||
\Jio\Ticket\TicketSettings::$order_link_enabled = false;
|
||||
$admin->create_ticket(new \WP_REST_Request([
|
||||
'user_id' => 10,
|
||||
'title' => '代客提交标题',
|
||||
@@ -244,9 +244,9 @@ namespace {
|
||||
'content' => '这是问题描述内容',
|
||||
'order_number' => 'ORD-1',
|
||||
]));
|
||||
$check('关闭订单关联时忽略订单号', (\AKSRT\Ticket\TicketRepository::$creates[0]['order_number'] ?? null) === '');
|
||||
$check('关闭订单关联时忽略订单号', (\Jio\Ticket\TicketRepository::$creates[0]['order_number'] ?? null) === '');
|
||||
|
||||
\AKSRT\Ticket\TicketSettings::$order_link_enabled = true;
|
||||
\Jio\Ticket\TicketSettings::$order_link_enabled = true;
|
||||
$admin->create_ticket(new \WP_REST_Request([
|
||||
'user_id' => 10,
|
||||
'title' => '代客提交标题',
|
||||
@@ -254,7 +254,7 @@ namespace {
|
||||
'content' => '这是问题描述内容',
|
||||
'order_number' => '<b>ORD-2</b>',
|
||||
]));
|
||||
$check('开启订单关联时保存已消毒订单号', (\AKSRT\Ticket\TicketRepository::$creates[1]['order_number'] ?? null) === 'ORD-2');
|
||||
$check('开启订单关联时保存已消毒订单号', (\Jio\Ticket\TicketRepository::$creates[1]['order_number'] ?? null) === 'ORD-2');
|
||||
|
||||
// Internal note writes enforce existence, trim, non-empty and configured maximum length.
|
||||
$reset();
|
||||
@@ -262,10 +262,10 @@ namespace {
|
||||
$empty = $admin->create_note(new \WP_REST_Request(['id' => 1, 'content' => " \n\t "]));
|
||||
$long = $admin->create_note(new \WP_REST_Request(['id' => 1, 'content' => '123456']));
|
||||
$valid = $admin->create_note(new \WP_REST_Request(['id' => 1, 'content' => ' memo ']));
|
||||
$created = \AKSRT\Ticket\TicketNoteService::$created;
|
||||
$check('备注工单不存在时返回 404', $error_code($missing) === 'aksrt_ticket_missing');
|
||||
$check('备注拒绝空内容', $error_code($empty) === 'aksrt_ticket_note_content');
|
||||
$check('备注拒绝超长内容', $error_code($long) === 'aksrt_ticket_note_content');
|
||||
$created = \Jio\Ticket\TicketNoteService::$created;
|
||||
$check('备注工单不存在时返回 404', $error_code($missing) === 'jio_ticket_missing');
|
||||
$check('备注拒绝空内容', $error_code($empty) === 'jio_ticket_note_content');
|
||||
$check('备注拒绝超长内容', $error_code($long) === 'jio_ticket_note_content');
|
||||
$check('备注 trim 后保存', !\is_wp_error($valid) && ($created[0]['content'] ?? null) === 'memo');
|
||||
$check('无效备注未调用创建服务', count($created) === 1);
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace {
|
||||
function wp_get_current_user() { return (object) ['ID' => 7, 'display_name' => 'Operator']; }
|
||||
function get_users($args) { return [(object) ['ID' => 7, 'display_name' => 'Operator'], (object) ['ID' => 8, 'display_name' => 'Second']]; }
|
||||
function get_edit_user_link($id) { return admin_url('user-edit.php?user_id=' . $id); }
|
||||
function aksrt_get_avatar_url($id, $size) { return 'https://example.test/avatar/' . $id; }
|
||||
function jio_get_avatar_url($id, $size) { return 'https://example.test/avatar/' . $id; }
|
||||
function add_query_arg($args, $url, $base = null) {
|
||||
if (!is_array($args)) { $args = [$args => $url]; $url = $base; }
|
||||
$parts = parse_url($url);
|
||||
@@ -30,7 +30,7 @@ namespace {
|
||||
return $parts['scheme'] . '://' . $parts['host'] . ($parts['path'] ?? '') . '?' . http_build_query(array_merge($query, $args));
|
||||
}
|
||||
}
|
||||
namespace AKSRT\Ticket {
|
||||
namespace Jio\Ticket {
|
||||
class TicketSettings {
|
||||
public static bool $orders = true;
|
||||
public static function title_max() { return 100; }
|
||||
@@ -94,8 +94,8 @@ namespace AKSRT\Ticket {
|
||||
}
|
||||
}
|
||||
}
|
||||
namespace AKSRT\Core {
|
||||
class DBMigrator { public static function table($name) { return 'wp_aksrt_' . $name; } }
|
||||
namespace Jio\Core {
|
||||
class DBMigrator { public static function table($name) { return 'wp_jio_' . $name; } }
|
||||
}
|
||||
namespace {
|
||||
$root = dirname(__DIR__, 2);
|
||||
@@ -104,12 +104,12 @@ namespace {
|
||||
}
|
||||
require $root . '/src/Ticket/TicketQuery.php';
|
||||
class TicketPage {
|
||||
use \AKSRT\Admin\Native\UsersMenusTicketsCenter;
|
||||
use \Jio\Admin\Native\UsersMenusTicketsCenter;
|
||||
public const CAPABILITY = 'manage_options';
|
||||
private function ensure_cap() {}
|
||||
}
|
||||
class QueryProbe {
|
||||
use \AKSRT\Ticket\TicketQuery;
|
||||
use \Jio\Ticket\TicketQuery;
|
||||
public function where($args) { return $this->ticket_where($args, true); }
|
||||
public function counts($args) { return $this->ticket_statistic($args, true); }
|
||||
}
|
||||
@@ -126,20 +126,20 @@ namespace {
|
||||
$page->render_tickets_page();
|
||||
return (string) ob_get_clean();
|
||||
}
|
||||
$repo = \AKSRT\Ticket\TicketRepository::class;
|
||||
$repo = \Jio\Ticket\TicketRepository::class;
|
||||
$page = new TicketPage();
|
||||
$list = render_page($page, ['status' => 'handling', 'ticket_type_id' => 1, 's' => 'needle', 'paged' => 99]);
|
||||
$initial = ['queries' => $repo::$queries, 'counts' => $repo::$counts, 'detail_reads' => $repo::$reads, 'reply_reads' => $repo::$replies_read];
|
||||
$list_queries = count($repo::$queries);
|
||||
$open = render_page($page, ['id' => 21, 'status' => 'pending', 's' => 'not in list', 'paged' => 3, 'aksrt_ticket_msg' => 'reply']);
|
||||
$open = render_page($page, ['id' => 21, 'status' => 'pending', 's' => 'not in list', 'paged' => 3, 'jio_ticket_msg' => 'reply']);
|
||||
$closed = render_page($page, ['id' => 99]);
|
||||
$missing = render_page($page, ['id' => 404]);
|
||||
$detail_list_queries = count($repo::$queries) - $list_queries;
|
||||
$bogus_notice = render_page($page, ['id' => 21, 'aksrt_ticket_msg' => '<script>']);
|
||||
\AKSRT\Ticket\TicketTypeService::$empty = true;
|
||||
$bogus_notice = render_page($page, ['id' => 21, 'jio_ticket_msg' => '<script>']);
|
||||
\Jio\Ticket\TicketTypeService::$empty = true;
|
||||
$no_types = render_page($page, []);
|
||||
\AKSRT\Ticket\TicketTypeService::$empty = false;
|
||||
\AKSRT\Ticket\TicketSettings::$orders = false;
|
||||
\Jio\Ticket\TicketTypeService::$empty = false;
|
||||
\Jio\Ticket\TicketSettings::$orders = false;
|
||||
$no_orders = render_page($page, []);
|
||||
$GLOBALS['wpdb'] = new FakeDb();
|
||||
$query = new QueryProbe();
|
||||
|
||||
@@ -16,10 +16,10 @@ function rendered() {
|
||||
test('list view links rows to the workspace and offers creation only as a dialog', () => {
|
||||
const { list, initial } = rendered();
|
||||
assert.equal((list.match(/<dialog /g) || []).length, 1, 'only the create dialog');
|
||||
assert.match(list, /aksrt-native-header__actions[^]*data-aksrt-dialog-open="aksrt-ticket-create-dialog"/);
|
||||
assert.match(list, /jio-native-header__actions[^]*data-jio-dialog-open="jio-ticket-create-dialog"/);
|
||||
assert.doesNotMatch(list, /view=new/);
|
||||
assert.match(list, /<a class="row-title" href="[^"]*page=aksrt_tickets&id=21&status=handling&ticket_type_id=1&s=needle&paged=2"/);
|
||||
assert.doesNotMatch(list, /data-aksrt-ticket-detail=|data-aksrt-ticket-form="(?:reply|status|note)"/);
|
||||
assert.match(list, /<a class="row-title" href="[^"]*page=jio_tickets&id=21&status=handling&ticket_type_id=1&s=needle&paged=2"/);
|
||||
assert.doesNotMatch(list, /data-jio-ticket-detail=|data-jio-ticket-form="(?:reply|status|note)"/);
|
||||
assert.equal(initial.detail_reads, 0);
|
||||
assert.equal(initial.reply_reads, 0);
|
||||
assert.equal(initial.queries.at(-1).offset, 20);
|
||||
@@ -31,31 +31,31 @@ test('list view links rows to the workspace and offers creation only as a dialog
|
||||
test('workspace loads by ID independent of list filters and keeps the list context for going back', () => {
|
||||
const { open, closed, detail_list_queries: listQueries } = rendered();
|
||||
assert.equal(listQueries, 0, 'workspace must not query the list');
|
||||
assert.match(open, /<h1 class="aksrt-native-header__title">[^]*Question <svg onload=alert\(1\)><\/h1>/);
|
||||
assert.match(open, /href="[^"]*page=aksrt_tickets&status=pending&s=not\+in\+list&paged=3"[^>]*>[^]*返回工单列表/);
|
||||
assert.match(open, /data-aksrt-ticket-detail="21"/);
|
||||
for (const action of ['reply', 'status', 'note']) assert.ok(open.includes(`data-aksrt-ticket-form="${action}"`));
|
||||
assert.match(open, /action="https:\/\/example.test\/wp-json\/aksrt\/v1\/admin\/tickets\/21\/notes"/);
|
||||
assert.match(open, /data-aksrt-ticket-close-confirm/);
|
||||
assert.match(open, /<h1 class="jio-native-header__title">[^]*Question <svg onload=alert\(1\)><\/h1>/);
|
||||
assert.match(open, /href="[^"]*page=jio_tickets&status=pending&s=not\+in\+list&paged=3"[^>]*>[^]*返回工单列表/);
|
||||
assert.match(open, /data-jio-ticket-detail="21"/);
|
||||
for (const action of ['reply', 'status', 'note']) assert.ok(open.includes(`data-jio-ticket-form="${action}"`));
|
||||
assert.match(open, /action="https:\/\/example.test\/wp-json\/jio\/v1\/admin\/tickets\/21\/notes"/);
|
||||
assert.match(open, /data-jio-ticket-close-confirm/);
|
||||
assert.match(open, /value="closed" data-closed="1"/);
|
||||
assert.match(open, /<dt>工单号<\/dt><dd>TK-00021<\/dd>/);
|
||||
assert.match(open, /data-aksrt-ticket-feedback/);
|
||||
assert.match(closed, /data-aksrt-ticket-detail="99"/);
|
||||
assert.match(closed, /aksrt-ticket-detail__closed/);
|
||||
assert.match(closed, /data-aksrt-ticket-form="note"/);
|
||||
assert.doesNotMatch(closed, /data-aksrt-ticket-form="(?:reply|status)"/);
|
||||
assert.match(open, /data-jio-ticket-feedback/);
|
||||
assert.match(closed, /data-jio-ticket-detail="99"/);
|
||||
assert.match(closed, /jio-ticket-detail__closed/);
|
||||
assert.match(closed, /data-jio-ticket-form="note"/);
|
||||
assert.doesNotMatch(closed, /data-jio-ticket-form="(?:reply|status)"/);
|
||||
// 回复框是会话卡片的底部,不是独立卡片;工作区唯一的弹窗是内部备注。
|
||||
const conversation = open.match(/<section class="aksrt-ticket-conversation"[^]*?<\/section>/)?.[0] || '';
|
||||
assert.match(conversation, /aksrt-ticket-conversation__scroll[^]*data-aksrt-ticket-form="reply"/);
|
||||
assert.match(conversation, /data-aksrt-ticket-send-shortcut/);
|
||||
const conversation = open.match(/<section class="jio-ticket-conversation"[^]*?<\/section>/)?.[0] || '';
|
||||
assert.match(conversation, /jio-ticket-conversation__scroll[^]*data-jio-ticket-form="reply"/);
|
||||
assert.match(conversation, /data-jio-ticket-send-shortcut/);
|
||||
assert.match(conversation, /<button type="submit" class="button button-primary">[^]*发送回复<\/button>/);
|
||||
for (const html of [open, closed]) {
|
||||
assert.equal((html.match(/<dialog /g) || []).length, 1);
|
||||
assert.match(html, /<dialog id="aksrt-ticket-note-dialog"[^]*data-aksrt-ticket-form="note"[^]*<\/dialog>/);
|
||||
assert.match(html, /data-aksrt-dialog-open="aksrt-ticket-note-dialog"/);
|
||||
assert.match(html, /<dialog id="jio-ticket-note-dialog"[^]*data-jio-ticket-form="note"[^]*<\/dialog>/);
|
||||
assert.match(html, /data-jio-dialog-open="jio-ticket-note-dialog"/);
|
||||
}
|
||||
const sidebar = open.match(/<aside\b[^]*?<\/aside>/)?.[0] || '';
|
||||
assert.doesNotMatch(sidebar, /<textarea|data-aksrt-ticket-form="note"/, 'note input lives only in the dialog');
|
||||
assert.doesNotMatch(sidebar, /<textarea|data-jio-ticket-form="note"/, 'note input lives only in the dialog');
|
||||
});
|
||||
|
||||
test('result codes render as one native notice and unknown codes are ignored', () => {
|
||||
@@ -70,18 +70,18 @@ test('ticket information owns the only status editor and closed tickets stay rea
|
||||
const { open, closed } = rendered();
|
||||
const sidebarCards = html => {
|
||||
const sidebar = html.match(/<aside\b[^>]*>([\s\S]*?)<\/aside>/)?.[1] || '';
|
||||
return [...sidebar.matchAll(/<section class="aksrt-ticket-detail__card(?: [^"]+)?">([\s\S]*?)<\/section>/g)].map(match => match[1]);
|
||||
return [...sidebar.matchAll(/<section class="jio-ticket-detail__card(?: [^"]+)?">([\s\S]*?)<\/section>/g)].map(match => match[1]);
|
||||
};
|
||||
const cards = sidebarCards(open);
|
||||
assert.equal(cards.length, 2, 'only ticket information and internal notes remain');
|
||||
const info = cards[0];
|
||||
assert.match(info, /<h3>工单信息<\/h3>/);
|
||||
assert.match(info, /data-aksrt-ticket-form="status"/);
|
||||
assert.match(info, /data-aksrt-ticket-assign/);
|
||||
assert.match(info, /data-jio-ticket-form="status"/);
|
||||
assert.match(info, /data-jio-ticket-assign/);
|
||||
assert.match(info, /保存工单信息/);
|
||||
assert.doesNotMatch(info, /aksrt-ticket-badge/);
|
||||
assert.doesNotMatch(info, /jio-ticket-badge/);
|
||||
for (const [name, label] of [['status', '状态'], ['ticket_type_id', '部门'], ['admin_id', '跟进人']]) {
|
||||
const id = `aksrt-ticket-${name}-21`;
|
||||
const id = `jio-ticket-${name}-21`;
|
||||
assert.ok(info.includes(`<label for="${id}">${label}</label>`));
|
||||
assert.ok(info.includes(`<select id="${id}" name="${name}"`));
|
||||
assert.equal((open.match(new RegExp(`name="${name}"`, 'g')) || []).length, 1);
|
||||
@@ -89,25 +89,25 @@ test('ticket information owns the only status editor and closed tickets stay rea
|
||||
assert.match(cards[1], /内部备注/);
|
||||
const closedInfo = sidebarCards(closed)[0];
|
||||
assert.match(closedInfo, /<h3>工单信息<\/h3>/);
|
||||
assert.match(closedInfo, /aksrt-ticket-badge/);
|
||||
assert.doesNotMatch(closedInfo, /<form|<select|data-aksrt-ticket-assign|保存工单信息/);
|
||||
assert.match(closedInfo, /jio-ticket-badge/);
|
||||
assert.doesNotMatch(closedInfo, /<form|<select|data-jio-ticket-assign|保存工单信息/);
|
||||
});
|
||||
|
||||
test('missing ticket renders an empty state with a way back and no forms', () => {
|
||||
const { missing } = rendered();
|
||||
assert.match(missing, /工单不存在/);
|
||||
assert.match(missing, /返回工单列表/);
|
||||
assert.doesNotMatch(missing, /data-aksrt-ticket-form|data-aksrt-ticket-detail/);
|
||||
assert.doesNotMatch(missing, /data-jio-ticket-form|data-jio-ticket-detail/);
|
||||
});
|
||||
|
||||
test('create dialog is a labelled REST form with its own feedback and close controls', () => {
|
||||
const { list } = rendered();
|
||||
const dialog = list.match(/<dialog id="aksrt-ticket-create-dialog"[^]*?<\/dialog>/)?.[0] || '';
|
||||
assert.match(dialog, /aria-labelledby="aksrt-ticket-create-title"/);
|
||||
assert.match(dialog, /<form method="post" action="https:\/\/example.test\/wp-json\/aksrt\/v1\/admin\/tickets" class="aksrt-ticket-dialog__frame" data-aksrt-ticket-form="create">/);
|
||||
const dialog = list.match(/<dialog id="jio-ticket-create-dialog"[^]*?<\/dialog>/)?.[0] || '';
|
||||
assert.match(dialog, /aria-labelledby="jio-ticket-create-title"/);
|
||||
assert.match(dialog, /<form method="post" action="https:\/\/example.test\/wp-json\/jio\/v1\/admin\/tickets" class="jio-ticket-dialog__frame" data-jio-ticket-form="create">/);
|
||||
for (const name of ['user_id', 'ticket_type_id', 'title', 'order_number', 'content']) assert.ok(dialog.includes(`name="${name}"`), name);
|
||||
assert.match(dialog, /data-aksrt-ticket-feedback/);
|
||||
assert.equal((dialog.match(/data-aksrt-dialog-close/g) || []).length, 2, 'header X and footer cancel');
|
||||
assert.match(dialog, /data-jio-ticket-feedback/);
|
||||
assert.equal((dialog.match(/data-jio-dialog-close/g) || []).length, 2, 'header X and footer cancel');
|
||||
});
|
||||
|
||||
test('real rendering escapes user content, quotes and metadata', () => {
|
||||
@@ -125,9 +125,9 @@ test('real rendering escapes user content, quotes and metadata', () => {
|
||||
test('missing departments and disabled order links have explicit creation states', () => {
|
||||
const { no_types: noTypes, no_orders: noOrders } = rendered();
|
||||
assert.match(noTypes, /尚未配置工单部门/);
|
||||
assert.doesNotMatch(noTypes, /data-aksrt-ticket-form="create"/);
|
||||
assert.doesNotMatch(noTypes, /data-jio-ticket-form="create"/);
|
||||
assert.match(noTypes, /前往工单配置/);
|
||||
assert.match(noOrders, /data-aksrt-ticket-form="create"/);
|
||||
assert.match(noOrders, /data-jio-ticket-form="create"/);
|
||||
assert.doesNotMatch(noOrders, /name="order_number"/);
|
||||
});
|
||||
|
||||
|
||||
@@ -8,9 +8,9 @@ const vm = require('node:vm');
|
||||
|
||||
const scriptPath = path.resolve(__dirname, '../../assets/js/admin-tickets.js');
|
||||
const strings = { saving: 'Saving', saveFailed: 'Save failed', uncertain: 'Unknown result', required: 'Required', tooLong: 'Too long', discard: 'Discard?' };
|
||||
const REST = 'https://site.test/wp-json/aksrt/v1/admin/tickets';
|
||||
const REST = 'https://site.test/wp-json/jio/v1/admin/tickets';
|
||||
|
||||
function makeRuntime({ href = 'https://site.test/wp-admin/users.php?page=aksrt_tickets&id=41', page = 'detail', storage = {} } = {}) {
|
||||
function makeRuntime({ href = 'https://site.test/wp-admin/users.php?page=jio_tickets&id=41', page = 'detail', storage = {} } = {}) {
|
||||
const ids = new Map();
|
||||
const document = { activeElement: null };
|
||||
|
||||
@@ -134,69 +134,69 @@ function makeRuntime({ href = 'https://site.test/wp-admin/users.php?page=aksrt_t
|
||||
};
|
||||
|
||||
function makeDialog(id) {
|
||||
const dialog = el('dialog', { id, class: 'aksrt-ticket-dialog' }, root);
|
||||
const dialog = el('dialog', { id, class: 'jio-ticket-dialog' }, root);
|
||||
dialog.open = false;
|
||||
dialog.showModal = () => { dialog.open = true; };
|
||||
dialog.close = () => { if (!dialog.open) return; dialog.open = false; dialog.fire('close'); };
|
||||
return dialog;
|
||||
}
|
||||
|
||||
const root = new Element('div', { class: 'aksrt-tickets-admin' });
|
||||
const feedback = el('div', { 'data-aksrt-ticket-feedback': '', hidden: '' }, root);
|
||||
const root = new Element('div', { class: 'jio-tickets-admin' });
|
||||
const feedback = el('div', { 'data-jio-ticket-feedback': '', hidden: '' }, root);
|
||||
el('p', {}, feedback);
|
||||
el('button', { type: 'button', 'data-aksrt-ticket-reload': '', hidden: '' }, feedback);
|
||||
el('button', { type: 'button', 'data-jio-ticket-reload': '', hidden: '' }, feedback);
|
||||
const nodes = { root, feedback };
|
||||
|
||||
if (page === 'detail') {
|
||||
const detail = el('div', { 'data-aksrt-ticket-detail': '41' }, root);
|
||||
nodes.latest = el('button', { type: 'button', 'data-aksrt-ticket-latest': '', hidden: '' }, detail);
|
||||
nodes.conversation = el('div', { class: 'aksrt-ticket-conversation__scroll' }, detail);
|
||||
const reply = nodes.reply = el('form', { action: `${REST}/41/reply`, 'data-aksrt-ticket-form': 'reply' }, detail);
|
||||
nodes.replyText = el('textarea', { id: 'aksrt-ticket-reply-41', name: 'content', required: '', maxlength: '24', 'data-aksrt-ticket-editor': '' }, reply);
|
||||
nodes.replyCount = el('span', { 'data-aksrt-ticket-counter': 'aksrt-ticket-reply-41' }, reply);
|
||||
nodes.prereply = select(reply, { 'data-aksrt-ticket-prereply': 'aksrt-ticket-reply-41' }, [[''], ['short'], ['01234567890123456789012345']]);
|
||||
const detail = el('div', { 'data-jio-ticket-detail': '41' }, root);
|
||||
nodes.latest = el('button', { type: 'button', 'data-jio-ticket-latest': '', hidden: '' }, detail);
|
||||
nodes.conversation = el('div', { class: 'jio-ticket-conversation__scroll' }, detail);
|
||||
const reply = nodes.reply = el('form', { action: `${REST}/41/reply`, 'data-jio-ticket-form': 'reply' }, detail);
|
||||
nodes.replyText = el('textarea', { id: 'jio-ticket-reply-41', name: 'content', required: '', maxlength: '24', 'data-jio-ticket-editor': '' }, reply);
|
||||
nodes.replyCount = el('span', { 'data-jio-ticket-counter': 'jio-ticket-reply-41' }, reply);
|
||||
nodes.prereply = select(reply, { 'data-jio-ticket-prereply': 'jio-ticket-reply-41' }, [[''], ['short'], ['01234567890123456789012345']]);
|
||||
el('button', { type: 'submit' }, reply);
|
||||
const section = el('section', {}, detail);
|
||||
nodes.assign = el('button', { type: 'button', 'data-aksrt-ticket-assign': '' }, section);
|
||||
const status = nodes.status = el('form', { action: `${REST}/41/status`, 'data-aksrt-ticket-form': 'status' }, section);
|
||||
nodes.statusSelect = select(status, { name: 'status', 'data-aksrt-ticket-status': '' }, [['pending', { 'data-closed': '0' }], ['handling', { 'data-closed': '0' }], ['closed', { 'data-closed': '1' }]], 'pending');
|
||||
nodes.assign = el('button', { type: 'button', 'data-jio-ticket-assign': '' }, section);
|
||||
const status = nodes.status = el('form', { action: `${REST}/41/status`, 'data-jio-ticket-form': 'status' }, section);
|
||||
nodes.statusSelect = select(status, { name: 'status', 'data-jio-ticket-status': '' }, [['pending', { 'data-closed': '0' }], ['handling', { 'data-closed': '0' }], ['closed', { 'data-closed': '1' }]], 'pending');
|
||||
select(status, { name: 'ticket_type_id', required: '' }, [['3'], ['6']], '3');
|
||||
nodes.admin = select(status, { name: 'admin_id' }, [['0'], ['7'], ['44']], '7');
|
||||
nodes.warning = el('div', { 'data-aksrt-ticket-close-warning': '', hidden: '' }, status);
|
||||
nodes.confirm = el('input', { type: 'checkbox', 'data-aksrt-ticket-close-confirm': '' }, nodes.warning);
|
||||
nodes.warning = el('div', { 'data-jio-ticket-close-warning': '', hidden: '' }, status);
|
||||
nodes.confirm = el('input', { type: 'checkbox', 'data-jio-ticket-close-confirm': '' }, nodes.warning);
|
||||
nodes.statusSubmit = el('button', { type: 'submit' }, status);
|
||||
nodes.noteOpener = el('button', { type: 'button', 'data-aksrt-dialog-open': 'aksrt-ticket-note-dialog' }, detail);
|
||||
const noteDialog = nodes.noteDialog = makeDialog('aksrt-ticket-note-dialog');
|
||||
const note = nodes.note = el('form', { action: `${REST}/41/notes`, 'data-aksrt-ticket-form': 'note' }, noteDialog);
|
||||
nodes.noteClose = el('button', { type: 'button', 'data-aksrt-dialog-close': '' }, note);
|
||||
nodes.noteFeedback = el('div', { 'data-aksrt-ticket-feedback': '', hidden: '' }, note);
|
||||
nodes.noteOpener = el('button', { type: 'button', 'data-jio-dialog-open': 'jio-ticket-note-dialog' }, detail);
|
||||
const noteDialog = nodes.noteDialog = makeDialog('jio-ticket-note-dialog');
|
||||
const note = nodes.note = el('form', { action: `${REST}/41/notes`, 'data-jio-ticket-form': 'note' }, noteDialog);
|
||||
nodes.noteClose = el('button', { type: 'button', 'data-jio-dialog-close': '' }, note);
|
||||
nodes.noteFeedback = el('div', { 'data-jio-ticket-feedback': '', hidden: '' }, note);
|
||||
el('p', {}, nodes.noteFeedback);
|
||||
el('button', { type: 'button', 'data-aksrt-ticket-reload': '', hidden: '' }, nodes.noteFeedback);
|
||||
nodes.noteText = el('textarea', { id: 'aksrt-ticket-note-41', name: 'content', required: '', autofocus: '', maxlength: '24', 'data-aksrt-ticket-editor': '' }, note);
|
||||
el('button', { type: 'button', 'data-jio-ticket-reload': '', hidden: '' }, nodes.noteFeedback);
|
||||
nodes.noteText = el('textarea', { id: 'jio-ticket-note-41', name: 'content', required: '', autofocus: '', maxlength: '24', 'data-jio-ticket-editor': '' }, note);
|
||||
el('button', { type: 'submit' }, note);
|
||||
note.reset = () => { nodes.noteText.value = ''; };
|
||||
nodes.replyText.attrs['data-aksrt-ticket-send-shortcut'] = '';
|
||||
nodes.replyText.attrs['data-jio-ticket-send-shortcut'] = '';
|
||||
reply.requestSubmit = () => reply.fire('submit') && root.fire('submit', { target: reply });
|
||||
} else {
|
||||
nodes.opener = el('button', { type: 'button', 'data-aksrt-dialog-open': 'aksrt-ticket-create-dialog' }, root);
|
||||
const dialog = nodes.dialog = makeDialog('aksrt-ticket-create-dialog');
|
||||
const create = nodes.create = el('form', { action: REST, 'data-aksrt-ticket-form': 'create' }, dialog);
|
||||
nodes.closeButton = el('button', { type: 'button', 'data-aksrt-dialog-close': '' }, create);
|
||||
nodes.opener = el('button', { type: 'button', 'data-jio-dialog-open': 'jio-ticket-create-dialog' }, root);
|
||||
const dialog = nodes.dialog = makeDialog('jio-ticket-create-dialog');
|
||||
const create = nodes.create = el('form', { action: REST, 'data-jio-ticket-form': 'create' }, dialog);
|
||||
nodes.closeButton = el('button', { type: 'button', 'data-jio-dialog-close': '' }, create);
|
||||
el('input', { type: 'number', name: 'user_id', required: '' }, create);
|
||||
select(create, { name: 'ticket_type_id', required: '' }, [[''], ['3']], '');
|
||||
el('input', { type: 'text', name: 'title', required: '' }, create);
|
||||
el('textarea', { id: 'aksrt-ticket-create-content', name: 'content', required: '', maxlength: '500', 'data-aksrt-ticket-editor': '' }, create);
|
||||
el('textarea', { id: 'jio-ticket-create-content', name: 'content', required: '', maxlength: '500', 'data-jio-ticket-editor': '' }, create);
|
||||
el('button', { type: 'submit' }, create);
|
||||
create.reset = () => create.querySelectorAll('input, select, textarea').forEach((control) => { control.value = ''; });
|
||||
}
|
||||
|
||||
document.querySelector = (selector) => (selector === '.aksrt-tickets-admin' ? root : null);
|
||||
document.querySelector = (selector) => (selector === '.jio-tickets-admin' ? root : null);
|
||||
document.getElementById = (id) => ids.get(id) || null;
|
||||
|
||||
const store = new Map(Object.entries(storage));
|
||||
const window = new Element('window');
|
||||
Object.assign(window, {
|
||||
AKSRTAdminTickets: { restNonce: 'rest-nonce', currentUser: 44, strings },
|
||||
JioAdminTickets: { restNonce: 'rest-nonce', currentUser: 44, strings },
|
||||
location: { href, assigned: null, reloads: 0, assign(url) { this.assigned = url; }, reload() { this.reloads += 1; } },
|
||||
history: { state: null, replaceState(_state, _title, url) { window.location.href = url; } },
|
||||
sessionStorage: { getItem: (key) => store.get(key) ?? null, setItem: (key, value) => store.set(key, String(value)), removeItem: (key) => store.delete(key) },
|
||||
@@ -218,7 +218,7 @@ const submit = (rt, form) => rt.root.fire('submit', { target: form });
|
||||
const unload = (rt) => rt.window.fire('beforeunload', { returnValue: undefined });
|
||||
|
||||
test('a successful reply posts JSON to REST, then reloads the workspace with a result code and stashes other drafts', async () => {
|
||||
const rt = makeRuntime({ href: 'https://site.test/wp-admin/users.php?page=aksrt_tickets&id=41&status=pending&paged=3' });
|
||||
const rt = makeRuntime({ href: 'https://site.test/wp-admin/users.php?page=jio_tickets&id=41&status=pending&paged=3' });
|
||||
type(rt, rt.noteText, 'pending note');
|
||||
type(rt, rt.replyText, 'hello');
|
||||
const event = submit(rt, rt.reply);
|
||||
@@ -237,27 +237,27 @@ test('a successful reply posts JSON to REST, then reloads the workspace with a r
|
||||
await tick();
|
||||
const next = new URL(rt.window.location.assigned);
|
||||
assert.equal(next.searchParams.get('id'), '41');
|
||||
assert.equal(next.searchParams.get('aksrt_ticket_msg'), 'reply');
|
||||
assert.equal(next.searchParams.get('jio_ticket_msg'), 'reply');
|
||||
assert.equal(next.searchParams.get('status'), 'pending');
|
||||
assert.equal(next.searchParams.get('paged'), '3');
|
||||
assert.deepEqual(JSON.parse(rt.store.get('aksrt-ticket-drafts-41')), { note: { content: 'pending note' } });
|
||||
assert.deepEqual(JSON.parse(rt.store.get('jio-ticket-drafts-41')), { note: { content: 'pending note' } });
|
||||
assert.equal(unload(rt).defaultPrevented, false, 'navigation after save is not blocked');
|
||||
});
|
||||
|
||||
test('after the reload, stashed drafts are restored once and the result code is stripped from the URL', () => {
|
||||
const rt = makeRuntime({
|
||||
href: 'https://site.test/wp-admin/users.php?page=aksrt_tickets&id=41&aksrt_ticket_msg=reply',
|
||||
storage: { 'aksrt-ticket-drafts-41': JSON.stringify({ note: { content: 'keep me' } }) },
|
||||
href: 'https://site.test/wp-admin/users.php?page=jio_tickets&id=41&jio_ticket_msg=reply',
|
||||
storage: { 'jio-ticket-drafts-41': JSON.stringify({ note: { content: 'keep me' } }) },
|
||||
});
|
||||
assert.equal(rt.noteText.value, 'keep me');
|
||||
assert.equal(rt.store.has('aksrt-ticket-drafts-41'), false);
|
||||
assert.equal(new URL(rt.window.location.href).searchParams.has('aksrt_ticket_msg'), false);
|
||||
assert.equal(rt.store.has('jio-ticket-drafts-41'), false);
|
||||
assert.equal(new URL(rt.window.location.href).searchParams.has('jio_ticket_msg'), false);
|
||||
assert.equal(new URL(rt.window.location.href).searchParams.get('id'), '41');
|
||||
assert.equal(unload(rt).defaultPrevented, true, 'a restored draft still counts as unsaved');
|
||||
});
|
||||
|
||||
test('create dialog opens from the header button, guards dirty close, resets and returns focus', () => {
|
||||
const rt = makeRuntime({ page: 'create', href: 'https://site.test/wp-admin/users.php?page=aksrt_tickets' });
|
||||
const rt = makeRuntime({ page: 'create', href: 'https://site.test/wp-admin/users.php?page=jio_tickets' });
|
||||
rt.opener.isConnected = true;
|
||||
rt.root.fire('click', { target: rt.opener });
|
||||
assert.equal(rt.dialog.open, true);
|
||||
@@ -286,7 +286,7 @@ test('create dialog opens from the header button, guards dirty close, resets and
|
||||
});
|
||||
|
||||
test('create posts to the collection route and opens the new ticket workspace with the list context kept', async () => {
|
||||
const rt = makeRuntime({ page: 'create', href: 'https://site.test/wp-admin/users.php?page=aksrt_tickets&status=pending&paged=2' });
|
||||
const rt = makeRuntime({ page: 'create', href: 'https://site.test/wp-admin/users.php?page=jio_tickets&status=pending&paged=2' });
|
||||
rt.root.fire('click', { target: rt.opener });
|
||||
const form = rt.create;
|
||||
form.querySelector('[name="user_id"]').value = '12';
|
||||
@@ -301,7 +301,7 @@ test('create posts to the collection route and opens the new ticket workspace wi
|
||||
const next = new URL(rt.window.location.assigned);
|
||||
assert.equal(next.searchParams.has('view'), false);
|
||||
assert.equal(next.searchParams.get('id'), '77');
|
||||
assert.equal(next.searchParams.get('aksrt_ticket_msg'), 'created');
|
||||
assert.equal(next.searchParams.get('jio_ticket_msg'), 'created');
|
||||
assert.equal(next.searchParams.get('status'), 'pending');
|
||||
assert.equal(next.searchParams.get('paged'), '2');
|
||||
});
|
||||
@@ -318,12 +318,12 @@ test('a confirmed validation error keeps the form editable and shows the server
|
||||
const rt = makeRuntime();
|
||||
type(rt, rt.replyText, 'hello');
|
||||
submit(rt, rt.reply);
|
||||
rt.fetchCalls[0].resolve(reply({ code: 'aksrt_ticket_reply_content', message: 'Bad content', data: { status: 400 } }, 400));
|
||||
rt.fetchCalls[0].resolve(reply({ code: 'jio_ticket_reply_content', message: 'Bad content', data: { status: 400 } }, 400));
|
||||
await tick();
|
||||
assert.equal(rt.window.location.assigned, null);
|
||||
assert.equal(rt.feedback.hidden, false);
|
||||
assert.equal(rt.feedback.querySelector('p').textContent, 'Bad content');
|
||||
assert.equal(rt.feedback.querySelector('[data-aksrt-ticket-reload]').hidden, true);
|
||||
assert.equal(rt.feedback.querySelector('[data-jio-ticket-reload]').hidden, true);
|
||||
assert.equal(rt.replyText.disabled, false);
|
||||
assert.equal(rt.replyText.value, 'hello');
|
||||
submit(rt, rt.reply);
|
||||
@@ -332,8 +332,8 @@ test('a confirmed validation error keeps the form editable and shows the server
|
||||
|
||||
for (const [label, settle] of [
|
||||
['an unknown network result', (call) => call.reject(new TypeError('network'))],
|
||||
['a 409 state conflict', (call) => call.resolve(reply({ code: 'aksrt_ticket_closed', message: 'Closed', data: { status: 409 } }, 409))],
|
||||
['a 404 missing ticket', (call) => call.resolve(reply({ code: 'aksrt_ticket_missing', message: 'Missing', data: { status: 404 } }, 404))],
|
||||
['a 409 state conflict', (call) => call.resolve(reply({ code: 'jio_ticket_closed', message: 'Closed', data: { status: 409 } }, 409))],
|
||||
['a 404 missing ticket', (call) => call.resolve(reply({ code: 'jio_ticket_missing', message: 'Missing', data: { status: 404 } }, 404))],
|
||||
]) {
|
||||
test(`${label} locks every write form and only offers a page reload`, async () => {
|
||||
const rt = makeRuntime();
|
||||
@@ -341,7 +341,7 @@ for (const [label, settle] of [
|
||||
submit(rt, rt.reply);
|
||||
settle(rt.fetchCalls[0]);
|
||||
await tick();
|
||||
const reload = rt.feedback.querySelector('[data-aksrt-ticket-reload]');
|
||||
const reload = rt.feedback.querySelector('[data-jio-ticket-reload]');
|
||||
assert.equal(reload.hidden, false);
|
||||
for (const control of [rt.replyText, rt.noteText, rt.statusSelect, rt.statusSubmit, rt.assign]) assert.equal(control.disabled, true);
|
||||
submit(rt, rt.note);
|
||||
|
||||
@@ -42,7 +42,7 @@ namespace {
|
||||
function get_option($k, $default = false) { return $GLOBALS['wechat_options'][$k] ?? $default; }
|
||||
function update_option($k, $v) { $GLOBALS['wechat_options'][$k] = $v; return true; }
|
||||
|
||||
$GLOBALS['wechat_options'] = ['aksrt_wechat_settings' => [
|
||||
$GLOBALS['wechat_options'] = ['jio_wechat_settings' => [
|
||||
'enabled' => true,
|
||||
'app_id' => 'wxAPPID',
|
||||
'app_secret' => 'SECRET123',
|
||||
@@ -56,7 +56,7 @@ namespace {
|
||||
};
|
||||
}
|
||||
|
||||
namespace AKSRT\Settings {
|
||||
namespace Jio\Settings {
|
||||
// Settings 层仅为薄委托,harness 直接对接真实 Integration。
|
||||
class Settings {
|
||||
public static function get_wechat_settings(): array { return Integration::get_wechat_settings(); }
|
||||
@@ -83,11 +83,11 @@ namespace {
|
||||
}
|
||||
};
|
||||
$stored = static function (): array {
|
||||
$v = get_option('aksrt_wechat_settings', []);
|
||||
$v = get_option('jio_wechat_settings', []);
|
||||
return is_array($v) ? $v : [];
|
||||
};
|
||||
|
||||
$controller = \AKSRT\Admin\UserController::get_instance();
|
||||
$controller = \Jio\Admin\UserController::get_instance();
|
||||
|
||||
// 1) GET /wechat/status 返回完整状态形状(密钥掩码)。
|
||||
$response = $controller->get_wechat_status(new WP_REST_Request());
|
||||
@@ -135,7 +135,7 @@ namespace {
|
||||
$check('未知键被忽略', !array_key_exists('evil', $stored()));
|
||||
|
||||
// 6) 未配置时空密钥可显式清空。
|
||||
update_option('aksrt_wechat_settings', ['enabled' => true, 'app_id' => 'a', 'app_secret' => '', 'template_id' => '']);
|
||||
update_option('jio_wechat_settings', ['enabled' => true, 'app_id' => 'a', 'app_secret' => '', 'template_id' => '']);
|
||||
$controller->update_wechat_settings(new WP_REST_Request(['app_secret' => '']));
|
||||
$check('空密钥可清存', ($stored()['app_secret'] ?? '') === '');
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
* 2) 状态读取委托 WeChatNotify::get_status(),写入委托 Settings::update_wechat_settings();
|
||||
* 3) app_secret 掩码 *** 不覆盖已存密钥,输入经 sanitize_text_field 消毒;
|
||||
* 4) PHP harness 驱动真实控制器验证上述行为;
|
||||
* 5) aksrt_settings 上的微信通知死键(wechat_notify_* 等)已彻底移除,
|
||||
* 活配置面仅剩 aksrt_wechat_settings(/users/wechat-template 自定义页)。
|
||||
* 5) jio_settings 上的微信通知死键(wechat_notify_* 等)已彻底移除,
|
||||
* 活配置面仅剩 jio_wechat_settings(/users/wechat-template 自定义页)。
|
||||
*/
|
||||
|
||||
const assert = require('node:assert/strict');
|
||||
@@ -22,7 +22,7 @@ const schemaUsers = fs.readFileSync(path.join(root, 'src/Admin/Schema/SchemaUser
|
||||
const usersSchemaIndex = fs.readFileSync(path.join(root, 'assets/admin/src/schema/sections/users/index.js'), 'utf8');
|
||||
const usersSections = fs.readFileSync(path.join(root, 'assets/admin/src/constants/sections/users.js'), 'utf8');
|
||||
|
||||
// 死双路面:写进 aksrt_settings、全仓无消费方的微信通知键。
|
||||
// 死双路面:写进 jio_settings、全仓无消费方的微信通知键。
|
||||
const DEAD_WECHAT_KEYS = [
|
||||
'wechat_notify_enabled',
|
||||
'wechat_app_id',
|
||||
@@ -66,7 +66,7 @@ test('微信设置行为 harness 通过', () => {
|
||||
assert.match(output, /WeChat settings harness passed/);
|
||||
});
|
||||
|
||||
test('死双路面(aksrt_settings 微信通知键)已彻底移除', () => {
|
||||
test('死双路面(jio_settings 微信通知键)已彻底移除', () => {
|
||||
for (const key of DEAD_WECHAT_KEYS) {
|
||||
assert.ok(!schemaUsers.includes(key), `SchemaUsers 残留: ${key}`);
|
||||
assert.ok(!usersSections.includes(key), `users 菜单常量残留: ${key}`);
|
||||
@@ -76,7 +76,7 @@ test('死双路面(aksrt_settings 微信通知键)已彻底移除', () => {
|
||||
assert.ok(!fs.existsSync(path.join(root, 'assets/admin/src/schema/sections/users/wechat.js')), 'wechat.js 未删除');
|
||||
});
|
||||
|
||||
test('活配置面保持唯一(aksrt_wechat_settings + 自定义页)', () => {
|
||||
test('活配置面保持唯一(jio_wechat_settings + 自定义页)', () => {
|
||||
assert.match(usersSections, /key: 'users-wechat-template'/);
|
||||
assert.match(usersSections, /route: '\/users\/wechat-template'/);
|
||||
assert.match(controller, /WeChatNotify::get_instance\(\)->get_status\(\)/);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
|
||||
use AKSRT\Ledger\AssetAmount;
|
||||
use AKSRT\Ledger\AssetType;
|
||||
use Jio\Ledger\AssetAmount;
|
||||
use Jio\Ledger\AssetType;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
use AKSRT\Finance\BusinessNumber;
|
||||
use Jio\Finance\BusinessNumber;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class BusinessNumberTest extends TestCase {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
use AKSRT\Payment\CommissionLedger;
|
||||
use Jio\Payment\CommissionLedger;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class CommissionLedgerTest extends TestCase {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
|
||||
use AKSRT\Core\DomainModel;
|
||||
use AKSRT\Core\FrontSubmitPay;
|
||||
use Jio\Core\DomainModel;
|
||||
use Jio\Core\FrontSubmitPay;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
require_once dirname(__DIR__, 2) . '/src/Core/DomainModel.php';
|
||||
@@ -9,7 +9,7 @@ require_once dirname(__DIR__, 2) . '/src/Core/FrontSubmitPay.php';
|
||||
|
||||
if (!function_exists('update_post_meta')) {
|
||||
function update_post_meta(int $post_id, string $key, $value): void {
|
||||
$GLOBALS['aksrt_test_post_meta'][$post_id][$key] = $value;
|
||||
$GLOBALS['jio_test_post_meta'][$post_id][$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,21 +29,21 @@ final class FrontSubmitPayTest extends TestCase {
|
||||
private FrontSubmitPayHarness $submit;
|
||||
|
||||
protected function setUp(): void {
|
||||
$GLOBALS['aksrt_test_post_meta'] = [];
|
||||
$GLOBALS['jio_test_post_meta'] = [];
|
||||
$this->submit = new FrontSubmitPayHarness();
|
||||
}
|
||||
|
||||
public function test_manual_cash_price_only_requires_enabled_vip_level(): void {
|
||||
$result = $this->submit->save(101, [
|
||||
'pay_enabled' => '1',
|
||||
'aksrt_pay' => [
|
||||
'jio_pay' => [
|
||||
'price' => '10.00',
|
||||
'vip1_price' => '8.00',
|
||||
],
|
||||
], $this->settings(true, false));
|
||||
|
||||
self::assertTrue($result['success']);
|
||||
$meta = $GLOBALS['aksrt_test_post_meta'][101][DomainModel::PAY_META_KEY];
|
||||
$meta = $GLOBALS['jio_test_post_meta'][101][DomainModel::PAY_META_KEY];
|
||||
self::assertSame('8.00', $meta['vip1_price']);
|
||||
self::assertSame('0.00', $meta['vip2_price']);
|
||||
}
|
||||
@@ -51,7 +51,7 @@ final class FrontSubmitPayTest extends TestCase {
|
||||
public function test_manual_points_price_only_requires_enabled_vip_level(): void {
|
||||
$result = $this->submit->save(102, [
|
||||
'pay_enabled' => '1',
|
||||
'aksrt_pay' => [
|
||||
'jio_pay' => [
|
||||
'pay_modo' => 'points',
|
||||
'points_price' => '100',
|
||||
'vip2_points' => '50',
|
||||
@@ -59,7 +59,7 @@ final class FrontSubmitPayTest extends TestCase {
|
||||
], $this->settings(false, true));
|
||||
|
||||
self::assertTrue($result['success']);
|
||||
$meta = $GLOBALS['aksrt_test_post_meta'][102][DomainModel::PAY_META_KEY];
|
||||
$meta = $GLOBALS['jio_test_post_meta'][102][DomainModel::PAY_META_KEY];
|
||||
self::assertSame(0, $meta['vip1_points']);
|
||||
self::assertSame(50, $meta['vip2_points']);
|
||||
}
|
||||
@@ -67,7 +67,7 @@ final class FrontSubmitPayTest extends TestCase {
|
||||
public function test_manual_price_rejects_missing_enabled_vip_level(): void {
|
||||
$result = $this->submit->save(103, [
|
||||
'pay_enabled' => '1',
|
||||
'aksrt_pay' => ['price' => '10.00'],
|
||||
'jio_pay' => ['price' => '10.00'],
|
||||
], $this->settings(true, false));
|
||||
|
||||
self::assertFalse($result['success']);
|
||||
@@ -77,11 +77,11 @@ final class FrontSubmitPayTest extends TestCase {
|
||||
public function test_manual_price_does_not_require_disabled_vip_levels(): void {
|
||||
$result = $this->submit->save(104, [
|
||||
'pay_enabled' => '1',
|
||||
'aksrt_pay' => ['price' => '10.00'],
|
||||
'jio_pay' => ['price' => '10.00'],
|
||||
], $this->settings(false, false));
|
||||
|
||||
self::assertTrue($result['success']);
|
||||
$meta = $GLOBALS['aksrt_test_post_meta'][104][DomainModel::PAY_META_KEY];
|
||||
$meta = $GLOBALS['jio_test_post_meta'][104][DomainModel::PAY_META_KEY];
|
||||
self::assertSame('0.00', $meta['vip1_price']);
|
||||
self::assertSame('0.00', $meta['vip2_price']);
|
||||
}
|
||||
@@ -110,7 +110,7 @@ final class FrontSubmitPayTest extends TestCase {
|
||||
$url = 'https://localhost/wp-content/uploads/2026/08/resource.zip';
|
||||
$result = $this->submit->save(105, [
|
||||
'pay_enabled' => '1',
|
||||
'aksrt_pay' => [
|
||||
'jio_pay' => [
|
||||
'price' => '10.00',
|
||||
'downloads' => [
|
||||
[
|
||||
@@ -122,7 +122,7 @@ final class FrontSubmitPayTest extends TestCase {
|
||||
], $this->settings(false, false));
|
||||
|
||||
self::assertTrue($result['success']);
|
||||
$meta = $GLOBALS['aksrt_test_post_meta'][105][DomainModel::PAY_META_KEY];
|
||||
$meta = $GLOBALS['jio_test_post_meta'][105][DomainModel::PAY_META_KEY];
|
||||
self::assertSame(DomainModel::PAY_TYPE_DOWNLOAD, $meta['pay_type']);
|
||||
self::assertCount(1, $meta['downloads']);
|
||||
self::assertSame($url, $meta['downloads'][0]['url']);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
|
||||
use AKSRT\Ledger\AssetType;
|
||||
use AKSRT\Ledger\LedgerCommand;
|
||||
use Jio\Ledger\AssetType;
|
||||
use Jio\Ledger\LedgerCommand;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class LedgerCommandTest extends TestCase {
|
||||
@@ -43,7 +43,7 @@ final class LedgerCommandTest extends TestCase {
|
||||
]]);
|
||||
$error = $command->validate();
|
||||
self::assertInstanceOf(WP_Error::class, $error);
|
||||
self::assertSame('aksrt_ledger_operation_key', $error->get_error_code());
|
||||
self::assertSame('jio_ledger_operation_key', $error->get_error_code());
|
||||
self::assertSame(400, $error->get_error_data()['status']);
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ final class LedgerCommandTest extends TestCase {
|
||||
|
||||
public function test_validation_rejects_empty_entries_and_invalid_asset(): void {
|
||||
$empty = new LedgerCommand('empty:key', 'credit', []);
|
||||
self::assertSame('aksrt_ledger_entries', $empty->validate()->get_error_code());
|
||||
self::assertSame('jio_ledger_entries', $empty->validate()->get_error_code());
|
||||
|
||||
$invalid = new LedgerCommand('asset:key', 'credit', [[
|
||||
'user_id' => 1,
|
||||
@@ -80,7 +80,7 @@ final class LedgerCommandTest extends TestCase {
|
||||
'delta_minor' => 1,
|
||||
'entry_type' => 'credit',
|
||||
]]);
|
||||
self::assertSame('aksrt_ledger_asset', $invalid->validate()->get_error_code());
|
||||
self::assertSame('jio_ledger_asset', $invalid->validate()->get_error_code());
|
||||
}
|
||||
|
||||
public function test_validation_requires_integer_non_zero_entry_values(): void {
|
||||
@@ -91,7 +91,7 @@ final class LedgerCommandTest extends TestCase {
|
||||
'delta_minor' => $delta,
|
||||
'entry_type' => 'credit',
|
||||
]]);
|
||||
self::assertSame('aksrt_ledger_entry', $command->validate()->get_error_code());
|
||||
self::assertSame('jio_ledger_entry', $command->validate()->get_error_code());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ final class LedgerCommandTest extends TestCase {
|
||||
'delta_minor' => 1,
|
||||
'entry_type' => 'credit',
|
||||
]], '', '', -1);
|
||||
self::assertSame('aksrt_ledger_actor', $actor->validate()->get_error_code());
|
||||
self::assertSame('jio_ledger_actor', $actor->validate()->get_error_code());
|
||||
|
||||
$type = new LedgerCommand('type:key', 'credit', [[
|
||||
'user_id' => 1,
|
||||
@@ -110,6 +110,6 @@ final class LedgerCommandTest extends TestCase {
|
||||
'delta_minor' => 1,
|
||||
'entry_type' => 'Bad Type',
|
||||
]]);
|
||||
self::assertSame('aksrt_ledger_entry', $type->validate()->get_error_code());
|
||||
self::assertSame('jio_ledger_entry', $type->validate()->get_error_code());
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
|
||||
use AKSRT\Ledger\AssetType;
|
||||
use AKSRT\Ledger\LedgerResult;
|
||||
use Jio\Ledger\AssetType;
|
||||
use Jio\Ledger\LedgerResult;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class LedgerResultTest extends TestCase {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<?php
|
||||
|
||||
use AKSRT\Ledger\OperationKey;
|
||||
use Jio\Ledger\OperationKey;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class OperationKeyTest extends TestCase {
|
||||
public function test_request_key_requires_uuid_header(): void {
|
||||
$error = OperationKey::from_request(new WP_REST_Request(), 'transfer', 9);
|
||||
self::assertInstanceOf(WP_Error::class, $error);
|
||||
self::assertSame('aksrt_idempotency_key', $error->get_error_code());
|
||||
self::assertSame('jio_idempotency_key', $error->get_error_code());
|
||||
self::assertSame(400, $error->get_error_data()['status']);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
use AKSRT\Payment\Withdrawal;
|
||||
use Jio\Payment\Withdrawal;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class WithdrawalTest extends TestCase {
|
||||
|
||||
@@ -28,7 +28,7 @@ function get_current_user_id() { return 1; }
|
||||
function is_user_logged_in() { return true; }
|
||||
function wp_unslash($value) { return $value; }
|
||||
function current_time($type) { return date('Y-m-d H:i:s'); }
|
||||
function get_theme_file_uri($file = '') { return 'https://example.test/wp-content/themes/aksrt/' . ltrim((string) $file, '/'); }
|
||||
function get_theme_file_uri($file = '') { return 'https://example.test/wp-content/themes/jio/' . ltrim((string) $file, '/'); }
|
||||
function get_theme_file_path($file = '') { return dirname(__DIR__, 2) . '/' . ltrim((string) $file, '/'); }
|
||||
function get_template_directory() { return dirname(__DIR__, 2); }
|
||||
function esc_url_raw($url) { return (string) $url; }
|
||||
@@ -51,8 +51,8 @@ require $repo . '/src/Admin/SchemaProvider.php';
|
||||
require $repo . '/src/Settings/Settings.php';
|
||||
require $repo . '/src/Settings/UserRuntime.php';
|
||||
|
||||
$reasons = \AKSRT\Settings\UserRuntime::ban_settings()['reasons'] ?? null;
|
||||
$report = \AKSRT\Settings\UserRuntime::report_settings();
|
||||
$reasons = \Jio\Settings\UserRuntime::ban_settings()['reasons'] ?? null;
|
||||
$report = \Jio\Settings\UserRuntime::report_settings();
|
||||
|
||||
echo "=== ban_settings()['reasons'] (empty option) ===\n";
|
||||
var_export($reasons);
|
||||
@@ -64,14 +64,14 @@ var_export([
|
||||
]);
|
||||
|
||||
// 再模拟“后台保存过自定义原因”的场景
|
||||
$GLOBALS['__test_options']['aksrt_settings'] = [
|
||||
$GLOBALS['__test_options']['jio_settings'] = [
|
||||
'ban_preset_reason' => [
|
||||
['t' => '自定义原因A'],
|
||||
['t' => '自定义原因B'],
|
||||
],
|
||||
];
|
||||
\AKSRT\Settings\Settings::flush_settings_cache();
|
||||
$stored = \AKSRT\Settings\UserRuntime::ban_settings()['reasons'] ?? null;
|
||||
\Jio\Settings\Settings::flush_settings_cache();
|
||||
$stored = \Jio\Settings\UserRuntime::ban_settings()['reasons'] ?? null;
|
||||
echo "\n\n=== ban_settings()['reasons'] (custom stored) ===\n";
|
||||
var_export($stored);
|
||||
echo "\n";
|
||||
+6
-6
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"schema": "aksrt.page-visual-matrix.v1",
|
||||
"schema": "jio.page-visual-matrix.v1",
|
||||
"reference": "https://localhost:9443",
|
||||
"candidate": "https://localhost",
|
||||
"viewports": {
|
||||
@@ -12,11 +12,11 @@
|
||||
"routes": [
|
||||
{"id": "home", "path": "/", "states": ["default", "hover", "loading", "error"]},
|
||||
{"id": "archive", "path": "/?post_type=post", "states": ["default", "numbered", "load-more", "infinite"]},
|
||||
{"id": "category", "path": "/category/aksrt_zibll_v1_101/", "states": ["default", "empty"]},
|
||||
{"id": "tag", "path": "/tag/aksrt_zibll_v1_103/", "states": ["default", "empty"]},
|
||||
{"id": "search", "path": "/?s=AKSRT", "states": ["default", "empty", "focus"]},
|
||||
{"id": "single", "path": "/aksrt_zibll_v1_301/", "states": ["default", "hover", "toc-open"]},
|
||||
{"id": "resource", "path": "/aksrt_zibll_v1_303/", "states": ["locked", "authorized", "download"]},
|
||||
{"id": "category", "path": "/category/jio_zibll_v1_101/", "states": ["default", "empty"]},
|
||||
{"id": "tag", "path": "/tag/jio_zibll_v1_103/", "states": ["default", "empty"]},
|
||||
{"id": "search", "path": "/?s=Jio", "states": ["default", "empty", "focus"]},
|
||||
{"id": "single", "path": "/jio_zibll_v1_301/", "states": ["default", "hover", "toc-open"]},
|
||||
{"id": "resource", "path": "/jio_zibll_v1_303/", "states": ["locked", "authorized", "download"]},
|
||||
{"id": "page", "path": "/sample-page/", "states": ["default"]},
|
||||
{"id": "login", "path": "/wp-login.php", "states": ["default", "focus", "error"]},
|
||||
{"id": "user", "path": "/user/", "states": ["guest", "member", "vip"]},
|
||||
|
||||
Vendored
+515
-515
File diff suppressed because it is too large.
Load diff
@@ -28,7 +28,7 @@ test('the Zibll toolbar affix rules stay removed', () => {
|
||||
.map((entry) => read(`assets/js/${entry.name}`))
|
||||
.join('\n');
|
||||
|
||||
// No AKSRT script adds .affix to .mce-top-part (that was Zibll's
|
||||
// No Jio script adds .affix to .mce-top-part (that was Zibll's
|
||||
// editextend.js), so the affix positioning rules must not come back.
|
||||
assert.doesNotMatch(css, /mce-top-part\.affix/);
|
||||
assert.doesNotMatch(js, /mce-top-part/);
|
||||
|
||||
@@ -15,7 +15,7 @@ test('featured box shows only the icon image/video buttons and no slide button',
|
||||
// multi-select, so it renders whenever either capability is granted.
|
||||
assert.match(
|
||||
featured,
|
||||
/if \(\$can_image \|\| \$can_slide\) : \?>\s*\n\s*<button type="button" class="but btn-image" data-featured-action="image"\s*\n\s*title="<\?php esc_attr_e\('图片', 'aksrt'\); \?>"\s*\n\s*aria-label="<\?php esc_attr_e\('图片', 'aksrt'\); \?>">\s*\n\s*<i class="fa fas fa-image"><\/i>\s*\n\s*<\/button>/
|
||||
/if \(\$can_image \|\| \$can_slide\) : \?>\s*\n\s*<button type="button" class="but btn-image" data-featured-action="image"\s*\n\s*title="<\?php esc_attr_e\('图片', 'jio'\); \?>"\s*\n\s*aria-label="<\?php esc_attr_e\('图片', 'jio'\); \?>">\s*\n\s*<i class="fa fas fa-image"><\/i>\s*\n\s*<\/button>/
|
||||
);
|
||||
|
||||
// The dedicated slide button is gone; slide covers come from multi-select.
|
||||
|
||||
@@ -17,11 +17,11 @@ test('featured box renders a playable video preview and a video-cover button', (
|
||||
assert.match(featured, /fa fas fa-play-circle/);
|
||||
|
||||
// The video-cover button is capability-gated like the video button.
|
||||
assert.match(featured, /if \(\$can_video\) : \?>\s*\n\s*<button type="button" class="but btn-video-pic" data-featured-action="video_pic"\s*\n\s*title="<\?php esc_attr_e\('设置视频封面', 'aksrt'\); \?>"/);
|
||||
assert.match(featured, /if \(\$can_video\) : \?>\s*\n\s*<button type="button" class="but btn-video-pic" data-featured-action="video_pic"\s*\n\s*title="<\?php esc_attr_e\('设置视频封面', 'jio'\); \?>"/);
|
||||
|
||||
// The video row (video cover / video / clear) is icon-only with hover titles.
|
||||
assert.match(featured, /btn-video" data-featured-action="video"\s*\n\s*title="<\?php esc_attr_e\('视频', 'aksrt'\); \?>"\s*\n\s*aria-label="<\?php esc_attr_e\('视频', 'aksrt'\); \?>">\s*\n\s*<i class="fa fas fa-video"><\/i>\s*\n\s*<\/button>/);
|
||||
assert.match(featured, /btn-delete" data-featured-action="close"[\s\S]*?title="<\?php esc_attr_e\('清除', 'aksrt'\); \?>"/);
|
||||
assert.match(featured, /btn-video" data-featured-action="video"\s*\n\s*title="<\?php esc_attr_e\('视频', 'jio'\); \?>"\s*\n\s*aria-label="<\?php esc_attr_e\('视频', 'jio'\); \?>">\s*\n\s*<i class="fa fas fa-video"><\/i>\s*\n\s*<\/button>/);
|
||||
assert.match(featured, /btn-delete" data-featured-action="close"[\s\S]*?title="<\?php esc_attr_e\('清除', 'jio'\); \?>"/);
|
||||
assert.doesNotMatch(featured, /首图|>视频<|>清除</);
|
||||
|
||||
// Dead capability attributes are gone.
|
||||
@@ -78,7 +78,7 @@ test('i18n exposes the video-cover strings and dead keys are removed', () => {
|
||||
const editorJs = read('assets/js/front-submit-editor.js');
|
||||
const submitJs = read('assets/js/front-submit.js');
|
||||
|
||||
assert.match(page, /'setVideoCoverTitle' => __\('为视频设置封面', 'aksrt'\)/);
|
||||
assert.match(page, /'setVideoCoverTitle' => __\('为视频设置封面', 'jio'\)/);
|
||||
// The unused setVideoCover key died with the visible button label it served.
|
||||
assert.doesNotMatch(page, /'setVideoCover' =>/);
|
||||
assert.doesNotMatch(page, /首图/);
|
||||
@@ -92,9 +92,9 @@ test('i18n exposes the video-cover strings and dead keys are removed', () => {
|
||||
assert.doesNotMatch(submitJs, /i18n\.pickVideoCover/);
|
||||
});
|
||||
|
||||
test('the unused aksrt_update_post ajax endpoint is removed', () => {
|
||||
test('the unused jio_update_post ajax endpoint is removed', () => {
|
||||
const frontSubmit = read('src/Core/FrontSubmit.php');
|
||||
|
||||
assert.doesNotMatch(frontSubmit, /aksrt_update_post/);
|
||||
assert.doesNotMatch(frontSubmit, /jio_update_post/);
|
||||
assert.doesNotMatch(frontSubmit, /ajax_update_post/);
|
||||
});
|
||||
@@ -12,7 +12,7 @@ const read = (file) => fs.readFileSync(path.join(root, file), 'utf8');
|
||||
function renderHeaderParts() {
|
||||
const php = String.raw`
|
||||
define('ABSPATH', __DIR__ . '/');
|
||||
$GLOBALS['aksrt_test_user_id'] = 0;
|
||||
$GLOBALS['jio_test_user_id'] = 0;
|
||||
function esc_attr($value) { return htmlspecialchars((string) $value, ENT_QUOTES); }
|
||||
function esc_html($value) { return htmlspecialchars((string) $value, ENT_QUOTES); }
|
||||
function esc_attr__($value, $domain = null) { return esc_attr($value); }
|
||||
@@ -20,23 +20,23 @@ function esc_html__($value, $domain = null) { return esc_html($value); }
|
||||
function esc_url($value) { return $value; }
|
||||
function esc_url_raw($value) { return $value; }
|
||||
function home_url($path = '/') { return 'https://example.test' . $path; }
|
||||
function get_bloginfo($field = '') { return 'AKSRT'; }
|
||||
function get_current_user_id() { return (int) $GLOBALS['aksrt_test_user_id']; }
|
||||
function get_bloginfo($field = '') { return 'Jio'; }
|
||||
function get_current_user_id() { return (int) $GLOBALS['jio_test_user_id']; }
|
||||
function get_option($name, $default = false) { return $name === 'users_can_register'; }
|
||||
function aksrt_render_header_menu_buttons() { return '<a class="newadd-btns">发布</a>'; }
|
||||
function aksrt_get_translate_language_btn($class = '') { return '<a class="' . esc_attr($class) . '">语言</a>'; }
|
||||
function aksrt_header_user_box_html($ctx) { return '<div class="sub-user-box">账户</div>'; }
|
||||
function aksrt_get_avatar_html($userId, $size, $args = []) { return '<img class="avatar" width="' . (int) $size . '">'; }
|
||||
function jio_render_header_menu_buttons() { return '<a class="newadd-btns">发布</a>'; }
|
||||
function jio_get_translate_language_btn($class = '') { return '<a class="' . esc_attr($class) . '">语言</a>'; }
|
||||
function jio_header_user_box_html($ctx) { return '<div class="sub-user-box">账户</div>'; }
|
||||
function jio_get_avatar_html($userId, $size, $args = []) { return '<img class="avatar" width="' . (int) $size . '">'; }
|
||||
function has_nav_menu($location) { return false; }
|
||||
function is_super_admin() { return false; }
|
||||
function is_singular($type = '') { return false; }
|
||||
require __DIR__ . '/inc/helpers/header/aksrt-header-navbar.php';
|
||||
require __DIR__ . '/inc/helpers/header/aksrt-header-mobile.php';
|
||||
require __DIR__ . '/inc/helpers/header/jio-header-navbar.php';
|
||||
require __DIR__ . '/inc/helpers/header/jio-header-mobile.php';
|
||||
|
||||
$results = [];
|
||||
foreach (['1', '2', '3'] as $layout) {
|
||||
foreach ([0, 7] as $userId) {
|
||||
$GLOBALS['aksrt_test_user_id'] = $userId;
|
||||
$GLOBALS['jio_test_user_id'] = $userId;
|
||||
$ctx = [
|
||||
'layout' => $layout,
|
||||
'user_id' => $userId,
|
||||
@@ -45,28 +45,28 @@ foreach (['1', '2', '3'] as $layout) {
|
||||
'messages_url' => '',
|
||||
'unread_count' => 0,
|
||||
'mobile_layout' => 'center',
|
||||
'brand_name' => 'AKSRT',
|
||||
'brand_name' => 'Jio',
|
||||
'logo_html' => '',
|
||||
];
|
||||
ob_start(); aksrt_menu_button($ctx); $buttons = ob_get_clean();
|
||||
ob_start(); jio_menu_button($ctx); $buttons = ob_get_clean();
|
||||
$results['desktop'][$layout][$userId] = $buttons;
|
||||
}
|
||||
$ctx['message_icon_positions'] = ['nav_menu'];
|
||||
$ctx['messages_url'] = 'https://example.test/messages';
|
||||
$ctx['unread_count'] = 3;
|
||||
ob_start(); aksrt_menu_button($ctx); $results['notifications'][$layout] = ob_get_clean();
|
||||
ob_start(); jio_menu_button($ctx); $results['notifications'][$layout] = ob_get_clean();
|
||||
}
|
||||
foreach (['center', 'left'] as $mobileLayout) {
|
||||
$ctx = [
|
||||
'mobile_layout' => $mobileLayout,
|
||||
'mobile_align' => 'left',
|
||||
'theme_positions' => [],
|
||||
'brand_name' => 'AKSRT',
|
||||
'brand_name' => 'Jio',
|
||||
'logo_html' => '',
|
||||
'user_id' => 0,
|
||||
];
|
||||
ob_start(); aksrt_navbar_header($ctx); $results['mobile'][$mobileLayout]['header'] = ob_get_clean();
|
||||
ob_start(); aksrt_nav_mobile_content($ctx); $results['mobile'][$mobileLayout]['drawer'] = ob_get_clean();
|
||||
ob_start(); jio_navbar_header($ctx); $results['mobile'][$mobileLayout]['header'] = ob_get_clean();
|
||||
ob_start(); jio_nav_mobile_content($ctx); $results['mobile'][$mobileLayout]['drawer'] = ob_get_clean();
|
||||
}
|
||||
echo json_encode($results, JSON_UNESCAPED_SLASHES);
|
||||
`;
|
||||
@@ -82,9 +82,9 @@ test('rendered desktop layouts retain reference control order for guests and mem
|
||||
const html = output.desktop[layout][userId];
|
||||
assert.match(html, /class="but" aria-label=/, `layout ${layout} menu fold`);
|
||||
assert.match(html, /class="list-inline splitters relative"/, `layout ${layout} account list`);
|
||||
assert.doesNotMatch(html, /aksrt-navbar-account|aksrt-nav-avatar/);
|
||||
assert.doesNotMatch(html, /jio-navbar-account|jio-nav-avatar/);
|
||||
if (layout === '2' && userId === '0') assert.match(html, /class="signin-loader"/);
|
||||
else assert.match(html, /class="aksrt-nav-icon[^"]*"|class="navbar-avatar"/);
|
||||
else assert.match(html, /class="jio-nav-icon[^"]*"|class="navbar-avatar"/);
|
||||
const right = html.indexOf('list-inline splitters relative');
|
||||
const actions = html.indexOf('navbar-but');
|
||||
if (layout === '3' && actions !== -1) assert.ok(right < actions, 'layout 3 puts account group before actions');
|
||||
@@ -120,14 +120,14 @@ test('account icons keep the reference spacing on both sides of each divider', (
|
||||
assert.doesNotMatch(account[1], /<\/li>\s+<li\b/, 'extra text space shifts the divider');
|
||||
assert.doesNotMatch(account[1], /<\/li>\s+$/, 'trailing text space widens the action group');
|
||||
}
|
||||
assert.match(output.notifications['1'], /aksrt-nav-notification/);
|
||||
assert.match(output.notifications['1'], /jio-nav-notification/);
|
||||
});
|
||||
|
||||
test('header source restores the reference typography and Bootstrap control dimensions', () => {
|
||||
const css = read('assets/css/frontend/header-userbox.css');
|
||||
const mainCss = read('assets/css/main.css');
|
||||
assert.match(css, /\.header,\s*\.mobile-header,\s*\.navbar-search\s*\{[^}]*font-family:\s*"Helvetica Neue", Helvetica, Arial, sans-serif;[^}]*font-size:\s*14px;[^}]*font-weight:\s*400;[^}]*line-height:\s*1\.42857143;[^}]*-webkit-font-smoothing:\s*auto;[^}]*text-rendering:\s*auto;/s);
|
||||
assert.match(css, /\.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.match(css, /\.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);
|
||||
assert.match(mainCss, /\.navbar-avatar\s*\{[^}]*margin-top:\s*3px;[^}]*width:\s*29px;[^}]*height:\s*29px;/s);
|
||||
assert.doesNotMatch(css, /\.aksrt-navbar-account|\.aksrt-nav-avatar|\.navbar-form \.toggle-radius:not|navbar-form\.flex\.ac\s*\{[^}]*min-height|\.navbar-search(?:\.show)?\s*\{[^}]*transform:/s);
|
||||
assert.doesNotMatch(css, /\.jio-navbar-account|\.jio-nav-avatar|\.navbar-form \.toggle-radius:not|navbar-form\.flex\.ac\s*\{[^}]*min-height|\.navbar-search(?:\.show)?\s*\{[^}]*transform:/s);
|
||||
});
|
||||
@@ -28,20 +28,20 @@ const scripts = join([
|
||||
'src/Core/ScriptsPart1b.php',
|
||||
'src/Core/ScriptsPart1c.php',
|
||||
]);
|
||||
// The Font Awesome handle is registered inside aksrt_enqueue_fontawesome(), so the
|
||||
// The Font Awesome handle is registered inside jio_enqueue_fontawesome(), so the
|
||||
// 5.15.4 asset contract has to follow that indirection rather than the enqueue call site.
|
||||
const iconRuntime = join([
|
||||
'src/Core/ScriptsPart1b.php',
|
||||
'src/Core/ScriptsPart1c.php',
|
||||
'inc/helpers/aksrt-icons.php',
|
||||
'inc/helpers/jio-icons.php',
|
||||
]);
|
||||
const header = join([
|
||||
'header.php',
|
||||
'inc/helpers/header/aksrt-header-render.php',
|
||||
'inc/helpers/header/aksrt-header-navbar.php',
|
||||
'inc/helpers/header/aksrt-header-mobile.php',
|
||||
'inc/helpers/aksrt-header-menu-buttons.php',
|
||||
'inc/helpers/aksrt-header-userbox.php',
|
||||
'inc/helpers/header/jio-header-render.php',
|
||||
'inc/helpers/header/jio-header-navbar.php',
|
||||
'inc/helpers/header/jio-header-mobile.php',
|
||||
'inc/helpers/jio-header-menu-buttons.php',
|
||||
'inc/helpers/jio-header-userbox.php',
|
||||
]);
|
||||
|
||||
test('the public dependency graph uses WordPress jQuery with the local runtime stack', () => {
|
||||
@@ -49,7 +49,7 @@ test('the public dependency graph uses WordPress jQuery with the local runtime s
|
||||
assert.doesNotMatch(scripts, /wp_deregister_script\('jquery'\)/);
|
||||
assert.doesNotMatch(scripts, /jquery\.min\.js'[\s\S]*?'1\.9\.1'/);
|
||||
assert.match(scripts, /bootstrap\.min\.css'[\s\S]*?'3\.4\.1'/);
|
||||
assert.match(scripts, /aksrt_enqueue_fontawesome\(/);
|
||||
assert.match(scripts, /jio_enqueue_fontawesome\(/);
|
||||
assert.match(iconRuntime, /fontawesome5\/css\/all\.min\.css'[\s\S]*?'5\.15\.4'/);
|
||||
assert.match(scripts, /main\.min\.css/);
|
||||
assert.match(scripts, /loader\.js'[\s\S]*?\['jquery', 'bootstrap'\]/);
|
||||
@@ -63,7 +63,7 @@ test('the public dependency graph uses WordPress jQuery with the local runtime s
|
||||
'assets/js/runtime/main.min.js',
|
||||
]) assert.equal(exists(file), true, `${file} must be local`);
|
||||
|
||||
assert.doesNotMatch(scripts, /bootstrap5|bootstrap-5|font-awesome\.min\.css|swiper(?:\.min)?\.(?:css|js)|aksrt-mini-touch/);
|
||||
assert.doesNotMatch(scripts, /bootstrap5|bootstrap-5|font-awesome\.min\.css|swiper(?:\.min)?\.(?:css|js)|jio-mini-touch/);
|
||||
assert.equal(exists('assets/css/frontend/nav.css'), false);
|
||||
assert.equal(exists('assets/js/core/nav.js'), false);
|
||||
assert.equal(exists('assets/js/core/search.js'), false);
|
||||
@@ -72,7 +72,7 @@ test('the public dependency graph uses WordPress jQuery with the local runtime s
|
||||
assert.equal(exists('assets/js/mini-touch.js'), false);
|
||||
});
|
||||
|
||||
test('payment entry points use only the AKSRT payment runtime', () => {
|
||||
test('payment entry points use only the Jio payment runtime', () => {
|
||||
const loader = read('assets/js/runtime/loader.js');
|
||||
const main = join([
|
||||
'assets/js/runtime/main.js',
|
||||
@@ -88,9 +88,9 @@ test('payment entry points use only the AKSRT payment runtime', () => {
|
||||
assert.doesNotMatch(main, /tbquire\(\[['"]pay['"]\]\)/);
|
||||
assert.doesNotMatch(main, /indexOf\(['"]zippay['"]\)/);
|
||||
assert.match(paymentRuntime, /\.pay-vip/);
|
||||
assert.match(paymentRuntime, /data-aksrt-pay-content/);
|
||||
assert.match(paymentRuntime, /'aksrt-pay-vip'/);
|
||||
assert.match(paymentRuntime, /'aksrt-cms-download'/);
|
||||
assert.match(paymentRuntime, /data-jio-pay-content/);
|
||||
assert.match(paymentRuntime, /'jio-pay-vip'/);
|
||||
assert.match(paymentRuntime, /'jio-cms-download'/);
|
||||
});
|
||||
|
||||
test('Font Awesome 5 is the only icon contract across every runtime path', () => {
|
||||
@@ -159,7 +159,7 @@ test('guest account member VIP and publish contracts share reference header DOM'
|
||||
const userboxCss = read('assets/css/frontend/header-userbox.css');
|
||||
|
||||
assert.match(header, /is_user_logged_in\(\)/);
|
||||
assert.match(header, /sub-user-box aksrt-header-userbox/);
|
||||
assert.match(header, /sub-user-box jio-header-userbox/);
|
||||
assert.match(header, /signin-loader|signin/);
|
||||
assert.match(header, /get_payment_vip_settings/);
|
||||
assert.match(header, /nav_user_newposts_btn/);
|
||||
@@ -173,12 +173,12 @@ test('guest account member VIP and publish contracts share reference header DOM'
|
||||
assert.doesNotMatch(userboxCss, /--(?:main|muted|key)-|@import/);
|
||||
// Account rows render through the shared legacy classes; the module keeps only
|
||||
// the shell anchoring, the account href geometry and the top-nav bell badge.
|
||||
assert.match(userboxCss, /\.aksrt-nav-notification \.aksrt-notification-badge/);
|
||||
assert.doesNotMatch(userboxCss, /aksrt-header-wallet|aksrt-header-checkin|aksrt-header-payvip|\.aksrt-header-userbox \.aksrt-notification-badge/);
|
||||
assert.match(userboxCss, /\.jio-nav-notification \.jio-notification-badge/);
|
||||
assert.doesNotMatch(userboxCss, /jio-header-wallet|jio-header-checkin|jio-header-payvip|\.jio-header-userbox \.jio-notification-badge/);
|
||||
assert.doesNotMatch(userboxCss, /width:\s*260px/);
|
||||
assert.match(userboxCss, /\.aksrt-header-userbox\s*\{[^}]*line-height:\s*1\.42857143/s);
|
||||
assert.match(userboxCss, /\.aksrt-header-userbox\s+\.muted-box\.padding-6\s*\{[^}]*background-color:\s*var\(--this-bg,\s*var\(--color-line-soft\)\)[^}]*border-radius:\s*4px[^}]*padding:\s*6px/s);
|
||||
assert.match(userboxCss, /\.aksrt-header-userbox\s*>\s*\.mt6\.em09\s*>\s*\.but\.block\s*\{[^}]*display:\s*block;[^}]*box-sizing:\s*border-box;[^}]*width:\s*100%;/s);
|
||||
assert.match(userboxCss, /\.jio-header-userbox\s*\{[^}]*line-height:\s*1\.42857143/s);
|
||||
assert.match(userboxCss, /\.jio-header-userbox\s+\.muted-box\.padding-6\s*\{[^}]*background-color:\s*var\(--this-bg,\s*var\(--color-line-soft\)\)[^}]*border-radius:\s*4px[^}]*padding:\s*6px/s);
|
||||
assert.match(userboxCss, /\.jio-header-userbox\s*>\s*\.mt6\.em09\s*>\s*\.but\.block\s*\{[^}]*display:\s*block;[^}]*box-sizing:\s*border-box;[^}]*width:\s*100%;/s);
|
||||
});
|
||||
|
||||
test('the top navigation keeps the reference Bootstrap control geometry', () => {
|
||||
@@ -186,8 +186,8 @@ test('the top navigation keeps the reference Bootstrap control geometry', () =>
|
||||
const publicCss = read('assets/css/build/public.css');
|
||||
|
||||
assert.match(userboxCss, /\.header,\s*\.mobile-header,\s*\.navbar-search\s*\{[^}]*font-family:\s*['"]Helvetica Neue['"],\s*Helvetica,\s*Arial,\s*sans-serif;[^}]*font-weight:\s*400;[^}]*line-height:\s*1\.42857143;/s);
|
||||
assert.match(userboxCss, /\.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(userboxCss, /\.aksrt-navbar-account|\.aksrt-nav-avatar|\.navbar-form \.toggle-radius:not|navbar-form\.flex\.ac\s*\{[^}]*min-height|\.aksrt-navbar-language\s*\{[^}]*margin:/);
|
||||
assert.match(userboxCss, /\.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);
|
||||
assert.doesNotMatch(userboxCss, /\.jio-navbar-account|\.jio-nav-avatar|\.navbar-form \.toggle-radius:not|navbar-form\.flex\.ac\s*\{[^}]*min-height|\.jio-navbar-language\s*\{[^}]*margin:/);
|
||||
assert.doesNotMatch(publicCss, /\.navbar-top \.navbar-form \.toggle-radius:not\(\.social-login-item\)/);
|
||||
});
|
||||
|
||||
@@ -221,23 +221,23 @@ test('route and feature modules do not restyle the legacy top-nav chips', () =>
|
||||
})
|
||||
|
||||
test('the account popover rows match the reference user box structure', () => {
|
||||
const identity = read('inc/helpers/aksrt-identity.php');
|
||||
const identity = read('inc/helpers/jio-identity.php');
|
||||
const legacyMainCss = read('assets/css/main.css');
|
||||
const buttonsCss = read('assets/css/core/buttons.css');
|
||||
const userboxCss = read('assets/css/frontend/header-userbox.css');
|
||||
|
||||
// Stats row is wired with the reference wrapper and carries the posts chip.
|
||||
assert.match(header, /aksrt_header_user_badges_row\(\$user_id\)/);
|
||||
assert.match(header, /jio_header_user_badges_row\(\$user_id\)/);
|
||||
assert.match(header, /class="em09 author-tag mb10 mt6 flex jc"/);
|
||||
assert.match(identity, /tag-posts/);
|
||||
assert.match(identity, /fa fas fa-file-alt/);
|
||||
assert.match(identity, /fa fas fa-comment-dots/);
|
||||
assert.match(identity, /fa fas fa-thumbs-up/);
|
||||
assert.match(identity, /fa fas fa-fire/);
|
||||
assert.doesNotMatch(identity, /aksrt_renderer_svg_icon/);
|
||||
assert.doesNotMatch(identity, /jio_renderer_svg_icon/);
|
||||
|
||||
// Admin row exposes the AKSRT payment center at the reference mall slot.
|
||||
assert.match(header, /admin\.php\?page=aksrt_pay/);
|
||||
// Admin row exposes the Jio payment center at the reference mall slot.
|
||||
assert.match(header, /admin\.php\?page=jio_pay/);
|
||||
assert.match(header, /支付中心/);
|
||||
|
||||
// Wallet tiles: balance first, muted-box geometry, reference sprite icons.
|
||||
@@ -245,8 +245,8 @@ test('the account popover rows match the reference user box structure', () => {
|
||||
assert.match(header, /muted-box padding-6 flex1 radius4/);
|
||||
assert.match(header, /font-bold c-blue-2/);
|
||||
assert.match(header, /font-bold c-yellow/);
|
||||
assert.match(header, /aksrt_icon_markup\('zibsvg-money-color-2', 'em14'\)/);
|
||||
assert.match(header, /aksrt_icon_markup\('zibsvg-points-color', 'em14'\)/);
|
||||
assert.match(header, /jio_icon_markup\('zibsvg-money-color-2', 'em14'\)/);
|
||||
assert.match(header, /jio_icon_markup\('zibsvg-points-color', 'em14'\)/);
|
||||
// The value icons are the reference theme's multi-tone SVGs, not FA5 glyphs;
|
||||
// the tile arrow stays on fa-angle-right exactly as the reference renders it.
|
||||
assert.doesNotMatch(header, /fa fas fa-money-bill-wave/);
|
||||
@@ -267,18 +267,18 @@ test('the account popover rows match the reference user box structure', () => {
|
||||
// cover background, color, border and text-shadow, and must not be scoped to
|
||||
// one call site or the top-nav chip keeps the wrong gradient.
|
||||
assert.match(userboxCss, /\.payvip-icon\s*\{[\s\S]*?background:\s*linear-gradient\(300deg,[\s\S]*?color:\s*#ece0e0;[\s\S]*?--this-color:\s*#ece0e0;[\s\S]*?border:\s*none;[\s\S]*?text-shadow:\s*none;/s);
|
||||
assert.doesNotMatch(userboxCss, /\.aksrt-header-userbox \.pay-vip\.but\.payvip-icon/)
|
||||
assert.doesNotMatch(userboxCss, /\.jio-header-userbox \.pay-vip\.but\.payvip-icon/)
|
||||
assert.doesNotMatch(header, /vipbg-v2/);
|
||||
|
||||
// Check-in entry is an anchor with the reference button classes.
|
||||
assert.match(header, /but block c-yellow/);
|
||||
assert.match(header, /aksrt_get_user_checkin_details_link\([\s\S]*?'but block c-yellow'/);
|
||||
assert.match(header, /jio_get_user_checkin_details_link\([\s\S]*?'but block c-yellow'/);
|
||||
assert.match(header, /checkin-details-link/);
|
||||
assert.match(buttonsCss, /\.but\.block\s*\{\s*display:\s*block;\s*\}/);
|
||||
assert.match(buttonsCss, /\.btn-block\s*\{\s*display:\s*block;\s*width:\s*100%;\s*\}/);
|
||||
// The hover wrapper uses the reference inline-block utility so the absolutely
|
||||
// positioned VIP card keeps its original static position.
|
||||
assert.match(read('inc/helpers/aksrt-header-menu-buttons.php'), /class="hover-show inline-block"/);
|
||||
assert.match(read('inc/helpers/jio-header-menu-buttons.php'), /class="hover-show inline-block"/);
|
||||
assert.match(legacyMainCss, /\.navbar-top \.navbar-but \.but\s*\{[^}]*font-size:\s*0\.8em;[^}]*padding:\s*0\.3em 1em !important;[^}]*margin:\s*0\.4em;/s);
|
||||
assert.doesNotMatch(read('assets/css/frontend/header-userbox.css'), /\.navbar-top \.navbar-but > \.hover-show/);
|
||||
assert.doesNotMatch(header, /but block c-yellow[^"']*btn-block/);
|
||||
@@ -303,7 +303,7 @@ test('the only menu metadata and Walker contract is the legacy runtime', () => {
|
||||
const decoration = read('inc/helpers/menu-decoration.php');
|
||||
const source = walker + menuAdmin + decoration;
|
||||
|
||||
assert.match(source, /aksrt_menu_options/);
|
||||
assert.match(source, /jio_menu_options/);
|
||||
assert.doesNotMatch(source, /zib_menu_options/);
|
||||
assert.match(walker, /start_lvl|start_el|end_el/);
|
||||
assert.match(walker, /submenu_graphic_card/);
|
||||
@@ -317,13 +317,13 @@ test('the only menu metadata and Walker contract is the legacy runtime', () => {
|
||||
});
|
||||
|
||||
test('search is footer-lazy and exposes only reference actions and request fields', () => {
|
||||
const shell = read('inc/helpers/header/aksrt-header-menu-search.php');
|
||||
const box = read('inc/helpers/search/aksrt-search-box.php');
|
||||
const shell = read('inc/helpers/header/jio-header-menu-search.php');
|
||||
const box = read('inc/helpers/search/jio-search-box.php');
|
||||
const results = read('search.php');
|
||||
const settings = read('src/Settings/ThemeSearch.php');
|
||||
const source = shell + box + results + settings;
|
||||
|
||||
assert.match(shell, /add_action\('wp_footer', 'aksrt_footer_navbar_search'/);
|
||||
assert.match(shell, /add_action\('wp_footer', 'jio_footer_navbar_search'/);
|
||||
assert.match(shell, /wp_ajax_menu_search/);
|
||||
assert.match(shell, /wp_ajax_search_box/);
|
||||
assert.match(shell, /wp_verify_nonce/);
|
||||
@@ -335,9 +335,9 @@ test('search is footer-lazy and exposes only reference actions and request field
|
||||
assert.match(box, /array_slice\(\$history, 0, 30\)/);
|
||||
assert.match(box, /search_keywords/);
|
||||
assert.match(settings, /search_custom_key/);
|
||||
assert.match(settings, /aksrt_search_terms\.name LIKE/);
|
||||
assert.match(settings, /jio_search_terms\.name LIKE/);
|
||||
assert.match(results, /search-tab-filter/);
|
||||
assert.match(results, /aksrt_get_search_tab_nav_filter/);
|
||||
assert.match(results, /jio_get_search_tab_nav_filter/);
|
||||
assert.equal(exists('templates/parts/navbar-search.php'), false);
|
||||
assert.equal(exists('templates/parts/search-panel.php'), false);
|
||||
});
|
||||
@@ -366,8 +366,8 @@ test('header slider is the only top visual path and uses global plus page_config
|
||||
const defaults = read('src/Settings/DisplayPageGroups.php');
|
||||
const source = renderer + globalSchema + pageMeta + defaults;
|
||||
|
||||
assert.match(renderer, /function aksrt_header_slide\(\): void/);
|
||||
assert.match(renderer, /function aksrt_header_slide_is_show\(\): bool/);
|
||||
assert.match(renderer, /function jio_header_slide\(\): void/);
|
||||
assert.match(renderer, /function jio_header_slide_is_show\(\): bool/);
|
||||
assert.match(pageMeta, /page_config/);
|
||||
for (const key of [
|
||||
'header_slider_show', 'header_slider_show_type', 'header_slider_nav_color',
|
||||
@@ -404,6 +404,6 @@ test('old navigation settings and runtime handles do not survive the hard switch
|
||||
'src/Core/ScriptsPart1c.php',
|
||||
'assets/js/runtime/main.js',
|
||||
]);
|
||||
assert.doesNotMatch(source, /bootstrap5|aksrt-mini-touch|aksrt\.Drawer|aksrt\.minitouch|updateScrollBus/);
|
||||
assert.doesNotMatch(source, /aksrt_nav_|legacy_nav|nav_compat/);
|
||||
assert.doesNotMatch(source, /bootstrap5|jio-mini-touch|jio\.Drawer|jio\.minitouch|updateScrollBus/);
|
||||
assert.doesNotMatch(source, /jio_nav_|legacy_nav|nav_compat/);
|
||||
});
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
This directory is intentionally empty until validation runs against the user-provided WordPress site.
|
||||
|
||||
The browser run must create the screenshot paths declared by `tests/fixtures/widget-golden-v1.json` and a `manifest.json` using schema `aksrt.widget-visual-results.v1`. Validate completed results with:
|
||||
The browser run must create the screenshot paths declared by `tests/fixtures/widget-golden-v1.json` and a `manifest.json` using schema `jio.widget-visual-results.v1`. Validate completed results with:
|
||||
|
||||
```text
|
||||
php tools/validate-widget-golden.php --require-results
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# AKSRT widget static accessibility audit
|
||||
# Jio widget static accessibility audit
|
||||
|
||||
Scope: `src/WidgetSystem/Renderers/`, `assets/widgets/frontend.js`, and `assets/widgets/frontend.css`.
|
||||
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
# AKSRT / Zibll visual reference
|
||||
# Jio / Zibll visual reference
|
||||
|
||||
The reference stack is isolated from the AKSRT site and exposes Zibll at
|
||||
The reference stack is isolated from the Jio site and exposes Zibll at
|
||||
https://localhost:9443. The local Zibll source remains read-only.
|
||||
|
||||
~~~powershell
|
||||
$env:ZIBLL_THEME_PATH='C:\Users\Xing\Desktop\zibll'
|
||||
docker compose -f tests/visual/docker-compose.zibll.yml up -d
|
||||
docker compose -f tests/visual/docker-compose.zibll.yml run --rm wp core install --url=https://localhost:9443 --title='Zibll Reference' --admin_user=visual_admin --admin_password='AKSRT-Visual-2026!' --admin_email=visual@example.test --skip-email
|
||||
docker compose -f tests/visual/docker-compose.zibll.yml run --rm wp core install --url=https://localhost:9443 --title='Zibll Reference' --admin_user=visual_admin --admin_password='Jio-Visual-2026!' --admin_email=visual@example.test --skip-email
|
||||
docker compose -f tests/visual/docker-compose.zibll.yml run --rm wp theme activate zibll
|
||||
docker compose -f tests/visual/docker-compose.zibll.yml run --rm wp eval-file /visual-fixtures/article-fixtures.php create
|
||||
~~~
|
||||
|
||||
Run the same theme-neutral article generator inside the AKSRT WordPress
|
||||
Run the same theme-neutral article generator inside the Jio WordPress
|
||||
container. Mount `tests/visual/` read-only at `/visual-fixtures` in that stack:
|
||||
|
||||
~~~powershell
|
||||
@@ -22,7 +22,7 @@ wp eval-file /visual-fixtures/article-fixtures.php cleanup
|
||||
The shared generator creates the same authors, category, tag, dates, images,
|
||||
featured/no-image/multi-image/first-image/external-cover posts, slide and video
|
||||
metadata, pay/VIP states, sticky state, and interaction counters in both sites.
|
||||
Fixture-only Zibll and AKSRT metadata are written together so the rendered
|
||||
Fixture-only Zibll and Jio metadata are written together so the rendered
|
||||
business state is identical without adding compatibility code to either theme.
|
||||
|
||||
Use `tests/visual/article-visual-matrix-v1.json` for the article-only captures.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
/**
|
||||
* Theme-neutral article fixtures shared by the Zibll and AKSRT visual sites.
|
||||
* Theme-neutral article fixtures shared by the Zibll and Jio visual sites.
|
||||
*
|
||||
* Usage: wp eval-file /visual-fixtures/article-fixtures.php create|cleanup
|
||||
*/
|
||||
@@ -76,7 +76,7 @@ $manifest = [
|
||||
'files' => [],
|
||||
'options' => [],
|
||||
];
|
||||
foreach (['aksrt_settings', 'zibll_options'] as $option_name) {
|
||||
foreach (['jio_settings', 'zibll_options'] as $option_name) {
|
||||
$manifest['options'][$option_name] = [
|
||||
'exists' => get_option($option_name, null) !== null,
|
||||
'value' => get_option($option_name),
|
||||
@@ -110,7 +110,7 @@ try {
|
||||
'post_related_orderby' => 'views',
|
||||
'related_title' => '相关推荐',
|
||||
];
|
||||
foreach (['aksrt_settings', 'zibll_options'] as $option_name) {
|
||||
foreach (['jio_settings', 'zibll_options'] as $option_name) {
|
||||
$current = get_option($option_name, []);
|
||||
update_option($option_name, array_merge(is_array($current) ? $current : [], $visual_options), false);
|
||||
}
|
||||
@@ -123,7 +123,7 @@ try {
|
||||
foreach ($authors as $author) {
|
||||
$user_id = wp_insert_user([
|
||||
'user_login' => $author['login'],
|
||||
'user_pass' => 'AKSRT-Visual-2026!',
|
||||
'user_pass' => 'Jio-Visual-2026!',
|
||||
'user_email' => $author['login'] . '@example.test',
|
||||
'display_name' => $author['name'],
|
||||
'description' => '固定作者简介,用于文章卡片视觉对比。',
|
||||
@@ -197,7 +197,7 @@ try {
|
||||
$term_other['cover_image'] = $image_urls[4];
|
||||
update_term_meta($category_id, 'zib_other_data', $term_other);
|
||||
update_term_meta($category_id, 'cover_image', $image_urls[4]);
|
||||
update_term_meta($category_id, 'aksrt_category_cover_id', $attachment_ids[4]);
|
||||
update_term_meta($category_id, 'jio_category_cover_id', $attachment_ids[4]);
|
||||
|
||||
$scenarios = [
|
||||
['slug' => 'featured', 'title' => '特色图文章:标准列表与卡片基线', 'featured' => 0],
|
||||
@@ -211,7 +211,7 @@ try {
|
||||
['slug' => 'first-image', 'title' => '文章首图作为缩略图', 'first_image' => 3],
|
||||
];
|
||||
foreach ($scenarios as $index => $scenario) {
|
||||
$content = '<p>固定摘要正文,用于 Zibll 与 AKSRT 的文章列表逐像素比较。</p>';
|
||||
$content = '<p>固定摘要正文,用于 Zibll 与 Jio 的文章列表逐像素比较。</p>';
|
||||
if (!empty($scenario['gallery'])) {
|
||||
$content .= implode('', array_map(static fn(string $url): string => '<img src="' . esc_url($url) . '" alt="">', array_slice($image_urls, 0, 4)));
|
||||
} elseif (isset($scenario['first_image'])) {
|
||||
@@ -262,15 +262,15 @@ try {
|
||||
$zib_meta['views'] = 1200 + ($index * 137);
|
||||
$zib_meta['like'] = 30 + ($index * 7);
|
||||
update_post_meta($post_id, 'zib_other_data', $zib_meta);
|
||||
update_post_meta($post_id, 'aksrt_post_meta', $post_meta);
|
||||
update_post_meta($post_id, 'aksrt_post_views', $zib_meta['views']);
|
||||
update_post_meta($post_id, '_aksrt_like_count', $zib_meta['like']);
|
||||
update_post_meta($post_id, '_aksrt_favorite_count', 10 + $index);
|
||||
update_post_meta($post_id, 'jio_post_meta', $post_meta);
|
||||
update_post_meta($post_id, 'jio_post_views', $zib_meta['views']);
|
||||
update_post_meta($post_id, '_jio_like_count', $zib_meta['like']);
|
||||
update_post_meta($post_id, '_jio_favorite_count', 10 + $index);
|
||||
|
||||
$pay_type = (string) ($scenario['pay'] ?? 'no');
|
||||
$pay_limit = $pay_type === 'vip' ? 2 : 0;
|
||||
$pay_price = $pay_type === 'reading' ? 19.9 : 0;
|
||||
update_post_meta($post_id, '_aksrt_pay', [
|
||||
update_post_meta($post_id, '_jio_pay', [
|
||||
'pay_type' => $pay_type,
|
||||
'pay_modo' => '0',
|
||||
'price' => $pay_price,
|
||||
@@ -287,7 +287,7 @@ try {
|
||||
stick_post((int) $manifest['posts'][0]);
|
||||
}
|
||||
update_option($manifest_key, $manifest, false);
|
||||
WP_CLI::success('Created identical Zibll/AKSRT article fixtures.');
|
||||
WP_CLI::success('Created identical Zibll/Jio article fixtures.');
|
||||
} catch (Throwable $error) {
|
||||
$cleanup($manifest);
|
||||
WP_CLI::error('Fixture creation rolled back: ' . $error->getMessage());
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"schema": "aksrt.zibll-article-visual-matrix.v1",
|
||||
"schema": "jio.zibll-article-visual-matrix.v1",
|
||||
"reference": "https://localhost:9443",
|
||||
"candidate": "https://localhost",
|
||||
"fixture": "/visual-fixtures/article-fixtures.php",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: aksrt-zibll-visual-reference
|
||||
name: jio-zibll-visual-reference
|
||||
|
||||
services:
|
||||
db:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"schema": "aksrt.zibll-article-baseline.v1",
|
||||
"schema": "jio.zibll-article-baseline.v1",
|
||||
"referencePath": "C:\\Users\\Xing\\Desktop\\zibll",
|
||||
"theme": "Zibll",
|
||||
"version": "9.0",
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
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