126 lines
7.4 KiB
JavaScript
126 lines
7.4 KiB
JavaScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { readFile } from "node:fs/promises";
|
|
|
|
const html = await readFile(new URL("../public/index.html", import.meta.url), "utf8");
|
|
const app = await readFile(new URL("../public/app.js", import.meta.url), "utf8");
|
|
const css = await readFile(new URL("../public/styles.css", import.meta.url), "utf8");
|
|
|
|
test("history toolbar exposes collect selected action", () => {
|
|
assert.match(html, /id="history-collect-selected"/);
|
|
assert.match(html, />采集选中</);
|
|
});
|
|
|
|
test("history collect selected action is wired to selection state", () => {
|
|
assert.match(app, /historyCollectSelected\s*=\s*document\.querySelector\("#history-collect-selected"\)/);
|
|
assert.match(app, /selectedHistoryPrograms\.size \? `采集选中\(\$\{selectedHistoryPrograms\.size\}\)` : "采集选中"/);
|
|
assert.match(app, /collectHistoryPrograms\(names/);
|
|
});
|
|
test("history selection mode has a neutral entry point", () => {
|
|
assert.match(html, /id="history-bulk-button"[^>]*>批量选择<\/button>/);
|
|
assert.doesNotMatch(html, /id="history-bulk-button"[^>]*>批量删除<\/button>/);
|
|
});
|
|
test("history bulk button toggles select all and cancel selection", () => {
|
|
assert.match(app, /selectedHistoryPrograms = new Set\(programsCache\.map\(\(program\) => program\.name\)\)/);
|
|
assert.match(app, /function clearHistorySelection\(\)/);
|
|
assert.match(app, /historyBulkButton\.textContent = historyBulkMode \? "取消选择" : "批量选择"/);
|
|
assert.match(app, /historyCancelBulk\.addEventListener\("click", \(\) => \{\s*clearHistorySelection\(\);/);
|
|
assert.match(app, /historyBulkButton\.hidden = false;/);
|
|
});
|
|
test("history delete options appear after pressing delete selected", () => {
|
|
assert.match(app, /let historyDeleteMode = false;/);
|
|
assert.match(app, /historyDeleteMode = true;[\s\S]*renderPrograms\(programsCache\);/);
|
|
assert.match(app, /\$\{historyDeleteMode \? `<button class="delete-program"/);
|
|
assert.match(app, /historyBulkMode && !historyDeleteMode \? "bulk" : ""/);
|
|
assert.match(app, /historyDeleteSelected\.textContent = historyDeleteMode/);
|
|
});
|
|
test("history rows always render selection checkboxes", () => {
|
|
assert.match(app, /<label class="program-select">/);
|
|
assert.doesNotMatch(app, /\$\{historyBulkMode \? `<label class="program-select">/);
|
|
});
|
|
test("history sidebar is wide enough for program names", () => {
|
|
assert.match(css, /\.workspace \{[\s\S]*grid-template-columns: 280px minmax\(0, 1fr\);/);
|
|
assert.match(css, /\.program-item-row \{[\s\S]*grid-template-columns: 24px minmax\(130px, 1fr\) 44px;/);
|
|
assert.match(css, /\.program-item-row\.bulk \{[\s\S]*grid-template-columns: 24px minmax\(170px, 1fr\);/);
|
|
});
|
|
test("top collection name input stays compact", () => {
|
|
assert.match(css, /\.searchbar \{[\s\S]*grid-template-columns: minmax\(220px, 360px\) 108px 108px 108px minmax\(0, 1fr\);/);
|
|
});
|
|
test("collect all history action is prominent in the title area", () => {
|
|
assert.match(html, /<div class="brand-block">[\s\S]*<button id="collect-history-button" class="top-collect-all" type="button">采集全部历史节目<\/button>[\s\S]*<form id="collect-form"/);
|
|
assert.doesNotMatch(html, /id="collect-button"[^>]*>采集一次<\/button>\s*<button id="collect-history-button"/);
|
|
assert.match(css, /\.topbar \{[\s\S]*grid-template-columns: minmax\(360px, 480px\) minmax\(320px, 1fr\);/);
|
|
assert.match(css, /h1 \{[\s\S]*font-size: 30px;/);
|
|
assert.match(css, /#subtitle \{[\s\S]*font-size: 16px;/);
|
|
assert.match(css, /\.top-collect-all \{[\s\S]*width: 100%;[\s\S]*height: 54px;[\s\S]*font-size: 16px;/);
|
|
assert.doesNotMatch(html, /<div class="history-actions">[\s\S]*id="collect-history-button"/);
|
|
});
|
|
test("history toolbar exposes retry pending platforms action", () => {
|
|
assert.match(html, /id="retry-pending-button"[^>]*>复查无数据<\/button>/);
|
|
assert.match(app, /retryPendingButton\s*=\s*document\.querySelector\("#retry-pending-button"\)/);
|
|
assert.match(app, /postJson\("\/api\/retry-pending"/);
|
|
assert.match(app, /retryPendingButton\.disabled = isBusy;/);
|
|
});
|
|
test("compare chart renders trend lines instead of latest-value bars", () => {
|
|
assert.match(app, /renderCompareLineChart\(/);
|
|
assert.match(app, /function comparePlatformSeries\(/);
|
|
assert.doesNotMatch(app, /renderCompareBars\(rows\)/);
|
|
});
|
|
test("compare line chart is very compact and limits point labels", () => {
|
|
assert.match(app, /const height = 188;/);
|
|
assert.match(app, /buildCompareLabelIndexes\(item\.points, 13\)/);
|
|
assert.match(app, /function buildCompareLabelIndexes\(points, maxLabels = 13\)/);
|
|
assert.match(app, /class="compare-point-value"/);
|
|
assert.match(app, /formatCompactNumber\(point\.number\)/);
|
|
assert.match(html, /id="compare-chart" class="compare-chart empty"/);
|
|
});
|
|
test("compare line chart magnifies small value differences", () => {
|
|
assert.match(app, /function buildCompareValueDomain\(/);
|
|
assert.match(app, /const \{ minValue, maxValue \} = buildCompareValueDomain\(allPoints\);/);
|
|
assert.match(app, /\(value - minValue\) \/ \(maxValue - minValue\)/);
|
|
assert.doesNotMatch(app, /value \/ maxValue/);
|
|
});
|
|
test("compare line chart shows multiple time ticks", () => {
|
|
assert.match(app, /function buildCompareTimeTicks\(/);
|
|
assert.match(app, /class="compare-time-tick"/);
|
|
assert.match(app, /class="compare-time-label"/);
|
|
assert.match(app, /buildCompareTimeTicks\(times, 10\)/);
|
|
assert.match(app, /minimumGap = 92/);
|
|
assert.match(app, /formatCompareTimeLabel\(time, timeLabelsUseTimeOnly\)/);
|
|
assert.match(app, /function compareTimesOnSameDay\(times\)/);
|
|
assert.match(app, /function formatCompareTimeLabel\(value, timeOnly = false\)/);
|
|
assert.match(app, /<tspan x="\$\{xx\}">\$\{escapeHtml\(label\.primary\)\}<\/tspan>/);
|
|
assert.doesNotMatch(app, /const startLabel = formatShortDate\(minTime\);/);
|
|
assert.doesNotMatch(app, /const endLabel = formatShortDate\(maxTime\);/);
|
|
});
|
|
test("compare chart can filter by date range", () => {
|
|
assert.match(html, /id="compare-range"[\s\S]*value="today"[\s\S]*当天/);
|
|
assert.match(html, /value="3d"[\s\S]*近三天/);
|
|
assert.match(html, /value="7d"[\s\S]*近七天/);
|
|
assert.match(html, /value="all" selected[\s\S]*全部/);
|
|
assert.match(app, /const compareRange = document\.querySelector\("#compare-range"\)/);
|
|
assert.match(app, /compareRange\.addEventListener\("change"/);
|
|
assert.match(app, /filterCompareSeriesByRange\(sourceSeries, range\)/);
|
|
assert.match(app, /function compareRangeCutoff\(latestTime, range\)/);
|
|
assert.match(css, /\.compare-controls \{[\s\S]*grid-template-columns: 180px 108px;/);
|
|
});
|
|
test("compare chart omits duplicated latest-value rows", () => {
|
|
assert.match(app, /compareTable\.innerHTML = "";/);
|
|
assert.doesNotMatch(app, /compareTable\.innerHTML = rows\.map/);
|
|
});
|
|
test("manual batch collection panel is removed", () => {
|
|
assert.doesNotMatch(html, /id="batch-form"/);
|
|
assert.doesNotMatch(html, /批量采集/);
|
|
assert.doesNotMatch(app, /batchForm\.addEventListener/);
|
|
});
|
|
test("compare line chart css keeps the visual short", () => {
|
|
assert.match(css, /\.compare-line-chart \{\s*min-height: 204px;/);
|
|
assert.match(css, /\.compare-line-svg \{[\s\S]*min-height: 188px;/);
|
|
assert.match(css, /\.compare-time-label \{[\s\S]*font-size: 6px;/);
|
|
assert.match(css, /\.compare-point-value \{\s*font-size: 5px;/);
|
|
assert.match(css, /\.compare-legend \{[\s\S]*font-size: 16px;/);
|
|
assert.match(css, /\.compare-legend i \{[\s\S]*width: 16px;[\s\S]*height: 16px;/);
|
|
});
|
|
|
|
|