'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`); } });