Compare commits

..

1 Commits

Author SHA1 Message Date
Flea Flicker 6f471a4934 fix(GRO-1026): re-apply GRO-730 scrollbar-hide to portal tab rows
CI / Test (pull_request) Successful in 33s
CI / Lint & Typecheck (pull_request) Successful in 42s
CI / Build & Push Docker Image (pull_request) Successful in 45s
The scrollbar-hide CSS utility and its usage in BillingPayments.tsx and
PetProfiles.tsx were lost in the groombook/app → groombook/web migration.

- src/index.css: add cross-browser .scrollbar-hide utility (Firefox/IE/WebKit)
- BillingPayments.tsx: replace flex-wrap with overflow-x-auto scrollbar-hide
- PetProfiles.tsx: add scrollbar-hide to pet-selector row and section tabs row
- UAT_PLAYBOOK.md: add §5.16a portal tab-row mobile overflow test cases

Updated UAT_PLAYBOOK.md §5.16a — new portal mobile overflow test cases TC-WEB-5.16.4–7.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-06-26 08:55:34 +00:00
3 changed files with 1 additions and 67 deletions
+1 -1
View File
@@ -86,7 +86,7 @@ export const { signIn, signOut, useSession, changePassword } = authClient;
| # | Scenario | Steps | Pass Criteria | Fail Criteria | | # | Scenario | Steps | Pass Criteria | Fail Criteria |
|---|----------|-------|---------------|---------------| |---|----------|-------|---------------|---------------|
| TC-WEB-SSO-1 | Sign-in page shows SSO button | Navigate to app root URL | Sign-in page displayed with "Sign in with SSO" button visible | No SSO button, 403 before page loads | | TC-WEB-SSO-1 | Sign-in page shows SSO button | Navigate to app root URL | Sign-in page displayed with "Sign in with SSO" button visible | No SSO button, 403 before page loads |
| TC-WEB-SSO-2 | Click SSO redirects to Authentik (GRO-2572) | **Fresh session only (no pre-existing auth cookie).** Click "Sign in with SSO" button | Browser navigates to Authentik login at auth.farh.net within ~1 s — address bar changes to auth.farh.net URL | No redirect, error shown, button stays disabled, user remains on /login. Regression: prior to GRO-2572 fix the client never followed the `data.url` returned by Better Auth. Run from a clean incognito context to avoid a stale cookie masking the defect. | | TC-WEB-SSO-2 | Click SSO redirects to Authentik | Click "Sign in with SSO" button | Browser redirected to Authentik login at auth.farh.net | No redirect, error shown, button does nothing |
| TC-WEB-SSO-3 | Valid OIDC credentials authenticate | At Authentik, enter valid credentials and authenticate | Redirected back to app with active session | Redirect loop, 403, session not established | | TC-WEB-SSO-3 | Valid OIDC credentials authenticate | At Authentik, enter valid credentials and authenticate | Redirected back to app with active session | Redirect loop, 403, session not established |
| TC-WEB-SSO-4 | Post-login dashboard accessible | After SSO flow completes, dashboard loads | Dashboard displays correctly with user identity shown | Blank page, 403, session not active | | TC-WEB-SSO-4 | Post-login dashboard accessible | After SSO flow completes, dashboard loads | Dashboard displays correctly with user identity shown | Blank page, 403, session not active |
| TC-WEB-SSO-5 | User identity displayed correctly | After SSO login, check header/nav | User name/email/initials shown in nav, role reflected in UI | No user indicator, wrong user shown | | TC-WEB-SSO-5 | User identity displayed correctly | After SSO login, check header/nav | User name/email/initials shown in nav, role reflected in UI | No user indicator, wrong user shown |
-6
View File
@@ -45,12 +45,6 @@ function LoginPage() {
if (result?.error) { if (result?.error) {
setError(result.error.message ?? "Sign-in failed"); setError(result.error.message ?? "Sign-in failed");
setIsLoading(false); setIsLoading(false);
return;
}
// Better Auth returns the IdP authorize URL in data.url with redirect:true rather than
// issuing an HTTP 30x — the client must follow it (GRO-2572).
if (result?.data?.url) {
window.location.href = result.data.url;
} }
}; };
-60
View File
@@ -1,6 +1,5 @@
import { describe, it, expect, vi, beforeEach } from "vitest"; import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, screen, within, waitFor } from "@testing-library/react"; import { render, screen, within, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { MemoryRouter } from "react-router-dom"; import { MemoryRouter } from "react-router-dom";
import { App } from "../App"; import { App } from "../App";
@@ -233,7 +232,6 @@ describe("Dev login selector", () => {
}); });
it("does not redirect when a dev user is already selected", async () => { it("does not redirect when a dev user is already selected", async () => {
localStorage.setItem("dev-user", JSON.stringify({ type: "staff", id: "s1", name: "Sarah" })); localStorage.setItem("dev-user", JSON.stringify({ type: "staff", id: "s1", name: "Sarah" }));
global.fetch = vi.fn((url: string) => { global.fetch = vi.fn((url: string) => {
@@ -271,61 +269,3 @@ describe("Dev login selector", () => {
).toBeInTheDocument(); ).toBeInTheDocument();
}); });
}); });
describe("GRO-2572 — SSO button follows redirect URL", () => {
it("navigates to data.url when signIn.social returns a redirect", async () => {
// Mock signIn.social to return the redirect payload Better Auth sends
vi.mock("../lib/auth-client.js", () => ({
useSession: () => ({ data: null, isPending: false }),
signIn: {
social: vi.fn().mockResolvedValue({
data: { redirect: true, url: "https://auth.farh.net/application/o/authorize/?test=1" },
error: null,
}),
},
signOut: vi.fn(),
changePassword: vi.fn(),
}));
const assignMock = vi.fn();
Object.defineProperty(window, "location", {
value: { ...window.location, href: "", origin: "https://uat.groombook.dev" },
writable: true,
});
Object.defineProperty(window.location, "href", {
set: assignMock,
get: () => "",
});
global.fetch = vi.fn((url: string) => {
if (url === "/api/dev/config") {
return Promise.resolve({ ok: true, json: async () => ({ authDisabled: false }) } as Response);
}
if (url === "/api/auth/get-session") {
return Promise.resolve({ ok: true, json: async () => null } as unknown as Response);
}
if (url === "/api/setup/status") {
return Promise.resolve({ ok: true, json: async () => ({ needsSetup: false }) } as Response);
}
if (url === "/api/auth/providers") {
return Promise.resolve({ ok: true, json: async () => ({ providers: ["authentik"] }) } as Response);
}
return Promise.resolve({ ok: true, json: async () => [] } as Response);
}) as unknown as typeof fetch;
render(
<MemoryRouter initialEntries={["/login"]}>
<App />
</MemoryRouter>
);
const ssoButton = await screen.findByRole("button", { name: /sign in with sso/i });
await userEvent.click(ssoButton);
await waitFor(() => {
expect(assignMock).toHaveBeenCalledWith(
"https://auth.farh.net/application/o/authorize/?test=1"
);
});
});
});