feat: flip routing — customer portal at /, admin at /admin (#57)
* feat: flip routing — customer portal at /, admin at /admin Move all admin dashboard routes under /admin prefix and mount the customer portal at root (/). This gives customers clean, shareable URLs while staff bookmark /admin. - Admin routes: /admin, /admin/clients, /admin/services, etc. - Customer portal: / (root) - Admin nav "Customer Portal" link points to / for staff preview - Updated tests for new route structure and fixed React 19 act compat Closes #56 Co-Authored-By: Paperclip <noreply@paperclip.ing> * fix(e2e): update tests for routing flip — admin at /admin, portal at / All E2E tests now use /admin prefix for admin routes (clients, services, staff, invoices, reports, book). Adds customer portal smoke test at /. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(e2e): use specific locator for customer portal test getByText('Paws & Reflect') matched 3 elements causing strict mode violation. Scope to navigation role for unique match. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Groom Book CTO <cto@groombook.dev> Co-authored-by: Paperclip <noreply@paperclip.ing> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit was merged in pull request #57.
This commit is contained in:
committed by
GitHub
parent
1136824fe3
commit
c901b1135d
@@ -1,5 +1,5 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { render, screen, within, act } from "@testing-library/react";
|
||||
import { render, screen, within } from "@testing-library/react";
|
||||
import { MemoryRouter } from "react-router-dom";
|
||||
import { App } from "../App.js";
|
||||
|
||||
@@ -11,30 +11,28 @@ beforeEach(() => {
|
||||
} as unknown as Response);
|
||||
});
|
||||
|
||||
async function renderApp(route = "/") {
|
||||
await act(async () => {
|
||||
render(
|
||||
<MemoryRouter initialEntries={[route]}>
|
||||
<App />
|
||||
</MemoryRouter>
|
||||
);
|
||||
});
|
||||
function renderApp(route = "/admin") {
|
||||
render(
|
||||
<MemoryRouter initialEntries={[route]}>
|
||||
<App />
|
||||
</MemoryRouter>
|
||||
);
|
||||
return screen.getByRole("navigation");
|
||||
}
|
||||
|
||||
describe("App navigation", () => {
|
||||
it("renders the Groom Book brand", async () => {
|
||||
const nav = await renderApp();
|
||||
it("renders the Groom Book brand", () => {
|
||||
const nav = renderApp();
|
||||
expect(within(nav).getByText("Groom Book")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders the Book CTA button", async () => {
|
||||
const nav = await renderApp();
|
||||
it("renders the Book CTA button", () => {
|
||||
const nav = renderApp();
|
||||
expect(within(nav).getByText("Book")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders all primary nav links", async () => {
|
||||
const nav = await renderApp();
|
||||
it("renders all primary nav links", () => {
|
||||
const nav = renderApp();
|
||||
const expectedLinks = [
|
||||
"Appointments",
|
||||
"Clients",
|
||||
@@ -49,10 +47,20 @@ describe("App navigation", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("highlights the active route link", async () => {
|
||||
const nav = await renderApp("/clients");
|
||||
it("highlights the active route link", () => {
|
||||
const nav = renderApp("/admin/clients");
|
||||
const clientsLink = within(nav).getByText("Clients");
|
||||
// Active links use fontWeight 600
|
||||
expect(clientsLink).toHaveStyle({ fontWeight: "600" });
|
||||
});
|
||||
|
||||
it("renders customer portal at root", () => {
|
||||
render(
|
||||
<MemoryRouter initialEntries={["/"]}>
|
||||
<App />
|
||||
</MemoryRouter>
|
||||
);
|
||||
// Customer portal should render at root - no admin nav present
|
||||
expect(screen.queryByText("Groom Book")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user