feat: add public account overview visibility

This commit is contained in:
2026-08-14 14:38:22 +08:00
parent bf37999833
commit 915d26d51d
14 changed files with 660 additions and 62 deletions
+118 -5
View File
@@ -23,6 +23,7 @@ const responses: Record<string, unknown> = {
email: "test@example.com",
planType: "plus",
expectedKind: "personal",
publicVisible: false,
actualKind: "personal",
validationStatus: "matched",
possibleDuplicate: false,
@@ -180,6 +181,54 @@ test("shows every account as a collapsible summary on the overview", async ({
await expect(page.locator(".account-detail")).toHaveCount(0);
});
test("shows a read-only overview before login", async ({ page }) => {
await page.route("**/api/v1/**", async (route) => {
const key = new URL(route.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({ status: 401, json: { error: "未登录" } });
if (key === "accounts") return route.fulfill({ json: responses.accounts });
if (key === "dashboard")
return route.fulfill({
json: {
accountId: 1,
displayName: "默认账号",
account: {
email: null,
planType: "plus",
connected: true,
},
limits: [],
summary: {},
usage: [],
fetchedAt: 1_900_000_000,
stale: false,
},
});
return route.fulfill({ json: responses[key] ?? {} });
});
await page.goto("/");
await expect(page.getByRole("button", { name: "登录后配置" })).toBeVisible();
await expect(page.locator(".account-overview")).toHaveCount(1);
await expect(page.getByRole("button", { name: "刷新全部" })).toHaveCount(0);
await expect(page.getByRole("button", { name: "设置中心" })).toHaveCount(0);
const account = page.locator('[data-account-id="1"]');
await account.getByRole("button", { name: "展开默认账号详情" }).click();
await expect(account.getByText("登录后查看")).toBeVisible();
await expect(account.locator(".account-detail .refresh")).toHaveCount(0);
await page.goto("/settings");
await expect(page).toHaveURL(/\/$/);
await page.getByRole("button", { name: "登录后配置" }).click();
await expect(page).toHaveURL(/\/login$/);
await expect(page.getByRole("heading", { name: "欢迎回来" })).toBeVisible();
});
test("shows the build version in the authenticated brand", async ({ page }) => {
await openSettings(page);
const mobile = (page.viewportSize()?.width ?? 0) <= 800;
@@ -260,7 +309,7 @@ test("shows the build version on login", async ({ page }) => {
await page.route("**/api/v1/auth/me", (route) =>
route.fulfill({ status: 401, json: { error: "unauthorized" } }),
);
await page.goto("/");
await page.goto("/login");
const badge = page.locator(".login .version-badge");
await expect(badge).toBeVisible();
await expect(badge).toHaveText("vtest");
@@ -570,6 +619,71 @@ test("deleting a newly added account clears its device authorization", async ({
).toBeVisible();
});
test("sets public visibility when adding and editing an account", async ({
page,
}) => {
let accounts: Array<(typeof responses.accounts)[number]> = [];
let createdBody: { publicVisible?: boolean } | undefined;
let updatedBody: { publicVisible?: boolean } | undefined;
await page.route("**/api/v1/**", async (route) => {
const request = route.request();
const key = new URL(request.url()).pathname.replace("/api/v1/", "");
if (key === "accounts" && request.method() === "GET")
return route.fulfill({ json: accounts });
if (key === "accounts" && request.method() === "POST") {
createdBody = request.postDataJSON() as { publicVisible?: boolean };
const account = {
...responses.accounts[0],
id: 2,
displayName: "账号 1",
email: "",
connected: false,
validationStatus: "pending" as const,
publicVisible: createdBody.publicVisible === true,
};
accounts = [account];
return route.fulfill({ json: account });
}
if (key === "accounts/2/login/device")
return route.fulfill({
json: {
verificationUrl: "https://auth.openai.com/codex/device",
userCode: "PLTJ-7M6I6",
},
});
if (key === "accounts/2" && request.method() === "PUT") {
updatedBody = request.postDataJSON() as { publicVisible?: boolean };
accounts = [
{
...accounts[0],
publicVisible: updatedBody.publicVisible === true,
},
];
return route.fulfill({ json: { ok: true } });
}
return route.fulfill({ json: responses[key] ?? {} });
});
await page.goto("/settings");
await page.getByRole("tab", { name: "Codex" }).click();
const addVisibility = page.locator(
".add-account-controls input[type=checkbox]",
);
await expect(addVisibility).not.toBeChecked();
await addVisibility.check();
await page.getByRole("button", { name: "添加账号" }).click();
await expect.poll(() => createdBody?.publicVisible).toBe(true);
const accountCard = page.locator(".account-card");
const accountVisibility = accountCard.locator('input[type="checkbox"]');
await expect(accountVisibility).toBeChecked();
await accountVisibility.evaluate((checkbox) =>
(checkbox as HTMLInputElement).click(),
);
await expect.poll(() => updatedBody?.publicVisible).toBe(false);
await expect(accountVisibility).not.toBeChecked();
});
test("Codex account cards fit within the viewport", async ({ page }) => {
await openSettings(page);
await page.getByRole("tab", { name: "Codex" }).click();
@@ -645,12 +759,12 @@ test("Codex account emails are masked everywhere they are displayed", async ({
await expect(page.locator("body")).not.toContainText("test@example.com");
});
test("returns to login when an authenticated request receives 401", async ({
test("shows the public overview when the session is unavailable", async ({
page,
}) => {
await page.route("**/api/v1/**", async (route) => {
const key = new URL(route.request().url()).pathname.replace("/api/v1/", "");
if (key === "accounts/1/sync")
if (key === "auth/me")
return route.fulfill({ status: 401, json: { error: "未登录" } });
if (key === "dashboard")
return route.fulfill({
@@ -669,8 +783,7 @@ test("returns to login when an authenticated request receives 401", async ({
});
await page.goto("/");
await expect(page.locator(".account-overview")).toBeVisible();
await page.getByRole("button", { name: "刷新全部" }).click();
await expect(page.getByRole("heading", { name: "欢迎回来" })).toBeVisible();
await expect(page.getByRole("button", { name: "登录后配置" })).toBeVisible();
});
test("keeps each account dashboard in its own card when responses finish out of order", async ({