fix(GRO-2373): add Sign out button to in-portal chrome sidebar (#77)
This commit was merged in pull request #77.
This commit is contained in:
@@ -1057,4 +1057,75 @@ describe("OOBE portal-creation flow (GRO-2359)", () => {
|
||||
|
||||
Object.defineProperty(window, "location", { value: originalLocation, configurable: true });
|
||||
});
|
||||
|
||||
it("reaches the shared signOut() handler from the in-portal chrome sidebar (GRO-2373)", async () => {
|
||||
// Pre-GRO-2373, the customer portal chrome (Home, Appointments, My Pets,
|
||||
// Report Cards, Billing, Messages, Settings) had no visible sign-out
|
||||
// control — only the OOBE and the no-access card exposed one. This
|
||||
// leaves users signed-in with no escape hatch. The fix lands a
|
||||
// "Sign out" button in the sidebar footer that wires to the same
|
||||
// canonical `signOut()` already used by OOBE / no-access / AdminLayout.
|
||||
signOutSpy.mockClear();
|
||||
|
||||
const originalLocation = window.location;
|
||||
Object.defineProperty(window, "location", {
|
||||
value: { href: "" },
|
||||
writable: true,
|
||||
configurable: true,
|
||||
});
|
||||
|
||||
global.fetch = vi.fn((input: RequestInfo, init?: RequestInit) => {
|
||||
const url = typeof input === "string" ? input : input.toString();
|
||||
if (url === "/api/branding") return Promise.resolve(brandingResponse);
|
||||
if (url === "/api/auth/get-session") {
|
||||
return Promise.resolve({
|
||||
ok: true,
|
||||
json: async () => ({ user: { email: "uat-customer@groombook.dev", role: "customer" } }),
|
||||
} as Response);
|
||||
}
|
||||
if (url === "/api/portal/session-from-auth" && init?.method === "POST") {
|
||||
return Promise.resolve({
|
||||
ok: true,
|
||||
status: 201,
|
||||
json: async () => ({ sessionId: "chrome-sess-1", clientId: "client-1", clientName: "Jane Doe" }),
|
||||
} as Response);
|
||||
}
|
||||
return Promise.resolve({ ok: true, json: async () => ({}) } as Response);
|
||||
}) as unknown as typeof fetch;
|
||||
|
||||
const { CustomerPortal } = await import("../portal/CustomerPortal.js");
|
||||
render(
|
||||
<MemoryRouter initialEntries={["/"]}>
|
||||
<CustomerPortal />
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
// Land on the chrome (proof: customer greeting is rendered, no
|
||||
// no-access card, no OOBE).
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/Hi, Jane/)).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.queryByText(/Portal access not configured/i)).not.toBeInTheDocument();
|
||||
expect(screen.queryByText(/set up your portal/i)).not.toBeInTheDocument();
|
||||
|
||||
// The new chrome sign-out is scoped by data-testid so it doesn't
|
||||
// collide with other surfaces that may also render "Sign out" labels
|
||||
// (e.g. the impersonation banner uses "End Session").
|
||||
const signOutButton = screen.getByTestId("portal-chrome-signout");
|
||||
expect(signOutButton).toHaveTextContent(/Sign out/i);
|
||||
fireEvent.click(signOutButton);
|
||||
|
||||
// Same canonical handler as OOBE / no-access / AdminLayout — never
|
||||
// a raw fetch("/api/auth/sign-out") and never a navigate() without
|
||||
// signOut() (the OOBE/no-access surface uses window.location.href
|
||||
// for a hard reload so cached state is reset).
|
||||
await waitFor(() => {
|
||||
expect(signOutSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(window.location.href).toBe("/login");
|
||||
});
|
||||
|
||||
Object.defineProperty(window, "location", { value: originalLocation, configurable: true });
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user