const assert = require('node:assert/strict'); const fs = require('node:fs'); const path = require('node:path'); const test = require('node:test'); const createConfig = require('../config.js'); const projectRoot = path.resolve(__dirname, '..'); function read(relativePath) { return fs.readFileSync(path.join(projectRoot, relativePath), 'utf8'); } test('NavigationUtil 在隔离的新窗口中打开有效地址', () => { const openedWindow = { opener: 'source-window' }; const calls = []; const root = { location: { hostname: 'localhost', port: '5500' }, open(...args) { calls.push(args); return openedWindow; } }; createConfig(root); const result = root.NavigationUtil.open('profile.html'); assert.equal(result, openedWindow); assert.deepEqual(calls, [['profile.html', '_blank', 'noopener']]); assert.equal(openedWindow.opener, null); }); test('NavigationUtil 拒绝可执行协议', () => { let openCount = 0; const root = { location: { hostname: 'localhost', port: '5500' }, open() { openCount += 1; } }; createConfig(root); assert.equal(root.NavigationUtil.open('javascript:alert(1)'), null); assert.equal(root.NavigationUtil.open('data:text/html,unsafe'), null); assert.equal(openCount, 0); }); test('所有页面在公共交互脚本前加载全站跳转配置', () => { const htmlFiles = fs.readdirSync(projectRoot).filter((file) => file.endsWith('.html')); htmlFiles.forEach((file) => { const source = read(file); const configIndex = source.indexOf(''); const effectsIndex = source.indexOf(''); assert.ok(configIndex >= 0, `${file} 缺少 config.js`); assert.ok(effectsIndex > configIndex, `${file} 必须先加载 config.js`); }); }); test('全站链接覆盖初始和动态生成的有效链接', () => { const source = read('public/js/page-effects.js'); assert.match(source, /link\.target = "_blank"/); assert.match(source, /link\.setAttribute\("rel", \[\.\.\.relValues, "noopener"\]/); assert.match(source, /new MutationObserver/); assert.match(source, /attributeFilter: \["href", "target", "download"\]/); }); test('Logo 和首页提供稳定入口,主导航不注入重复返回操作', () => { const htmlFiles = fs.readdirSync(projectRoot).filter((file) => file.endsWith('.html')); const effects = read('public/js/page-effects.js'); const profileCommon = read('public/js/profile-common.js'); htmlFiles.forEach((file) => { const source = read(file); const brandLinks = [...source.matchAll(/ { assert.equal(match[1], 'index.html', `${file} 的 Logo 没有指向首页`); }); }); assert.doesNotMatch(effects, /data-page-navigation-controls|data-navigation-back/); assert.match(profileCommon, /\{ label: '首页', href: 'index\.html' \}/); assert.match(profileCommon, /\.attr\('href', 'index\.html'\)/); assert.doesNotMatch(profileCommon, /data-navigation-back/); }); test('登录态顶栏以头像和昵称作为唯一账户入口', () => { const effects = read('public/js/page-effects.js'); const authPages = read('public/js/auth-pages.js'); const profilePages = read('public/js/profile-pages.js'); assert.match(effects, /className = "nav-user-entry"/); assert.match(effects, /className = "nav-user-avatar"/); assert.match(effects, /name\.textContent = user\.nickName/); assert.doesNotMatch(effects, /loginLink\.textContent = "个人中心"/); assert.doesNotMatch(effects, /registerLink\.textContent = "账户资料"/); assert.match(authPages, /storeUser\(await api\.currentProfile\(\)\)/); assert.match(profilePages, /PublicNavigation\.storeUser\(profile\)/); }); test('业务脚本不再直接覆盖当前窗口地址', () => { const scriptsDirectory = path.join(projectRoot, 'public', 'js'); const scriptFiles = fs.readdirSync(scriptsDirectory).filter((file) => file.endsWith('.js')); scriptFiles.forEach((file) => { assert.doesNotMatch( read(path.join('public', 'js', file)), /(?:root|window)\.location\.(?:href\s*=|replace\()/, `${file} 仍在当前窗口跳转` ); }); });