Files
wordpress-theme-aksrt/tests/widget-system/frontend-lifecycle-contract.test.cjs

225 lines
10 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use strict';
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const test = require('node:test');
const vm = require('node:vm');
const root = path.resolve(__dirname, '..', '..');
const read = (file) => fs.readFileSync(path.join(root, file), 'utf8');
function sourceFiles(directory) {
const files = [];
for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
if (['.git', 'build', 'node_modules', 'runtime', 'third-party', 'vendor'].includes(entry.name)) continue;
if (/\.min\.js$/.test(entry.name)) continue;
const full = path.join(directory, entry.name);
if (entry.isDirectory()) files.push(...sourceFiles(full));
else if (/\.(?:js|cjs|mjs|php)$/.test(entry.name)) files.push(full);
}
return files;
}
const routed = [
'assets/js/auth.js',
'assets/js/author.js',
'assets/js/credits.js',
'assets/js/order-center.js',
'assets/js/routes/user-center.js',
'assets/js/signin.js',
'assets/js/section-navs.js',
];
test('loader maps and boots the single main module', () => {
const loader = read('assets/js/runtime/loader.js');
assert.match(loader, /['"]main['"]\s*:\s*['"]main\.min['"]/);
assert.match(loader, /tbquire\(\[['"]main['"]\]\)/);
assert.equal((loader.match(/tbquire\(\[['"]main['"]\]\)/g) || []).length, 1);
});
test('main owns the one global auto_fun boot', () => {
const main = read('assets/js/runtime/main.js');
assert.match(main, /window\.auto_fun\s*=\s*debounce\(/);
assert.match(main, /jQuery\(document\)\.trigger\(['"]auto_fun['"]\)/);
});
test('theme and comments do not depend on the removed aksrt namespace APIs', () => {
const theme = read('assets/js/core/theme.js');
const comments = read('assets/js/comment-ajax.js');
const runtime = read('assets/js/runtime/main.min.js');
assert.match(theme, /window\.aksrtData\.themeMode/);
assert.doesNotMatch(theme, /(?:window\.)?aksrt\.toggleTheme/);
assert.doesNotMatch(comments, /(?:window\.)?aksrt\.(?:__|confirm)/);
assert.doesNotMatch(runtime, /(?:window\.)?aksrt\.(?:toggleTheme|__|confirm)/);
});
test('theme initializes when the removed aksrt namespace is absent', () => {
const classList = { toggle() {}, contains() { return false; } };
const document = {
readyState: 'complete',
cookie: '',
body: { classList, setAttribute() {} },
documentElement: { classList, setAttribute() {} },
head: { appendChild() {} },
querySelector() { return null; },
querySelectorAll() { return []; },
getElementById() { return null; },
createElement() { return { setAttribute() {} }; },
dispatchEvent() {},
};
const window = {
document,
location: { protocol: 'https:' },
localStorage: { getItem() { return null; }, setItem() {} },
matchMedia() { return { matches: false }; },
setTimeout,
innerWidth: 1280,
innerHeight: 720,
};
assert.doesNotThrow(() => vm.runInNewContext(read('assets/js/core/theme.js'), {
window,
document,
localStorage: window.localStorage,
CustomEvent: class {},
Date,
Math,
}));
assert.equal(window.aksrt, undefined);
});
test('routed modules use one namespaced auto_fun owner and no registry', () => {
for (const file of routed) {
const source = read(file);
assert.match(source, /\.off\(['"]auto_fun\.[^'"]+['"]\)\.on\(['"]auto_fun\.[^'"]+['"]/, file);
assert.doesNotMatch(source, /AksrtFrontend|aksrt\.autoFun|aksrt:(?:ready|rehydrated)/, file);
}
});
test('wallet reward content opens in the shared modal runtime once', () => {
const reward = read('assets/js/reward.js');
const helper = read('inc/helpers/aksrt-rewards.php');
const authorBar = read('templates/parts/single-author-bar.php');
const singleActions = read('inc/helpers/aksrt-single-render.php');
const scripts = read('src/Core/ScriptsPart1b.php');
const scriptsPart1c = read('src/Core/ScriptsPart1c.php');
assert.match(reward, /runtime\.open\(\{/);
assert.match(reward, /className: 'aksrt-reward-modal modal-mini full-sm'/);
assert.match(reward, /mobileBottom: true/);
assert.match(reward, /shell\.setAttribute\('aria-labelledby', title\.id\)/);
assert.match(reward, /'Idempotency-Key': key/);
assert.equal((reward.match(/document\.addEventListener\('click'/g) || []).length, 1);
assert.doesNotMatch(reward, /auto_fun|reward-modal\.is-open/);
assert.equal((helper.match(/<template\b/g) || []).length, 1);
assert.match(helper, /data-template-id="%2\$s"/);
assert.match(helper, /static \$done = \[\]/);
assert.match(helper, /isset\(\$done\[\$key\]\)/);
assert.match(helper, /modal-colorful-header colorful-bg jb-blue/);
assert.match(helper, /data-aksrt-modal-close/);
assert.match(helper, /data-aksrt-reward-submit/);
assert.match(authorBar, /aksrt_render_rewards_template_for_user/);
assert.match(singleActions, /aksrt_render_rewards_template_for_user/);
assert.match(scriptsPart1c, /\$requirements\['needs_reward'\]\s*=\s*\$is_article && !empty\(\$settings\['rewards_enabled'\]\)/);
assert.match(scriptsPart1c, /\$singular_assets\['needs_reward'\][\s\S]*?\$reward_js[\s\S]*?'aksrt-reward'/);
assert.doesNotMatch(helper + authorBar + singleActions, /aksrt_render_rewards_modal_for_user|reward-modal__qr/);
});
test('user center navigation initializes once across auto_fun rescans', () => {
const source = read('assets/js/routes/user-center.js');
const initAt = source.indexOf('function initDashboardNavigation()');
const guardAt = source.indexOf('aksrtUserCenterNavigationInitialized', initAt);
const initialActivateAt = source.indexOf('activate(initialTab, false)', initAt);
assert.ok(initAt >= 0);
assert.ok(guardAt > initAt && guardAt < initialActivateAt);
assert.match(source.slice(initAt, initialActivateAt), /if \(document\.documentElement\.dataset\.aksrtUserCenterNavigationInitialized === 'true'\) return;/);
assert.match(source.slice(initAt, initialActivateAt), /document\.documentElement\.dataset\.aksrtUserCenterNavigationInitialized = 'true';/);
assert.doesNotMatch(source, /name === 'vip' && !forceLoad\) forceLoad = true/);
});
test('homepage tabs refresh the original pane DOM through the runtime post_ajax pipeline', () => {
const layout = read('inc/helpers/homepage/layout.php')
const homepage = read('inc/helpers/aksrt-homepage.php')
const controller = read('src/Admin/GlobalController.php')
// legacy zib_index_tab_html 契约: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') 契约。
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="/)
assert.match(layout, /<div class="post_ajax_loader">/)
// nofilter:栏目页响应不再输出筛选菜单,避免 win-ajax-replace 串页。
assert.match(layout, /'nofilter' => 'true'/)
// 不再有旁路刷新:REST 面板、骨架屏、面板 footer 全部移除。
assert.equal(fs.existsSync(path.join(root, 'assets/js/homepage-fixed.js')), false)
assert.doesNotMatch(layout, /data-home-tab-panel|aksrt-homepage__panel|aksrt-homepage__tab|requestHomepagePanel|data-sticky-tabs/)
assert.doesNotMatch(controller, /homepage\/tab-posts|rest_homepage_tab_posts/)
// 排序菜单 = zib_ajax_option_menu('home') 契约:win-ajax-replace="filter" + ajax-next 链接。
assert.match(homepage, /function aksrt_homepage_render_ajax_option_menu/)
assert.match(homepage, /win-ajax-replace="filter"/)
assert.match(homepage, /ajax-replace="true" class="' \. esc_attr\(\$_class\)/)
assert.match(homepage, /!empty\(\$_GET\['nofilter'\]\)/)
// 主查询吃 ?orderby=(legacy zib_sift_posts_per_orde),排序原位刷新才有效。
assert.match(homepage, /function aksrt_homepage_get_request_sort/)
assert.match(read('inc/helpers/homepage/content.php'), /aksrt_homepage_get_request_sort\(\)/)
// runtime 是 tab/排序的唯一引擎。
const runtime = read('assets/js/runtime/main.js')
assert.match(runtime, /_win\.bd\.on\('show\.bs\.tab', 'a\[data-ajax\]'/)
assert.match(runtime, /function post_ajax\(/)
// ajax-list.js 让出 runtime 管辖的控件,避免双请求。
assert.match(read('assets/js/core/ajax-list.js'), /link\.closest\("\.ajax-option, \.post_ajax_trigger"\)/)
})
test('archive streams nest pagination inside the ajaxpager for tab extraction', () => {
const archive = read('inc/helpers/aksrt-archive.php')
assert.match(archive, /append_inner_html/)
assert.doesNotMatch(archive, /content-stream__footer/)
assert.match(read('inc/helpers/posts-renderer/markup.php'), /append_inner_html/)
assert.doesNotMatch(read('assets/js/core/pagination.js'), /data-home-tab-panel|content-stream__footer/)
})
test('HTML fragment injection announces post_ajax before auto_fun', () => {
for (const file of ['assets/js/core/pagination.js', 'assets/js/author.js', 'assets/js/credits.js', 'assets/js/routes/user-center.js']) {
const source = read(file);
assert.match(source, /post_ajax\.ed[\s\S]*?window\.auto_fun\(\)/, file);
}
});
test('modal fragment lifecycle reruns auto_fun after loaded.bs.modal', () => {
const source = read('assets/js/runtime/main.js');
assert.match(source, /loaded\.bs\.modal[\s\S]*?auto_fun/);
});
test('JSON success lifecycle remains zib_ajax.success', () => {
const source = read('assets/js/runtime/main.js');
assert.match(source, /zib_ajax\.success/);
});
test('Miniimgbox owns data-imgbox and lazySizes has one integration owner', () => {
const imgbox = read('assets/js/runtime/imgbox.js');
const lazy = read('assets/js/runtime/main.js');
assert.match(imgbox, /Miniimgbox/);
assert.match(imgbox, /data-imgbox/);
assert.match(lazy, /lazybeforeunveil/);
assert.match(lazy, /MutationObserver/);
});
test('non-generated first-party sources contain no deprecated lifecycle API', () => {
const files = sourceFiles(root).filter((file) => {
const relative = path.relative(root, file).replaceAll('\\', '/');
return relative !== 'tests/widget-system/frontend-lifecycle-contract.test.cjs';
});
for (const file of files) {
const source = fs.readFileSync(file, 'utf8');
assert.doesNotMatch(source, /AksrtFrontend|aksrt\.autoFun|aksrt:(?:ready|rehydrated)/, path.relative(root, file));
}
});