The theme was renamed Zibll/子比 → AKSRT → Jio, but upstream branding survived the sweeps in CSS classes, the SVG sprite prefix, a persisted widget field ID, JS events, admin Vue class names, test infrastructure and docblocks. Renames: - zib-widget / zib-widget-wrap / zib-slider → jio-widget-box / jio-widget-box-wrap / jio-slider-box (jio-widget is taken by the modern widget shell, jio-box-* by the card primitives) - zib-partners-scroll + zps-* → jio-partners-scroll + jps-* - zib-combo-info-card + zci-* → jio-combo-info-card + jci-* - zibsvg- → jiosvg-, including the exported picker helpers and the fa-zibsvg- family-prefix guard - zibpay_type → pay_type; the dual-read fallback in PostQuery collapses to a single key - [zibajax="submit"] dropped (no PHP emitter); zib_ajax.success → jio_ajax.success - aks-* admin Vue classes → jio-* - zib_palette() → palette() User-facing vendor traces are gone: the floating "more" button default no longer ships a zibll.com link or 子比主题 marketing copy, the notice widget ships no preset promo entries, and the four 官网教程 links are removed from the post-list admin schema. Test infrastructure renamed to neutral names (docker-compose.reference.yml, Caddyfile, article-baseline.json, modern-widgets-contract.test.cjs) with their DB/volume/env/slug identifiers following. Kept intentionally, documented in tests/fixtures/README.md: the golden fixture reference fingerprint (a SHA-256 record of an external directory, read by validate-widget-golden / generate-widget-golden-fixture / audit-widget-system), the doesNotMatch guards that prove the old tokens are gone, and the reference theme's own meta key in the visual fixtures. No backward-compat or data migration: saved widget configs holding old icon keys or the old field ID silently lose them, per the project's rewrite-over-compatibility rule. Verified: 132/132 node contract tests, php tests/widget-system/run.php (146 assertions), audit-admin-contracts, audit-psr4-paths and validate-widget-golden all green; tracked bundles regenerated and in sync. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
115 lines
6.0 KiB
JavaScript
115 lines
6.0 KiB
JavaScript
const test = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const { execFileSync } = require('node:child_process');
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
|
|
const root = path.resolve(__dirname, '..', '..');
|
|
const read = (file) => fs.readFileSync(path.join(root, file), 'utf8');
|
|
|
|
function renderFixtures() {
|
|
const output = execFileSync('php', [path.join(__dirname, 'discovery-reference-harness.php')], { encoding: 'utf8' });
|
|
return JSON.parse(output);
|
|
}
|
|
|
|
test('term aggregation renders the real SVG use element, total count, and configured first page', () => {
|
|
const fixture = renderFixtures();
|
|
const card = fixture.aggregation;
|
|
assert.match(card, /<svg class="jiosvg-hot" viewbox="0 0 16 16" aria-hidden="true"><use href="#jiosvg-hot"><\/use><\/svg>/);
|
|
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篇文章/);
|
|
assert.match(card, /文章 101/);
|
|
assert.match(card, /文章 102/);
|
|
assert.match(card, /class="fa far fa-file-alt fa-fw"/);
|
|
assert.match(card, /class="fa far fa-clock mr3"/);
|
|
assert.doesNotMatch(card, /文章 103|空分类/);
|
|
|
|
const baseCss = read('assets/css/frontend/base.css');
|
|
assert.match(baseCss, /\.fa\.far\s*\{[^}]*font-weight:\s*400;[^}]*\}/s);
|
|
|
|
const firstQuery = fixture.aggregation_queries[0];
|
|
assert.equal(firstQuery.posts_per_page, 2);
|
|
assert.equal(firstQuery.no_found_rows, false);
|
|
assert.equal(firstQuery.cat, '7');
|
|
assert.equal(fixture.aggregation_queries[1].cat, '8');
|
|
});
|
|
|
|
test('category filters keep dropdown and inline terms in separate rendered groups on the ajax-option pipeline', () => {
|
|
const fixture = renderFixtures();
|
|
const html = fixture.archive_filter_html;
|
|
const group = fixture.filter_group;
|
|
assert.deepEqual(group.chips.map((chip) => chip.label), ['技术分类']);
|
|
assert.deepEqual(group.dropdown.map((chip) => chip.label), ['下拉分类']);
|
|
assert.match(html, /class="ajax-option ajax-replace" win-ajax-replace="filter"/);
|
|
assert.match(html, /class="ajax-next" href="https:\/\/example\.test\/category\/7"/);
|
|
const dropdown = html.match(/<ul class="dropdown-menu">([\s\S]*?)<\/ul>/)[1];
|
|
const inline = html.match(/<ul class="list-inline[^>]*option-items">([\s\S]*?)<\/ul>/)[1];
|
|
assert.match(dropdown, /下拉分类/);
|
|
assert.doesNotMatch(dropdown, /技术分类/);
|
|
assert.match(inline, /技术分类/);
|
|
assert.doesNotMatch(inline, /下拉分类/);
|
|
});
|
|
|
|
test('category header matches first-page, later-page, and simple-title render contracts', () => {
|
|
const fixture = renderFixtures();
|
|
const first = fixture.term_header_first;
|
|
assert.match(first, /win-ajax-replace="page-cover"/);
|
|
assert.match(first, /src="https:\/\/example\.test\/theme\/assets\/img\/user_t\.jpg"/);
|
|
assert.match(first, /共31篇/);
|
|
assert.match(first, /class="main-search-btn"[^>]*search-trem="7"/);
|
|
|
|
const second = fixture.term_header_second;
|
|
assert.doesNotMatch(second, /win-ajax-replace="page-cover"|共31篇/);
|
|
assert.match(second, /第2页/);
|
|
assert.match(second, /search-trem="7"/);
|
|
|
|
const simple = fixture.term_header_simple;
|
|
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 jio-panel|<img/);
|
|
});
|
|
|
|
test('homepage sorting retains distinct dropdown and inline option containers', () => {
|
|
const html = renderFixtures().home_sort;
|
|
assert.match(html, /<ul class="dropdown-menu">[\s\S]*更新[\s\S]*随机[\s\S]*<\/ul>/);
|
|
assert.match(html, /<ul class="list-inline[^>]*option-items"><a[^>]*orderby=date[\s\S]*发布[\s\S]*<a[^>]*orderby=views[\s\S]*浏览[\s\S]*<\/ul>/);
|
|
assert.equal((html.match(/class="ajax-next/g) || []).length, 9);
|
|
});
|
|
|
|
test('number pagination renders a page trigger and tab panes distinguish loaded from lazy content', () => {
|
|
const fixture = renderFixtures();
|
|
assert.match(fixture.posts, /class="widget-ajaxpager"[^>]*data-loaded="1"/);
|
|
assert.match(fixture.posts, /class="next-page page-numbers"[^>]*data-widget-page="2"/);
|
|
assert.match(fixture.tabs, /data-segment="0" data-loaded="1"/);
|
|
const lazyPane = fixture.tabs.match(/<div class="widget-ajaxpager"[^>]*data-segment="1"[^>]*>[\s\S]*?<\/div>/)[0];
|
|
assert.doesNotMatch(lazyPane, /data-loaded="1"/);
|
|
assert.match(fixture.tabs, /class="next-page page-numbers"[^>]*data-widget-page="2"/);
|
|
|
|
const runtime = read('assets/widgets/frontend.js');
|
|
assert.match(runtime, /container\.dataset\.loaded = '1'/);
|
|
assert.match(runtime, /if \(panel\.dataset\.loaded !== '1'\) loadItems\(panel, 1, false\)/);
|
|
assert.match(runtime, /var pageLink = event\.target\.closest\('\.page-numbers\[data-widget-page\]'\)/);
|
|
assert.match(runtime, /loadItems\(container, Number\(pageLink\.dataset\.widgetPage \|\| 1\), false\)/);
|
|
});
|
|
|
|
test('archive filters use the shared ajax-next handler without a category REST/archive script path', () => {
|
|
const filters = read('inc/helpers/jio-archive-filters.php');
|
|
const ajax = read('assets/js/core/ajax-list.js');
|
|
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/);
|
|
assert.match(ajax, /link\.closest\("\.ajax-option, \.post_ajax_trigger"\)/);
|
|
assert.doesNotMatch(archives, /register_rest_route|rest_archive_posts|\/archive\/posts/);
|
|
assert.doesNotMatch(globalController, /register_rest_route\([^\n]*['"][^'"]*archive\/posts/);
|
|
assert.equal(fs.existsSync(path.join(root, 'assets', 'js', 'core', 'archive.js')), false);
|
|
|
|
const widgetCss = read('assets/widgets/frontend.css');
|
|
assert.match(widgetCss, /\.jio-widget-box\.posts-mini-lists\s*\{\s*padding:\s*5px;/);
|
|
assert.doesNotMatch(widgetCss, /\.widget-tab-post[^{}]*\{[^}]*!important/s);
|
|
});
|