feat: show all accounts in collapsible overview

This commit is contained in:
2026-08-14 11:07:23 +08:00
parent f5a7031e6a
commit 1ec9001387
6 changed files with 815 additions and 140 deletions
+124 -11
View File
@@ -68,6 +68,106 @@ async function openSettings(page: Page, version = "0.3.0-beta.1") {
await expect(page.locator(".settings-loading")).toHaveCount(0);
}
test("shows every account as a collapsible summary on the overview", async ({
page,
}) => {
const requestedDashboards = new Set<number>();
const dashboards: Record<number, unknown> = {
1: {
accountId: 1,
displayName: "个人账号",
account: {
email: "personal@example.com",
planType: "plus",
connected: true,
},
limits: [
{
limitId: "primary",
limitName: "5 小时限额",
windowType: "five_hour",
usedPercent: 35,
windowDurationMinutes: 300,
resetsAt: 1_900_000_000,
},
],
summary: {
lifetimeTokens: 12_345,
peakDailyTokens: 3_000,
currentStreakDays: 4,
longestRunningTurnSec: 80,
},
usage: [{ date: "2026-08-13", totalTokens: 3_000 }],
fetchedAt: 1_900_000_000,
stale: false,
},
2: {
accountId: 2,
displayName: "Team 工作区",
account: {
email: "team@example.com",
planType: "team",
connected: true,
},
limits: [],
summary: {},
usage: [],
fetchedAt: 0,
stale: true,
},
};
await page.route("**/api/v1/**", async (route) => {
const request = route.request();
const key = new URL(request.url()).pathname.replace("/api/v1/", "");
if (key === "system/status")
return route.fulfill({
json: { initialized: true, appServer: true, version: "test" },
});
if (key === "auth/me")
return route.fulfill({ json: { username: "admin" } });
if (key === "accounts")
return route.fulfill({
json: [
{ ...responses.accounts[0], displayName: "个人账号" },
{
...responses.accounts[0],
id: 2,
displayName: "Team 工作区",
email: "team@example.com",
planType: "team",
expectedKind: "team",
},
],
});
if (key === "dashboard") {
const id = Number(new URL(request.url()).searchParams.get("accountId"));
requestedDashboards.add(id);
return route.fulfill({ json: dashboards[id] });
}
return route.fulfill({ json: responses[key] ?? { ok: true } });
});
await page.goto("/");
await expect(page.locator(".account-overview")).toHaveCount(2);
await expect.poll(() => requestedDashboards.size).toBe(2);
await expect(page.getByText("最低 65% 剩余")).toBeVisible();
await expect(page.getByText("限额暂无")).toBeVisible();
await expect(page.locator(".account-detail")).toHaveCount(0);
await expect(page.locator(".account-select")).toHaveCount(0);
const first = page.locator('[data-account-id="1"]');
await first.getByRole("button", { name: "展开个人账号详情" }).click();
await expect(first.locator(".account-detail")).toBeVisible();
await expect(first.getByRole("heading", { name: "个人账号" })).toBeVisible();
await expect(
first.getByRole("heading", { name: "每日 Token 趋势" }),
).toBeVisible();
await expect(page.locator(".account-detail")).toHaveCount(1);
await first.getByRole("button", { name: "收起个人账号详情" }).click();
await expect(page.locator(".account-detail")).toHaveCount(0);
});
test("shows the build version in the authenticated brand", async ({ page }) => {
await openSettings(page);
const mobile = (page.viewportSize()?.width ?? 0) <= 800;
@@ -233,6 +333,10 @@ test("keeps daily Token axis labels visible on narrow screens", async ({
});
await page.goto("/");
await page
.locator('[data-account-id="1"]')
.getByRole("button", { name: "展开默认账号详情" })
.click();
const chart = page.locator(".chart");
await expect(
chart.getByRole("heading", { name: "每日 Token 趋势" }),
@@ -505,13 +609,17 @@ test("Codex account emails are masked everywhere they are displayed", async ({
});
await page.goto("/");
await expect(page.locator(".account-select")).toContainText(
const account = page.locator('[data-account-id="1"]');
await expect(account.locator(".account-summary-identity")).toContainText(
"t**t@e*****e.com",
);
await account.getByRole("button", { name: "展开默认账号详情" }).click();
await expect(account.locator(".account-info-item").first()).toContainText(
"t**t@e*****e.com",
);
await expect(page.locator(".account b")).toContainText("t**t@e*****e.com");
await expect(page.getByText("已使用 25%", { exact: true })).toHaveCount(0);
await expect(page.getByText("剩余 75%", { exact: true })).toHaveCount(0);
await expect(page.locator(".bar")).toHaveAttribute(
await expect(account.locator(".bar")).toHaveAttribute(
"aria-label",
"5h:已使用 25%,剩余 75%",
);
@@ -548,12 +656,12 @@ test("returns to login when an authenticated request receives 401", async ({
return route.fulfill({ json: responses[key] ?? {} });
});
await page.goto("/");
await expect(page.locator(".account-select")).toBeVisible();
await page.getByRole("button", { name: "立即刷新" }).click();
await expect(page.locator(".account-overview")).toBeVisible();
await page.getByRole("button", { name: "刷新全部" }).click();
await expect(page.getByRole("heading", { name: "欢迎回来" })).toBeVisible();
});
test("a slow previous account response cannot replace the selected account", async ({
test("keeps each account dashboard in its own card when responses finish out of order", async ({
page,
}) => {
const accounts = [
@@ -593,10 +701,15 @@ test("a slow previous account response cannot replace the selected account", asy
return route.fulfill({ json: responses[key] ?? {} });
});
await page.goto("/");
const selector = page.locator(".account-select");
await selector.selectOption("2");
await expect(page.locator(".account b")).toContainText("第二账号");
const second = page.locator('[data-account-id="2"]');
await expect(second.locator(".account-summary-identity")).toContainText(
"第二账号",
);
await page.waitForTimeout(350);
await expect(page.locator(".account b")).toContainText("第二账号");
await expect(page.locator(".account b")).not.toContainText("默认账号");
await expect(second.locator(".account-summary-identity")).toContainText(
"第二账号",
);
await expect(
page.locator('[data-account-id="1"] .account-summary-identity'),
).toContainText("默认账号");
});