Files
wordpress-theme-aksrt/tests/widget-system/user-badges-modal.test.cjs
T
kate522andClaude Opus 5 35bb96e82f 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>
2026-11-01 20:05:04 +08:00

98 lines
5.3 KiB
JavaScript

const assert = require('node:assert/strict');
const { spawnSync } = require('node:child_process');
const fs = require('node:fs');
const path = require('node:path');
const test = require('node:test');
const root = path.resolve(__dirname, '..', '..');
const harness = path.join(__dirname, 'user-badges-modal-harness.php');
function render() {
const result = spawnSync('php', [harness], { encoding: 'utf8', cwd: root });
assert.equal(result.status, 0, `PHP modal harness failed:\n${result.stderr || result.stdout}`);
return JSON.parse(result.stdout);
}
function assertBefore(html, first, second, message, from = 0) {
const firstIndex = html.indexOf(first, from);
const secondIndex = html.indexOf(second, from);
assert.notEqual(firstIndex, -1, `missing ${first}`);
assert.notEqual(secondIndex, -1, `missing ${second}`);
assert.ok(firstIndex < secondIndex, message);
}
test('overview matches the reference header, safely renders labels and places owned badges first', () => {
const cases = render();
const html = cases.self.html;
assert.match(html, /modal-colorful-header colorful-bg c-blue/);
assert.match(html, /class="avatar-img"[^>]*>[\s\S]*?width="38"/);
assert.doesNotMatch(html, /<a[^>]*>\s*<img[^>]*avatar-img/);
assert.match(html, /medal-list-box[^>]*flex[^>]*at[^>]*hh|medal-list-box[^>]*hh[^>]*flex/);
const listStart = html.indexOf('mini-scrollbar scroll-y max-vh5');
assertBefore(html, '自动已得', '手动已得', 'owned medals keep the reference category order', listStart);
assertBefore(html, '手动已得', '手动未得', 'owned medals render before unearned medals', listStart);
assert.match(html, /类别 &lt;svg onload=alert\(1\)&gt;/);
assert.match(html, /手动未得 &lt;b&gt;危险&lt;\/b&gt;/);
assert.match(cases.grant_admin.html, /说明 &lt;script&gt;bad\(\)&lt;\/script&gt;/);
assert.doesNotMatch(html, /<script>bad\(\)<\/script>|<svg onload=alert\(1\)>|<b>危险<\/b>/);
assert.match(html, /data-jio-modal-height="201"|data-height="201"/);
assert.match(html, /data-jio-modal-new="true"|new="new"/);
assert.match(html, /data-jio-modal-mobile-bottom="true"|mobile-bottom="true"/);
});
test('own and other-user overviews apply the ownership visibility rule for ordinary users and administrators', () => {
const cases = render();
for (const key of ['other', 'admin_other']) {
const html = cases[key].html;
assert.match(html, /自动已得/);
assert.match(html, /手动已得/);
assert.doesNotMatch(html, /手动未得|自动未得/);
}
assert.match(cases.other.html, /Other &lt;img src=x onerror=alert\(1\)&gt;/);
assert.doesNotMatch(cases.other.html, /<img src=x onerror=alert\(1\)>/);
assert.match(cases.admin_self.html, /授予徽章/);
assert.match(cases.admin_self.html, /data-jio-modal-height="240"|data-height="240"/);
assert.match(cases.admin_self.html, /data-jio-modal-mobile-bottom="true"|mobile-bottom="true"/);
});
test('single-badge details show ownership state and only the permitted action', () => {
const cases = render();
assert.match(cases.single_owned_self.html, /2026-02-03 04:05:06 获得/);
assert.match(cases.single_owned_self.html, /data-jio-badge-action="wear"/);
assert.match(cases.single_unowned_self.html, /暂未获得/);
assert.doesNotMatch(cases.single_unowned_self.html, /data-jio-badge-action=/);
assert.match(cases.single_wearing_self.html, /当前佩戴此徽章/);
assert.doesNotMatch(cases.single_wearing_self.html, /data-jio-badge-action="wear"/);
assert.match(cases.single_manual_admin.html, /收回此徽章/);
assert.match(cases.single_manual_admin.html, /data-jio-badge-action="revoke"/);
assert.doesNotMatch(cases.single_manual_admin.html, /2026-03-04 05:06:07 获得|暂未获得/);
assert.doesNotMatch(cases.single_auto_admin.html, /data-jio-badge-action="revoke"|收回此徽章/);
assert.match(cases.single_auto_unowned_admin.html, /参数传入错误或没有此徽章/);
assert.doesNotMatch(cases.single_auto_unowned_admin.html, /data-jio-badge-action=/);
assert.match(cases.single_owned_self.html, /data-jio-modal-close/);
});
test('grant modal preserves configured manual-badge order, actions, mobile sizing, and illustrated empty state', () => {
const cases = render();
const html = cases.grant_admin.html;
assert.match(html, /手动未得/);
assert.match(html, /手动已得/);
assert.doesNotMatch(html, /自动已得|自动未得/);
assertBefore(html, '手动未得', '手动已得', 'grant catalog follows the configured order');
assert.match(html, /data-jio-badge-action="grant"/);
assert.match(html, /data-jio-badge-action="revoke"/);
const css = fs.readFileSync(path.join(root, 'assets/css/main.css'), 'utf8');
assert.match(css, /\.medal-list\s*\{[^}]*width:\s*calc\(20%\s*-\s*14px\)[^}]*margin:\s*10px\s+7px/s);
assert.match(css, /@media\s*\(max-width:\s*767px\)[\s\S]*?\.medal-list\s*\{[^}]*width:\s*calc\(25%\s*-\s*14px\)/);
assert.match(css, /\.avatar-img\s*\{[^}]*--this-size:\s*38px/s);
const badgeCss = fs.readFileSync(path.join(root, 'assets/css/frontend/medal.css'), 'utf8');
assert.doesNotMatch(badgeCss, /\.medal-list-box\s*\{|\.medal-card__name|\.medal-card \.medal-icon/);
const empty = cases.illustrated_empty;
assert.match(empty.html, /jio-test-null/);
assert.ok(empty.null_calls.some((call) => call[1] === 30 && call[2] === 'null-2.svg'));
});