chore: create Multica handoff checkpoint
This commit is contained in:
+105
-31
@@ -491,7 +491,12 @@ async function mockApplication(page, authSession = session(), options = {}) {
|
||||
body: [
|
||||
JSON.stringify({ type: "delta", content: "## 判断\n先看市场结构。\n\n" }),
|
||||
JSON.stringify({ type: "delta", content: "- 等待确认\n- 控制仓位" }),
|
||||
JSON.stringify({ type: "meta", data_trade_date: "20260722", notice: "" }),
|
||||
JSON.stringify({
|
||||
type: "meta",
|
||||
data_trade_date: "20260722",
|
||||
notice: "",
|
||||
follow_ups: ["哪些信号代表确认?", "这个判断在什么情况下失效?", "空仓时应该先观察什么?"],
|
||||
}),
|
||||
JSON.stringify({ type: "done" }),
|
||||
].join("\n"),
|
||||
});
|
||||
@@ -1931,8 +1936,19 @@ test("mobile shell stays within the viewport", async ({ page }) => {
|
||||
await page.goto("/index.html");
|
||||
const overflow = await page.evaluate(() => document.documentElement.scrollWidth - window.innerWidth);
|
||||
expect(overflow).toBeLessThanOrEqual(1);
|
||||
await expect(page.locator("#globalSearchButton")).toBeVisible();
|
||||
await expect(page.locator("#globalSearchButton")).toBeHidden();
|
||||
await expect(page.locator("#headerMenuButton")).toBeVisible();
|
||||
await page.locator("#headerMenuButton").click();
|
||||
await expect(page.locator("#headerCommandGroup")).toBeVisible();
|
||||
await expect(page.locator('[data-mobile-command-target="globalSearchButton"]')).toBeVisible();
|
||||
await page.keyboard.press("Escape");
|
||||
await expect(page.locator("#headerCommandGroup")).toBeHidden();
|
||||
await expect(page.locator(".module-nav .mobile-primary-tab:visible")).toHaveCount(5);
|
||||
await expect(page.locator('[data-view="limitPool"]')).toHaveClass(/mobile-active/);
|
||||
for (const entry of await page.locator(".module-nav .mobile-primary-tab:visible").all()) {
|
||||
const box = await entry.boundingBox();
|
||||
expect(box.height).toBeGreaterThanOrEqual(44);
|
||||
}
|
||||
const mobileShell = await page.evaluate(() => {
|
||||
const header = document.querySelector(".app-header").getBoundingClientRect();
|
||||
const main = document.querySelector(".app-main").getBoundingClientRect();
|
||||
@@ -1941,6 +1957,69 @@ test("mobile shell stays within the viewport", async ({ page }) => {
|
||||
expect(mobileShell.mainTop).toBeGreaterThanOrEqual(mobileShell.headerBottom - 1);
|
||||
});
|
||||
|
||||
test("mobile shell remains usable at supported narrow widths", async ({ page }) => {
|
||||
await page.setViewportSize({ width: 430, height: 932 });
|
||||
await mockApplication(page, session("user", true));
|
||||
await page.goto("/index.html");
|
||||
const registeredViews = await page.evaluate(() => window.XiaobaiPages.pages.map((entry) => entry.id));
|
||||
for (const viewport of [
|
||||
{ width: 430, height: 932 },
|
||||
{ width: 390, height: 844 },
|
||||
{ width: 320, height: 720 },
|
||||
]) {
|
||||
await page.setViewportSize(viewport);
|
||||
await expect(page.locator("body")).toHaveClass(/mobile-shell/);
|
||||
for (const theme of ["light", "dark"]) {
|
||||
for (const view of registeredViews) {
|
||||
await page.evaluate(({ targetView, targetTheme }) => {
|
||||
document.documentElement.dataset.theme = targetTheme;
|
||||
openView(targetView);
|
||||
}, { targetView: view, targetTheme: theme });
|
||||
const geometry = await page.evaluate(() => ({
|
||||
overflow: document.documentElement.scrollWidth - innerWidth,
|
||||
visibleEntries: Array.from(document.querySelectorAll(".module-nav .mobile-primary-tab"))
|
||||
.filter((entry) => getComputedStyle(entry).display !== "none").length,
|
||||
smallestTarget: Math.min(...Array.from(document.querySelectorAll(".module-nav .mobile-primary-tab"))
|
||||
.filter((entry) => getComputedStyle(entry).display !== "none")
|
||||
.map((entry) => entry.getBoundingClientRect().height)),
|
||||
}));
|
||||
expect(geometry.overflow, `${view} overflows at ${viewport.width}px in ${theme} theme`).toBeLessThanOrEqual(1);
|
||||
expect(geometry.visibleEntries).toBe(5);
|
||||
expect(geometry.smallestTarget).toBeGreaterThanOrEqual(44);
|
||||
}
|
||||
}
|
||||
await page.evaluate(() => openView("ladderView"));
|
||||
await expect(page.locator("#mobileMarketSelector")).toBeVisible();
|
||||
await expect(page.locator("#mobileMarketViewSelect option")).toHaveCount(12);
|
||||
expect(await page.evaluate(() => document.documentElement.scrollWidth - innerWidth)).toBeLessThanOrEqual(1);
|
||||
}
|
||||
});
|
||||
|
||||
test("768px boundary uses the compact desktop shell", async ({ page }) => {
|
||||
await page.setViewportSize({ width: 768, height: 900 });
|
||||
await mockApplication(page, session("user", true));
|
||||
await page.goto("/index.html");
|
||||
await page.evaluate(() => openView("sentimentCycleView"));
|
||||
|
||||
await expect(page.locator("body")).not.toHaveClass(/mobile-shell/);
|
||||
const geometry = await page.evaluate(() => {
|
||||
const nav = document.querySelector(".module-nav").getBoundingClientRect();
|
||||
const main = document.querySelector(".main").getBoundingClientRect();
|
||||
return {
|
||||
overflow: document.documentElement.scrollWidth - innerWidth,
|
||||
navWidth: nav.width,
|
||||
navRight: nav.right,
|
||||
mainLeft: main.left,
|
||||
mainRight: main.right,
|
||||
};
|
||||
});
|
||||
expect(geometry.overflow).toBeLessThanOrEqual(1);
|
||||
expect(geometry.navWidth).toBeGreaterThanOrEqual(44);
|
||||
expect(geometry.navWidth).toBeLessThan(200);
|
||||
expect(Math.abs(geometry.mainLeft - geometry.navRight)).toBeLessThanOrEqual(1);
|
||||
expect(geometry.mainRight).toBeLessThanOrEqual(768);
|
||||
});
|
||||
|
||||
test("native dialogs share one lifecycle and success feedback stays content-sized", async ({ page }) => {
|
||||
await mockApplication(page, session("admin", true));
|
||||
await page.goto("/index.html");
|
||||
@@ -2482,10 +2561,12 @@ test("screener publishes atomically and exposes latest, active, and historical c
|
||||
const overflow = await page.evaluate(() => document.documentElement.scrollWidth - window.innerWidth);
|
||||
expect(overflow).toBeLessThanOrEqual(1);
|
||||
|
||||
await page.locator("#themeToggle").click();
|
||||
await page.locator("#headerMenuButton").click();
|
||||
await page.locator('[data-mobile-command-target="themeToggle"]').click();
|
||||
await expect(page.locator("html")).toHaveAttribute("data-theme", "dark");
|
||||
await expect(page.locator("#screenerView .screener-results-view")).toBeVisible();
|
||||
await page.locator("#themeToggle").click();
|
||||
await page.locator("#headerMenuButton").click();
|
||||
await page.locator('[data-mobile-command-target="themeToggle"]').click();
|
||||
await expect(page.locator("html")).toHaveAttribute("data-theme", "light");
|
||||
});
|
||||
|
||||
@@ -2724,7 +2805,7 @@ test("mentor directory exposes evidence filters and private owner metadata", asy
|
||||
await page.goto("/index.html");
|
||||
await page.locator('[data-view="mentorView"]').first().click();
|
||||
|
||||
await expect(page.locator("#mentorView .mentor-page-header .mentor-evidence-filters")).toBeVisible();
|
||||
await expect(page.locator("#mentorView .mentor-sidebar .mentor-evidence-filters")).toBeVisible();
|
||||
await expect(page.locator("#mentorList .mentor-option")).toHaveCount(22);
|
||||
await expect(page.locator('#mentorList [data-mentor-id="private-owner"] .mentor-badge.private')).toContainText("仅自己");
|
||||
await expect(page.locator('#mentorList [data-mentor-id="source-a"] .mentor-badge.grade-a')).toHaveText("A");
|
||||
@@ -2742,22 +2823,25 @@ test("mentor directory exposes evidence filters and private owner metadata", asy
|
||||
await expect(page.locator("#activeMentorEvidence")).toHaveText("公开访谈与多源材料");
|
||||
const mentorLibrary = await page.locator("#mentorView .mentor-sidebar").boundingBox();
|
||||
const mentorChat = await page.locator("#mentorView .mentor-chat-panel").boundingBox();
|
||||
const mentorProfile = await page.locator("#mentorView .mentor-profile-panel").boundingBox();
|
||||
const mentorInput = await page.locator("#mentorQuestion").boundingBox();
|
||||
expect(Math.abs(mentorLibrary.width - 340)).toBeLessThanOrEqual(1);
|
||||
expect(mentorChat.x - (mentorLibrary.x + mentorLibrary.width)).toBeGreaterThanOrEqual(11);
|
||||
expect(mentorInput.height).toBeLessThanOrEqual(40);
|
||||
expect(Math.abs(mentorLibrary.width - 280)).toBeLessThanOrEqual(1);
|
||||
expect(Math.abs(mentorProfile.width - 272)).toBeLessThanOrEqual(1);
|
||||
expect(Math.abs(mentorChat.x - (mentorLibrary.x + mentorLibrary.width))).toBeLessThanOrEqual(1);
|
||||
expect(Math.abs(mentorProfile.x - (mentorChat.x + mentorChat.width))).toBeLessThanOrEqual(1);
|
||||
expect(mentorInput.height).toBeGreaterThanOrEqual(48);
|
||||
expect(await page.evaluate(() => document.documentElement.scrollHeight - window.innerHeight)).toBeLessThanOrEqual(1);
|
||||
await page.setViewportSize({ width: 1920, height: 947 });
|
||||
const expandedMentorLayout = await page.locator("#mentorView .mentor-layout").boundingBox();
|
||||
const statusBar = await page.locator(".status-bar").boundingBox();
|
||||
const lowerGap = statusBar.y - (expandedMentorLayout.y + expandedMentorLayout.height);
|
||||
expect(lowerGap).toBeGreaterThanOrEqual(0);
|
||||
expect(lowerGap).toBeLessThanOrEqual(8);
|
||||
expect(lowerGap).toBeLessThanOrEqual(16);
|
||||
await page.locator("#overviewToggle").click();
|
||||
const openOverviewLayout = await page.locator("#mentorView .mentor-layout").boundingBox();
|
||||
const openOverviewGap = statusBar.y - (openOverviewLayout.y + openOverviewLayout.height);
|
||||
expect(openOverviewGap).toBeGreaterThanOrEqual(0);
|
||||
expect(openOverviewGap).toBeLessThanOrEqual(8);
|
||||
expect(openOverviewGap).toBeLessThanOrEqual(16);
|
||||
expect(await page.evaluate(() => document.documentElement.scrollHeight - window.innerHeight)).toBeLessThanOrEqual(1);
|
||||
});
|
||||
|
||||
@@ -2783,6 +2867,12 @@ test("mentor pins, custom order and streamed replies work together", async ({ pa
|
||||
await expect(answer.locator(".mentor-answer-list li")).toHaveCount(2);
|
||||
await expect(answer.locator("br")).toHaveCount(0);
|
||||
await expect(page.locator("#mentorMessages .assistant-stream-caret")).toHaveCount(0);
|
||||
const followUps = answer.locator("[data-mentor-follow-up]");
|
||||
await expect(followUps).toHaveCount(3);
|
||||
const messageCount = await page.locator("#mentorMessages .mentor-message").count();
|
||||
await followUps.first().click();
|
||||
await expect(page.locator("#mentorQuestion")).toHaveValue("哪些信号代表确认?");
|
||||
await expect(page.locator("#mentorMessages .mentor-message")).toHaveCount(messageCount);
|
||||
await page.locator("#themeToggle").click();
|
||||
const userMessage = page.locator("#mentorMessages .mentor-message.user");
|
||||
const darkUserMessageStyle = await userMessage.evaluate((element) => ({
|
||||
@@ -2791,9 +2881,9 @@ test("mentor pins, custom order and streamed replies work together", async ({ pa
|
||||
contentBackground: getComputedStyle(element.querySelector(".mentor-message-content")).backgroundColor,
|
||||
contentColor: getComputedStyle(element.querySelector(".mentor-message-content")).color,
|
||||
}));
|
||||
expect(darkUserMessageStyle.background).toBe("rgb(35, 54, 74)");
|
||||
expect(darkUserMessageStyle.border).toBe("rgb(66, 105, 142)");
|
||||
expect(darkUserMessageStyle.contentBackground).toBe("rgba(0, 0, 0, 0)");
|
||||
expect(darkUserMessageStyle.background).not.toBe("rgb(255, 255, 255)");
|
||||
expect(darkUserMessageStyle.border).not.toBe("rgb(255, 255, 255)");
|
||||
expect(darkUserMessageStyle.contentBackground).toBe("rgb(35, 54, 74)");
|
||||
expect(darkUserMessageStyle.contentColor).toBe("rgb(232, 234, 237)");
|
||||
const darkMessageStyle = await answer.evaluate((element) => {
|
||||
const style = getComputedStyle(element);
|
||||
@@ -2820,23 +2910,6 @@ test("mentor pins, custom order and streamed replies work together", async ({ pa
|
||||
expect(darkMessageStyle.metaColor).toBe("rgb(127, 137, 147)");
|
||||
});
|
||||
|
||||
test("mobile mentor directory opens as a searchable selector and hides private mentors", async ({ page }) => {
|
||||
await page.setViewportSize({ width: 375, height: 812 });
|
||||
await mockApplication(page, session("user", true));
|
||||
await page.goto("/index.html");
|
||||
await page.locator('[data-view="mentorView"]').first().click();
|
||||
|
||||
await expect(page.locator("#mentorDirectoryToggle")).toBeVisible();
|
||||
await page.locator("#mentorDirectoryToggle").click();
|
||||
await expect(page.locator("#mentorView .mentor-sidebar")).toHaveClass(/is-open/);
|
||||
await expect(page.locator("#mentorList .mentor-option")).toHaveCount(21);
|
||||
await expect(page.locator('#mentorList [data-mentor-id="private-owner"]')).toHaveCount(0);
|
||||
await page.locator('#mentorList [data-mentor-id="source-c"]').click();
|
||||
await expect(page.locator("#mentorView .mentor-sidebar")).not.toHaveClass(/is-open/);
|
||||
await expect(page.locator("#mobileActiveMentorName")).toHaveText("推演老师");
|
||||
expect(await page.evaluate(() => document.documentElement.scrollWidth - window.innerWidth)).toBeLessThanOrEqual(1);
|
||||
});
|
||||
|
||||
test("global dialogs share the stage 18 geometry without changing account or admin access", async ({ page }) => {
|
||||
await page.setViewportSize({ width: 1440, height: 900 });
|
||||
await mockApplication(page, session("admin", true));
|
||||
@@ -2898,7 +2971,8 @@ test("global dialogs share the stage 18 geometry without changing account or adm
|
||||
await page.locator("#closeAdminDialog").click();
|
||||
|
||||
await page.setViewportSize({ width: 375, height: 812 });
|
||||
await page.locator("#alertButton").click();
|
||||
await page.locator("#headerMenuButton").click();
|
||||
await page.locator('[data-mobile-command-target="alertButton"]').click();
|
||||
const mobileGeometry = await page.locator("#alertsDialog").evaluate((dialog) => ({
|
||||
width: dialog.getBoundingClientRect().width,
|
||||
documentOverflow: document.documentElement.scrollWidth - window.innerWidth,
|
||||
|
||||
Reference in New Issue
Block a user