fix: restore authenticated home and account navigation
This commit is contained in:
@@ -11,7 +11,7 @@ import {
|
||||
const root = resolve(import.meta.dirname!, "../../../..");
|
||||
const origin = "http://127.0.0.1:15411";
|
||||
const ready = (page: Page, name: string) =>
|
||||
page.getByRole("link", { name, exact: true }).waitFor();
|
||||
page.getByRole("main").getByRole("link", { name, exact: true }).waitFor();
|
||||
async function focusWithoutSessionCheck(page: Page) {
|
||||
let checks = 0;
|
||||
const observe = (request: import("playwright").Request) => {
|
||||
@@ -123,6 +123,24 @@ Deno.test("public entry isolates Workspace authority and authenticated dashboard
|
||||
await page.getByRole("heading", { name: "Yoi", exact: true })
|
||||
.waitFor();
|
||||
await ready(page, "Log in");
|
||||
assertEquals(
|
||||
await page.locator(
|
||||
'a[href^="/register"], a[href="/activate"]',
|
||||
).count(),
|
||||
0,
|
||||
);
|
||||
assertEquals(await page.getByText(/registration/i).count(), 0);
|
||||
await page.getByRole("link", { name: "Log in", exact: true })
|
||||
.click();
|
||||
await page.getByRole("button", { name: "Log in with passkey" })
|
||||
.waitFor();
|
||||
assertEquals(
|
||||
await page.getByRole("link", {
|
||||
name: "Activate an account with a code",
|
||||
exact: true,
|
||||
}).getAttribute("href"),
|
||||
"/activate",
|
||||
);
|
||||
if (mode === "anonymous") {
|
||||
await ready(page, "Create a new account");
|
||||
assertEquals(
|
||||
@@ -146,19 +164,6 @@ Deno.test("public entry isolates Workspace authority and authenticated dashboard
|
||||
0,
|
||||
);
|
||||
}
|
||||
await page.getByRole("link", { name: "Log in", exact: true })
|
||||
.click();
|
||||
await page.getByRole("button", { name: "Log in with passkey" })
|
||||
.waitFor();
|
||||
if (mode === "policy-disabled") {
|
||||
await page.getByText(/registration is disabled/).waitFor();
|
||||
assertEquals(
|
||||
await page.getByRole("link", {
|
||||
name: "Create a new account",
|
||||
}).count(),
|
||||
0,
|
||||
);
|
||||
}
|
||||
assertEquals(workspaceCalls, 0);
|
||||
assertEquals(
|
||||
await page.locator(".workspace-catalog-alert").count(),
|
||||
|
||||
@@ -289,7 +289,9 @@ export async function sessionNavigation(
|
||||
if (active) {
|
||||
await at(page, target);
|
||||
assertEquals(await page.getByText("Loading session…").count(), 0);
|
||||
await page.getByRole("link", { name: "Dashboard / Workspaces" })
|
||||
await page.getByRole("main").getByRole("link", {
|
||||
name: "Dashboard / Workspaces",
|
||||
})
|
||||
.click();
|
||||
await at(page, loginTarget("/dashboard"));
|
||||
await login(page);
|
||||
|
||||
@@ -0,0 +1,223 @@
|
||||
/// <reference lib="dom" />
|
||||
// 本番shell/routerと合成HTTP。実アカウントや認証器は操作しない。
|
||||
import { assertEquals } from "@std/assert";
|
||||
import { resolve } from "@std/path";
|
||||
import { chromium } from "playwright";
|
||||
import {
|
||||
startOwnedProcesses,
|
||||
stopOwnedProcesses,
|
||||
} from "../../../../tools/web-ux/src/processes.ts";
|
||||
|
||||
const root = resolve(import.meta.dirname!, "../../../..");
|
||||
const origin = "http://127.0.0.1:15421";
|
||||
const output = `${root}/target/web-ux/sidebar-restoration`;
|
||||
|
||||
Deno.test("only authenticated account and home show the sidebar; public entry remains standalone", async (t) => {
|
||||
const processes = await startOwnedProcesses(
|
||||
[{
|
||||
id: "sidebar-fixture",
|
||||
command: "deno",
|
||||
cwd: root,
|
||||
args: [
|
||||
"run",
|
||||
"--config",
|
||||
"tools/web-ux/deno.json",
|
||||
"--allow-read",
|
||||
"--allow-net",
|
||||
"web/workspace/test/auth-browser/fixture-server.ts",
|
||||
"15421",
|
||||
"web/workspace/build",
|
||||
],
|
||||
readyUrl: `${origin}/health`,
|
||||
}],
|
||||
`${root}/tools/web-ux/scenarios/anonymous-entry.json`,
|
||||
`${output}/processes`,
|
||||
[],
|
||||
);
|
||||
try {
|
||||
const browser = await chromium.launch();
|
||||
try {
|
||||
for (const width of [1440, 390]) {
|
||||
for (
|
||||
const [path, mode] of [
|
||||
["/account", "workspace-one"],
|
||||
["/account", "account"],
|
||||
["/account", "workspace-error"],
|
||||
["/account", "workspace-loading"],
|
||||
["/", "anonymous"],
|
||||
["/", "account"],
|
||||
["/", "session-error"],
|
||||
["/", "session-loading"],
|
||||
]
|
||||
) {
|
||||
await t.step(`${width}: ${path} ${mode}`, async () => {
|
||||
const context = await browser.newContext({
|
||||
viewport: { width, height: 900 },
|
||||
colorScheme: width === 390 ? "dark" : "light",
|
||||
reducedMotion: "reduce",
|
||||
});
|
||||
try {
|
||||
const page = await context.newPage();
|
||||
page.setDefaultTimeout(10_000);
|
||||
const errors: string[] = [];
|
||||
let catalogCalls = 0;
|
||||
page.on("pageerror", (error) => errors.push(error.message));
|
||||
page.on("request", (request) => {
|
||||
if (new URL(request.url()).pathname === "/api/workspaces") {
|
||||
catalogCalls++;
|
||||
}
|
||||
});
|
||||
await page.request.get(`${origin}/?fixture=${mode}`);
|
||||
await page.goto(`${origin}${path}`);
|
||||
await page.getByRole("heading", {
|
||||
name: path === "/" ? "Yoi" : "Account",
|
||||
exact: true,
|
||||
}).waitFor();
|
||||
if (path === "/" && mode !== "account") {
|
||||
if (mode === "anonymous") {
|
||||
await page.getByRole("link", { name: "Log in", exact: true })
|
||||
.waitFor();
|
||||
} else if (mode === "session-error") {
|
||||
await page.getByRole("alert").waitFor();
|
||||
} else {
|
||||
await page.getByText("Loading session…", { exact: true })
|
||||
.waitFor();
|
||||
}
|
||||
assertEquals(await page.locator(".sidebar-frame").count(), 0);
|
||||
assertEquals(
|
||||
await page.locator(".app-shell__topbar").count(),
|
||||
0,
|
||||
);
|
||||
if (mode === "anonymous") {
|
||||
await page.screenshot({
|
||||
path: `${output}/anonymous-${width}.png`,
|
||||
});
|
||||
await page.getByRole("link", { name: "Log in", exact: true })
|
||||
.click();
|
||||
await page.getByRole("button", {
|
||||
name: "Log in with passkey",
|
||||
exact: true,
|
||||
}).waitFor();
|
||||
assertEquals(await page.locator(".sidebar-frame").count(), 0);
|
||||
await page.goBack();
|
||||
await page.getByRole("link", { name: "Log in", exact: true })
|
||||
.waitFor();
|
||||
assertEquals(await page.locator(".sidebar-frame").count(), 0);
|
||||
}
|
||||
assertEquals(catalogCalls, 0);
|
||||
assertEquals(errors, []);
|
||||
return;
|
||||
}
|
||||
const sidebar = page.getByRole("complementary", {
|
||||
name: "Sidebar",
|
||||
exact: true,
|
||||
});
|
||||
if (width === 390) {
|
||||
await page.getByRole("button", {
|
||||
name: "Show sidebar",
|
||||
exact: true,
|
||||
}).click();
|
||||
assertEquals(
|
||||
await page.locator("main").evaluate((main) =>
|
||||
main.hasAttribute("inert")
|
||||
),
|
||||
true,
|
||||
);
|
||||
}
|
||||
const navigation = sidebar.getByRole("navigation", {
|
||||
name: "Global pages",
|
||||
exact: true,
|
||||
});
|
||||
await navigation.getByRole("link", {
|
||||
name: "Dashboard / Workspaces",
|
||||
exact: true,
|
||||
}).waitFor();
|
||||
const account = navigation.getByRole("link", {
|
||||
name: "Account",
|
||||
exact: true,
|
||||
});
|
||||
assertEquals(await account.getAttribute("href"), "/account");
|
||||
assertEquals(
|
||||
await account.getAttribute("aria-current"),
|
||||
path === "/account" ? "page" : null,
|
||||
);
|
||||
if (mode === "workspace-one") {
|
||||
await navigation.getByRole("link", {
|
||||
name: "New Workspace",
|
||||
exact: true,
|
||||
}).waitFor();
|
||||
} else if (path === "/account" && mode === "account") {
|
||||
await navigation.getByText("No accessible Workspaces.", {
|
||||
exact: true,
|
||||
}).waitFor();
|
||||
} else {
|
||||
assertEquals(
|
||||
await navigation.getByText("No accessible Workspaces.", {
|
||||
exact: true,
|
||||
}).count(),
|
||||
0,
|
||||
);
|
||||
}
|
||||
if (path === "/") assertEquals(catalogCalls, 0);
|
||||
if (
|
||||
mode === "workspace-one" ||
|
||||
(path === "/" && mode === "account")
|
||||
) {
|
||||
await page.screenshot({
|
||||
path: `${output}/${
|
||||
path === "/" ? "home" : "account"
|
||||
}-${width}.png`,
|
||||
});
|
||||
}
|
||||
if (width === 390) {
|
||||
await page.getByRole("button", {
|
||||
name: "Hide sidebar",
|
||||
exact: true,
|
||||
}).click();
|
||||
assertEquals(
|
||||
await page.locator("main").evaluate((main) =>
|
||||
main.hasAttribute("inert")
|
||||
),
|
||||
false,
|
||||
);
|
||||
}
|
||||
if (path === "/account") {
|
||||
await page.getByRole("button", {
|
||||
name: "Add passkey to alice",
|
||||
exact: true,
|
||||
}).waitFor();
|
||||
await page.getByRole("button", { name: "Log out", exact: true })
|
||||
.waitFor();
|
||||
} else {
|
||||
// The authenticated home has a sidebar; logout must remove it on return.
|
||||
await page.getByRole("main").getByRole("link", {
|
||||
name: "Account",
|
||||
exact: true,
|
||||
}).click();
|
||||
await page.getByRole("button", { name: "Log out", exact: true })
|
||||
.click();
|
||||
await page.getByRole("link", { name: "Log in", exact: true })
|
||||
.waitFor();
|
||||
assertEquals(new URL(page.url()).pathname, "/");
|
||||
assertEquals(await page.locator(".sidebar-frame").count(), 0);
|
||||
}
|
||||
assertEquals(
|
||||
await page.evaluate(() =>
|
||||
document.documentElement.scrollWidth > innerWidth
|
||||
),
|
||||
false,
|
||||
);
|
||||
assertEquals(errors, []);
|
||||
} finally {
|
||||
await context.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
} finally {
|
||||
await stopOwnedProcesses(processes);
|
||||
}
|
||||
});
|
||||
@@ -10,7 +10,6 @@ Deno.test("authentication and hosting shells never require Workspace catalog aut
|
||||
"/activate",
|
||||
"/login",
|
||||
"/register",
|
||||
"/account",
|
||||
"/login/device",
|
||||
"/hosting/accounts",
|
||||
"/hosting/settings",
|
||||
@@ -49,6 +48,45 @@ Deno.test("authentication and hosting shells never require Workspace catalog aut
|
||||
}
|
||||
});
|
||||
|
||||
Deno.test("account loads its sidebar catalog without making catalog failure an admission failure", async () => {
|
||||
const original = globalThis.fetch;
|
||||
try {
|
||||
globalThis.fetch = async () =>
|
||||
Response.json({
|
||||
actor: {
|
||||
user_id: "u",
|
||||
account_id: "a",
|
||||
handle: "alice",
|
||||
display_name: "Alice",
|
||||
auth_method: "browser_session",
|
||||
},
|
||||
session: { kind: "authenticated", expires_at: "2099-01-01T00:00:00Z" },
|
||||
});
|
||||
browserSession.invalidate();
|
||||
let calls = 0;
|
||||
const event = {
|
||||
depends: () => {},
|
||||
url: new URL("https://workspace.test/account"),
|
||||
fetch: () => {
|
||||
calls++;
|
||||
throw new Error("Workspace catalog unavailable");
|
||||
},
|
||||
} as unknown as Parameters<typeof load>[0];
|
||||
const result = await load(event);
|
||||
assertEquals(calls, 1);
|
||||
assertEquals(await result.deferredCatalog, {
|
||||
ok: true,
|
||||
data: {
|
||||
accessibleWorkspaces: [],
|
||||
workspaceCatalogError: "Workspace catalog unavailable",
|
||||
},
|
||||
});
|
||||
} finally {
|
||||
browserSession.dispose();
|
||||
globalThis.fetch = original;
|
||||
}
|
||||
});
|
||||
|
||||
Deno.test("workspace frontend serves deep links through the client SPA", () => {
|
||||
assertEquals(ssr, false);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user