Files
Sansheng-Intelligence/tests/test_frontend.mjs
T

224 lines
13 KiB
JavaScript
Raw Normal View History

import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import test from "node:test";
const root = new URL("../", import.meta.url);
const text = async (path) => readFile(new URL(path, root), "utf8");
test("独立首页只展示家纺情报并加载独立入口", async () => {
const html = await text("public/index.html");
const dongguanCss = await text("public/assets/fonts/dongguan-bold/result.css");
assert.match(html, /三笙AI情报局/);
assert.match(html, /assets\/js\/app\.js/);
assert.match(html, /assets\/js\/app\.js\?v=20260817a/);
assert.match(html, /assets\/css\/base\.css\?v=20260814h/);
assert.match(html, /assets\/css\/intelligence\.css\?v=20260817a/);
assert.match(html, /<details[^>]+class="intelligence-metrics"/);
assert.match(html, /<summary[^>]+class="intelligence-utility-row"/);
assert.doesNotMatch(html, /id="intelligenceShareBtn"/);
assert.match(html, /class="tool-icon"/);
assert.match(html, /assets\/fonts\/dongguan-bold\/result\.css/);
assert.doesNotMatch(html, /cn-font\.claude-code-best\.win/);
assert.match(dongguanCss, /font-family:"STDongGuanTi Bld"/);
assert.match(dongguanCss, /\.woff2/);
assert.doesNotMatch(html, />↗<|>◐</);
assert.doesNotMatch(html, /AI 资讯|WaytoAGI|tabAiNews|aiNewsWrap/);
});
test("数据快照不复用浏览器旧缓存", async () => {
const app = await text("public/assets/js/app.js");
assert.match(app, /\?t=\$\{Date\.now\(\)\}/);
assert.match(app, /cache:\s*"no-store"/);
assert.doesNotMatch(app, /cache:\s*"no-cache"/);
});
test("七个业务入口与趋势四子类均保留", async () => {
const renderer = await text("public/assets/js/render-intelligence.js");
const modules = renderer + await text("public/assets/js/render-intelligence-core.js") + await text("public/assets/js/render-intelligence-scenes.js") + await text("public/assets/js/render-intelligence-platforms.js");
for (const label of ["今日必看", "备货日历", "展会通", "政策预警", "趋势雷达", "平台渠道", "跨境活动"]) {
assert.match(modules, new RegExp(label));
}
for (const label of ["灵感图库", "趋势博客", "时装趋势", "图案灵感检索"]) {
assert.match(modules, new RegExp(label));
}
assert.doesNotMatch(modules, /设计师检索|设计师图片检索台/);
assert.match(modules, /appendPrefixedTranslationLink/);
assert.match(modules, /appendPrefixedTranslationLink\(actions, item, "seller"\)/);
assert.doesNotMatch(modules, /appendPrefixedTranslationLink\(actions, item, "official"\)/);
assert.doesNotMatch(modules, /appendPrefixedTranslationLink\(actions, item, "homepage"\)/);
assert.doesNotMatch(modules, /查看家纺类目/);
assert.doesNotMatch(modules, /家纺类目成熟度|新兴渠道参考分/);
assert.match(modules, /打开平台官网/);
assert.match(modules, /去官方入驻/);
assert.match(modules, /辅助翻译入驻说明/);
assert.match(modules, /官方/);
assert.match(modules, /requires-cross-border-network/);
});
test("所有外部业务链接使用新窗口和安全rel", async () => {
const files = [
await text("public/assets/js/render-intelligence.js"),
await text("public/assets/js/render-intelligence-core.js"),
await text("public/assets/js/render-intelligence-scenes.js"),
await text("public/assets/js/render-intelligence-platforms.js"),
await text("public/assets/js/render-intelligence-activities.js"),
].join("\n");
assert.match(files, /target\s*=\s*["']_blank["']/);
assert.match(files, /noopener noreferrer/);
});
test("趋势分类读取真实 items 数组而不是分类对象", async () => {
const renderer = await text("public/assets/js/render-intelligence.js");
const modules = renderer + await text("public/assets/js/render-intelligence-core.js") + await text("public/assets/js/render-intelligence-scenes.js") + await text("public/assets/js/render-intelligence-platforms.js");
assert.match(modules, /category\.items/);
assert.match(modules, /item\.image\s*\|\|\s*item\.thumbnail/);
assert.match(modules, /function renderCalendarMonthGrid/);
assert.match(modules, /calendar-month-grid/);
assert.match(modules, /calendar-weekday/);
assert.match(modules, /create\("details", "calendar-local-names"\)/);
assert.match(modules, /calendar-local-names-summary/);
assert.match(modules, /item\.title_english/);
assert.match(modules, /个国家\/地区/);
const calendarCss = await text("public/assets/css/intelligence.css");
assert.match(calendarCss, /\.calendar-local-names:not\(\[open\]\) \.calendar-local-name-list\s*\{[^}]*display:\s*none/s);
assert.match(calendarCss, /\.calendar-local-summary-name\s*\{[^}]*white-space:\s*nowrap/s);
assert.match(modules, /registration_translation_url/);
assert.match(modules, /registration_translation_fallback_url/);
});
test("灵感图库点击缩略图时使用原图浮层", async () => {
const core = await text("public/assets/js/render-intelligence-core.js");
const scenes = await text("public/assets/js/render-intelligence-scenes.js");
const css = await text("public/assets/css/intelligence.css");
assert.match(scenes, /item\.image_original\s*\|\|\s*item\.thumbnail/);
assert.match(scenes, /trend-gallery-zoom/);
assert.match(scenes, /openLightbox\([^,]+,\s*item\.title/);
assert.match(scenes, /previewSrc:\s*item\.thumbnail/);
assert.match(core, /role["']?,?\s*["']dialog/);
assert.match(core, /aria-modal/);
assert.match(core, /assets\/logo\.svg/);
assert.match(core, /lightbox-loader/);
assert.match(core, /lightbox-retry/);
assert.match(core, /lightbox-download/);
assert.match(core, /\.decode\(\)/);
assert.match(core, /download\s*=/);
assert.match(core, /fetch\(src/);
assert.match(core, /URL\.createObjectURL/);
assert.match(core, /window\.open\(src/);
assert.match(css, /\.lightbox-overlay/);
assert.match(css, /\.lightbox-preview/);
assert.match(css, /\.lightbox-loader-logo-wrap::before\s*\{\s*animation:\s*lightbox-logo-spin\s+1\.8s\s+linear\s+infinite\s*!important;/);
assert.match(css, /@media\s*\(prefers-reduced-motion:\s*reduce\)/);
});
test("网页翻译严格按 IP 门控", async () => {
const region = await text("public/assets/js/visitor-region.js");
assert.match(region, /AbortController/);
assert.match(region, /\["\/cdn-cgi\/trace",\s*"https:\/\/www\.cloudflare\.com\/cdn-cgi\/trace"\]/);
assert.match(region, /nonChineseLanguage\s*\|\|\s*outsideChinaTimeZone/);
const { translationProvider } = await import(new URL("../public/assets/js/visitor-region.js", import.meta.url));
assert.equal(translationProvider("CN", true), "yandex");
assert.equal(translationProvider("US", true), "google");
assert.equal(translationProvider("US", false), "google");
assert.equal(translationProvider("UNKNOWN", true), "google");
assert.equal(translationProvider("UNKNOWN", false), "yandex");
});
test("官方来源位于译文入口之前且可自动退到可用翻译服务", async () => {
const core = await text("public/assets/js/render-intelligence-core.js");
const scenes = await text("public/assets/js/render-intelligence-scenes.js");
const activities = await text("public/assets/js/render-intelligence-activities.js");
const seoBuilder = await text("scripts/build_seo.py");
const seoRegion = await text("public/assets/js/seo-visitor-region.js");
const policy = scenes.slice(scenes.indexOf("function makePolicyCard"), scenes.indexOf("function renderPolicies"));
assert.ok(policy.indexOf("sourceLinkLabel(item)") < policy.indexOf("appendTranslationLinks(actions, item)"));
assert.match(core, /function preferredTranslation/);
assert.match(activities, /googleUrl \|\| yandexUrl/);
assert.match(activities, /yandexUrl \|\| googleUrl/);
assert.doesNotMatch(`${core}\n${activities}`, /百度中文|translationProvider === "baidu"/);
assert.match(seoBuilder, /data-translation-group/);
assert.match(seoRegion, /links\[0\]/);
});
test("跨境活动不把发现栏目页冒充具体来源或译文", async () => {
const activities = await text("public/assets/js/render-intelligence-activities.js");
assert.doesNotMatch(activities, /source_translation_url|source_translation_fallback_url/);
assert.match(activities, /sourceUrl !== registrationUrl/);
assert.match(activities, /activityDayOffset\(item\.start_date\) >= 0/);
});
test("跨境活动按访问当天重新计算相对日期并排除已结束活动", async () => {
const { activityDayOffset } = await import(new URL("../public/assets/js/render-intelligence-activities.js", import.meta.url));
const now = new Date(2026, 7, 14, 12, 0, 0);
assert.equal(activityDayOffset("2026-08-13", now), -1);
assert.equal(activityDayOffset("2026-08-14", now), 0);
assert.equal(activityDayOffset("2026-08-15", now), 1);
});
test("日历按当前 UTC 日期优先展示未过期节点并把已过节点压到后面", async () => {
const { calendarTodayIso, sortCalendarItems, calendarDefaultItems } = await import(new URL(`../public/assets/js/render-intelligence-core.js?calendar-date=${Date.now()}`, import.meta.url));
const now = new Date("2026-08-15T00:30:00.000Z");
const items = [
{ id: "july", start_date: "2026-07-15", priority_score: 90 },
{ id: "past-august", start_date: "2026-08-10", priority_score: 90 },
{ id: "today", start_date: "2026-08-15", priority_score: 1 },
{ id: "future", start_date: "2026-08-17", priority_score: 1 },
];
assert.equal(calendarTodayIso(now), "2026-08-15");
assert.deepEqual(sortCalendarItems(items, now).map((item) => item.id), ["today", "future", "past-august", "july"]);
assert.deepEqual(calendarDefaultItems(items, 2026, now).map((item) => item.id), ["today", "future"]);
});
test("最新家纺交互与移动端契约已同步", async () => {
const renderer = await text("public/assets/js/render-intelligence.js");
const modules = renderer + await text("public/assets/js/render-intelligence-core.js") + await text("public/assets/js/render-intelligence-scenes.js") + await text("public/assets/js/render-intelligence-platforms.js");
const baseCss = await text("public/assets/css/base.css");
const css = await text("public/assets/css/intelligence.css");
assert.doesNotMatch(renderer, /displayLimit/);
assert.match(modules, /data\.calendar_insights\?\.length \|\| data\.calendar\?\.length/);
assert.match(modules, /platform-filter-toolbar/);
const activities = await text("public/assets/js/render-intelligence-activities.js");
assert.match(modules + activities, /activity-filter-toolbar/);
assert.match(activities, /activity-filter-fields/);
assert.match(css, /\.activity-focus-switch\s*\{[^}]*grid-template-columns:\s*repeat\(4,\s*minmax\(0,\s*1fr\)\)/s);
assert.match(css, /\.activity-focus-btn\s*\{[^}]*min-height:\s*40px/s);
assert.match(css, /@media \(max-width: 680px\)[\s\S]*?\.activity-focus-switch\s*\{[^}]*grid-template-columns:\s*1fr 1fr/s);
assert.match(css, /@media \(max-width: 680px\)[\s\S]*?\.activity-focus-btn\s*\{[^}]*min-height:\s*48px/s);
assert.doesNotMatch(modules, /家纺成熟度|新兴参考/);
assert.doesNotMatch(modules, /scope_type === "state\.region"|\["state\.region", "地区政策"\]/);
assert.match(modules, /scope_type === "region"/);
assert.match(renderer, /closest\("label"\)/);
assert.match(renderer, /regionField\.hidden\s*=\s*state\.activeSection === "calendar"/);
assert.match(css, /@media \(max-width: 680px\)/);
assert.match(css, /\.intelligence-tabs\s*\{[^}]*grid-template-columns: repeat\(3/);
assert.match(css, /min-height: 48px/);
for (const family of ["STDongGuanTi Light", "STDongGuanTi", "STDongGuanTi Bld"]) assert.match(baseCss, new RegExp(family));
assert.match(activities, /activity-date-month/);
assert.match(activities, /activity-date-day/);
assert.match(activities, /activity-date-countdown/);
});
test("地址栏状态不会写入 undefined", async () => {
const app = await text("public/assets/js/app.js");
assert.match(app, /value \|\| getIntelligenceSection\(\)/);
});
test("东观体只用于标题且窄卡海报为日历保留空间", async () => {
const baseCss = await text("public/assets/css/base.css");
const css = await text("public/assets/css/intelligence.css");
assert.match(baseCss, /body\s*\{[^}]*font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans SC", sans-serif/);
assert.doesNotMatch(baseCss, /body\s*\{[^}]*font-family: Inter/);
assert.match(baseCss, /\.brand strong\s*\{[^}]*font-family: inherit[^}]*font-weight: 700/);
assert.match(baseCss, /\.page-heading h1\s*\{[^}]*font-family: inherit[^}]*font-size: clamp\(30px, 3vw, 40px\)[^}]*font-weight: 700/);
assert.match(css, /\.site-shell\.textile-wrap\s*\{[^}]*width: min\(82vw, 1680px\)/);
assert.match(css, /@media \(max-width: 680px\)[\s\S]*?\.site-shell\.textile-wrap\s*\{[^}]*width: calc\(100% - 24px\)/);
assert.doesNotMatch(baseCss, /\.brand small\s*\{[^}]*font-family:/);
assert.doesNotMatch(baseCss, /\.page-heading > p\s*\{[^}]*font-family:/);
assert.match(css, /\.intelligence-poster-title\s*\{[^}]*font-family: var\(--textile-editorial-font-bold\)/s);
assert.match(css, /\.activity-card-body h3\s*\{[^}]*font-family: var\(--textile-editorial-font-bold\)/);
assert.doesNotMatch(css, /\.activity-generated-poster small, \.activity-poster-source\s*\{[^}]*font-family:/);
assert.doesNotMatch(css, /\.activity-date-(?:month|day|countdown)\s*\{[^}]*font-family:/);
assert.doesNotMatch(css, /\.activity-(?:title-original|summary)[^\{]*\{[^}]*font-family:/);
assert.match(css, /\.activity-card-visual\s*\{[^}]*width: 100%[^}]*min-height: 220px/);
});