事故根因:HEL-214/221/226/233 均直接基于 main(8a5e78f) 构建部署,
绕过了 .11 正式线(HEL-183/188/190/191/192/193/199/207/208 + HEL-164
共 14 个已验收提交),导致问天工具行右对齐、侧栏等高、管理员刷新
结果、快照补档、收盘日线修复等整体丢失。
本合并以 deploy 前最后完整基线 89b8d33(镜像 official-limit-guard-d9ee725)
为底,合入 cefc869(HEL-221 情绪周期等高、HEL-226 登录门户与免密切换、
HEL-233 移动端系统管理五页),三处 CSS 缓存版本统一刷新为 20260829-hel237,
architecture-inventory 按合并后源码重新生成。
Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
@@ -47,6 +47,72 @@ function session(role = "admin", subscribed = true) {
|
||||
};
|
||||
}
|
||||
|
||||
function sentimentHistoryPayload(days = 20) {
|
||||
const phases = ["冰点", "修复", "发酵", "高潮", "分化", "退潮"];
|
||||
const rows = Array.from({ length: days }, (_, index) => {
|
||||
const score = 28 + ((index * 7) % 55);
|
||||
return {
|
||||
trade_date: `2026-08-${String(index + 1).padStart(2, "0")}`,
|
||||
score,
|
||||
label: "情绪观察",
|
||||
phase: phases[index % phases.length],
|
||||
direction: index % 2 ? "升温" : "降温",
|
||||
day_change: index % 2 ? 3.2 : -2.1,
|
||||
seal_rate: 71.5,
|
||||
limit_up_count: 40 + index,
|
||||
first_board_count: 18,
|
||||
second_board_count: 8,
|
||||
three_plus_count: 4,
|
||||
max_height: 5,
|
||||
broken_count: 12,
|
||||
limit_down_count: 3,
|
||||
previous_limit_count: 38,
|
||||
previous_positive_count: 22,
|
||||
previous_positive_rate: 57.9,
|
||||
average_previous_change: 1.2,
|
||||
normalization: "固定锚点",
|
||||
components: {
|
||||
breadth: { label: "市场宽度", score: 55.8, weight: 20, summary: "红盘家数回升" },
|
||||
limit: { label: "涨停连板", score: 79.6, weight: 25, summary: "连板生态改善" },
|
||||
profit: { label: "赚钱效应", score: 67.0, weight: 30, summary: "昨日反馈尚可" },
|
||||
ladder: { label: "涨幅结构", score: 81.5, weight: 15, summary: "高度仍在扩张" },
|
||||
amount: { label: "成交活跃度", score: 46.1, weight: 10, summary: "量能略低于均值" },
|
||||
},
|
||||
};
|
||||
});
|
||||
return { available_days: days, rows };
|
||||
}
|
||||
|
||||
async function renderSentimentFixture(page, days = 20) {
|
||||
await page.evaluate((payload) => {
|
||||
state.sentimentHistory = payload;
|
||||
renderSentimentHistory();
|
||||
}, sentimentHistoryPayload(days));
|
||||
await page.locator('[data-view="sentimentCycleView"]').first().click();
|
||||
await page.waitForTimeout(900);
|
||||
}
|
||||
|
||||
function readSentimentLayout() {
|
||||
const analysis = document.querySelector(".redesigned-emotion-grid").getBoundingClientRect();
|
||||
const trend = document.querySelector(".sentiment-trend-panel").getBoundingClientRect();
|
||||
const summary = document.querySelector(".sentiment-cycle-summary").getBoundingClientRect();
|
||||
const components = document.querySelector(".sentiment-components-panel").getBoundingClientRect();
|
||||
const chart = document.querySelector(".sentiment-chart-shell").getBoundingClientRect();
|
||||
const detail = document.querySelector(".sentiment-detail-toolbar").getBoundingClientRect();
|
||||
const table = document.querySelector(".sentiment-history-frame").getBoundingClientRect();
|
||||
const small = document.querySelector(".sentiment-component-item small");
|
||||
return {
|
||||
topDelta: Math.abs(trend.top - summary.top),
|
||||
bottomDelta: Math.abs(trend.bottom - components.bottom),
|
||||
analysisHeight: analysis.height,
|
||||
chartHeight: chart.height,
|
||||
detailAfterAnalysis: detail.top > Math.max(trend.bottom, components.bottom) - 0.5,
|
||||
tableVisible: table.top < window.innerHeight && table.bottom > detail.bottom,
|
||||
smallVisible: small ? getComputedStyle(small).display !== "none" : false,
|
||||
overflowX: document.documentElement.scrollWidth > document.documentElement.clientWidth + 1,
|
||||
};
|
||||
}
|
||||
|
||||
function waitForApplicationRuntime(page) {
|
||||
return expect(page.locator("body")).toHaveAttribute("data-runtime-ready", "true");
|
||||
}
|
||||
@@ -721,6 +787,7 @@ test("collapsed overview and sentiment layout keep a single current reading", as
|
||||
await expect(page.locator(".sentiment-stage-guide, [data-sentiment-stage]")).toHaveCount(0);
|
||||
await expect(page.locator("#sentimentPhaseAdvice")).toHaveText("情绪指标继续走弱。");
|
||||
const alignment = await page.evaluate(() => {
|
||||
const analysis = document.querySelector(".redesigned-emotion-grid").getBoundingClientRect();
|
||||
const components = document.querySelector(".sentiment-components-panel").getBoundingClientRect();
|
||||
const trend = document.querySelector(".sentiment-trend-panel").getBoundingClientRect();
|
||||
const summary = document.querySelector(".sentiment-cycle-summary").getBoundingClientRect();
|
||||
@@ -732,6 +799,8 @@ test("collapsed overview and sentiment layout keep a single current reading", as
|
||||
const statusStyle = getComputedStyle(document.querySelector(".sentiment-block .sentiment-text"));
|
||||
return {
|
||||
columnsAligned: Math.abs(trend.top - summary.top) < 1,
|
||||
bottomsAligned: Math.abs(trend.bottom - components.bottom) < 1,
|
||||
analysisHeight: analysis.height,
|
||||
railAligned: Math.abs(summary.x - components.x) < 1 && Math.abs(summary.width - components.width) < 1 && components.top > summary.bottom,
|
||||
detailAfterAnalysis: detail.top > Math.max(trend.bottom, components.bottom),
|
||||
chartHeight: chart.height,
|
||||
@@ -743,6 +812,8 @@ test("collapsed overview and sentiment layout keep a single current reading", as
|
||||
};
|
||||
});
|
||||
expect(alignment.columnsAligned).toBe(true);
|
||||
expect(alignment.bottomsAligned).toBe(true);
|
||||
expect(Math.abs(alignment.analysisHeight - 600)).toBeLessThanOrEqual(1);
|
||||
expect(alignment.railAligned).toBe(true);
|
||||
expect(alignment.detailAfterAnalysis).toBe(true);
|
||||
expect(alignment.chartHeight).toBeGreaterThanOrEqual(340);
|
||||
@@ -780,6 +851,68 @@ test("collapsed overview and sentiment layout keep a single current reading", as
|
||||
}
|
||||
});
|
||||
|
||||
test("sentiment cycle keeps 600px equal-height layout across zoom viewports", async ({ page }, testInfo) => {
|
||||
const shotDir = testInfo.outputPath("hel-221-shots");
|
||||
await mockApplication(page, session("user", true));
|
||||
const viewports = [
|
||||
{ name: "zoom-100", width: 2560, height: 1440 },
|
||||
{ name: "zoom-110", width: 2327, height: 1309 },
|
||||
{ name: "zoom-125", width: 2048, height: 1152 },
|
||||
];
|
||||
|
||||
for (const viewport of viewports) {
|
||||
await page.setViewportSize({ width: viewport.width, height: viewport.height });
|
||||
await page.goto("/index.html");
|
||||
await renderSentimentFixture(page, 20);
|
||||
const layout = await page.evaluate(readSentimentLayout);
|
||||
expect(layout.topDelta, viewport.name).toBeLessThanOrEqual(1);
|
||||
expect(layout.bottomDelta, viewport.name).toBeLessThanOrEqual(1);
|
||||
expect(Math.abs(layout.analysisHeight - 600), viewport.name).toBeLessThanOrEqual(1);
|
||||
expect(layout.chartHeight, viewport.name).toBeGreaterThanOrEqual(450);
|
||||
expect(layout.detailAfterAnalysis, viewport.name).toBe(true);
|
||||
expect(layout.tableVisible, viewport.name).toBe(true);
|
||||
expect(layout.smallVisible, viewport.name).toBe(true);
|
||||
expect(layout.overflowX, viewport.name).toBe(false);
|
||||
await page.screenshot({
|
||||
path: `${shotDir}/day-${viewport.name}.png`,
|
||||
fullPage: true,
|
||||
});
|
||||
}
|
||||
|
||||
await page.setViewportSize({ width: 2560, height: 1440 });
|
||||
await page.locator("#themeToggle").click();
|
||||
await expect(page.locator("html")).toHaveAttribute("data-theme", "dark");
|
||||
await page.waitForTimeout(200);
|
||||
const nightLayout = await page.evaluate(readSentimentLayout);
|
||||
expect(nightLayout.topDelta).toBeLessThanOrEqual(1);
|
||||
expect(nightLayout.bottomDelta).toBeLessThanOrEqual(1);
|
||||
expect(Math.abs(nightLayout.analysisHeight - 600)).toBeLessThanOrEqual(1);
|
||||
await page.locator("#sentimentTrendChart").hover({ position: { x: 280, y: 120 } });
|
||||
const tooltip = page.locator("#sentimentChartTooltip");
|
||||
await expect(tooltip).toBeVisible();
|
||||
await expect(tooltip).toContainText("温度");
|
||||
const tooltipStyle = await tooltip.evaluate((node) => {
|
||||
const style = getComputedStyle(node);
|
||||
const bold = getComputedStyle(node.querySelector("b") || node);
|
||||
return { background: style.backgroundColor, color: style.color, bold: bold.color };
|
||||
});
|
||||
expect(tooltipStyle.background).toBe("rgb(38, 41, 62)");
|
||||
expect(tooltipStyle.color).toBe("rgb(232, 234, 237)");
|
||||
expect(tooltipStyle.bold).toBe("rgb(232, 234, 237)");
|
||||
await page.screenshot({ path: `${shotDir}/night-zoom-100.png`, fullPage: true });
|
||||
|
||||
await page.locator("#themeToggle").click();
|
||||
await expect(page.locator("html")).toHaveAttribute("data-theme", "light");
|
||||
await page.locator("#sentimentTrendChart").hover({ position: { x: 280, y: 120 } });
|
||||
await expect(tooltip).toBeVisible();
|
||||
const lightTooltip = await tooltip.evaluate((node) => {
|
||||
const style = getComputedStyle(node);
|
||||
return { background: style.backgroundColor, color: style.color };
|
||||
});
|
||||
expect(lightTooltip.background).toBe("rgb(31, 35, 41)");
|
||||
expect(lightTooltip.color).toBe("rgb(255, 255, 255)");
|
||||
});
|
||||
|
||||
test("limit-up pool separates stock identity and restores the reason column", async ({ page }) => {
|
||||
await mockApplication(page, session("user", true));
|
||||
await page.goto("/index.html");
|
||||
|
||||
Reference in New Issue
Block a user