fix(runtime): 按参考主题硬切换为 loader/main/auto_fun 单一 DOM 生命周期
- 删除 AksrtFrontend registry/rehydrate 双路架构及 build/public.js 拼包入口,
前台唯一初始路径为 PHP 本地栈(jQuery 1.9.1 → Bootstrap 3.4.1)
→ loader_js(_win/aksrtData 唯一注入)→ tbquire(['main']) → runtime/main.min.js
- window.auto_fun = debounce(z_auto_fun, 100) 为唯一重扫入口;所有模块改为
执行一次 + off/on('auto_fun.aksrt-*') 命名空间重扫,data/class 标记幂等
- 局部更新协议统一:HTML 注入 → post_ajax.ed → auto_fun();
远程 modal → loaded.bs.modal → auto_fun();JSON 成功 → zib_ajax.success
- 灯箱恢复 Miniimgbox/imgbox 唯一 owner(imgbox-container、a[data-imgbox]、
imgbox_data),删除 AksrtLightbox/data-aksrt-lightbox 第二实现及 CSS/overlay,
_win.imgbox* 重新接上 post-display 设置
- 死代码清理:runtime 下 11 组被自有实现替代的旧模块副本、无消费 vendored 库、
顶层 grade.min.js、revenue.js、无调用方的 aksrt_get_win_runtime_config(),
以及 loader 中 16 条死 mapping
- PHP 入队改为本地栈 + loader_js,routed/widget/submit 依赖统一换绑;
用户卡片发布按钮恢复 nav_user_newposts_btn 设置门控
- 测试对齐:新增 frontend-lifecycle-contract,重写 navigation/lazyload/toast/
auth-source/comment/page-display/post-visual 等断言为目标架构契约
验证:node --test widget-system+navigation 223 通过 0 失败;
构建脚本重建 main/imgbox min 与 CSS bundles;改动 PHP php -l、JS node --check
全部通过;git diff --check 干净;credits 重入 harness 通过。
This commit is contained in:
@@ -22,7 +22,7 @@ test('modal and sign page use one renderer and one state machine', () => {
|
||||
assert.match(shared, /data-auth-panel="bind"/);
|
||||
assert.match(shared, /data-auth-panel="oauth"/);
|
||||
assert.match(scripts, /assets\/js\/auth\.js/);
|
||||
assert.match(scripts, /'aksrt-auth',[\s\S]*?assets\/js\/auth\.js[\s\S]*?\['aksrt-runtime'\]/);
|
||||
assert.match(scripts, /'aksrt-auth',[\s\S]*?assets\/js\/auth\.js[\s\S]*?\['loader_js'\]/);
|
||||
assert.doesNotMatch(scripts, /modal-auth\.js|sign-register\.js/);
|
||||
assert.doesNotMatch(runtime, /signsubmit-loader|tbquire\(\['sign-register'\]\)|\.on\('click', '\.(?:signin|signup)-loader'/);
|
||||
assert.doesNotMatch(loader, /sign-register/);
|
||||
|
||||
@@ -72,11 +72,7 @@ function createHarness() {
|
||||
});
|
||||
|
||||
let providerCallback;
|
||||
let captchaInit = null;
|
||||
const window = {
|
||||
AksrtFrontend: {
|
||||
register(_name, init) { captchaInit = init; },
|
||||
},
|
||||
fetch(url) {
|
||||
if (url === '/challenge') return Promise.resolve({ ok: true, json: () => Promise.resolve({ success: true, data: { challenge: 'challenge-token' } }) });
|
||||
if (url === '/exchange') return Promise.resolve({ ok: true, json: () => Promise.resolve({ success: true, data: { proof: 'proof-token' } }) });
|
||||
@@ -91,7 +87,6 @@ function createHarness() {
|
||||
Promise,
|
||||
};
|
||||
vm.runInNewContext(runtime, { window, document, Promise, MutationObserver: undefined, setTimeout, clearTimeout, console });
|
||||
if (captchaInit) captchaInit(document, 'init');
|
||||
return { api: window.AKSRTCaptcha, container };
|
||||
}
|
||||
|
||||
|
||||
@@ -21,9 +21,11 @@ test('comment smilie assets and their runtime chain stay removed', () => {
|
||||
'assets/css/build/public.min.css',
|
||||
'assets/css/main.css',
|
||||
'assets/css/main.min.css',
|
||||
'assets/js/build/public.js',
|
||||
'assets/js/runtime/main.min.js',
|
||||
'assets/js/comment-tools.js',
|
||||
'assets/js/enhancements.js',
|
||||
'assets/js/runtime/imgbox.js',
|
||||
'assets/js/runtime/imgbox.min.js',
|
||||
'assets/js/runtime/main.js',
|
||||
'assets/js/runtime/main.min.js',
|
||||
'inc/helpers/aksrt-comments.php',
|
||||
|
||||
@@ -40,15 +40,14 @@ test('published post content reaches the front submit editor unchanged', () => {
|
||||
assert.match(editor, /wp_editor\(\(string\) \$ctx\['current_content'\], 'post_content', \$editor_settings\);/);
|
||||
});
|
||||
|
||||
test('front submit uses the WordPress registered jQuery runtime', () => {
|
||||
test('front submit uses the registered local jQuery runtime', () => {
|
||||
const source = read('src/Core/ScriptsPart1b.php');
|
||||
|
||||
assert.match(source, /wp_enqueue_script\('jquery'\);/);
|
||||
assert.match(source, /registered\['jquery'\]->deps\s*=\s*\['jquery-core'\]/);
|
||||
assert.doesNotMatch(source, /wp_deregister_script\('jquery'\)/);
|
||||
assert.doesNotMatch(source, /wp_register_script\(\s*'jquery'/);
|
||||
assert.match(source, /wp_register_script\('jquery', \$base_uri \. '\/assets\/js\/runtime\/libs\/jquery\.min\.js', \[\], '1\.9\.1', false\)/);
|
||||
assert.doesNotMatch(source, /jquery-migrate/);
|
||||
assert.doesNotMatch(source, /runtime\/libs\/jquery\.min\.js|['"]1\.9\.1['"]/);
|
||||
|
||||
const page = read('templates/pages/page-submit.php');
|
||||
assert.match(page, /\['loader_js', 'aksrt-front-submit-pay-download'\]/);
|
||||
});
|
||||
|
||||
test('legacy runtime modules use jQuery explicitly without a dollar alias', () => {
|
||||
@@ -85,7 +84,7 @@ test('theme runtime never changes native listener passivity for third-party edit
|
||||
const scripts = read('src/Core/Scripts.php');
|
||||
const scriptsPart2 = read('src/Core/ScriptsPart2.php');
|
||||
const buildTool = read('tools/build-public-assets.mjs');
|
||||
const publicRuntime = read('assets/js/build/public.js');
|
||||
const publicRuntime = read('assets/js/runtime/main.min.js');
|
||||
|
||||
assert.doesNotMatch(scripts, /enqueue_passive_polyfill|aksrt-passive-events/);
|
||||
assert.doesNotMatch(scriptsPart2, /enqueue_passive_polyfill|aksrt-passive-events/);
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
'use strict';
|
||||
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const test = require('node:test');
|
||||
|
||||
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/reward.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('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('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));
|
||||
}
|
||||
});
|
||||
@@ -1,117 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const test = require('node:test');
|
||||
|
||||
const root = path.resolve(__dirname, '..', '..');
|
||||
const read = (file) => fs.readFileSync(path.join(root, file), 'utf8');
|
||||
|
||||
const publicSources = [
|
||||
'assets/js/core/comment-fixed-input.js',
|
||||
'assets/js/core/misc.js',
|
||||
'assets/js/follow.js',
|
||||
'assets/js/enhancements.js',
|
||||
'assets/js/enhancements2.js',
|
||||
'assets/js/color-extract.js',
|
||||
'assets/js/effects.js',
|
||||
'assets/js/captcha-front.js',
|
||||
'assets/widgets/frontend.js',
|
||||
];
|
||||
|
||||
const routedSources = [
|
||||
'assets/js/auth.js',
|
||||
'assets/js/author.js',
|
||||
'assets/js/routes/user-center.js',
|
||||
'assets/js/order-center.js',
|
||||
'assets/js/credits.js',
|
||||
'assets/js/signin.js',
|
||||
'assets/js/reward.js',
|
||||
'assets/js/report.js',
|
||||
'assets/js/page-navs.js',
|
||||
'assets/js/section-navs.js',
|
||||
'assets/js/posts-navs-page.js',
|
||||
'assets/js/front-submit.js',
|
||||
'assets/js/mini-upload.js',
|
||||
'assets/js/page-template.js',
|
||||
'assets/js/page-edit.js',
|
||||
'assets/js/precode.js',
|
||||
'assets/js/post-share.js',
|
||||
'assets/js/poster-share.js',
|
||||
'assets/js/pay-vip.js',
|
||||
'assets/js/video-player.js',
|
||||
'assets/js/download-status.js',
|
||||
'assets/js/ban-appeal-page.js',
|
||||
'assets/js/docs-navigation.js',
|
||||
'assets/js/input-expand.js',
|
||||
'assets/js/waterfall.js',
|
||||
'assets/js/yiyan.js',
|
||||
'assets/js/messages.js',
|
||||
'assets/js/site-notice.js',
|
||||
'assets/js/user-badge-actions.js',
|
||||
];
|
||||
|
||||
const readyPattern = /(?:document\.addEventListener\(\s*['"]DOMContentLoaded['"]|document\.readyState\s*===|jQuery\(document\)\.ready\s*\()/;
|
||||
|
||||
test('global DOM boot is the only initial page lifecycle owner', () => {
|
||||
const runtime = read('assets/js/aksrt-runtime.js');
|
||||
const registry = read('assets/js/rehydrate.js');
|
||||
assert.match(registry, /boot:\s*function/);
|
||||
assert.match(registry, /register:[\s\S]*?_booted/);
|
||||
assert.match(runtime, /AksrtFrontend\.boot\(root\)/);
|
||||
assert.match(runtime, /aksrt\.autoFun\(document, 'init'\)/);
|
||||
assert.match(runtime, /reason === 'init'[\s\S]*?AksrtFrontend\.boot\(root\)[\s\S]*?else[\s\S]*?AksrtFrontend\.rehydrate\(root, reason\)/);
|
||||
});
|
||||
|
||||
test('public bundle components register instead of owning DOM ready', () => {
|
||||
for (const file of publicSources) {
|
||||
const source = read(file);
|
||||
assert.doesNotMatch(source, readyPattern, `${file} still owns a DOM-ready boot`);
|
||||
assert.match(source, /AksrtFrontend\.register\(/, `${file} must register with the global lifecycle`);
|
||||
}
|
||||
});
|
||||
|
||||
test('active routed UI scripts register with the global lifecycle', () => {
|
||||
for (const file of routedSources) {
|
||||
const source = read(file);
|
||||
if (/API|coordinator|runtime/i.test(source.slice(0, 500)) && !/querySelector|addEventListener/.test(source)) continue;
|
||||
assert.doesNotMatch(source, readyPattern, `${file} still owns a DOM-ready boot`);
|
||||
assert.match(source, /AksrtFrontend\.register\(/, `${file} must register with the global lifecycle`);
|
||||
}
|
||||
});
|
||||
|
||||
test('high-risk routed components remain idempotent under global rehydrate', () => {
|
||||
const pageNavs = read('assets/js/page-navs.js');
|
||||
const sectionNavs = read('assets/js/section-navs.js');
|
||||
const poster = read('assets/js/poster-share.js');
|
||||
|
||||
assert.match(pageNavs, /__aksrtPageNavInitialized/);
|
||||
assert.match(pageNavs, /var grid = self\.root\.querySelector\('\.aksrt-doc-grid'\)/);
|
||||
assert.match(sectionNavs, /if \(scrollspyBound\) return/);
|
||||
assert.match(sectionNavs, /if \(autoHashHandled \|\| !window\.location\.hash\) return/);
|
||||
assert.match(poster, /triggers\.filter\(function \(el\) \{ return el\.dataset\.posterInit !== '1'; \}\)/);
|
||||
assert.doesNotMatch(poster, /triggers\.some\(function \(el\) \{ return el\.dataset\.posterInit === '1'; \}\)/);
|
||||
});
|
||||
|
||||
test('resource and SDK load listeners are not mistaken for page boot', () => {
|
||||
const captcha = read('assets/js/captcha-front.js');
|
||||
const color = read('assets/js/color-extract.js');
|
||||
assert.match(captcha, /\.onload\s*=|addEventListener\(['"]load['"]/);
|
||||
assert.match(color, /addEventListener\(['"]load['"]/);
|
||||
assert.doesNotMatch(captcha, /DOMContentLoaded/);
|
||||
assert.doesNotMatch(color, /DOMContentLoaded/);
|
||||
});
|
||||
|
||||
test('route scripts that use the registry load after the public runtime', () => {
|
||||
const scripts = read('src/Core/ScriptsPart1b.php') + read('src/Core/ScriptsPart1c.php');
|
||||
const files = ['section-navs.js', 'page-navs.js', 'posts-navs-page.js', 'author.js', 'auth.js'];
|
||||
for (const file of files) {
|
||||
const uri = scripts.indexOf("/assets/js/" + file);
|
||||
assert.notEqual(uri, -1, `${file} must be enqueued`);
|
||||
const blockStart = scripts.lastIndexOf('wp_enqueue_script(', uri);
|
||||
const blockEnd = scripts.indexOf(');', uri);
|
||||
const block = scripts.slice(blockStart, blockEnd + 2);
|
||||
assert.match(block, /aksrt-runtime/, `${file} must depend on aksrt-runtime`);
|
||||
}
|
||||
});
|
||||
@@ -1,85 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const vm = require('node:vm');
|
||||
const test = require('node:test');
|
||||
|
||||
const root = path.resolve(__dirname, '..', '..');
|
||||
const read = (file) => fs.readFileSync(path.join(root, file), 'utf8');
|
||||
|
||||
test('homepage uses the global DOM lifecycle without a second ready path', () => {
|
||||
const runtime = read('assets/js/aksrt-runtime.js');
|
||||
const homepage = read('assets/js/homepage-fixed.js');
|
||||
|
||||
assert.match(runtime, /aksrt\.autoFun\s*=\s*function\s*\(root, reason\)/);
|
||||
assert.match(runtime, /function initRuntime\(\)\s*\{\s*aksrt\.autoFun\(document, 'init'\);/s);
|
||||
assert.doesNotMatch(runtime, /function initRuntime\(\)\s*\{\s*aksrt\.autoFun\(document\);/s);
|
||||
|
||||
assert.match(homepage, /window\.AksrtFrontend\.register\('aksrt-homepage', init\)/);
|
||||
assert.doesNotMatch(homepage, /DOMContentLoaded/);
|
||||
assert.doesNotMatch(homepage, /document\.querySelectorAll\('\[data-sort-dropdown-trigger\]'\)/);
|
||||
assert.doesNotMatch(homepage, /document\.querySelectorAll\('\.aksrt-homepage__stream'\)/);
|
||||
assert.doesNotMatch(homepage, /document\.querySelectorAll\('\.aksrt-homepage__tabs\.home-tab-nav-box'\)/);
|
||||
assert.match(homepage, /dataset\.aksrtHomeTabsBound === '1'/);
|
||||
assert.match(homepage, /dataset\.aksrtSortDropdownBound === '1'/);
|
||||
assert.match(homepage, /dataset\.aksrtSortbarBound === '1'/);
|
||||
assert.match(homepage, /dataset\.aksrtStickyTabsBound === '1'/);
|
||||
});
|
||||
|
||||
test('homepage initializer is registered once and remains idempotent across rehydrate calls', () => {
|
||||
const source = read('assets/js/homepage-fixed.js');
|
||||
const registrations = [];
|
||||
let sortbarClickBindings = 0;
|
||||
const sortbar = {
|
||||
dataset: {},
|
||||
getAttribute() {
|
||||
return null;
|
||||
},
|
||||
addEventListener(type) {
|
||||
if (type === 'click') sortbarClickBindings++;
|
||||
},
|
||||
};
|
||||
const stream = {
|
||||
querySelector(selector) {
|
||||
return selector === '.aksrt-homepage__sortbar' ? sortbar : null;
|
||||
},
|
||||
};
|
||||
const scope = {
|
||||
querySelectorAll(selector) {
|
||||
return selector === '.aksrt-homepage__stream' ? [stream] : [];
|
||||
},
|
||||
};
|
||||
const context = {
|
||||
window: {
|
||||
AksrtFrontend: {
|
||||
register(name, init) {
|
||||
registrations.push({ name, init });
|
||||
},
|
||||
},
|
||||
},
|
||||
document: scope,
|
||||
console,
|
||||
URLSearchParams,
|
||||
URL,
|
||||
setTimeout,
|
||||
clearTimeout,
|
||||
};
|
||||
context.window.window = context.window;
|
||||
context.window.document = scope;
|
||||
vm.runInNewContext(source, context, { filename: 'homepage-fixed.js' });
|
||||
|
||||
assert.equal(registrations.length, 1);
|
||||
assert.equal(registrations[0].name, 'aksrt-homepage');
|
||||
assert.equal(typeof registrations[0].init, 'function');
|
||||
registrations[0].init(scope, 'init');
|
||||
registrations[0].init(scope, 'ajax');
|
||||
assert.equal(sortbarClickBindings, 1);
|
||||
assert.equal(sortbar.dataset.aksrtSortbarBound, '1');
|
||||
});
|
||||
|
||||
test('homepage AJAX content continues to use the shared ajax rehydrate contract', () => {
|
||||
const homepage = read('assets/js/homepage-fixed.js');
|
||||
assert.match(homepage, /window\.AksrtFrontend\.rehydrate\(panel, 'ajax'\)/);
|
||||
});
|
||||
@@ -121,9 +121,9 @@ echo aksrt_lazy_content_images($html);
|
||||
assert.doesNotMatch(avatar, /loading=/);
|
||||
});
|
||||
|
||||
test('the legacy runtime owns lazySizes while AKSRT consumers share one image-attribute bridge', () => {
|
||||
test('lazySizes has one auto_fun integration owner and loader boots main once', () => {
|
||||
const runtime = read('assets/js/runtime/main.js');
|
||||
const bridge = read('assets/js/lazy-image-attr.js');
|
||||
const integration = runtime;
|
||||
const scripts = read('src/Core/ScriptsPart1c.php');
|
||||
const routeScripts = read('src/Core/ScriptsPart1b.php');
|
||||
const builder = read('tools/build-public-assets.mjs');
|
||||
@@ -133,17 +133,17 @@ test('the legacy runtime owns lazySizes while AKSRT consumers share one image-at
|
||||
const widgets = read('assets/widgets/frontend.js');
|
||||
|
||||
assert.match(runtime, /window\.lazySizes = lazySizes/);
|
||||
assert.match(bridge, /window\.aksrtGetLazyImageAttr = function/);
|
||||
assert.match(builder, /assets\/js\/lazy-image-attr\.js/);
|
||||
assert.match(builder, /assets\/js\/rehydrate\.js/);
|
||||
assert.match(builder, /assets\/js\/runtime\/main\.min\.js/);
|
||||
assert.doesNotMatch(loader, /['"]main['"]\s*:|tbquire\(\[['"]main['"]\]\)/);
|
||||
assert.match(scripts, /assets\/js\/build\/public\.js'[\s\S]*?\['jquery'\]/);
|
||||
assert.match(loader, /['"]main['"]\s*:\s*['"]main\.min['"]/);
|
||||
assert.match(loader, /tbquire\(\[['"]main['"]\]\)/);
|
||||
assert.match(integration, /img\[data-src\]:not\(\.lazyload,\.lazyloaded,\.lazyloading,\.swiper-lazy\)/);
|
||||
assert.match(integration, /new MutationObserver/);
|
||||
assert.match(scripts, /'loader_js'[\s\S]*?assets\/js\/runtime\/loader\.js/);
|
||||
for (const file of ['pay-vip.js', 'order-center.js', 'page-navs.js', 'credits.js']) {
|
||||
const blocks = enqueueBlocksFor(routeScripts, file);
|
||||
assert.ok(blocks.length > 0, `${file} must be enqueued`);
|
||||
for (const block of blocks) {
|
||||
assert.match(block, /aksrt-runtime/, `${file} must depend on the bundled image-attribute bridge`);
|
||||
assert.match(block, /loader_js/, `${file} must depend on the bundled image-attribute bridge`);
|
||||
}
|
||||
}
|
||||
assert.match(runtime, /lazybeforeunveil/);
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const test = require('node:test');
|
||||
|
||||
const root = path.resolve(__dirname, '..', '..');
|
||||
const read = (file) => fs.readFileSync(path.join(root, file), 'utf8');
|
||||
|
||||
test('runtime exposes the article media settings to the single lightbox owner', () => {
|
||||
const scripts = read('src/Core/ScriptsPart1c.php');
|
||||
const enhancements = read('assets/js/enhancements.js');
|
||||
|
||||
assert.match(scripts, /Settings::get_post_page_settings\(\)/);
|
||||
for (const key of ['lightbox', 'lightbox_thumbs', 'lightbox_zoom', 'lightbox_full', 'lightbox_play', 'lightbox_down']) {
|
||||
assert.match(scripts, new RegExp(`['"]${key}['"]`));
|
||||
}
|
||||
assert.doesNotMatch(scripts, /'imgbox'\s*=>\s*false/);
|
||||
assert.match(enhancements, /window\._win/);
|
||||
assert.doesNotMatch(enhancements, /aksrtData\.imgbox/);
|
||||
});
|
||||
|
||||
test('the modern lightbox owns content, galleries and explicit links without a hard size gate', () => {
|
||||
const enhancements = read('assets/js/enhancements.js');
|
||||
const gallery = read('templates/parts/content-gallery.php');
|
||||
|
||||
assert.match(enhancements, /data-aksrt-lightbox-trigger/);
|
||||
assert.match(enhancements, /data-caption/);
|
||||
assert.match(enhancements, /data-lazy-src/);
|
||||
assert.doesNotMatch(enhancements, /img\.width\s*<\s*100/);
|
||||
assert.doesNotMatch(enhancements, /img\.height\s*<\s*100/);
|
||||
assert.match(gallery, /data-aksrt-lightbox/);
|
||||
});
|
||||
|
||||
test('legacy overlay and imgbox entry points are no longer enqueued or built', () => {
|
||||
const scripts = read('src/Core/ScriptsPart1c.php');
|
||||
const build = read('tools/build-public-assets.mjs');
|
||||
const loader = read('assets/js/runtime/loader.js');
|
||||
const part2 = read('inc/core-hooks/part-2.php');
|
||||
const single = read('single.php');
|
||||
const page = read('page.php');
|
||||
|
||||
assert.doesNotMatch(scripts, /needs_page_template[^\n]*lightbox/);
|
||||
assert.doesNotMatch(read('assets/js/page-template.js'), /function initLightbox|lightbox-overlay/);
|
||||
assert.doesNotMatch(build, /assets\/js\/runtime\/imgbox/);
|
||||
assert.doesNotMatch(loader, /['"]imgbox['"]\s*:/);
|
||||
assert.doesNotMatch(part2, /aksrt_print_win_var/);
|
||||
assert.doesNotMatch(single, /id="lightbox-overlay"/);
|
||||
assert.doesNotMatch(page, /id="lightbox-overlay"/);
|
||||
});
|
||||
|
||||
test('non-navigational page media and carousel images opt into the shared contract', () => {
|
||||
const page = read('page.php');
|
||||
const carousel = read('inc/core-hooks/part-3.php');
|
||||
|
||||
assert.match(page, /entry-thumb[^>]*data-aksrt-lightbox/);
|
||||
assert.match(carousel, /data-aksrt-lightbox/);
|
||||
});
|
||||
@@ -150,8 +150,12 @@ test('OAuth result handler maps safe errors and preserves unrelated URL state',
|
||||
body: { classList: { add() {}, remove() {} } },
|
||||
}
|
||||
let authInit = null
|
||||
const jQueryApi = {
|
||||
off() { return jQueryApi },
|
||||
on(_name, fn) { authInit = fn; return jQueryApi },
|
||||
}
|
||||
const jQueryStub = () => jQueryApi
|
||||
const window = {
|
||||
AksrtFrontend: { register: (_name, init) => { authInit = init } },
|
||||
aksrtData: {},
|
||||
URL,
|
||||
location,
|
||||
@@ -176,8 +180,9 @@ test('OAuth result handler maps safe errors and preserves unrelated URL state',
|
||||
Date,
|
||||
Math,
|
||||
Promise,
|
||||
jQuery: jQueryStub,
|
||||
})
|
||||
authInit(document, 'init')
|
||||
if (authInit) authInit()
|
||||
assert.deepEqual(notices, [{ message: '第三方授权失败,请重试', type: 'danger' }])
|
||||
assert.equal(replaced, '/sign/?keep=1#section')
|
||||
assert.equal(replaced.includes('script'), false)
|
||||
|
||||
@@ -353,8 +353,9 @@ test('search dropdown clears the fixed header and closes from the panel lower-ri
|
||||
|
||||
test('header user-card publish controls are removed from configuration and runtime', () => {
|
||||
assert.doesNotMatch(schema, /nav_user_newposts_btn/)
|
||||
assert.doesNotMatch(headerNavbar, /nav_user_newposts_btn/)
|
||||
assert.match(headerNavbar, /esc_html__\('发布内容', 'aksrt'\)/)
|
||||
assert.match(headerNavbar, /nav_user_newposts_btn/)
|
||||
assert.match(headerNavbar, /nav_user_newposts_btn_text/)
|
||||
|
||||
})
|
||||
|
||||
test('header user-card membership stays on one row with a date-only expiry', () => {
|
||||
|
||||
@@ -323,7 +323,8 @@ test('AJAX pagination can recover, caps auto loading and rehydrates appended med
|
||||
assert.match(admin, /\$query_vars\['paged'\] = \$page/);
|
||||
assert.match(js, /failed = false;\s*loadNextPage\(fromAuto\)/);
|
||||
assert.match(js, /successfulAutoLoads >= iasMax/);
|
||||
assert.match(js, /AksrtFrontend\.rehydrate\(target, "ajax"\)/);
|
||||
assert.match(js, /post_ajax\.ed/);
|
||||
assert.match(js, /window\.auto_fun\(\)/);
|
||||
assert.match(js, /classList\.add\("lazyload"\)/);
|
||||
assert.match(homepage, /aksrt_render_pagination\(\$query,[\s\S]*?'query_context'\s*=>\s*\$contract\['query_context'\]/);
|
||||
assert.doesNotMatch(homepage, /href="javascript:;" class="ajax-next aksrt-load-more-btn"/);
|
||||
@@ -335,7 +336,7 @@ test('AJAX pagination can recover, caps auto loading and rehydrates appended med
|
||||
assert.match(media, /window\.Grade\(container/);
|
||||
assert.match(media, /linear-gradient\(135deg/);
|
||||
assert.match(media, /aksrtGradientState/);
|
||||
assert.match(read('tools/build-public-assets.mjs'), /assets\/js\/grade\.min\.js/);
|
||||
assert.match(read('assets/js/runtime/main.js'), /tbquire\(\['grade'\]/);
|
||||
});
|
||||
|
||||
test('front-end carousel CSS is conditional on carousel block content', () => {
|
||||
@@ -490,7 +491,8 @@ test('public user home stays param-free and swaps panels via REST fragments', ()
|
||||
assert.doesNotMatch(authorJs, /searchParams|pushState|replaceState/);
|
||||
assert.match(authorJs, /\/user-home\/panel\/' \+ userId \+ '/);
|
||||
assert.match(authorJs, /data-uh-sub/);
|
||||
assert.match(authorJs, /AksrtFrontend\.rehydrate\(panel, 'ajax'\)/);
|
||||
assert.match(authorJs, /post_ajax\.ed/);
|
||||
assert.match(authorJs, /window\.auto_fun\(\)/);
|
||||
});
|
||||
|
||||
test('single articles enqueue only content and settings driven assets', () => {
|
||||
@@ -565,7 +567,7 @@ test('single post more menu has one accessible runtime and an in-flow author anc
|
||||
assert.match(article, /setAttribute\("aria-expanded", open \? "true" : "false"\)/);
|
||||
assert.match(article, /setAttribute\("aria-hidden", open \? "false" : "true"\)/);
|
||||
assert.match(article, /event\.key === "Escape"/);
|
||||
assert.match(article, /window\.AksrtFrontend\.register\("aksrt-post-meta-menus"/);
|
||||
assert.match(article, /jQuery\(document\)\.off\(.*auto_fun/);
|
||||
assert.doesNotMatch(components + article, /data-toggle=["']dropdown|\.dropdown\(/);
|
||||
|
||||
assert.match(components, /\$menu_id = 'post-drop-meta-menu-' \. \$post_id/);
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const test = require('node:test');
|
||||
|
||||
const root = path.resolve(__dirname, '..', '..');
|
||||
const read = (file) => fs.readFileSync(path.join(root, file), 'utf8');
|
||||
|
||||
test('public bundle has one source list and one generated legacy main artifact', () => {
|
||||
const build = read('tools/build-public-assets.mjs');
|
||||
assert.match(build, /'assets\/js\/rehydrate\.js'[\s\S]*?'assets\/js\/aksrt-runtime\.js'/);
|
||||
assert.match(build, /'assets\/js\/runtime\/main\.min\.js'/);
|
||||
assert.match(build, /async function buildRuntimeMainMin\(\)/);
|
||||
assert.match(build, /assets\/js\/runtime\/main\.js/);
|
||||
assert.match(build, /await buildRuntimeMainMin\(\)/);
|
||||
});
|
||||
|
||||
test('global lifecycle source precedes every registered public component', () => {
|
||||
const build = read('tools/build-public-assets.mjs');
|
||||
const registry = build.indexOf("'assets/js/rehydrate.js'");
|
||||
const runtime = build.indexOf("'assets/js/aksrt-runtime.js'");
|
||||
const legacy = build.indexOf("'assets/js/runtime/main.min.js'");
|
||||
assert.ok(registry >= 0 && registry < runtime && runtime < legacy);
|
||||
});
|
||||
|
||||
test('deleted orphan entry files do not return to enqueue or build graphs', () => {
|
||||
const graph = [
|
||||
read('tools/build-public-assets.mjs'),
|
||||
read('src/Core/ScriptsPart1b.php'),
|
||||
read('src/Core/ScriptsPart1c.php'),
|
||||
].join('\n');
|
||||
assert.equal(fs.existsSync(path.join(root, 'assets/js/main.js')), false);
|
||||
assert.equal(fs.existsSync(path.join(root, 'assets/js/loader.js')), false);
|
||||
assert.doesNotMatch(graph, /assets\/js\/(?:main|loader)\.js/);
|
||||
});
|
||||
|
||||
test('built public JavaScript contains the canonical global boot', () => {
|
||||
const bundle = read('assets/js/build/public.js');
|
||||
assert.match(bundle, /AksrtFrontend\.boot\(root\)/);
|
||||
assert.match(bundle, /aksrt\.autoFun\(document, 'init'\)/);
|
||||
assert.match(bundle, /aksrt:ready/);
|
||||
});
|
||||
@@ -1,97 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const vm = require('node:vm');
|
||||
const test = require('node:test');
|
||||
|
||||
const root = path.resolve(__dirname, '..', '..');
|
||||
const source = fs.readFileSync(path.join(root, 'assets/js/rehydrate.js'), 'utf8');
|
||||
|
||||
function createRuntime() {
|
||||
const events = [];
|
||||
const document = {
|
||||
dispatchEvent(event) {
|
||||
events.push(event);
|
||||
return true;
|
||||
},
|
||||
};
|
||||
function CustomEvent(type, options) {
|
||||
this.type = type;
|
||||
this.bubbles = Boolean(options && options.bubbles);
|
||||
this.detail = options && options.detail;
|
||||
}
|
||||
const errors = [];
|
||||
const context = {
|
||||
window: {},
|
||||
document,
|
||||
CustomEvent,
|
||||
console: {
|
||||
error(...args) {
|
||||
errors.push(args);
|
||||
},
|
||||
},
|
||||
};
|
||||
context.window.window = context.window;
|
||||
context.window.document = document;
|
||||
vm.runInNewContext(source, context, { filename: 'rehydrate.js' });
|
||||
return { api: context.window.AksrtFrontend, document, events, errors };
|
||||
}
|
||||
|
||||
test('global DOM boot runs registered components once with init reason', () => {
|
||||
const runtime = createRuntime();
|
||||
const calls = [];
|
||||
runtime.api.register('first', (rootNode, reason) => calls.push([rootNode, reason]));
|
||||
|
||||
runtime.api.boot(runtime.document);
|
||||
runtime.api.boot(runtime.document);
|
||||
|
||||
assert.equal(runtime.api.isBooted(), true);
|
||||
assert.deepEqual(calls, [[runtime.document, 'init']]);
|
||||
assert.deepEqual(runtime.events.map((event) => event.type), ['aksrt:rehydrated', 'aksrt:ready']);
|
||||
assert.equal(runtime.events[0].detail.reason, 'init');
|
||||
assert.equal(runtime.events[1].detail.reason, 'init');
|
||||
});
|
||||
|
||||
test('late registrations initialize immediately after global boot', () => {
|
||||
const runtime = createRuntime();
|
||||
const calls = [];
|
||||
runtime.api.boot(runtime.document);
|
||||
runtime.api.register('late', (rootNode, reason) => calls.push([rootNode, reason]));
|
||||
|
||||
assert.deepEqual(calls, [[runtime.document, 'init']]);
|
||||
assert.deepEqual(Array.from(runtime.api.getRegistered()), ['late']);
|
||||
});
|
||||
|
||||
test('same-name registration replaces the owner without duplicating it', () => {
|
||||
const runtime = createRuntime();
|
||||
const calls = [];
|
||||
runtime.api.register('owner', () => calls.push('old'));
|
||||
runtime.api.register('owner', () => calls.push('new'));
|
||||
runtime.api.boot(runtime.document);
|
||||
|
||||
assert.deepEqual(Array.from(runtime.api.getRegistered()), ['owner']);
|
||||
assert.deepEqual(calls, ['new']);
|
||||
});
|
||||
|
||||
test('rehydrate preserves root and reason while isolating component failures', () => {
|
||||
const runtime = createRuntime();
|
||||
const fragment = {
|
||||
dispatchEvent(event) {
|
||||
runtime.events.push(event);
|
||||
return true;
|
||||
},
|
||||
};
|
||||
const calls = [];
|
||||
runtime.api.register('broken', () => { throw new Error('broken'); });
|
||||
runtime.api.register('healthy', (rootNode, reason) => calls.push([rootNode, reason]));
|
||||
|
||||
runtime.api.rehydrate(fragment, 'modal');
|
||||
|
||||
assert.deepEqual(calls, [[fragment, 'modal']]);
|
||||
assert.equal(runtime.errors.length, 1);
|
||||
assert.equal(runtime.events.at(-1).type, 'aksrt:rehydrated');
|
||||
assert.equal(runtime.events.at(-1).detail.reason, 'modal');
|
||||
assert.equal(runtime.events.at(-1).bubbles, true);
|
||||
});
|
||||
@@ -191,8 +191,8 @@ test('notyf CSS and WordPress enqueue match the reference contract', () => {
|
||||
|
||||
assert.match(builder, /assets\/js\/notyf\.js/);
|
||||
assert.match(builder, /assets\/css\/frontend\/notyf\.css/);
|
||||
assert.match(builder, /assets\/js\/runtime\/loader\.js/);
|
||||
assert.match(scripts, /'aksrt-runtime',[\s\S]*?'\/assets\/js\/build\/public\.js',[\s\S]*?\['jquery'\]/);
|
||||
assert.match(builder, /assets\/js\/runtime\/main\.js/);
|
||||
assert.match(scripts, /'loader_js',[\s\S]*?'\/assets\/js\/runtime\/loader\.js',[\s\S]*?\['jquery', 'bootstrap'\]/);
|
||||
});
|
||||
|
||||
test('balance center actions use direct fixed-id notyf flows', () => {
|
||||
@@ -240,13 +240,13 @@ test('balance center actions use direct fixed-id notyf flows', () => {
|
||||
assert.match(signinController, /querySelectorAll\('\[data-signin-btn\]'\)/);
|
||||
assert.match(signinController, /freshButtons\.forEach[\s\S]*?addEventListener\('click'/);
|
||||
assert.match(credits, /onTimeout:[\s\S]*?notyf\([^;]+?'danger'[^;]+?'aksrt-balance-charge-payment-toast'\)/);
|
||||
assert.match(credits, /function bootAssets\(root, reason\)[\s\S]*?initSigninGoto\(root\);[\s\S]*?initBalanceCharge\(root\);[\s\S]*?initTransfer\(root\);[\s\S]*?initPointsExchange\(root\);[\s\S]*?initWithdraw\(root\);/);
|
||||
assert.match(credits, /register\('credits', bootAssets\)/);
|
||||
assert.match(credits, /auto_fun\.aksrt-credits'[\s\S]*?bootAssets\(\);/);
|
||||
assert.match(credits, /function bootAssets\(\)[\s\S]*?initSigninGoto\(\);[\s\S]*?initBalanceCharge\(\);[\s\S]*?initPointsExchange\(\);[\s\S]*?initWithdraw\(\);/);
|
||||
assert.doesNotMatch(credits, /function initSignin\(|\/signin\/today|POST \/signin/);
|
||||
assert.match(signinController, /aksrtSigninInitialized/);
|
||||
assert.match(credits, /aksrtPointsExchangeInitialized[\s\S]*?aksrtWithdrawInitialized/);
|
||||
assert.match(scripts, /'aksrt-credits',[\s\S]*?\/assets\/js\/credits\.js',[\s\S]*?\[[^\]]*'aksrt-runtime'[^\]]*\]/);
|
||||
assert.match(scripts, /\$signin_on\s*=\s*is_user_logged_in\(\)[\s\S]*?'aksrt-signin',[\s\S]*?\/assets\/js\/signin\.js',[\s\S]*?'aksrt-runtime'/);
|
||||
assert.match(scripts, /'aksrt-credits',[\s\S]*?\/assets\/js\/credits\.js',[\s\S]*?\[[^\]]*'loader_js'[^\]]*\]/);
|
||||
assert.match(scripts, /\$signin_on\s*=\s*is_user_logged_in\(\)[\s\S]*?'aksrt-signin',[\s\S]*?\/assets\/js\/signin\.js',[\s\S]*?'loader_js'/);
|
||||
assert.ok(header.indexOf('data-signin-btn') < header.indexOf('uc-header__message-btn'));
|
||||
assert.match(header, /uc-header__message-btn[^>]+aria-label="<\?php esc_attr_e\('消息通知'/);
|
||||
assert.doesNotMatch(header, /<i class="fa fas fa-bell"[^>]*><\/i>\s*<\?php esc_html_e\('消息通知'/);
|
||||
@@ -285,7 +285,7 @@ test('VIP purchase and points exchange use direct fixed-id notyf flows', () => {
|
||||
|
||||
assert.doesNotMatch(vip, /setTip|aksrt-vip-modal-tip/);
|
||||
assert.doesNotMatch(css, /\.aksrt-vip-modal-tip/);
|
||||
assert.match(scripts, /'aksrt-pay-vip',[\s\S]*?\/assets\/js\/pay-vip\.js',[\s\S]*?\[[^\]]*'aksrt-runtime'[^\]]*\]/);
|
||||
assert.match(scripts, /'aksrt-pay-vip',[\s\S]*?\/assets\/js\/pay-vip\.js',[\s\S]*?\[[^\]]*'loader_js'[^\]]*\]/);
|
||||
});
|
||||
|
||||
test('payment entry points never load the removed legacy pay runtime', () => {
|
||||
@@ -308,7 +308,7 @@ test('payment entry points never load the removed legacy pay runtime', () => {
|
||||
assert.match(content, /data-aksrt-pay-content/);
|
||||
assert.match(scripts, /'aksrt-pay-vip'/);
|
||||
assert.match(scripts, /'aksrt-cms-download'/);
|
||||
assert.match(scripts, /\$public_runtime_path = get_template_directory\(\) \. '\/assets\/js\/build\/public\.js'/);
|
||||
assert.match(scripts, /\$runtime_main_path = get_template_directory\(\) \. '\/assets\/js\/runtime\/main\.min\.js'/);
|
||||
assert.match(scripts, /\$runtime_version = file_exists\(\$runtime_main_path\)/);
|
||||
assert.match(scripts, /'ver'\s*=> \$runtime_version/);
|
||||
});
|
||||
@@ -322,12 +322,12 @@ test('profile save uses AJAX rehydrate and a fixed-id notyf flow without query f
|
||||
const scripts = read('src/Core/ScriptsPart1b.php');
|
||||
|
||||
assert.match(profile, /function initProfileForm\(root\)[\s\S]*?aksrtProfileInitialized[\s\S]*?addEventListener\('submit'/);
|
||||
assert.match(profile, /register\('aksrt-user-center',[\s\S]*?initProfileForm\(root\)/);
|
||||
assert.match(profile, /auto_fun\.aksrt-user-center'[\s\S]*?initProfileForm\(\)/);
|
||||
assert.match(profile, /notyf\([^;]+?'load'[^;]+?'aksrt-profile-save-toast'\);/);
|
||||
assert.match(profile, /notyf\([^;]+?'success'[^;]+?'aksrt-profile-save-toast'\);/);
|
||||
assert.match(profile, /notyf\([^;]+?'danger'[^;]+?'aksrt-profile-save-toast'\);/);
|
||||
assert.match(profile, /fetch\(ajaxUrl, \{ method: 'POST', credentials: 'same-origin', body: data \}\)/);
|
||||
assert.match(scripts, /\$user_center_deps[\s\S]*?\[[^\]]*'aksrt-runtime'[^\]]*\][\s\S]*?\/assets\/js\/routes\/user-center\.js/);
|
||||
assert.match(scripts, /\$user_center_deps[\s\S]*?\[[^\]]*'loader_js'[^\]]*\][\s\S]*?\/assets\/js\/routes\/user-center\.js/);
|
||||
|
||||
assert.doesNotMatch(page, /REQUEST_METHOD|profile_error|['"]saved['"]|add_query_arg/);
|
||||
assert.doesNotMatch([profile, form, layout, lists].join('\n'), /profile-save-msg|profile-form__submit-msg/);
|
||||
@@ -351,7 +351,7 @@ test('settings actions use rehydrated fixed-id notyf flows', () => {
|
||||
assert.match(userCenter, /notyf\([^;]+?'load'[^;]+?toastId\);/);
|
||||
assert.match(userCenter, /notyf\([^;]+?'success'[^;]+?toastId\);/);
|
||||
assert.match(userCenter, /notyf\([^;]+?'danger'[^;]+?toastId\);/);
|
||||
assert.match(userCenter, /register\('aksrt-user-center',[\s\S]*?initSettings\(root\);[\s\S]*?initBindPhone\(root\);/);
|
||||
assert.match(userCenter, /auto_fun\.aksrt-user-center'[\s\S]*?initSettings\(\);[\s\S]*?initBindPhone\(\);/);
|
||||
assert.match(userCenter, /aksrtSettingsInitialized/);
|
||||
assert.match(userCenter, /oauth_action[\s\S]*?notyf\(message, type\)[\s\S]*?history\.replaceState/);
|
||||
assert.doesNotMatch(userCenter, /oauth_unbound/);
|
||||
@@ -407,7 +407,7 @@ test('front submit actions use direct fixed-id notyf flows', () => {
|
||||
assert.match(submit, /needContent[^;]+?'warning'/);
|
||||
assert.doesNotMatch(submit, /\b(?:window\.)?alert\s*\(/);
|
||||
|
||||
assert.match(page, /'aksrt-front-submit',[\s\S]*?'\/assets\/js\/front-submit\.js',[\s\S]*?\[[^\]]*'aksrt-runtime'[^\]]*\]/);
|
||||
assert.match(page, /'aksrt-front-submit',[\s\S]*?'\/assets\/js\/front-submit\.js',[\s\S]*?\[[^\]]*'loader_js'[^\]]*\]/);
|
||||
});
|
||||
|
||||
test('legacy frontend toast APIs and selectors are fully removed', () => {
|
||||
|
||||
Reference in New Issue
Block a user