chore(branding): remove leftover upstream theme identifiers
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>
This commit is contained in:
1 parent
42cc64106d
commit
427968a721
188 files changed
+915
-967
No files matched your search
@@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* 「百度推送」后台设置页契约(对齐参考主题 zibll):
|
||||
* 「百度推送」后台设置页契约:
|
||||
* 1) PHP schema(SchemaExtensionsPush.php)与 JS schema(push.js)字段键、顺序、默认值一致;
|
||||
* 2) 参考主题字段全集存在:自动推送 JS、普通收录、快速收录、准入 Token、发布/更新触发;
|
||||
* 3) 双路/迁移字段(SEO 页 seo_baidu_*、熊掌号 xzh_*、批量推送 batch_*)已彻底移除,无双路兼容;
|
||||
@@ -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)百度推送字段全集,Jio 单一 canonical 配置面(扩展&增强 → 百度推送)。
|
||||
// 百度推送字段全集,Jio 单一 canonical 配置面(扩展&增强 → 百度推送)。
|
||||
const REFERENCE_KEYS = [
|
||||
'baidu_push_enabled',
|
||||
'baidu_push_token',
|
||||
|
||||
@@ -160,10 +160,8 @@ test('JS schema 与 PHP 字段键一致(ui_only 教程行除外)', () => {
|
||||
for (const legacy of LEGACY_KEYS) {
|
||||
assert.ok(!keys.includes(legacy), `JS schema 残留旧字段: ${legacy}`);
|
||||
}
|
||||
// 参考主题 desc 中的官网教程链接以 ui_only content 行承载。
|
||||
assert.match(js, /ui_only:\s*true/);
|
||||
assert.match(js, /https:\/\/www\.zibll\.com\/1717\.html/);
|
||||
assert.match(js, /https:\/\/www\.zibll\.com\/683\.html/);
|
||||
// 说明内置于字段 description,且不得再指向第三方站点。
|
||||
assert.doesNotMatch(js, /https?:\/\/www\.zibll\.com/);
|
||||
});
|
||||
|
||||
test('JS schema 默认值与选项对齐参考主题', () => {
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
/**
|
||||
* 高级筛选设置页参考主题 1:1 契约。
|
||||
*
|
||||
* 参考:zibll inc/options/admin-options.php「高级筛选」区块(5251-5424 行)与
|
||||
* 前台链路(zib-category.php zib_get_ajax_option_menu / zib_get_option_custom_filter_but、
|
||||
* zib-post.php zib_sift_posts_per_page)。
|
||||
* 参考实现「高级筛选」区块与前台链路(分类/标签归档的 AJAX 选项菜单、自定义
|
||||
* 筛选按钮、每页筛选条数)。
|
||||
*/
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert');
|
||||
@@ -92,10 +91,10 @@ test('JS schema 默认值、选项与依赖对齐参考主题', () => {
|
||||
assert.match(js, /key: 'orderby_lists',[\s\S]{0,220}?dependsOn:\s*\{\s*key:\s*'orderby_s',\s*value:\s*true\s*\}/);
|
||||
// custom_filters 为动态选项 checkbox(引用 custom_filter 维度定义)。
|
||||
assert.match(js, /key: 'custom_filters',[\s\S]{0,400}?options:\s*'custom_filter'/);
|
||||
// 提示行承载参考主题 submessage 与官网教程链接。
|
||||
// 提示行承载筛选说明,且不得再指向第三方站点。
|
||||
assert.match(js, /type:\s*'content'/);
|
||||
assert.match(js, /ui_only:\s*true/);
|
||||
assert.match(js, /zibll\.com\/15886\.html/);
|
||||
assert.doesNotMatch(js, /zibll\.com/);
|
||||
assert.match(js, /避免与其他mate_key重复/);
|
||||
});
|
||||
|
||||
@@ -127,11 +126,11 @@ test('JS schema 与 PHP schema 一致性', () => {
|
||||
assert.match(php, /'addText'\s*=>\s*'添加筛选类别'/);
|
||||
assert.match(php, /'addText'\s*=>\s*'添加值'/);
|
||||
assert.match(php, /'addText'\s*=>\s*'添加自定义分类筛选'/);
|
||||
// 排序选项不含 zibpay 系列与销量(本项目无对应运行时)。
|
||||
// 排序选项不含付费商品价格系列与销量(本项目无对应运行时)。
|
||||
assert.ok(!js.includes('zibpay_price') && !js.includes('sales_volume'));
|
||||
});
|
||||
|
||||
test('前台链路:覆盖与门禁(参考主题 zib-category.php 语义)', () => {
|
||||
test('前台链路:覆盖与门禁', () => {
|
||||
// cat 页按 cat_custom_filter 覆盖,回退 custom_filter_show 门禁。
|
||||
assert.match(postDisplay, /function get_archive_custom_filter_config\(int \$term_id, string \$page\)/);
|
||||
assert.match(postDisplay, /if \(\$page === 'cat'\)/);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* 「文章页面」后台设置页契约(对齐参考主题 zibll 1:1):
|
||||
* 「文章页面」后台设置页契约:
|
||||
* 1) PHP schema(SchemaPostList.php page section 2.0.0)字段键、顺序、默认值与
|
||||
* JS schema(assets/admin/src/schema/sections/post-list/page.js)一致;
|
||||
* 2) 参考主题字段全集存在:三类封面(article_image_cover / article_slide_cover /
|
||||
@@ -67,14 +67,14 @@ function phpSection(src, sectionKey) {
|
||||
|
||||
const pagePhp = phpSection(php, 'page');
|
||||
|
||||
// 参考主题 admin-options.php 4934–5249 的字段顺序(含 fieldset 父字段与子字段)。
|
||||
// page section 字段顺序(含 fieldset 父字段与子字段)。
|
||||
const PAGE_FIELD_SEQUENCE = [
|
||||
'article_image_cover',
|
||||
'article_image_cover_option',
|
||||
'scale', // article_image_cover_option.scale
|
||||
'article_slide_cover',
|
||||
'article_slide_cover_option',
|
||||
// article_slide_cover_option(CFS_Module::slide 全字段)
|
||||
// article_slide_cover_option(幻灯片封面全字段)
|
||||
'direction', 'loop', 'button', 'pagination', 'effect', 'scale_height', 'scale',
|
||||
'auto_height', 'max_height', 'min_height', 'pc_height', 'm_height',
|
||||
'spacebetween', 'speed', 'autoplay', 'interval',
|
||||
@@ -159,7 +159,7 @@ test('PHP page section 默认值对齐参考主题', () => {
|
||||
assert.match(pagePhp, /'post_related_orderby',\s*'label'\s*=>\s*'排序方式',\s*'type'\s*=>\s*'select',\s*'default'\s*=>\s*'views'/);
|
||||
});
|
||||
|
||||
test('PHP slide fieldset 全字段及嵌套依赖对齐 CFS_Module::slide', () => {
|
||||
test('PHP slide fieldset 全字段及嵌套依赖齐全', () => {
|
||||
// 幻灯片默认值(pagination: true, pc_height: 380, m_height: 180 为参考主题默认)。
|
||||
assert.match(pagePhp, /'direction'\s*=>\s*'horizontal',\s*'loop'\s*=>\s*true,\s*'button'\s*=>\s*false,\s*'pagination'\s*=>\s*true,\s*'effect'\s*=>\s*'slide'/);
|
||||
assert.match(pagePhp, /'pc_height'\s*=>\s*380,\s*'m_height'\s*=>\s*180,\s*'spacebetween'\s*=>\s*15,\s*'speed'\s*=>\s*0,\s*'autoplay'\s*=>\s*true,\s*'interval'\s*=>\s*4/);
|
||||
@@ -199,9 +199,10 @@ test('JS schema 与 PHP 字段键一致(ui_only 一言行除外)', () => {
|
||||
}
|
||||
assert.ok(!js.includes(`key: 'share_s'`), 'JS page schema 残留 share_s');
|
||||
assert.ok(!/key:\s*'post_rewards_/.test(js), 'JS page schema 残留 post_rewards_*');
|
||||
// 参考主题视频封面配置教程链接以 ui_only description 承载。
|
||||
// 视频封面配置说明以 ui_only description 承载,且不得再指向第三方站点。
|
||||
assert.match(js, /ui_only:\s*true/);
|
||||
assert.match(js, /https:\/\/www\.zibll\.com\/1900\.html/);
|
||||
assert.match(js, /视频封面配置/);
|
||||
assert.doesNotMatch(js, /zibll\.com/);
|
||||
});
|
||||
|
||||
test('JS schema 默认值与依赖对齐参考主题', () => {
|
||||
@@ -251,7 +252,7 @@ test('社交分享位于全局功能并使用参考主题唯一键集', () => {
|
||||
assert.doesNotMatch(shareJs, /share_wechat_s|share_wechat_appid|share_wechat_app_secret|share_only_logo/);
|
||||
});
|
||||
|
||||
test('post_rewards_* 迁移至用户功能页 rewards 组(对齐参考主题 8977 行)', () => {
|
||||
test('post_rewards_* 迁移至用户功能页 rewards 组', () => {
|
||||
assert.match(usersPhp, /\['key' => 'rewards', 'title' => '打赏', 'fields' => \[/);
|
||||
assert.match(usersPhp, /'post_rewards_s', 'label' => '用户打赏功能', 'type' => 'switcher', 'default' => true/);
|
||||
assert.match(usersPhp, /'post_rewards_text', 'label' => '打赏按钮文案', 'type' => 'input', 'default' => '赞赏'/);
|
||||
@@ -287,7 +288,7 @@ test('运行时只读取新键并接线封面/缩进/版权/一言', () => {
|
||||
assert.match(runtime, /max\(20, min\(200, \(int\) \(\$image_cover_option\['scale'\] \?\? 35\)\)\)/);
|
||||
// 视频封面比例 clamp 0-200(0 = 不固定长宽比例)。
|
||||
assert.match(runtime, /max\(0, min\(200, \(int\) \(\$video_cover_option\['scale'\] \?\? 0\)\)\)/);
|
||||
// 幻灯片配置归一化(CFS_Module::slide 全字段)。
|
||||
// 幻灯片配置归一化(幻灯片封面全字段)。
|
||||
assert.match(runtime, /normalize_slide_cover_option\(\$slide_option_raw\)/);
|
||||
for (const key of ['direction', 'loop', 'button', 'pagination', 'effect', 'scale_height', 'scale', 'auto_height', 'max_height', 'min_height', 'pc_height', 'm_height', 'spacebetween', 'speed', 'autoplay', 'interval']) {
|
||||
assert.ok(runtime.includes(`'${key}'`), `normalize_slide_cover_option 缺少 ${key}`);
|
||||
|
||||
Vendored
+17
@@ -0,0 +1,17 @@
|
||||
# Fixtures
|
||||
|
||||
## `widget-golden-v1.json`
|
||||
|
||||
The `reference` block (`source_root`, `fingerprint`, `files`) is an intentional
|
||||
fingerprint of an **external** reference-theme checkout on the machine that
|
||||
generated the fixture. Its keys and paths describe files in that external
|
||||
directory, not identifiers of this theme, and `tools/validate-widget-golden.php`,
|
||||
`tools/generate-widget-golden-fixture.php`, and `tools/audit-widget-system.php`
|
||||
resolve `source_root` + relative path and compare SHA-256. Changing any of it
|
||||
produces `Reference drift` and breaks those audits — leave it verbatim, including
|
||||
when the upstream names look out of place.
|
||||
|
||||
## `page-visual-matrix-v1.json`
|
||||
|
||||
Page-level capture routes for `tools/wp-cli-visual-fixtures.php`. The route paths
|
||||
carry the tool's `$fixture_prefix`, so the two must be changed together.
|
||||
+4
-4
@@ -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/jio_zibll_v1_101/", "states": ["default", "empty"]},
|
||||
{"id": "tag", "path": "/tag/jio_zibll_v1_103/", "states": ["default", "empty"]},
|
||||
{"id": "category", "path": "/category/jio_visual_v1_101/", "states": ["default", "empty"]},
|
||||
{"id": "tag", "path": "/tag/jio_visual_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": "single", "path": "/jio_visual_v1_301/", "states": ["default", "hover", "toc-open"]},
|
||||
{"id": "resource", "path": "/jio_visual_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
+7
-7
@@ -2619,7 +2619,7 @@
|
||||
"layout_bg",
|
||||
"load_mode",
|
||||
"cat",
|
||||
"zibpay_type",
|
||||
"pay_type",
|
||||
"limit_day",
|
||||
"orderby",
|
||||
"order",
|
||||
@@ -2692,7 +2692,7 @@
|
||||
},
|
||||
"load_mode": "ajax",
|
||||
"cat": "1",
|
||||
"zibpay_type": [],
|
||||
"pay_type": [],
|
||||
"limit_day": 0,
|
||||
"orderby": "",
|
||||
"order": "desc",
|
||||
@@ -2813,7 +2813,7 @@
|
||||
{
|
||||
"title": "Jio · Tab posts",
|
||||
"cat": "1",
|
||||
"zibpay_type": [],
|
||||
"pay_type": [],
|
||||
"limit_day": 0,
|
||||
"orderby": "",
|
||||
"order": "desc"
|
||||
@@ -2821,7 +2821,7 @@
|
||||
{
|
||||
"title": "Jio · Tab posts",
|
||||
"cat": "2",
|
||||
"zibpay_type": [],
|
||||
"pay_type": [],
|
||||
"limit_day": 0,
|
||||
"orderby": "",
|
||||
"order": "desc"
|
||||
@@ -2829,7 +2829,7 @@
|
||||
{
|
||||
"title": "Jio · Tab posts",
|
||||
"cat": "3",
|
||||
"zibpay_type": [],
|
||||
"pay_type": [],
|
||||
"limit_day": 0,
|
||||
"orderby": "",
|
||||
"order": "desc"
|
||||
@@ -2875,7 +2875,7 @@
|
||||
"layout_pin_m",
|
||||
"layout_bg",
|
||||
"cat",
|
||||
"zibpay_type",
|
||||
"pay_type",
|
||||
"orderby",
|
||||
"order",
|
||||
"style",
|
||||
@@ -2943,7 +2943,7 @@
|
||||
}
|
||||
},
|
||||
"cat": "1",
|
||||
"zibpay_type": [],
|
||||
"pay_type": [],
|
||||
"orderby": "",
|
||||
"order": "desc",
|
||||
"style": "card",
|
||||
|
||||
@@ -19,7 +19,7 @@ test('submit.css raises the TinyMCE fullscreen container above the fixed navbar'
|
||||
assert.match(css, /div\.mce-fullscreen \{\s*\n z-index: 100010;\s*\n\}/);
|
||||
});
|
||||
|
||||
test('the Zibll toolbar affix rules stay removed', () => {
|
||||
test('the upstream toolbar affix rules stay removed', () => {
|
||||
const css = read('assets/css/frontend/pages/submit.css');
|
||||
const jsRoot = path.join(root, 'assets/js');
|
||||
const js = fs
|
||||
@@ -28,8 +28,8 @@ test('the Zibll toolbar affix rules stay removed', () => {
|
||||
.map((entry) => read(`assets/js/${entry.name}`))
|
||||
.join('\n');
|
||||
|
||||
// No Jio script adds .affix to .mce-top-part (that was Zibll's
|
||||
// editextend.js), so the affix positioning rules must not come back.
|
||||
// No Jio script adds .affix to .mce-top-part (that came from an old
|
||||
// editor-extension script), so the affix positioning rules must not come back.
|
||||
assert.doesNotMatch(css, /mce-top-part\.affix/);
|
||||
assert.doesNotMatch(js, /mce-top-part/);
|
||||
});
|
||||
@@ -114,7 +114,7 @@ test('account icons keep the reference spacing on both sides of each divider', (
|
||||
for (const html of lists) {
|
||||
const account = html.match(/<ul class="list-inline splitters relative">([\s\S]*?)<\/ul><\/div>/);
|
||||
assert.ok(account, 'missing the account action list');
|
||||
// Reference zib_menu_button concatenates the items directly. Whitespace
|
||||
// The reference menu button concatenates the items directly. Whitespace
|
||||
// between inline-block li elements adds a font-dependent gap before the
|
||||
// following item's divider, beyond the shared 5px li / 12px button padding.
|
||||
assert.doesNotMatch(account[1], /<\/li>\s+<li\b/, 'extra text space shifts the divider');
|
||||
|
||||
@@ -115,9 +115,9 @@ test('Font Awesome 5 is the only icon contract across every runtime path', () =>
|
||||
'fa-chevron', 'fa-icon', 'fa-icon-picker', 'fa-icons', 'fa-input',
|
||||
'fa-mask', 'fa-spin-hover', 'fa-toggle-theme', 'fa-xxx',
|
||||
]);
|
||||
// The theme ships its own SVG sprite family, so fa-zibsvg-<key> is a project
|
||||
// The theme ships its own SVG sprite family, so fa-jiosvg-<key> is a project
|
||||
// icon rather than a Font Awesome glyph — allow the prefix instead of every key.
|
||||
const projectFamily = /^fa-zibsvg-/;
|
||||
const projectFamily = /^fa-jiosvg-/;
|
||||
|
||||
assert.doesNotMatch(runtime, /\bfa fa-/);
|
||||
assert.doesNotMatch(runtime, /FontAwesome|font-awesome\.min\.css|Font Awesome 4|4\.7\.0/);
|
||||
@@ -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, /jio_icon_markup\('zibsvg-money-color-2', 'em14'\)/);
|
||||
assert.match(header, /jio_icon_markup\('zibsvg-points-color', 'em14'\)/);
|
||||
assert.match(header, /jio_icon_markup\('jiosvg-money-color-2', 'em14'\)/);
|
||||
assert.match(header, /jio_icon_markup\('jiosvg-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/);
|
||||
|
||||
File renamed without changes.
+12
-12
@@ -1,14 +1,14 @@
|
||||
# Jio / Zibll visual reference
|
||||
# Jio / reference visual comparison
|
||||
|
||||
The reference stack is isolated from the Jio site and exposes Zibll at
|
||||
https://localhost:9443. The local Zibll source remains read-only.
|
||||
The reference stack is isolated from the Jio site and exposes the reference
|
||||
theme at https://localhost:9443. The local reference 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='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
|
||||
$env:REFERENCE_THEME_PATH='C:\Users\Xing\Desktop\zibll'
|
||||
docker compose -f tests/visual/docker-compose.reference.yml up -d
|
||||
docker compose -f tests/visual/docker-compose.reference.yml run --rm wp core install --url=https://localhost:9443 --title='Reference' --admin_user=visual_admin --admin_password='Jio-Visual-2026!' --admin_email=visual@example.test --skip-email
|
||||
docker compose -f tests/visual/docker-compose.reference.yml run --rm wp theme activate reference
|
||||
docker compose -f tests/visual/docker-compose.reference.yml run --rm wp eval-file /visual-fixtures/article-fixtures.php create
|
||||
~~~
|
||||
|
||||
Run the same theme-neutral article generator inside the Jio WordPress
|
||||
@@ -22,8 +22,8 @@ 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 Jio metadata are written together so the rendered
|
||||
business state is identical without adding compatibility code to either theme.
|
||||
Fixture-only metadata is 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.
|
||||
Every reference/candidate pair must record the screenshot, DOM, computed styles,
|
||||
@@ -38,8 +38,8 @@ or critical/serious accessibility issue.
|
||||
Clean the reference fixture before removing its volumes:
|
||||
|
||||
~~~powershell
|
||||
docker compose -f tests/visual/docker-compose.zibll.yml run --rm wp eval-file /visual-fixtures/article-fixtures.php cleanup
|
||||
docker compose -f tests/visual/docker-compose.zibll.yml down -v
|
||||
docker compose -f tests/visual/docker-compose.reference.yml run --rm wp eval-file /visual-fixtures/article-fixtures.php cleanup
|
||||
docker compose -f tests/visual/docker-compose.reference.yml down -v
|
||||
~~~
|
||||
|
||||
The older `tools/wp-cli-visual-fixtures.php`, widget golden file, and broad page
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"schema": "jio.zibll-article-baseline.v1",
|
||||
"schema": "jio.article-baseline.v1",
|
||||
"referencePath": "C:\\Users\\Xing\\Desktop\\zibll",
|
||||
"theme": "Zibll",
|
||||
"theme": "reference",
|
||||
"version": "9.0",
|
||||
"capturedAt": "2026-08-18",
|
||||
"files": {
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
/**
|
||||
* Theme-neutral article fixtures shared by the Zibll and Jio visual sites.
|
||||
* Theme-neutral article fixtures shared by the reference and Jio visual sites.
|
||||
*
|
||||
* Usage: wp eval-file /visual-fixtures/article-fixtures.php create|cleanup
|
||||
*/
|
||||
@@ -12,8 +12,15 @@ if (!defined('WP_CLI') || !WP_CLI) {
|
||||
}
|
||||
|
||||
$action = sanitize_key((string) ($args[0] ?? ''));
|
||||
$manifest_key = 'zibll_article_visual_fixture_v1';
|
||||
$prefix = 'zibll_article_v1_';
|
||||
$manifest_key = 'article_visual_fixture_v1';
|
||||
$prefix = 'article_v1_';
|
||||
|
||||
/**
|
||||
* 参考主题读取自己的 meta 键来渲染副标题、封面与计数器;Jio 读取 jio_* 键。
|
||||
* 这里同时写两套,保证两个站点渲染出的业务状态一致——这不是本主题的兼容层,
|
||||
* 而是给外部参考系统做固件管道。
|
||||
*/
|
||||
const REFERENCE_META_KEY = 'zib_other_data';
|
||||
|
||||
if (!in_array($action, ['create', 'cleanup'], true)) {
|
||||
WP_CLI::error('Expected create or cleanup.');
|
||||
@@ -76,7 +83,7 @@ $manifest = [
|
||||
'files' => [],
|
||||
'options' => [],
|
||||
];
|
||||
foreach (['jio_settings', 'zibll_options'] as $option_name) {
|
||||
foreach (['jio_settings', 'reference_options'] as $option_name) {
|
||||
$manifest['options'][$option_name] = [
|
||||
'exists' => get_option($option_name, null) !== null,
|
||||
'value' => get_option($option_name),
|
||||
@@ -110,7 +117,7 @@ try {
|
||||
'post_related_orderby' => 'views',
|
||||
'related_title' => '相关推荐',
|
||||
];
|
||||
foreach (['jio_settings', 'zibll_options'] as $option_name) {
|
||||
foreach (['jio_settings', 'reference_options'] as $option_name) {
|
||||
$current = get_option($option_name, []);
|
||||
update_option($option_name, array_merge(is_array($current) ? $current : [], $visual_options), false);
|
||||
}
|
||||
@@ -136,8 +143,8 @@ try {
|
||||
$manifest['users'][] = (int) $user_id;
|
||||
}
|
||||
|
||||
$category = wp_insert_term('视觉文章', 'category', ['slug' => 'zibll-article-fixture']);
|
||||
$tag = wp_insert_term('视觉标签', 'post_tag', ['slug' => 'zibll-visual-tag']);
|
||||
$category = wp_insert_term('视觉文章', 'category', ['slug' => 'article-fixture']);
|
||||
$tag = wp_insert_term('视觉标签', 'post_tag', ['slug' => 'visual-tag']);
|
||||
if (is_wp_error($category) || is_wp_error($tag)) {
|
||||
throw new RuntimeException('Unable to create article fixture terms.');
|
||||
}
|
||||
@@ -167,7 +174,7 @@ try {
|
||||
$background = imagecolorallocate($image, $rgb[0], $rgb[1], $rgb[2]);
|
||||
$foreground = imagecolorallocate($image, 255, 255, 255);
|
||||
imagefill($image, 0, 0, $background);
|
||||
imagestring($image, 5, 48, 48, 'Zibll 9.0 Article Fixture ' . ($index + 1), $foreground);
|
||||
imagestring($image, 5, 48, 48, 'Article Fixture ' . ($index + 1), $foreground);
|
||||
imagejpeg($image, $filepath, 92);
|
||||
imagedestroy($image);
|
||||
} else {
|
||||
@@ -193,9 +200,9 @@ try {
|
||||
file_put_contents($video_path, '');
|
||||
$manifest['files'][] = $video_path;
|
||||
$video_url = trailingslashit($upload['url']) . basename($video_path);
|
||||
$term_other = (array) get_term_meta($category_id, 'zib_other_data', true);
|
||||
$term_other = (array) get_term_meta($category_id, REFERENCE_META_KEY, true);
|
||||
$term_other['cover_image'] = $image_urls[4];
|
||||
update_term_meta($category_id, 'zib_other_data', $term_other);
|
||||
update_term_meta($category_id, REFERENCE_META_KEY, $term_other);
|
||||
update_term_meta($category_id, 'cover_image', $image_urls[4]);
|
||||
update_term_meta($category_id, 'jio_category_cover_id', $attachment_ids[4]);
|
||||
|
||||
@@ -211,7 +218,7 @@ try {
|
||||
['slug' => 'first-image', 'title' => '文章首图作为缩略图', 'first_image' => 3],
|
||||
];
|
||||
foreach ($scenarios as $index => $scenario) {
|
||||
$content = '<p>固定摘要正文,用于 Zibll 与 Jio 的文章列表逐像素比较。</p>';
|
||||
$content = '<p>固定摘要正文,用于参考主题与 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'])) {
|
||||
@@ -245,26 +252,26 @@ try {
|
||||
$post_meta = [
|
||||
'subtitle' => ' · 固定副标题',
|
||||
];
|
||||
$zib_meta = (array) get_post_meta($post_id, 'zib_other_data', true);
|
||||
$zib_meta['subtitle'] = $post_meta['subtitle'];
|
||||
$reference_meta = (array) get_post_meta($post_id, REFERENCE_META_KEY, true);
|
||||
$reference_meta['subtitle'] = $post_meta['subtitle'];
|
||||
if (isset($scenario['external'])) {
|
||||
$post_meta['thumbnail_url'] = $image_urls[(int) $scenario['external']];
|
||||
$zib_meta['thumbnail_url'] = $post_meta['thumbnail_url'];
|
||||
$reference_meta['thumbnail_url'] = $post_meta['thumbnail_url'];
|
||||
}
|
||||
if (!empty($scenario['slides'])) {
|
||||
$post_meta['featured_slide'] = implode(',', array_slice($attachment_ids, 0, 4));
|
||||
$zib_meta['featured_slide'] = $post_meta['featured_slide'];
|
||||
$reference_meta['featured_slide'] = $post_meta['featured_slide'];
|
||||
}
|
||||
if (!empty($scenario['video'])) {
|
||||
$post_meta['featured_video'] = $video_url;
|
||||
$zib_meta['featured_video'] = $video_url;
|
||||
$reference_meta['featured_video'] = $video_url;
|
||||
}
|
||||
$zib_meta['views'] = 1200 + ($index * 137);
|
||||
$zib_meta['like'] = 30 + ($index * 7);
|
||||
update_post_meta($post_id, 'zib_other_data', $zib_meta);
|
||||
$reference_meta['views'] = 1200 + ($index * 137);
|
||||
$reference_meta['like'] = 30 + ($index * 7);
|
||||
update_post_meta($post_id, REFERENCE_META_KEY, $reference_meta);
|
||||
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_post_views', $reference_meta['views']);
|
||||
update_post_meta($post_id, '_jio_like_count', $reference_meta['like']);
|
||||
update_post_meta($post_id, '_jio_favorite_count', 10 + $index);
|
||||
|
||||
$pay_type = (string) ($scenario['pay'] ?? 'no');
|
||||
@@ -287,7 +294,7 @@ try {
|
||||
stick_post((int) $manifest['posts'][0]);
|
||||
}
|
||||
update_option($manifest_key, $manifest, false);
|
||||
WP_CLI::success('Created identical Zibll/Jio article fixtures.');
|
||||
WP_CLI::success('Created identical reference/Jio article fixtures.');
|
||||
} catch (Throwable $error) {
|
||||
$cleanup($manifest);
|
||||
WP_CLI::error('Fixture creation rolled back: ' . $error->getMessage());
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"schema": "jio.zibll-article-visual-matrix.v1",
|
||||
"schema": "jio.article-visual-matrix.v1",
|
||||
"reference": "https://localhost:9443",
|
||||
"candidate": "https://localhost",
|
||||
"fixture": "/visual-fixtures/article-fixtures.php",
|
||||
@@ -19,11 +19,11 @@
|
||||
"routes": [
|
||||
{ "id": "home", "path": "/", "states": ["default", "hover", "loading", "error"] },
|
||||
{ "id": "archive", "path": "/?post_type=post", "states": ["default", "numbered", "load-more", "infinite", "no-more"] },
|
||||
{ "id": "category", "path": "/category/zibll-article-fixture/", "states": ["default", "empty"] },
|
||||
{ "id": "tag", "path": "/tag/zibll-visual-tag/", "states": ["default", "empty"] },
|
||||
{ "id": "category", "path": "/category/article-fixture/", "states": ["default", "empty"] },
|
||||
{ "id": "tag", "path": "/tag/visual-tag/", "states": ["default", "empty"] },
|
||||
{ "id": "search", "path": "/?s=视觉", "states": ["default", "empty"] },
|
||||
{ "id": "author-a", "path": "/author/zibll_article_v1_author_a/", "states": ["default", "numbered", "load-more"] },
|
||||
{ "id": "single-related", "path": "/zibll_article_v1_featured/", "states": ["img", "list", "text"] }
|
||||
{ "id": "author-a", "path": "/author/article_v1_author_a/", "states": ["default", "numbered", "load-more"] },
|
||||
{ "id": "single-related", "path": "/article_v1_featured/", "states": ["img", "list", "text"] }
|
||||
],
|
||||
"postScenarios": ["featured", "no-image", "multi", "external", "slides", "video", "paid", "vip", "first-image"],
|
||||
"capture": {
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
name: jio-zibll-visual-reference
|
||||
name: jio-visual-reference
|
||||
|
||||
services:
|
||||
db:
|
||||
image: mariadb:11.4
|
||||
environment:
|
||||
MARIADB_DATABASE: zibll_reference
|
||||
MARIADB_USER: zibll
|
||||
MARIADB_PASSWORD: zibll
|
||||
MARIADB_DATABASE: reference
|
||||
MARIADB_USER: visual
|
||||
MARIADB_PASSWORD: visual
|
||||
MARIADB_RANDOM_ROOT_PASSWORD: "1"
|
||||
volumes:
|
||||
- zibll_db:/var/lib/mysql
|
||||
- reference_db:/var/lib/mysql
|
||||
healthcheck:
|
||||
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
|
||||
interval: 5s
|
||||
@@ -23,14 +23,14 @@ services:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
WORDPRESS_DB_HOST: db
|
||||
WORDPRESS_DB_USER: zibll
|
||||
WORDPRESS_DB_PASSWORD: zibll
|
||||
WORDPRESS_DB_NAME: zibll_reference
|
||||
WORDPRESS_DB_USER: visual
|
||||
WORDPRESS_DB_PASSWORD: visual
|
||||
WORDPRESS_DB_NAME: reference
|
||||
volumes:
|
||||
- zibll_wp:/var/www/html
|
||||
- reference_wp:/var/www/html
|
||||
- type: bind
|
||||
source: ${ZIBLL_THEME_PATH}
|
||||
target: /var/www/html/wp-content/themes/zibll
|
||||
source: ${REFERENCE_THEME_PATH}
|
||||
target: /var/www/html/wp-content/themes/reference
|
||||
read_only: true
|
||||
- type: bind
|
||||
source: .
|
||||
@@ -44,8 +44,8 @@ services:
|
||||
ports:
|
||||
- "9443:443"
|
||||
volumes:
|
||||
- ./zibll-Caddyfile:/etc/caddy/Caddyfile:ro
|
||||
- zibll_caddy_data:/data
|
||||
- ./Caddyfile:/etc/caddy/Caddyfile:ro
|
||||
- reference_caddy_data:/data
|
||||
|
||||
wp:
|
||||
image: wordpress:cli-php8.1
|
||||
@@ -57,14 +57,14 @@ services:
|
||||
user: "33:33"
|
||||
environment:
|
||||
WORDPRESS_DB_HOST: db
|
||||
WORDPRESS_DB_USER: zibll
|
||||
WORDPRESS_DB_PASSWORD: zibll
|
||||
WORDPRESS_DB_NAME: zibll_reference
|
||||
WORDPRESS_DB_USER: visual
|
||||
WORDPRESS_DB_PASSWORD: visual
|
||||
WORDPRESS_DB_NAME: reference
|
||||
volumes:
|
||||
- zibll_wp:/var/www/html
|
||||
- reference_wp:/var/www/html
|
||||
- type: bind
|
||||
source: ${ZIBLL_THEME_PATH}
|
||||
target: /var/www/html/wp-content/themes/zibll
|
||||
source: ${REFERENCE_THEME_PATH}
|
||||
target: /var/www/html/wp-content/themes/reference
|
||||
read_only: true
|
||||
- type: bind
|
||||
source: .
|
||||
@@ -73,6 +73,6 @@ services:
|
||||
entrypoint: ["wp"]
|
||||
|
||||
volumes:
|
||||
zibll_db:
|
||||
zibll_wp:
|
||||
zibll_caddy_data:
|
||||
reference_db:
|
||||
reference_wp:
|
||||
reference_caddy_data:
|
||||
@@ -28,7 +28,7 @@ test('modal and sign page use one renderer and one state machine', () => {
|
||||
assert.doesNotMatch(loader, /sign-register/);
|
||||
});
|
||||
|
||||
test('static Zibll authentication dimensions and responsive contract are retained', () => {
|
||||
test('static authentication dimensions and responsive contract are retained', () => {
|
||||
const css = read('assets/css/routes/sign.css');
|
||||
assert.match(css, /\.sign\s*\{[\s\S]*?max-width:\s*330px/);
|
||||
assert.match(css, /#u_sign\.has-sign-img \.modal-dialog\s*\{\s*width:\s*800px/);
|
||||
@@ -258,15 +258,15 @@ test('brand icons resolve Gitee and Google to the reference SVG sprite', () => {
|
||||
|
||||
// FA5 Free ships no Gitee brand glyph and no multi-colour Google mark, so both
|
||||
// resolve to the theme's own symbol sheet exactly like the reference does.
|
||||
assert.match(icons, /'gitee'\s*=>\s*'zibsvg-gitee'/);
|
||||
assert.match(icons, /'google'\s*=>\s*'zibsvg-google-color'/);
|
||||
assert.match(icons, /'gitee'\s*=>\s*'jiosvg-gitee'/);
|
||||
assert.match(icons, /'google'\s*=>\s*'jiosvg-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, /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]*?jio_icon_markup\(\$class, \$extra_classes\)/);
|
||||
// in a bogus <i class="jiosvg-…">.
|
||||
assert.match(icons, /strpos\(\$class, 'jiosvg-'\) === 0[\s\S]*?jio_icon_markup\(\$class, \$extra_classes\)/);
|
||||
});
|
||||
|
||||
test('the top-nav user popover reproduces the reference quick-login block', () => {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/**
|
||||
* 余额充值弹窗壳契约(第 19 轮 Zibll 对齐 + 第 21 轮全页不可点击回归护栏)。
|
||||
* 余额充值弹窗壳契约(第 19 轮参考主题对齐 + 第 21 轮全页不可点击回归护栏)。
|
||||
*
|
||||
* 关键约束:本主题的弹窗壳是服务端静态输出,与 Zibll(refresh_modal JS
|
||||
* 按需创建、且其 CSS 无 bootstrap .modal 基础规则)架构不同。若弹窗壳在
|
||||
* 关键约束:本主题的弹窗壳是服务端静态输出,与按需创建弹窗壳的运行时方案
|
||||
* (其 CSS 无 bootstrap .modal 基础规则)架构不同。若弹窗壳在
|
||||
* 静止态没有被显式隐藏,main.css 的 .flex{display:flex}(后加载、与
|
||||
* bootstrap.min.css 的 .modal{display:none} 同特异性、后者胜)会让
|
||||
* `class="modal flex jc fade"` 壳永久 display:flex 铺满全屏,叠加 .fade
|
||||
@@ -29,7 +29,7 @@ const creditsJs = read('assets', 'js', 'credits.js');
|
||||
const creditsCss = read('assets', 'css', 'routes', 'user-center', 'credits.css');
|
||||
const balanceRest = read('src', 'Credits', 'BalanceServiceRest.php');
|
||||
|
||||
test('弹窗壳为 Zibll refresh_modal 同构静态壳并带 data 钩子', () => {
|
||||
test('弹窗壳为静态服务端渲染壳并带 data 钩子', () => {
|
||||
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"/);
|
||||
@@ -41,7 +41,7 @@ test('弹窗壳静止态必须被 credits.css 显式隐藏(.flex 覆盖回归
|
||||
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 生产环境即此形态)。
|
||||
// .flex.jc 双轴居中(生产环境即此形态)。
|
||||
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),静止态同样
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
* 卡密兑换渠道契约(三个购买弹窗 · 1:1 对齐参考主题)。
|
||||
*
|
||||
* 背景:余额卡片顶部的独立「卡密兑换」通用弹窗已删除,兑换入口收敛到
|
||||
* 三个购买弹窗的支付方式中(对齐参考主题 zibpay_get_payment_methods +
|
||||
* zibpay_is_allow_card_pass_pay 过滤器):
|
||||
* 三个购买弹窗的支付方式中(对齐参考实现的支付方式装配 +
|
||||
* 卡密支付开关过滤器):
|
||||
* 1) 余额充值弹窗(credits-wallet.php,服务端渲染)
|
||||
* 2) 购买积分弹窗(credits-balance.php,服务端渲染,Zibll points-pay modal 结构)
|
||||
* 2) 购买积分弹窗(credits-balance.php,服务端渲染,points-pay 弹窗结构)
|
||||
* 3) 会员购买/续费弹窗(VipServiceModal JSON 视图模型 + pay-vip.js 渲染)
|
||||
*
|
||||
* 后台语义:余额充值 → 卡密兑换 → 三个独立开关
|
||||
@@ -65,7 +65,7 @@ test('余额充值弹窗:卡密渠道为支付方式之一,卡号/卡密输
|
||||
assert.match(wallet, /\$charge_card_in_methods/);
|
||||
});
|
||||
|
||||
test('购买积分弹窗:Zibll points-pay modal 结构 + 卡密渠道', () => {
|
||||
test('购买积分弹窗:points-pay 弹窗结构 + 卡密渠道', () => {
|
||||
// 商品/自定义选择 + 支付方式 + hidden + 立即支付。
|
||||
assert.match(creditsRenderer, /data-jio-points-pay-modal/);
|
||||
assert.match(creditsRenderer, /data-jio-points-pay-form/);
|
||||
@@ -114,7 +114,7 @@ test('会员弹窗:卡密渠道注入与直兑分流(不创建支付订单
|
||||
assert.match(vipModal, /'currency_mark' =>/);
|
||||
// 后端防御:purchase() 拒绝 card_pass(卡密直兑走 redeem)。
|
||||
assert.match(vipPurchase, /jio_card_pass_redeem_required/);
|
||||
// 前端:Zibll initiate_pay_input 同构组件——payment-method-radio logo 行、
|
||||
// 前端:支付方式同构组件——payment-method-radio logo 行、
|
||||
// 我的余额盒(data-controller == balance)、卡密盒(password_box 类结构)、
|
||||
// hidden payment_method、initiate-pay 按钮。
|
||||
assert.match(payVipJs, /payment-method-radio hollow-radio flex-auto pointer/);
|
||||
@@ -128,11 +128,11 @@ test('会员弹窗:卡密渠道注入与直兑分流(不创建支付订单
|
||||
assert.match(payVipJs, /mt6 but jb-red initiate-pay btn-block radius/);
|
||||
assert.doesNotMatch(payVipJs, /uc-vip-shop__method|jio_vip_pay_method|jio-vip-submit/);
|
||||
// 按钮字样对齐参考主题:会员弹窗即 initiate_pay_input(4) 默认「立即支付」;
|
||||
// 无可用支付方式渲染 Zibll 同款 badg 提示(而非禁用按钮/自实现文案)。
|
||||
// 无可用支付方式渲染同款 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(jioPay, /'balance'\s+=> __\('余额', 'jio'\)/);
|
||||
assert.match(jioPay, /'wechatpay'\s+=> __\('微信', 'jio'\)/);
|
||||
// 显隐:通用 data-controller(==/!=)匹配 + 卡密选中隐藏套餐。
|
||||
@@ -204,10 +204,10 @@ test('核销订单契约:金额恒为 0,订单名称按 note 生成', () =>
|
||||
assert.match(userOrders, /永久/);
|
||||
});
|
||||
|
||||
test('多渠道防挤压:图标 flex:none + 行不换行项目收缩成芯片(对齐 Zibll)', () => {
|
||||
test('多渠道防挤压:图标 flex:none + 行不换行项目收缩成芯片', () => {
|
||||
// 图标禁用 flex 压缩并抵消 Jio bootstrap 全局 img{max-width:100%}
|
||||
// (flex 压缩时把图标钳制到被压窄的项宽,实测 5 渠道 400px 弹窗图标
|
||||
// 被压成 16px 细条)。行保持 Zibll 原样不换行:空间不足时项目收缩为
|
||||
// 被压成 16px 细条)。行保持不换行:空间不足时项目收缩为
|
||||
// 「图标上/文字下」芯片(实测 5 渠道一行 5 个、6 渠道一行 6 个,图标
|
||||
// 恒为固有比例),绝不允许整行提前断行。
|
||||
const rule = mainCss.match(/\.payment-method-radio img \{[\s\S]*?\n\}/);
|
||||
|
||||
@@ -155,7 +155,7 @@ test('every comment-rendering singular loads the owned build bundle', () => {
|
||||
|
||||
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)。
|
||||
// 点赞按钮:svg + 文字 + 计数。
|
||||
assert.ok(item.includes("<text></text><count>"), 'like button must render <text> before <count>');
|
||||
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');
|
||||
|
||||
@@ -15,7 +15,7 @@ function renderFixtures() {
|
||||
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="zibsvg-hot" viewbox="0 0 16 16" aria-hidden="true"><use href="#zibsvg-hot"><\/use><\/svg>/);
|
||||
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\]/);
|
||||
@@ -109,6 +109,6 @@ test('archive filters use the shared ajax-next handler without a category REST/a
|
||||
assert.equal(fs.existsSync(path.join(root, 'assets', 'js', 'core', 'archive.js')), false);
|
||||
|
||||
const widgetCss = read('assets/widgets/frontend.css');
|
||||
assert.match(widgetCss, /\.zib-widget\.posts-mini-lists\s*\{\s*padding:\s*5px;/);
|
||||
assert.match(widgetCss, /\.jio-widget-box\.posts-mini-lists\s*\{\s*padding:\s*5px;/);
|
||||
assert.doesNotMatch(widgetCss, /\.widget-tab-post[^{}]*\{[^}]*!important/s);
|
||||
});
|
||||
@@ -79,7 +79,7 @@ function wp_reset_postdata(): void { $GLOBALS['discovery_current_post'] = null;
|
||||
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_svg_icon(string $name): string { return '<svg class="jiosvg-' . esc_attr($name) . '" viewbox="0 0 16 16" aria-hidden="true"><use href="#jiosvg-' . 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];
|
||||
|
||||
@@ -146,11 +146,11 @@ test('homepage tabs refresh the original pane DOM through the runtime post_ajax
|
||||
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 栏目链接。
|
||||
// 栏目链接契约:data-toggle="tab" + data-ajax。
|
||||
assert.match(layout, /<a data-toggle="tab" data-ajax="/)
|
||||
assert.match(layout, /href="#index-tab-main"/)
|
||||
assert.match(layout, /class="index-tab affix-header-sm relative home-tab-nav-box mb10" offset-top="-8"/)
|
||||
// pane 本身是原位刷新容器:zib_index_tab('content') 契约。
|
||||
// pane 本身是原位刷新容器。
|
||||
assert.match(layout, /class="posts-row ajaxpager tab-pane fade in active" id="index-tab-main"/)
|
||||
assert.match(layout, /class="ajaxpager tab-pane fade" id="index-tab-/)
|
||||
assert.match(layout, /<span class="post_ajax_trigger"><a class="ajax_load ajax-next ajax-open" href="/)
|
||||
@@ -162,12 +162,12 @@ test('homepage tabs refresh the original pane DOM through the runtime post_ajax
|
||||
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 链接。
|
||||
// 排序菜单契约:win-ajax-replace="filter" + ajax-next 链接。
|
||||
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),排序原位刷新才有效。
|
||||
// 主查询吃 ?orderby=,排序原位刷新才有效。
|
||||
assert.match(homepage, /function jio_homepage_get_request_sort/)
|
||||
assert.match(read('inc/helpers/homepage/content.php'), /jio_homepage_get_request_sort\(\)/)
|
||||
|
||||
@@ -198,9 +198,9 @@ test('modal fragment lifecycle reruns auto_fun after loaded.bs.modal', () => {
|
||||
assert.match(source, /loaded\.bs\.modal[\s\S]*?auto_fun/);
|
||||
});
|
||||
|
||||
test('JSON success lifecycle remains zib_ajax.success', () => {
|
||||
test('JSON success lifecycle remains jio_ajax.success', () => {
|
||||
const source = read('assets/js/runtime/main.js');
|
||||
assert.match(source, /zib_ajax\.success/);
|
||||
assert.match(source, /jio_ajax\.success/);
|
||||
});
|
||||
|
||||
test('Miniimgbox owns data-imgbox and lazySizes has one integration owner', () => {
|
||||
|
||||
@@ -53,10 +53,10 @@ test('widget icon cards restore the reference surface height without compact ove
|
||||
|
||||
assert.match(
|
||||
widgetCss,
|
||||
/\.zib-widget\s*\{[^}]*margin-bottom:\s*20px[^}]*padding:\s*15px[^}]*box-shadow:\s*0 0 10px/s,
|
||||
/\.jio-widget-box\s*\{[^}]*margin-bottom:\s*20px[^}]*padding:\s*15px[^}]*box-shadow:\s*0 0 10px/s,
|
||||
);
|
||||
assert.match(widgetCss, /\.zib-widget\.padding-10\s*\{\s*padding:\s*10px/);
|
||||
assert.match(widgetCss, /\.zib-widget\.nobottom\.notop\s*\{[^}]*padding-top:\s*0[^}]*padding-bottom:\s*0/s);
|
||||
assert.match(widgetCss, /\.jio-widget-box\.padding-10\s*\{\s*padding:\s*10px/);
|
||||
assert.match(widgetCss, /\.jio-widget-box\.nobottom\.notop\s*\{[^}]*padding-top:\s*0[^}]*padding-bottom:\s*0/s);
|
||||
assert.doesNotMatch(widgetCss, /\.row\.gutters-5/);
|
||||
assert.doesNotMatch(widgetCss, /\.icon-card\s*,\s*\.icon-cover-card\s*\{\s*font-size:\s*12px/);
|
||||
assert.doesNotMatch(widgetCss, /\.icon-cover-card\s*\{[^}]*padding-(?:top|bottom)/s);
|
||||
@@ -70,7 +70,7 @@ test('header icon-card defaults use bundled Font Awesome icons', () => {
|
||||
];
|
||||
|
||||
for (const source of defaults) {
|
||||
assert.doesNotMatch(source, /'icon'\s*=>\s*'zibsvg-/);
|
||||
assert.doesNotMatch(source, /'icon'\s*=>\s*'jiosvg-/);
|
||||
for (const icon of ['gem', 'star', 'fire', 'wrench']) {
|
||||
assert.match(source, new RegExp(`'icon'\\s*=>\\s*'fa fas fa-${icon}'`));
|
||||
}
|
||||
@@ -85,15 +85,15 @@ test('icon card renderers route every picker format through the shared icon rend
|
||||
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
|
||||
// `<i class="fa fas fa-jiosvg-hot">`, i.e. an invisible glyph, for the two
|
||||
// SVG formats the admin picker can store.
|
||||
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
|
||||
// `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, /jiosvg-/);
|
||||
// `zibsvg-` prefix (never emitted by any picker) must be gone.
|
||||
assert.match(helper, /str_starts_with\(\$value, 'jiosvg-'\) \|\| str_starts_with\(\$value, 'customsvg-'\)/);
|
||||
assert.doesNotMatch(helper, /zibsvg-/);
|
||||
assert.doesNotMatch(helper, /'fa fas fa-'\s*\.\s*\$icon_fa/);
|
||||
assert.doesNotMatch(helper, /<i class="'\s*\.\s*esc_attr\(\$icon_fa\)\s*\.\s*' em09"/);
|
||||
});
|
||||
|
||||
@@ -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-jio-icon-picker', 'jio-icon-picker__value']) {
|
||||
for (const token of ['X-WP-Nonce', 'customsvg-', 'jiosvg-', '`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');
|
||||
@@ -132,7 +132,7 @@ 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, /str_starts_with\(\$icon_class, 'jiosvg-'\)/);
|
||||
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');
|
||||
});
|
||||
@@ -149,7 +149,7 @@ test('icon markup upgrades legacy FA4 values to FA5 and never emits the FA4 form
|
||||
assert.match(helper, /preg_replace\('\/\^fa\\s\+fa-\/i', '', \(string\) \$name\)/, 'icon keys normalize the legacy FA4 form');
|
||||
});
|
||||
|
||||
test('brand icons render despite the zibll-legacy fa-fab double prefix', () => {
|
||||
test('brand icons render despite the legacy fa-fab double prefix', () => {
|
||||
const rule = /\.fa\.fab\{font-family:"Font Awesome 5 Brands";font-weight:400\}/;
|
||||
for (const file of ['assets/css/main.css', 'assets/css/admin-icon-picker.css', 'assets/admin/src/style.css']) {
|
||||
assert.match(read(file), rule, `${file} re-asserts the brand family`);
|
||||
|
||||
@@ -119,10 +119,10 @@ test('admin operations run through admin_post handlers with capability and nonce
|
||||
assert.match(service, /min\(50, max\(4, \(int\) \(\$args\['code_length'\] \?\? self::CODE_LENGTH\)\)\)/)
|
||||
})
|
||||
|
||||
test('import accepts Zibll-compatible lines with per-code rewards', () => {
|
||||
test('import accepts the documented line format with per-code rewards', () => {
|
||||
const service = read('src', 'Payment', 'InviteCode.php')
|
||||
assert.match(service, /public function import_codes\(array \$args\)/)
|
||||
// Zibll 行格式:`邀请码<分隔符>奖励<分隔符>标识`,奖励参数串键位兼容。
|
||||
// 行格式:`邀请码<分隔符>奖励<分隔符>标识`,奖励参数串键位兼容。
|
||||
assert.match(service, /level_integral/)
|
||||
assert.match(service, /vip_time/)
|
||||
assert.match(service, /in_array\(\$reward, \['无', 'none', '0'\]/)
|
||||
|
||||
+6
-6
@@ -6,9 +6,9 @@ const path = require('node:path');
|
||||
const root = path.resolve(__dirname, '..', '..');
|
||||
const read = (file) => fs.readFileSync(path.join(root, file), 'utf8');
|
||||
|
||||
test('modern widget schemas expose Zibll field names and defaults', () => {
|
||||
test('modern widget schemas expose reference field names and defaults', () => {
|
||||
const source = read('src/WidgetSystem/WidgetSchemas.php');
|
||||
for (const field of ['mini_opt', 'paginate', 'tabs', "'id' => 'cat'", "'id' => 'zibpay_type'", 'layout_ratio', 'slide_scale', 'm_layout_type', 'm_cover_ratio', 'image_layer', 'image_select']) {
|
||||
for (const field of ['mini_opt', 'paginate', 'tabs', "'id' => 'cat'", "'id' => 'pay_type'", 'layout_ratio', 'slide_scale', 'm_layout_type', 'm_cover_ratio', 'image_layer', 'image_select']) {
|
||||
assert.match(source, new RegExp(field.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')));
|
||||
}
|
||||
assert.doesNotMatch(source, /'id' => 'topics'/);
|
||||
@@ -22,7 +22,7 @@ test('tab and slider renderers retain reference DOM contracts', () => {
|
||||
const slider = read('src/WidgetSystem/Renderers/SliderRenderer.php');
|
||||
for (const token of ['widget-tab-post', 'posts-mini-lists', 'index-tab', 'tab-nav-theme', 'tab-pane fade', 'data-widget-lazy', 'post_ajax_loader', 'widget-ajaxpager']) assert.match(content, new RegExp(token.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')));
|
||||
assert.doesNotMatch(content, /post_ajax_trigger|ias-btn|data-widget-ias/);
|
||||
for (const token of ['relative zib-slider', 'new-swiper slide-widget', 'swiper-wrapper', 'swiper-slide', 'slide-layer', 'data-swiper-parallax', 'swiper-button-prev', 'swiper-pagination', 'widget-slider-graphic', 'slider-col', 'cover-col', 'widget-graphic-cover']) assert.match(slider, new RegExp(token.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')));
|
||||
for (const token of ['relative jio-slider-box', 'new-swiper slide-widget', 'swiper-wrapper', 'swiper-slide', 'slide-layer', 'data-swiper-parallax', 'swiper-button-prev', 'swiper-pagination', 'widget-slider-graphic', 'slider-col', 'cover-col', 'widget-graphic-cover']) assert.match(slider, new RegExp(token.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')));
|
||||
});
|
||||
|
||||
test('widget skeleton lives inside the AJAX container so the REST client can find it', () => {
|
||||
@@ -67,7 +67,7 @@ test('reference slider CSS includes responsive height and combo selectors', () =
|
||||
for (const token of ['--m-cover-ratio', '--cover-ratio', '--layout-ratio']) assert.match(mainCss + css, new RegExp(token.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')));
|
||||
});
|
||||
|
||||
test('icon card widgets expose Zibll vertical and horizontal contracts', () => {
|
||||
test('icon card widgets expose reference vertical and horizontal contracts', () => {
|
||||
const schemas = read('src/WidgetSystem/WidgetSchemas.php');
|
||||
const renderer = read('src/WidgetSystem/Renderers/GeneralRenderer.php');
|
||||
const helper = read('inc/helpers/jio-icon-cards.php');
|
||||
@@ -88,7 +88,7 @@ test('icon card widgets expose Zibll vertical and horizontal contracts', () => {
|
||||
assert.doesNotMatch(helper, /wp_kses_post\(\$desc\)/);
|
||||
});
|
||||
|
||||
test('button group uses the complete Zibll schema, DOM and responsive sizing contract', () => {
|
||||
test('button group uses the complete schema, DOM and responsive sizing contract', () => {
|
||||
const schema = read('src/WidgetSystem/WidgetSchemas.php');
|
||||
const renderer = read('src/WidgetSystem/Renderers/GeneralRenderer.php');
|
||||
const css = read('assets/widgets/frontend.css');
|
||||
@@ -105,7 +105,7 @@ test('button group uses the complete Zibll schema, DOM and responsive sizing con
|
||||
assert.match(css, /\.widget-buttons\.m-size-medium \.but\s*\{\s*padding:\s*8px 16px/);
|
||||
});
|
||||
|
||||
test('vertical notice supports only custom and article sources with Zibll swiper sizing', () => {
|
||||
test('vertical notice supports only custom and article sources with reference swiper sizing', () => {
|
||||
const schema = read('src/WidgetSystem/WidgetSchemas.php');
|
||||
const renderer = read('src/WidgetSystem/Renderers/GeneralRenderer.php');
|
||||
const postQuery = read('src/WidgetSystem/PostQuery.php');
|
||||
@@ -175,7 +175,7 @@ test('header VIP hover cards match the legacy markup and style contract', () =>
|
||||
assert.match(legacyMainCss, /\.navbar-top \.navbar-right \.sub-menu\s*\{[^}]*right:\s*0;/s)
|
||||
})
|
||||
|
||||
test('user card VIP CTA matches the reference zib_get_header_payvip_icon()', () => {
|
||||
test('user card VIP CTA matches the reference header VIP 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__\('开通会员 尊享会员权益', 'jio'\)/)
|
||||
@@ -190,7 +190,7 @@ test('VIP purchase modal matches legacy desktop-center and mobile-bottom positio
|
||||
|
||||
assert.doesNotMatch(purchaseModal, /mobileBottom:\s*false/)
|
||||
assert.match(modalRuntimeJs, /options\.mobileBottom !== false && isMobile\(\)/)
|
||||
// 壳几何 1:1 对齐 Zibll modal_pay_uservip:桌面 600px(bootstrap3 ≥768px
|
||||
// 壳几何 1:1:桌面 600px(bootstrap3 ≥768px
|
||||
// .modal-dialog)+ max-width calc(100% - 20px)(.modal.flex .modal-dialog);
|
||||
// 高度/滚动 = .modal.flex .modal-content 的 max-height calc(100vh - 20px)
|
||||
// + overflow-y auto(阈值放宽到整屏,正常内容不出现滚动条);
|
||||
@@ -301,13 +301,13 @@ 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', 'jio-'/)
|
||||
assert.match(frontendRenderer, /\$classes = \['jio-widget-box-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 同构:封面、头像框、名字行、统计徽章、简介、按钮。
|
||||
// 登录卡片与参考实现的用户卡同构:封面、头像框、名字行、统计徽章、简介、按钮。
|
||||
assert.match(userRenderer, /<div class="mb20">/)
|
||||
assert.match(userRenderer, /user-card zib-widget widget/)
|
||||
assert.match(userRenderer, /user-card jio-widget-box widget/)
|
||||
assert.match(userRenderer, /user-cover graphic" style="padding-bottom: 50%;"/)
|
||||
assert.match(userRenderer, /avatar-img avatar-lg/)
|
||||
assert.match(userRenderer, /user-name flex jc/)
|
||||
@@ -339,7 +339,7 @@ test('user profile and author widgets render the reference user-card contract',
|
||||
assert.match(userRenderer, /loged_title/)
|
||||
assert.doesNotMatch(userRenderer, /user-card__auth-btns|render_social_buttons\(/)
|
||||
// 后加载的 widget bundle 明确保留单层卡片并让封面贴边。
|
||||
assert.match(widgetCss, /\.user-card\.zib-widget\s*\{[^}]*padding:\s*0;[^}]*overflow:\s*hidden;/s)
|
||||
assert.match(widgetCss, /\.user-card\.jio-widget-box\s*\{[^}]*padding:\s*0;[^}]*overflow:\s*hidden;/s)
|
||||
assert.match(legacyMainCss, /\.user-card\.widget \.user-cover \+ \.card-content\s*\{[^}]*margin-top:\s*-55px;/s)
|
||||
// 参考主题契约:.widget-content 是无样式结构层,小工具卡片自带内边距;
|
||||
// 任何样式层都不得给 .widget-content 加 padding,否则小工具与内容列宽之间出现左右空隙。
|
||||
@@ -426,7 +426,7 @@ test('header user-card membership stays on one row with a date-only expiry', ()
|
||||
// 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, /jio-header-payvip/)
|
||||
// Expiry badge follows zib_get_user_vip_exp_date_text(): an expiry that has
|
||||
// Expiry badge follows the reference expiry 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\(\)/)
|
||||
|
||||
@@ -8,15 +8,15 @@ const test = require('node:test');
|
||||
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', () => {
|
||||
test('paid article and sidebar widget use the purchase-card DOM', () => {
|
||||
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/jio-single-render.php');
|
||||
const hooks = read('inc/core-hooks.php');
|
||||
|
||||
assert.match(pay, /zib-widget pay-box pay-widget/);
|
||||
assert.match(pay, /zib-widget pay-box order-type-/);
|
||||
assert.match(pay, /jio-widget-box pay-box pay-widget/);
|
||||
assert.match(pay, /jio-widget-box pay-box order-type-/);
|
||||
assert.match(pay, /flex pay-flexbox/);
|
||||
assert.match(pay, /hide-sm pay-thumb/);
|
||||
assert.match(pay, /pay-tag abs-center/);
|
||||
@@ -38,7 +38,7 @@ test('paid article and sidebar widget use the Zibll purchase-card DOM', () => {
|
||||
assert.doesNotMatch(pay, /jio_get_user_center_tab_url\('vip'\)/);
|
||||
assert.match(pay, /cashier-link/);
|
||||
assert.match(pay, /function jio_get_paid_box_html/);
|
||||
assert.match(pay, /pay-box zib-widget paid-box order-type-/);
|
||||
assert.match(pay, /pay-box jio-widget-box paid-box order-type-/);
|
||||
assert.match(pay, /flex ac jb-green padding-10 em09/);
|
||||
assert.match(pay, /订单号/);
|
||||
assert.doesNotMatch(pay, /'流水号'/);
|
||||
@@ -65,7 +65,7 @@ test('the purchase card owns one direct stylesheet and no retired paywall path',
|
||||
|
||||
assert.match(assets, /assets\/css\/frontend\/pay-box\.css|\'pay-box\'/);
|
||||
assert.match(shortcodes, /return jio_get_paywall_html\(\$post, \['message' => \$message\]\);/);
|
||||
assert.match(css, /\.zib-widget\.pay-box/);
|
||||
assert.match(css, /\.jio-widget-box\.pay-box/);
|
||||
assert.match(css, /\.pay-box \.pay-vip-level-icon/);
|
||||
assert.match(css, /\.pay-box \.pay-vip-exclusive/);
|
||||
assert.doesNotMatch(css, /@media/);
|
||||
|
||||
@@ -88,7 +88,7 @@ test('基础渠道列表与卡密渠道都经过同一排序实现', () => {
|
||||
|
||||
test('会员弹窗不再保留重复的支付方式列表回退分支', () => {
|
||||
assert.doesNotMatch(vipPackages, /class_exists\('\\\\Jio\\\\Payment\\\\Gateway'\)/);
|
||||
// 卡密渠道注入契约保持(对齐参考主题 zibpay_get_payment_methods)。
|
||||
// 卡密渠道注入契约保持。
|
||||
assert.match(vipPackages, /'id' => 'card_pass', 'name' => __\('卡密', 'jio'\)/);
|
||||
});
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ test('front submit follows the reference newposts shell and cards', () => {
|
||||
assert.match(layout, /if \(is_page_template\('templates\/pages\/page-submit\.php'\)\) \{\s*return true;/);
|
||||
assert.equal((page.match(/class="sidebar show-sidebar"/g) || []).length, 1);
|
||||
assert.match(page, /'jio-submit',[\s\S]*?\['jio-public'\]/);
|
||||
assert.match(main, /zib-widget full-widget-sm editor-main-box/);
|
||||
assert.match(main, /jio-widget-box full-widget-sm editor-main-box/);
|
||||
assert.match(main, /relative newposts-title/);
|
||||
assert.match(terms, /newpost-cat-drop drop-select dropdown dependency-box/);
|
||||
assert.match(terms, /data-toggle-class="open" data-target="\.newpost-cat-drop"/);
|
||||
@@ -59,7 +59,7 @@ test('front submit follows the reference newposts shell and cards', () => {
|
||||
assert.doesNotMatch(submitCss, /\.term-select-box \.dropdown-menu|\.term-select-box \.drop-select\s*{/);
|
||||
assert.match(mainCss, /\.drop-select \.dropdown-menu\s*{[^}]*box-shadow:\s*0 0 0 8000px/);
|
||||
assert.match(mainCss, /\.open > \.dropdown-menu\s*{[^}]*visibility:\s*unset/);
|
||||
assert.match(pay, /zib-widget dependency-box mb10-sm newpost-pay-box/);
|
||||
assert.match(pay, /jio-widget-box dependency-box mb10-sm newpost-pay-box/);
|
||||
assert.match(pay, /pay-dawnload-edit/);
|
||||
assert.match(submit, /separator muted-3-color theme-box/);
|
||||
assert.match(submit, /new-posts-submit/);
|
||||
|
||||
@@ -175,9 +175,10 @@ test('quick-edit save is guarded by screen, autosave, revision and capability',
|
||||
assert.match(list, /\$post->post_type !== 'post'/)
|
||||
})
|
||||
|
||||
test('no legacy zib-style field names or topic links survive', () => {
|
||||
test('no legacy upstream field names or topic links survive', () => {
|
||||
assert.doesNotMatch(list, /zib_bulk_edit/)
|
||||
assert.doesNotMatch(list, /posts_zibpay/)
|
||||
// 商品类型筛选字段的规范名是 pay_type,zibpay_type 是历史名。
|
||||
assert.doesNotMatch(list, /zibpay_type/)
|
||||
assert.doesNotMatch(list, /pay_cuont/)
|
||||
// 本项目不使用「自动售卡」类目。
|
||||
|
||||
@@ -242,7 +242,7 @@ test('related posts, author page and widgets use their dedicated renderers', ()
|
||||
assert.match(related, /<li><a class="icon-circle"/);
|
||||
assert.match(related, /\['category', 'topics', 'post_tag'\]/);
|
||||
assert.match(related, /'terms'\s+=>\s+array_map\('intval', \(array\) \$term_ids\)/);
|
||||
assert.match(related, /class=\"zib-widget\"/);
|
||||
assert.match(related, /class=\"jio-widget-box\"/);
|
||||
assert.doesNotMatch(related, /post_related_source/);
|
||||
assert.doesNotMatch(related, /post__not_in/);
|
||||
assert.match(related, /\$args\['meta_key'\]\s*=\s*'jio_post_views'/);
|
||||
@@ -321,7 +321,7 @@ test('after-box author card mirrors legacy user-card structure', () => {
|
||||
assert.match(userCard, /swiper-container more-posts swiper-scroll/);
|
||||
assert.match(userCard, /'class'\s+=>\s+'em09', 'cut'\s+=>\s+32/);
|
||||
assert.doesNotMatch(userCard, /jio-more-post-card/);
|
||||
assert.match(css, /^\.zib-widget \{/m);
|
||||
assert.match(css, /^\.jio-widget-box \{/m);
|
||||
assert.doesNotMatch(css, /relates-thumb \.graphic\.style-3 \.graphic-img/);
|
||||
assert.doesNotMatch(authorActions, /\.user-card-author|\.user-card \.author|user-card-posts/);
|
||||
assert.match(identity, /function jio_get_user_follow\(/);
|
||||
@@ -354,7 +354,7 @@ test('AJAX pagination can recover, caps auto loading and rehydrates appended med
|
||||
assert.doesNotMatch(php, /next-page ajax-next/);
|
||||
assert.match(php, /lazyload-action="ias"/);
|
||||
assert.match(js, /lazybeforeunveil/);
|
||||
// 1:1 对齐参考主题 zib_paging():.ajax-pag 内只渲染翻页控件,不预渲染 loader。
|
||||
// .ajax-pag 内只渲染翻页控件,不预渲染 loader。
|
||||
// 预渲染的 loader 会被 runtime post_ajax() 复用并丢弃动态骨架,导致排序切换
|
||||
// 永远是写死 2 条 list 骨架。骨架改由 pagination.js 在请求时按视口创建。
|
||||
assert.doesNotMatch(php, /post_ajax_loader/);
|
||||
@@ -391,8 +391,8 @@ test('front-end carousel CSS is conditional on carousel block content', () => {
|
||||
});
|
||||
|
||||
test('the pinned visual baseline records all source checksums', () => {
|
||||
const baseline = JSON.parse(read('tests/visual/zibll-9.0-article-baseline.json'));
|
||||
assert.equal(baseline.theme, 'Zibll');
|
||||
const baseline = JSON.parse(read('tests/visual/article-baseline.json'));
|
||||
assert.equal(baseline.theme, 'reference');
|
||||
assert.equal(baseline.version, '9.0');
|
||||
const files = Object.values(baseline.files);
|
||||
assert.ok(files.length >= 6);
|
||||
@@ -868,7 +868,7 @@ test('content max width is one contract from admin option to runtime var', () =>
|
||||
const themeSearch = read('src/Settings/ThemeSearch.php');
|
||||
const builder = read('src/Frontend/InlineStyleBuilder.php');
|
||||
|
||||
// Free number input (zibll layout_max_width parity): UI range, REST validation
|
||||
// Free number input: UI range, REST validation
|
||||
// and runtime clamp must agree, or saves are rejected / silently clamped.
|
||||
const vueField = layoutSchema.match(/key: 'container_max_width'[^\n]*/)[0];
|
||||
assert.match(vueField, /type: 'number'/);
|
||||
|
||||
@@ -19,7 +19,7 @@ for (const style of ['list', 'card', 'mini']) {
|
||||
assert.match(html, /data-widget-page="2"/);
|
||||
assert.match(html, /class="post_ajax_loader"/);
|
||||
assert.doesNotMatch(html, /post_ajax_loader is-loading|class="posts-row|ajax-next/);
|
||||
if (style === 'mini') assert.match(html, /class="zib-widget posts-mini-lists"/);
|
||||
if (style === 'mini') assert.match(html, /class="jio-widget-box posts-mini-lists"/);
|
||||
});
|
||||
|
||||
test(`article ${style} widget reserves visible skeletons for lazy loading`, () => {
|
||||
|
||||
@@ -18,7 +18,7 @@ test('article TOC widget uses the shared reference tree styles, not posts-navs p
|
||||
'.posts-nav-lists li.active::before',
|
||||
]) assert.ok(mainCss.includes(selector), `main.css retains reference tree rule ${selector}`);
|
||||
assert.match(mainCss, /\.posts-nav-box\s*\{[^}]*font-family:\s*inherit;[^}]*font-size:\s*14px;[^}]*line-height:\s*1\.42857143;/s);
|
||||
const panel = mainCss.match(/\.posts-nav-box \.zib-widget\s*\{([^}]*)\}/)?.[1] || '';
|
||||
const panel = mainCss.match(/\.posts-nav-box \.jio-widget-box\s*\{([^}]*)\}/)?.[1] || '';
|
||||
for (const declaration of [
|
||||
/background-color:\s*var\(--main-bg-color\)/,
|
||||
/padding:\s*15px/,
|
||||
@@ -31,7 +31,7 @@ test('article TOC widget uses the shared reference tree styles, not posts-navs p
|
||||
test('mobile TOC panel and its collapse control remain styled and accessible', () => {
|
||||
const mainCss = read('assets/css/main.css');
|
||||
|
||||
assert.match(mainCss, /\.mobile-navbar \.posts-nav-box \.zib-widget\s*\{[^}]*background:\s*var\(--muted-border-color\);[^}]*box-shadow:\s*none;/s);
|
||||
assert.match(mainCss, /\.mobile-navbar \.posts-nav-box \.jio-widget-box\s*\{[^}]*background:\s*var\(--muted-border-color\);[^}]*box-shadow:\s*none;/s);
|
||||
assert.match(mainCss, /\.posts-nav-lists \.nav-toggle-collapse\s*\{[^}]*border:\s*0;[^}]*background:\s*transparent;/s);
|
||||
assert.match(mainCss, /\.posts-nav-box \.posts-nav-lists :where\(a, \.nav-toggle-collapse\):focus-visible/);
|
||||
assert.match(mainCss, /@media \(prefers-reduced-motion: reduce\)[\s\S]*?\.posts-nav-lists \.nav-toggle-collapse \{\s*transition:\s*none;/);
|
||||
|
||||
@@ -12,7 +12,7 @@ test('article list uses the reference widget container and keeps widget behavior
|
||||
const renderer = read('src/WidgetSystem/FrontendRenderer.php');
|
||||
const catalog = read('src/WidgetSystem/WidgetCatalog.php');
|
||||
assert.match(renderer, /\['user_card', 'author_card', 'tab_posts', 'posts'\]/);
|
||||
assert.match(renderer, /\$classes = \['zib-widget-wrap', 'jio-' \. str_replace/);
|
||||
assert.match(renderer, /\$classes = \['jio-widget-box-wrap', 'jio-' \. str_replace/);
|
||||
assert.match(renderer, /echo '<div class="widget-container"><div>'/);
|
||||
assert.match(renderer, /WidgetVisibility::device_class\(\$instance\)/);
|
||||
assert.match(renderer, /!empty\(\$instance\['sidebar_affix'\]\)/);
|
||||
|
||||
@@ -156,7 +156,7 @@ test('poster canvas layers match the reference rebate branch 1:1', () => {
|
||||
assert.match(body, /'推荐人: ' \+ String\(referrerName\)/)
|
||||
assert.match(body, /drawTextOverflow\(ctx, tags, width, 1, '24px Arial', '#fc4e1f', 36, 2, imgHeight \+ 162\)/)
|
||||
|
||||
// 站点名走「周」前缀(参考主题 zib__('week_prefix'))。
|
||||
// 站点名走「周」前缀。
|
||||
assert.match(body, /'周' \+ '日一二三四五六'\.charAt\(new Date\(\)\.getDay\(\)\)/)
|
||||
assert.doesNotMatch(body, /\['星期日', '星期一'/)
|
||||
|
||||
|
||||
@@ -201,12 +201,12 @@ test('governance display state is a single source shared by every prompt surface
|
||||
})
|
||||
|
||||
test('ban prompt surface mirrors the reference theme modals and block page', () => {
|
||||
// 查看禁封状态弹窗(1:1 zib_get_ban_info_modal:多彩头 + 信息行 + 申诉按钮)。
|
||||
// 查看禁封状态弹窗:多彩头 + 信息行 + 申诉按钮。
|
||||
assert.match(userBanHelper, /function jio_get_user_ban_info_modal/)
|
||||
assert.match(userBanHelper, /jio-user-ban-info-modal/)
|
||||
assert.match(userBanHelper, /您已解封,请刷新页面/)
|
||||
assert.match(userBanHelper, /您当前已无法进行申诉/)
|
||||
// 禁封申诉弹窗(1:1 zib_get_user_ban_appeal_modal:edit-header + 信息框 +
|
||||
// 禁封申诉弹窗:edit-header + 信息框 +
|
||||
// 资料项 inputs + 审核中态 + 禁止申诉态;仅本人)。
|
||||
assert.match(userBanHelper, /function jio_get_user_ban_appeal_modal/)
|
||||
assert.match(userBanHelper, /jio-user-ban-appeal-modal/)
|
||||
@@ -236,13 +236,13 @@ test('ban prompt surface mirrors the reference theme modals and block page', ()
|
||||
// 申诉表单 data[key] 结构与后端兼容。
|
||||
assert.match(banAppeal, /is_array\(\$_POST\['data'\]\)/)
|
||||
assert.match(banAppeal, /申诉说明/)
|
||||
// 禁封记录弹窗 + 记录链接(1:1 zib_get_ban_log_modal / zib_get_user_ban_log_link)。
|
||||
// 禁封记录弹窗 + 记录链接。
|
||||
assert.match(userBanHelper, /function jio_get_user_ban_log_modal/)
|
||||
assert.match(userBanHelper, /function jio_get_user_ban_log_link/)
|
||||
assert.match(userBanHelper, /data-jio-ban-log=/)
|
||||
assert.match(userBanHelper, /当前用户已有%d次禁封记录,请谨慎操作/)
|
||||
assert.match(userBanHelper, /该用户共有%d次禁封记录/)
|
||||
// 登录后禁封状态页(1:1 zib_user_ban_restrict_login / zib_die_page)。
|
||||
// 登录后禁封状态页。
|
||||
assert.match(userBanHelper, /function jio_render_user_ban_block_page/)
|
||||
assert.match(banBlock, /抱歉,您的账号当前处于禁封状态,暂时无法登录/)
|
||||
assert.match(banBlock, /退出登录/)
|
||||
@@ -254,7 +254,7 @@ test('ban prompt surface mirrors the reference theme modals and block page', ()
|
||||
assert.match(restriction, /add_action\('template_redirect', \[\$this, 'restrict_frontend_access'\], 1\)/)
|
||||
assert.match(restriction, /public function restrict_frontend_access\(\): void/)
|
||||
assert.ok(!restriction.includes('enforce_login_ban'), 'login-time ban block must stay removed')
|
||||
// 评论拒绝态(参考主题 zib_get_user_ban_nocan_info)。
|
||||
// 评论拒绝态。
|
||||
assert.match(userBanHelper, /function jio_get_user_ban_nocan_html/)
|
||||
assert.match(commentsHelper, /jio_get_user_ban_nocan_html/)
|
||||
assert.match(commentsHelper, /暂时无法发表评论/)
|
||||
@@ -287,7 +287,7 @@ test('ban prompt surface mirrors the reference theme modals and block page', ()
|
||||
})
|
||||
|
||||
test('report entry hides targets already banned or suspended', () => {
|
||||
// 参考主题 zib_get_report_link:已封禁/小黑屋用户不展示举报入口(禁言不隐藏)。
|
||||
// 已封禁/小黑屋用户不展示举报入口(禁言不隐藏)。
|
||||
assert.match(userBanHelper, /function jio_is_user_ban_restricted/)
|
||||
assert.match(commentItem, /jio_is_user_ban_restricted\(\$author_id\)/)
|
||||
assert.match(singleComponents, /jio_is_user_ban_restricted\(\$post_author_id\)/)
|
||||
@@ -448,7 +448,7 @@ test('comments expose a report entry for other users when reporting is enabled',
|
||||
assert.doesNotMatch(commentItem, /\$report\s*=\s*jio_get_comment_report_html\(\$comment\)/)
|
||||
})
|
||||
|
||||
test('frontend ban modal mirrors the reference theme zib_get_set_user_ban_modal 1:1', () => {
|
||||
test('frontend ban modal mirrors the reference ban modal 1:1', () => {
|
||||
// 禁封方式/禁封时间/预置原因徽章/其它原因/禁封说明/删除全部内容/禁止用户申诉,
|
||||
// 以及已禁封用户的状态信息与解封留言表单(弹窗经 REST /user-ban/modal 输出)。
|
||||
for (const label of ['拉入小黑屋', '禁封用户', '禁封方式', '禁封时间', '选择禁封原因', '其它原因', '禁封说明', '删除全部内容', '禁止用户申诉', '请填写禁止申诉说明', '解封用户', '移出小黑屋', '给用户的留言', '开始时间', '结束时间', '原因', '说明', '禁封记录', '确认禁封']) {
|
||||
@@ -474,7 +474,7 @@ test('frontend ban modal mirrors the reference theme zib_get_set_user_ban_modal
|
||||
assert.match(userBanHelper, /function jio_get_user_cover_menu_action_html/)
|
||||
assert.match(userBanHelper, /function jio_get_comment_ban_or_report_item/)
|
||||
assert.match(userBanHelper, /function jio_get_post_author_ban_item/)
|
||||
// 权限模型(参考主题 zib_current_user_can('set_user_ban') 语义):
|
||||
// 权限模型:
|
||||
// manage_options + 非本人 + 非超级管理员 + 功能开关。
|
||||
assert.match(userBanHelper, /current_user_can\('manage_options'\)/)
|
||||
assert.match(userBanHelper, /\$current_id === \$user_id/)
|
||||
@@ -483,7 +483,7 @@ test('frontend ban modal mirrors the reference theme zib_get_set_user_ban_modal
|
||||
// REST 路由:modal 拉取 + set 提交(permission_callback 校验)。
|
||||
assert.match(userController, /'\/user-ban\/modal'/)
|
||||
assert.match(userController, /'\/user-ban\/set'/)
|
||||
// 解封视图多彩横幅(参考主题 zib_get_modal_colorful_header('c-yellow', ...))。
|
||||
// 解封视图多彩横幅。
|
||||
assert.match(userBanHelper, /modal-colorful-header colorful-bg c-yellow/)
|
||||
// 提交后整页刷新(禁封 reload:true)/ 仅关闭弹窗(解封 hide_modal:true)。
|
||||
assert.match(userBanJs, /window\.location\.reload\(\)/)
|
||||
|
||||
@@ -122,10 +122,10 @@ final class WidgetSystemTest {
|
||||
$generic_categories = PostQuery::arguments(['category_ids' => '2,5', 'category_id' => 9], 1);
|
||||
$this->same([9], $generic_categories['category__in'], 'generic category_id continues to override category_ids');
|
||||
$this->false(isset($generic_categories['cat']), 'generic categories do not switch to modern cat syntax');
|
||||
$paywalled = PostQuery::arguments(['cat' => '1', 'zibpay_type' => ['1', '6'], 'paginate' => ''], 1, ['cat' => '1']);
|
||||
$paywalled = PostQuery::arguments(['cat' => '1', 'pay_type' => ['1', '6'], 'paginate' => ''], 1, ['cat' => '1']);
|
||||
$this->contains('reading', json_encode($paywalled['meta_query'] ?? []), 'tab commerce filters map to pay meta');
|
||||
$this->contains('video', json_encode($paywalled['meta_query'] ?? []), 'tab commerce filters cover every selected type');
|
||||
$top_level_paywall = PostQuery::arguments(['cat' => '1', 'zibpay_type' => ['2'], 'paginate' => 'ajax'], 1);
|
||||
$top_level_paywall = PostQuery::arguments(['cat' => '1', 'pay_type' => ['2'], 'paginate' => 'ajax'], 1);
|
||||
$this->contains('download', json_encode($top_level_paywall['meta_query'] ?? []), 'top-level reference commerce filters map to pay meta');
|
||||
$generic_paywall = PostQuery::arguments(['category_ids' => '1', 'pay_type' => 'gallery'], 1);
|
||||
$this->contains('gallery', json_encode($generic_paywall['meta_query'] ?? []), 'generic widget pay_type behavior remains available');
|
||||
@@ -144,12 +144,12 @@ final class WidgetSystemTest {
|
||||
$this->same(6, $tab_defaults['count'], 'modern tabs default to six posts');
|
||||
$this->same(['热门推荐', '最近更新', '猜你喜欢'], array_column($tab_defaults['tabs'], 'title'), 'modern tab defaults match reference ordering');
|
||||
$tab = FieldSchema::normalize($tab_fields, ['tabs' => [[
|
||||
'title' => '<b>栏目</b><script>x</script>', 'cat' => '1,-2 3', 'zibpay_type' => ['1', '6'],
|
||||
'title' => '<b>栏目</b><script>x</script>', 'cat' => '1,-2 3', 'pay_type' => ['1', '6'],
|
||||
'order' => 'asc',
|
||||
]]]);
|
||||
$this->same('1,-2,3', $tab['tabs'][0]['cat'], 'tab cat keeps signed reference IDs');
|
||||
$this->false(isset($tab['tabs'][0]['topics']), 'topic filters are gone from tab configuration');
|
||||
$this->same(['1', '6'], $tab['tabs'][0]['zibpay_type'], 'tab commerce filters are preserved');
|
||||
$this->same(['1', '6'], $tab['tabs'][0]['pay_type'], 'tab commerce filters are preserved');
|
||||
$this->contains('<b>栏目</b>', $tab['tabs'][0]['title'], 'tab title keeps controlled HTML');
|
||||
$this->false(str_contains($tab['tabs'][0]['title'], '<script>'), 'tab title strips unsafe HTML');
|
||||
|
||||
@@ -211,7 +211,7 @@ final class WidgetSystemTest {
|
||||
$notice_defaults = FieldSchema::defaults(WidgetSchemas::for('notice'));
|
||||
$this->same(3, $notice_defaults['interval'], 'notice keeps reference interval');
|
||||
$this->same('c-blue', $notice_defaults['color'], 'notice keeps reference color');
|
||||
$this->same(2, count($notice_defaults['img_ids']), 'notice ships reference custom messages');
|
||||
$this->same(0, count($notice_defaults['img_ids']), 'notice ships no preset messages');
|
||||
$notice = GeneralRenderer::render('notice', $notice_defaults);
|
||||
$this->contains('swiper-bulletin mb20 c-blue main-radius', $notice, 'notice emits reference surface classes');
|
||||
$this->contains('data-direction="vertical" data-loop="true" data-autoplay="1"', $notice, 'notice emits reference swiper options');
|
||||
|
||||
@@ -7,7 +7,7 @@ const { spawnSync } = require('node:child_process');
|
||||
const root = path.resolve(__dirname, '..', '..');
|
||||
const read = (file) => fs.readFileSync(path.join(root, file), 'utf8');
|
||||
|
||||
test('search route keeps the reference Zibll results and empty-state structure', () => {
|
||||
test('search route keeps the reference results and empty-state structure', () => {
|
||||
const template = read('search.php');
|
||||
const styles = read('assets/css/main.css');
|
||||
|
||||
|
||||
@@ -182,7 +182,7 @@ test('renders reference list shape for three real headings and excludes no-nav a
|
||||
assert.equal(list.querySelectorAll('li').length, 3);
|
||||
assert.deepEqual(list.querySelectorAll('a').map((link) => link.textContent), ['Main section', 'Nested section', 'Final section']);
|
||||
assert.equal(box.querySelector('.title-theme').textContent, '目录');
|
||||
assert.ok(box.querySelector('.zib-widget'));
|
||||
assert.ok(box.querySelector('.jio-widget-box'));
|
||||
headings.forEach((heading) => assert.match(heading.id, /^wznav_/));
|
||||
});
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
'use strict';
|
||||
|
||||
// 文章详情页顶部封面高度契约(对齐参考主题):
|
||||
// 单图/多图(轮播)封面标题叠加封面,高度 = calc(比例% + 50px)(zibll .single-cover /
|
||||
// 单图/多图(轮播)封面标题叠加封面,高度 = calc(比例% + 50px)(.single-cover /
|
||||
// .single-cover-slide);视频封面标题在下方 footer、无叠加,高度 = 裸比例%
|
||||
// (zibll .dplayer-scale-height)。
|
||||
// (视频封面高度计算契约)。
|
||||
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
@@ -36,8 +36,7 @@ test('single cover: video mode uses the bare ratio height like the reference dpl
|
||||
/single-cover--video imgbox-container" style="padding-bottom:<\?php echo esc_attr\(\$video_padding\); \?>%;"/,
|
||||
'视频封面必须输出裸比例高度(标题在下方 footer,无叠加补偿)'
|
||||
);
|
||||
// +50px 是标题叠加封面的补偿量,仅单图/多图封面适用;视频封面
|
||||
// 对齐参考主题 .dplayer-scale-height,不得继承该公式。
|
||||
// +50px 是标题叠加封面的补偿量,仅单图/多图封面适用;视频封面不得继承该公式。
|
||||
assert.doesNotMatch(
|
||||
singleRender,
|
||||
/single-cover--video imgbox-container" style="padding-bottom:calc\(/,
|
||||
@@ -140,7 +139,7 @@ test('video cover: footer breadcrumb sits in the reference flex row', () => {
|
||||
const breadcrumbCss = read('assets/css/frontend/breadcrumb.css');
|
||||
const coverCss = read('assets/css/frontend/single-post/cover.css');
|
||||
|
||||
// 对齐 zibll:footer 里面包屑直接位于 flex jsb at 行,无自造包装层。
|
||||
// footer 里面包屑直接位于 flex jsb at 行,无自造包装层。
|
||||
assert.match(
|
||||
singleRender,
|
||||
/single-video-footer dplayer-featured">\s*\n\s*<div class="flex jsb at">\s*\n\s*<\?php echo \$crumbs;/,
|
||||
|
||||
@@ -17,7 +17,7 @@ test('missing and invalid tab style use the reference mini layout and ignore glo
|
||||
for (const name of ['default', 'invalid']) {
|
||||
const current = cases[name];
|
||||
assert.equal(current.instance_style, 'mini');
|
||||
assert.match(current.tab_markup, /widget-tab-post style-mini posts-mini-lists zib-widget/);
|
||||
assert.match(current.tab_markup, /widget-tab-post style-mini posts-mini-lists jio-widget-box/);
|
||||
assert.match(current.first, /class="posts-mini ajax-item"/);
|
||||
assert.doesNotMatch(current.first, /class="mr10"|class="item-meta|class="badg badg-sm/);
|
||||
assert.doesNotMatch(current.first, /class="posts-item list|class="posts-item card|class="no_margin/);
|
||||
@@ -27,7 +27,7 @@ test('missing and invalid tab style use the reference mini layout and ignore glo
|
||||
test('un-normalized missing and invalid style use one shared mini resolution in both renderers', () => {
|
||||
const cases = render();
|
||||
for (const name of ['raw_default', 'raw_invalid']) {
|
||||
assert.match(cases[name].tab_markup, /widget-tab-post style-mini posts-mini-lists zib-widget/);
|
||||
assert.match(cases[name].tab_markup, /widget-tab-post style-mini posts-mini-lists jio-widget-box/);
|
||||
assert.match(cases[name].first, /class="posts-mini ajax-item"/);
|
||||
assert.doesNotMatch(cases[name].first, /class="posts-item list|class="posts-item card/);
|
||||
}
|
||||
@@ -140,7 +140,7 @@ test('list margin defaults inherit the site setting while an explicit widget fal
|
||||
|
||||
test('multi-column frontend output uses the reference widget wrapper and carries layout variables', () => {
|
||||
const wrapper = render().frontend_wrapper;
|
||||
assert.match(wrapper, /class="zib-widget-wrap jio-tab-posts-wrap"/);
|
||||
assert.match(wrapper, /class="jio-widget-box-wrap jio-tab-posts-wrap"/);
|
||||
assert.match(wrapper, /data-jio-widget="tab_posts" data-widget-id="jio_widget_tab_posts-4"/);
|
||||
assert.match(wrapper, /--jio-widget-space-top:8px/);
|
||||
assert.match(wrapper, /--jio-widget-space-m-bottom:10px/);
|
||||
|
||||
@@ -12,7 +12,7 @@ const { MiniDocument } = require('../helpers/mini-dom.cjs');
|
||||
|
||||
function tabMarkup(document, { firstPage = 1, firstSegmentLoaded = true } = {}) {
|
||||
const root = document.createElement('div');
|
||||
root.className = 'widget-tab-post style-mini posts-mini-lists zib-widget';
|
||||
root.className = 'widget-tab-post style-mini posts-mini-lists jio-widget-box';
|
||||
root.setAttribute('data-widget', 'tab_posts');
|
||||
root.setAttribute('data-instance', 'widget-7');
|
||||
const nav = document.createElement('ul');
|
||||
|
||||
@@ -8,7 +8,7 @@ const test = require('node:test')
|
||||
const root = path.resolve(__dirname, '..', '..')
|
||||
const read = (file) => fs.readFileSync(path.join(root, file), 'utf8')
|
||||
|
||||
// Reference theme (zibll css/main.css): .jb-yellow sets --this-bg-b: #f59f54 and
|
||||
// Reference: .jb-yellow sets --this-bg-b: #f59f54 and
|
||||
// --this-bg: linear-gradient(135deg, #f59f54 10%, #ff6922 100%). build/public.min.css
|
||||
// is enqueued after main.min.css, so the legacy .jb-* compat layer in core/buttons.css
|
||||
// overrides main.css there; the two must agree or every .jb-yellow surface (the
|
||||
@@ -49,7 +49,7 @@ test('the badge typography is pinned to the reference bootstrap body values', ()
|
||||
assert.notEqual(start, -1, 'missing the user-card img-badge adaptation rule')
|
||||
const block = main.slice(start, main.indexOf('}', start))
|
||||
assert.match(block, /line-height:\s*1\.42857143/)
|
||||
// Reference body font stack (zibll bootstrap.min.css; zibll main.css does not
|
||||
// Reference body font stack (bootstrap.min.css; main.css does not
|
||||
// override it), while Jio's reset imposes the modern --font-body stack.
|
||||
assert.match(block, /font-family:\s*"Helvetica Neue", Helvetica, Arial, sans-serif/)
|
||||
// The Jio html reset forces antialiased + optimizeLegibility; the badge
|
||||
|
||||
@@ -15,7 +15,7 @@ test('user and author widgets render the reference state contract', () => {
|
||||
assert.equal(result.status, 0, result.stderr || result.stdout)
|
||||
const data = JSON.parse(result.stdout)
|
||||
|
||||
assert.match(data.guest, /^<div class="mb20"><div class="user-card zib-widget widget">/)
|
||||
assert.match(data.guest, /^<div class="mb20"><div class="user-card jio-widget-box widget">/)
|
||||
assert.match(data.guest, /data-lazy-type="lazy_other"/)
|
||||
assert.match(data.guest, /alt="默认头像"/)
|
||||
assert.match(data.guest, /<div class="user-info mt10"><div class="text-center"><p class="muted-color box-body em12">/)
|
||||
@@ -26,7 +26,9 @@ test('user and author widgets render the reference state contract', () => {
|
||||
assert.match(data.guest, /class="social-login-item github toggle-radius"/)
|
||||
assert.match(data.guest, /class="social-login-item qq toggle-radius" data-provider="clogin_qq"/)
|
||||
assert.match(data.guest, /title="GitHub登录"/)
|
||||
assert.doesNotMatch(data.guest, /jio-widget|jio-panel-wrap|user-card__auth-btns|oauth-row|oauth-btn/)
|
||||
// The reference user card keeps its own shell; it must not adopt the modern
|
||||
// widget shell (jio-widget / jio-panel-wrap) or the OAuth button components.
|
||||
assert.doesNotMatch(data.guest, /class="[^"]*\bjio-widget(?![\w-])|jio-panel-wrap|user-card__auth-btns|oauth-row|oauth-btn/)
|
||||
// The social-login area carries no agreement copy; the reference guest card
|
||||
// only shows the separator, the .social_loginbar and the provider buttons.
|
||||
assert.doesNotMatch(data.guest, /我已阅读并同意|使用社交账号登录即表示|注册即表示/)
|
||||
@@ -35,7 +37,7 @@ test('user and author widgets render the reference state contract', () => {
|
||||
assert.doesNotMatch(data.guest_no_registration, /signup-loader/)
|
||||
assert.equal(data.closed, '')
|
||||
|
||||
assert.match(data.logged, /^<div class="mb20"><div class="user-card zib-widget widget">/)
|
||||
assert.match(data.logged, /^<div class="mb20"><div class="user-card jio-widget-box widget">/)
|
||||
assert.match(data.logged, /data-lazy-type="lazy_cover"/)
|
||||
assert.match(data.logged, /<div class="card-content mt10 relative">/)
|
||||
assert.match(data.logged, /<div class="user-name flex jc">/)
|
||||
@@ -47,7 +49,7 @@ test('user and author widgets render the reference state contract', () => {
|
||||
assert.match(data.logged_buttons, /rel="nofollow" href="https:\/\/example\.test\/user-center" class="but pw-1em ml6 jb-blue">用户中心<\/a>/)
|
||||
assert.doesNotMatch(data.logged_buttons, /<icon|<text>/)
|
||||
|
||||
assert.match(data.author, /^<div class="mb20"><div class="user-card zib-widget widget">/)
|
||||
assert.match(data.author, /^<div class="mb20"><div class="user-card jio-widget-box widget">/)
|
||||
assert.match(data.author, /data-lazy-type="lazy_cover"/)
|
||||
assert.match(data.author, /data-post-type="post" data-post-style="card"/)
|
||||
assert.doesNotMatch(data.author, /user-btns mt20/)
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
*
|
||||
* 硬约束:
|
||||
* 1) 「我的服务 / 功能设置」两个网格使用参考主题的 icon-but-box / user-icon-but-box
|
||||
* 外壳,图标全部来自主题内置 SVG sprite(jio_icon_markup('zibsvg-*')),
|
||||
* 外壳,图标全部来自主题内置 SVG sprite(jio_icon_markup('jiosvg-*')),
|
||||
* 不再出现 Font Awesome 字形。
|
||||
* 2) 侧栏其余部件(余额/积分卡、创作/推广卡、VIP 入口、抽屉触发器)同样只使用
|
||||
* sprite 图标,箭头统一为 zibsvg-right,保证侧栏只剩一种图标族。
|
||||
* sprite 图标,箭头统一为 jiosvg-right,保证侧栏只剩一种图标族。
|
||||
* 3) 旧的 uc-service-grid 外壳、色调底色与计数角标必须已彻底移除,不得残留。
|
||||
*/
|
||||
'use strict';
|
||||
@@ -34,7 +34,7 @@ test('服务/设置网格使用参考主题 icon-but-box 外壳与 sprite 图标
|
||||
assert.match(nav, /class="flex ac hh text-center icon-but-box user-icon-but-box"/);
|
||||
assert.match(nav, /<div class="em16"><\?php echo \$icon_html;/);
|
||||
assert.match(nav, /<div class="px12 muted-color mt3">/);
|
||||
assert.match(nav, /jio_icon_markup\('zibsvg-' \. \(string\) \$item\['icon'\]\)/);
|
||||
assert.match(nav, /jio_icon_markup\('jiosvg-' \. \(string\) \$item\['icon'\]\)/);
|
||||
|
||||
// 参考主题选型:我的服务 = trend-color / user-auth / order-color。
|
||||
assert.match(nav, /'icon'\s+=>\s+'trend-color'/);
|
||||
@@ -68,14 +68,14 @@ test('侧栏部件(VIP 与创作/推广卡除外)不再出现 Font Awesome
|
||||
|
||||
test('其余侧栏部件按参考主题选型替换为 sprite 图标', () => {
|
||||
const commerce = read(sidebarDir + 'sidebar-commerce.php');
|
||||
assert.match(commerce, /jio_icon_markup\('zibsvg-money-color-2'\)/);
|
||||
assert.match(commerce, /jio_icon_markup\('zibsvg-points-color'\)/);
|
||||
assert.match(commerce, /jio_icon_markup\('zibsvg-right'\)/);
|
||||
assert.match(commerce, /jio_icon_markup\('jiosvg-money-color-2'\)/);
|
||||
assert.match(commerce, /jio_icon_markup\('jiosvg-points-color'\)/);
|
||||
assert.match(commerce, /jio_icon_markup\('jiosvg-right'\)/);
|
||||
|
||||
const creator = read(sidebarDir + 'sidebar-creator.php');
|
||||
assert.match(creator, /jio_icon_markup\('zibsvg-merchant-color', 'mr6'\)/);
|
||||
assert.match(creator, /jio_icon_markup\('zibsvg-money-color', 'mr6'\)/);
|
||||
assert.match(creator, /class="mb10-sm zib-widget padding-10 flex1 pointer"/);
|
||||
assert.match(creator, /jio_icon_markup\('jiosvg-merchant-color', 'mr6'\)/);
|
||||
assert.match(creator, /jio_icon_markup\('jiosvg-money-color', 'mr6'\)/);
|
||||
assert.match(creator, /class="mb10-sm jio-widget-box padding-10 flex1 pointer"/);
|
||||
assert.match(creator, /flex ab jsb col-ml6/);
|
||||
assert.match(creator, /<i class="ml6 fa fas fa-angle-right em12"/);
|
||||
assert.match(creator, /'创作中心'/);
|
||||
@@ -93,8 +93,8 @@ test('其余侧栏部件按参考主题选型替换为 sprite 图标', () => {
|
||||
assert.doesNotMatch(creator, /data-onclick/);
|
||||
|
||||
const vip = read(sidebarDir + 'sidebar-vip.php');
|
||||
assert.match(vip, /jio_icon_markup\('zibsvg-vip_1', 'em12 mr6'\)/);
|
||||
assert.match(vip, /jio_icon_markup\('zibsvg-time', 'mr3'\)/);
|
||||
assert.match(vip, /jio_icon_markup\('jiosvg-vip_1', 'em12 mr6'\)/);
|
||||
assert.match(vip, /jio_icon_markup\('jiosvg-time', 'mr3'\)/);
|
||||
assert.match(vip, /mb20 mb10-sm main-radius payvip-icon btn-block badg pointer/);
|
||||
assert.match(vip, /style="padding: 14px 10px;"/);
|
||||
assert.match(vip, /fa-chevron-circle-right abs-right/);
|
||||
@@ -103,7 +103,7 @@ test('其余侧栏部件按参考主题选型替换为 sprite 图标', () => {
|
||||
assert.doesNotMatch(vip, /data-onclick/);
|
||||
|
||||
const profile = read(sidebarDir + 'profile-sidebar.php');
|
||||
assert.match(profile, /jio_icon_markup\('zibsvg-menu'\)/);
|
||||
assert.match(profile, /jio_icon_markup\('jiosvg-menu'\)/);
|
||||
});
|
||||
|
||||
test('旧 uc-service-grid 外壳与色调底色已彻底移除', () => {
|
||||
@@ -125,32 +125,32 @@ test('旧 uc-service-grid 外壳与色调底色已彻底移除', () => {
|
||||
assert.doesNotMatch(shell, /\.user-icon-but-box > a\.is-active/);
|
||||
assert.match(shell, /\.user-icon-but-box > a \{[^}]*color: inherit/s);
|
||||
|
||||
// 外壳与标题 1:1 参考主题 zib-widget padding-6 / padding-6 ml3。
|
||||
// 外壳与标题 1:1 参考主题 jio-widget-box padding-6 / padding-6 ml3。
|
||||
const nav = read(sidebarDir + 'sidebar-nav.php');
|
||||
assert.match(nav, /class="uc-sidebar-iconbox zib-widget padding-6 mb10-sm"/);
|
||||
assert.match(nav, /class="uc-sidebar-iconbox zib-widget padding-6"/);
|
||||
assert.match(nav, /class="uc-sidebar-iconbox jio-widget-box padding-6 mb10-sm"/);
|
||||
assert.match(nav, /class="uc-sidebar-iconbox jio-widget-box padding-6"/);
|
||||
assert.match(nav, /<div class="padding-6 ml3"><\?php esc_html_e\('我的服务'/);
|
||||
assert.match(nav, /<div class="padding-6 ml3"><\?php esc_html_e\('功能设置'/);
|
||||
assert.doesNotMatch(nav, /uc-sidebar__heading/);
|
||||
assert.match(shell, /\.uc-sidebar-iconbox\.zib-widget \{/);
|
||||
assert.match(shell, /\.uc-sidebar-iconbox\.jio-widget-box \{/);
|
||||
|
||||
// 卡片上的 <i> 已换成 <svg>,元素选择器需覆盖两者。
|
||||
assert.match(shell, /\.uc-summary-card__body > svg/);
|
||||
// 旧的琥珀 VIP 券与 uc-center-card 已被参考主题结构替换。
|
||||
assert.doesNotMatch(shell, /uc-center-card/);
|
||||
assert.doesNotMatch(shell, /uc-sidebar-vip__/);
|
||||
assert.match(shell, /\.uc-sidebar-centers > \.zib-widget/);
|
||||
assert.match(shell, /\.uc-sidebar-centers > \.jio-widget-box/);
|
||||
|
||||
// 文章/评论/收藏/粉丝统计 1:1 参考主题 zib_user_center_page_sidebar_statistics()。
|
||||
// 文章/评论/收藏/粉丝统计。
|
||||
const stats = read(sidebarDir + 'sidebar-stats.php');
|
||||
assert.match(stats, /mb10-sm mb20 flex ac jsa zib-widget padding-10 text-center/);
|
||||
assert.match(stats, /mb10-sm mb20 flex ac jsa jio-widget-box padding-10 text-center/);
|
||||
assert.match(stats, /<a class="user-statistics-item"/);
|
||||
assert.match(stats, /<div class="em14"><\?php echo esc_html\(jio_cut_count\(/);
|
||||
assert.match(stats, /<div class="em09 opacity5 mt6">/);
|
||||
assert.doesNotMatch(stats, /uc-stats|uc-sidebar__section/);
|
||||
assert.doesNotMatch(shell, /\.uc-stats/);
|
||||
assert.doesNotMatch(read('assets/css/routes/user-center/responsive.css'), /\.uc-stats/);
|
||||
assert.match(shell, /\.uc-sidebar-statistics\.zib-widget \{/);
|
||||
assert.match(shell, /\.uc-sidebar-statistics\.jio-widget-box \{/);
|
||||
});
|
||||
|
||||
test('所用 sprite key 均在前端 sprite 中定义', () => {
|
||||
@@ -168,10 +168,10 @@ test('导航栏用户浮层的余额/积分磁贴与侧栏使用同一组 sprite
|
||||
const nav = read(sidebarDir + 'sidebar-commerce.php');
|
||||
const wallet = read('inc/helpers/jio-header-userbox.php');
|
||||
|
||||
assert.match(wallet, /jio_icon_markup\('zibsvg-money-color-2', 'em14'\)/);
|
||||
assert.match(wallet, /jio_icon_markup\('zibsvg-points-color', 'em14'\)/);
|
||||
assert.match(nav, /jio_icon_markup\('zibsvg-money-color-2'\)/);
|
||||
assert.match(nav, /jio_icon_markup\('zibsvg-points-color'\)/);
|
||||
assert.match(wallet, /jio_icon_markup\('jiosvg-money-color-2', 'em14'\)/);
|
||||
assert.match(wallet, /jio_icon_markup\('jiosvg-points-color', 'em14'\)/);
|
||||
assert.match(nav, /jio_icon_markup\('jiosvg-money-color-2'\)/);
|
||||
assert.match(nav, /jio_icon_markup\('jiosvg-points-color'\)/);
|
||||
|
||||
// 磁贴箭头沿用参考主题的 fa-angle-right,不做多余替换。
|
||||
assert.match(wallet, /fa fas fa-angle-right em12/);
|
||||
|
||||
@@ -19,7 +19,7 @@ test('user level renderer keeps the canonical panel mapping and reference layout
|
||||
assert.match(renderer, /class="col-md-7"/);
|
||||
assert.match(renderer, /colorful-bg jb-vip1 jio-level-card/);
|
||||
assert.match(renderer, /colorful-bg c-gray jio-level-card/);
|
||||
assert.match(renderer, /class="zib-widget jio-level-experience"/);
|
||||
assert.match(renderer, /class="jio-widget-box jio-level-experience"/);
|
||||
assert.match(renderer, /id="tab_integral_get"/);
|
||||
assert.match(renderer, /id="tab_integral_my"/);
|
||||
assert.match(renderer, /id="tab_integral_date"/);
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
/**
|
||||
* VIP 后台设置弹窗文案字段契约(对照参考主题 zibll 修正)。
|
||||
* VIP 后台设置弹窗文案字段契约。
|
||||
*
|
||||
* 硬约束:
|
||||
* 1) 弹窗文案字段更名为「会员介绍」(pay_user_vip_desc, input) 与
|
||||
* 「开通会员更多内容」(pay_user_vip_more, textarea rows 4 +
|
||||
* sanitize_type html),并从「会员等级」组移到分区末尾的
|
||||
* 「界面文案」(modal-copy) 独立分组;
|
||||
* 2) 文案默认值对齐 zibll(黄金会员/钻石会员、尊享专属特权、权益四项、
|
||||
* 2) 文案默认值(黄金会员/钻石会员、尊享专属特权、权益四项、
|
||||
* 会员介绍与开通会员更多内容默认值),SPA schema / PHP schema /
|
||||
* PaymentRuntime 回退三处一致——Settings::get_all() 不合并
|
||||
* schema 默认值,运行时回退即实际默认值;
|
||||
@@ -49,7 +49,7 @@ test('弹窗文案字段更名为会员介绍/开通会员更多内容并改为
|
||||
assert.ok(!src.includes('弹窗顶部会员说明') && !src.includes('弹窗底部补充说明 HTML'), `${name} 旧名称应已移除`);
|
||||
assert.ok(src.includes('会员介绍') && src.includes('开通会员更多内容'), `${name} 应含新名称`);
|
||||
}
|
||||
// 会员介绍为单行 input(zibll text「一句话简介」);开通会员更多内容为 rows 4 的 HTML textarea。
|
||||
// 会员介绍为单行 input(「一句话简介」);开通会员更多内容为 rows 4 的 HTML textarea。
|
||||
assert.match(
|
||||
vipSchemaJs,
|
||||
/key: 'pay_user_vip_desc',\s*\n\s*label: '会员介绍',\s*\n\s*type: 'input',\s*\n\s*sanitize_type: 'html',\s*\n\s*default: '开通VIP会员,享受会员专属折扣以及多项特权'/
|
||||
@@ -66,7 +66,7 @@ test('弹窗文案字段更名为会员介绍/开通会员更多内容并改为
|
||||
schemaPhp,
|
||||
/'key' => 'pay_user_vip_more', 'label' => '开通会员更多内容', 'type' => 'textarea', 'sanitize_type' => 'html'[\s\S]*?'rows' => 4/
|
||||
);
|
||||
// 帮助文本对齐 zibll 的 desc。
|
||||
// 帮助文本与字段说明一致。
|
||||
assert.ok(vipSchemaJs.includes('显示在开通界面顶部一句话简介,可以为会员权益简介或者活动介绍'));
|
||||
assert.ok(vipSchemaJs.includes('显示在开通界面底部位置,可以为提醒事项、用户协议等,支持HTML代码'));
|
||||
});
|
||||
@@ -98,7 +98,7 @@ test('两个文案字段位于分区末尾的界面文案分组且不再属于
|
||||
assert.ok(basicPhp.includes("'key' => 'pay_user_vip_1_s'") && basicPhp.includes("'key' => 'pay_user_vip_2_s'"), 'SchemaPayment.php 会员等级组应保留两个等级开关');
|
||||
// 二级会员先于一级会员开启时的危险提示(参考主题同款)。
|
||||
assert.ok(basicJs.includes('vip_level_order_notice') && basicPhp.includes('vip_level_order_notice'));
|
||||
// 依赖:任一等级启用时显示(zibll pay_user_vip_1_s 依赖的双等级适配)。
|
||||
// 依赖:任一等级启用时显示(双等级开关适配)。
|
||||
assert.ok(
|
||||
vipSchemaJs.includes("dependsOn: { any: [{ key: 'pay_user_vip_1_s', value: true }, { key: 'pay_user_vip_2_s', value: true }] }"),
|
||||
'vip.js 应为 any 双等级依赖'
|
||||
@@ -124,7 +124,7 @@ test('续费/升级介绍字段存在且被档位卡消费', () => {
|
||||
assert.match(vipTabPhp, /\$card_action = 'upgrade'/);
|
||||
});
|
||||
|
||||
test('文案默认值三处一致并对齐 zibll', () => {
|
||||
test('文案默认值三处一致', () => {
|
||||
// SPA defaults 块与字段级 default。
|
||||
assert.ok(vipSchemaJs.includes("pay_user_vip_1_name: '黄金会员'"));
|
||||
assert.ok(vipSchemaJs.includes("pay_user_vip_2_name: '钻石会员'"));
|
||||
@@ -192,7 +192,7 @@ test('会员介绍按 post 级 HTML 安全净化并在弹窗与用户中心渲
|
||||
});
|
||||
|
||||
test('开通会员更多内容只在弹窗内渲染,不裸露在用户中心页面', () => {
|
||||
// 参考主题 zibpay_user_page_tab_content_vip 只渲染 pay_user_vip_desc;
|
||||
// 会员页 tab 只渲染 pay_user_vip_desc;
|
||||
// pay_user_vip_more 仅出现在两个弹窗函数(购买/续费弹窗)中。
|
||||
assert.ok(!vipTabPhp.includes("vip_settings['more']"), '用户中心 VIP 页不应渲染 more');
|
||||
assert.ok(!vipTabPhp.includes('muted-box padding-10 mini-radius mt15'), '用户中心 VIP 页不应有 more 提示盒');
|
||||
@@ -218,8 +218,7 @@ test('弹窗内 more 与卡密说明按 HTML 渲染而非纯文本', () => {
|
||||
});
|
||||
|
||||
test('积分兑换说明按 post 级 HTML 放行并原样渲染', () => {
|
||||
// 参考主题该字段 sanitize=false 且弹窗内原样拼接;Jio 对齐为保存/输出
|
||||
// 双端 wp_kses_post + 弹窗 wp_kses_post 渲染。
|
||||
// 积分兑换说明:保存端 wp_kses_post 且弹窗内原样拼接;
|
||||
assert.match(vipSchemaJs, /key: 'pay_vip_points_exchange_desc', label: '兑换说明', type: 'textarea', sanitize_type: 'html'/);
|
||||
assert.match(schemaPhp, /'key' => 'pay_vip_points_exchange_desc', 'label' => '兑换说明', 'type' => 'textarea', 'sanitize_type' => 'html'/);
|
||||
const vipSettings = slice(paymentRuntime, 'public static function get_payment_vip_settings', 'public static function get_payment_revenue_settings');
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
* 1) 服务端静态输出参考主题 refresh_modal 同构壳(`modal flex jc fade` >
|
||||
* `modal-dialog modal-mini full-sm` > `modal-content` > `modal-body`)。
|
||||
* 入口有两处,分别对应参考主题的两个变体:
|
||||
* - 会员购买弹窗内(zibpay_get_pay_uservip_modal 的
|
||||
* - 会员购买弹窗内(参考实现的会员购买弹窗的
|
||||
* `btn-block but mb10 padding-lg c-green mini-radius` 按钮);
|
||||
* - /user/vip/ 档位购买卡下方(zibpay_user_page_tab_content_vip 的
|
||||
* `c-yellow zib-widget flex ac jsb` 全宽横条,仅非会员展示)。
|
||||
* - /user/vip/ 档位购买卡下方(参考实现的会员页 tab 的
|
||||
* `c-yellow jio-widget-box flex ac jsb` 全宽横条,仅非会员展示)。
|
||||
* 两处入口均带 data-jio-vip-points-open,由 pay-vip.js 文档级委托打开
|
||||
* 同一静态弹窗。
|
||||
* 2) 静止态必须被 vip-modal.css 显式隐藏(.flex 覆盖回归护栏),打开态为
|
||||
@@ -169,7 +169,7 @@ test('开关自包含:不依赖 jQuery/Bootstrap modal 运行时', () => {
|
||||
});
|
||||
|
||||
test('会员弹窗内的入口为参考主题同款按钮并开本地弹窗', () => {
|
||||
// 参考主题 zibpay_get_pay_uservip_modal 的 class 与图标顺序。
|
||||
// 参考实现的会员购买弹窗的 class 与图标顺序。
|
||||
assert.match(vipJs, /node\("a", "btn-block but mb10 padding-lg c-green mini-radius"\)/);
|
||||
assert.match(vipJs, /#icon-points-color/);
|
||||
// 图标顺序与 class 串保持参考主题一致;FA5 样式类 `fas` 由图标契约(
|
||||
@@ -180,8 +180,8 @@ test('会员弹窗内的入口为参考主题同款按钮并开本地弹窗', ()
|
||||
});
|
||||
|
||||
test('/user/vip/ 档位购买卡下方的入口为参考主题用户中心同款横条', () => {
|
||||
// 参考主题 zibpay_user_page_tab_content_vip:my_box → vip_card → points link,
|
||||
// 横条为 `c-yellow zib-widget flex ac jsb`(zib-widget → 本主题jio-panel表面)。
|
||||
// 会员页 tab:my_box → vip_card → points link,
|
||||
// 横条为 `c-yellow jio-widget-box flex ac jsb`(jio-widget-box → 本主题jio-panel表面)。
|
||||
assert.match(footer, /function jio_get_vip_points_exchange_entry_html/);
|
||||
assert.match(vipTab, /jio_get_vip_points_exchange_entry_html\(\$user_id, \$level\)/);
|
||||
// 结构 1:1:左 points-color 图标 + 标题(flex1 em12 c-yellow),
|
||||
@@ -195,7 +195,7 @@ test('/user/vip/ 档位购买卡下方的入口为参考主题用户中心同款
|
||||
});
|
||||
|
||||
test('/user/vip/ 入口门控与弹窗壳同源:非会员 + 兑换开启 + 有可兑换套餐', () => {
|
||||
// 参考主题 zibpay_get_vip_points_exchange_link 门控:已是会员不展示。
|
||||
// 已是会员不展示积分兑换入口。
|
||||
assert.match(footer, /if \(\$vip_level > 0\) \{\s*return '';/);
|
||||
// 可用性直接取服务端 catalog(内含 credits 门控与等级启用过滤),
|
||||
// 与 wp_footer 弹窗壳的渲染条件一致,避免出现无弹窗可开的死入口。
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* VIP 后台设置结构化契约(对照参考主题 zibll 1:1)。
|
||||
* VIP 后台设置结构化契约。
|
||||
*
|
||||
* 硬约束:
|
||||
* 1) 会员商品 / 续费商品 / 升级价格 / 积分兑换明细全部为结构化 group 或
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*
|
||||
* 硬约束:
|
||||
* 1) 未开通用户不渲染到期时间行——状态标签已显示「未开通」,参考主题
|
||||
* zibpay_user_page_tab_content_vip 对从未开通用户同样不输出任何到期
|
||||
* 会员页 tab 对从未开通用户同样不输出任何到期
|
||||
* 信息(仅生效中/永久/已过期才有);
|
||||
* 2) 生效中、永久、已过期三个分支的到期文案保持产出;
|
||||
* 3) 到期行整体按文案非空条件渲染,不留空壳 div。
|
||||
|
||||
Reference in new issue
Block a user