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} 仍在当前窗口跳转`
);
});
});