Skip to content

Commit 2f5b5e6

Browse files
authored
Merge branch 'alpha' into feat/upload-abort-signal
2 parents 2d46a75 + 7910364 commit 2f5b5e6

5 files changed

Lines changed: 99 additions & 4 deletions

File tree

packages/app/src/app/layout.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@ import Script from "next/script";
44
import { MatomoAnalytics } from "~/modules/analytics";
55
import { SessionProviderWrapper } from "~/modules/auth";
66
import {
7-
Footer,
87
Header,
98
ImpersonateBanner,
10-
ResourceBanner,
9+
PublicChrome,
1110
SkipLinks,
1211
} from "~/modules/layout";
1312
import { ProfileModal } from "~/modules/profile";
@@ -59,8 +58,7 @@ export default function RootLayout({
5958
<ImpersonateBanner />
6059
<Header />
6160
{children}
62-
<ResourceBanner />
63-
<Footer />
61+
<PublicChrome />
6462
<ProfileModal />
6563
</TRPCReactProvider>
6664
</SessionProviderWrapper>

packages/app/src/e2e/admin.e2e.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,23 @@ test("admin user can access /admin and sees backoffice page", async ({
1010
await expect(page.getByText("administrateur")).toBeVisible();
1111
});
1212

13+
test("admin routes hide the public footer and help banner", async ({
14+
page,
15+
}) => {
16+
// Runs in the authenticated `chromium` Playwright project (see
17+
// playwright.config.ts): `page.goto("/admin")` reaches the real backoffice
18+
// page, so the assertions below exercise the PublicChrome branch, not a
19+
// login-redirect fallback that would trivially pass.
20+
await page.goto("/admin");
21+
await expect(
22+
page.getByRole("heading", { name: "Backoffice", level: 1 }),
23+
).toBeVisible();
24+
await expect(page.locator("footer#footer")).toHaveCount(0);
25+
await expect(
26+
page.getByRole("region", { name: "Ressources et aide" }),
27+
).toHaveCount(0);
28+
});
29+
1330
test("admin user can access /admin/impersonate and sees impersonate page", async ({
1431
page,
1532
}) => {
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
"use client";
2+
3+
import { usePathname } from "next/navigation";
4+
5+
import { Footer } from "./Footer";
6+
import { ResourceBanner } from "./ResourceBanner";
7+
8+
/**
9+
* Renders the public help banner + footer on every route except the backoffice.
10+
* Admin pages (`/admin/**`) get a stripped-down chrome so the sidebar + admin
11+
* content fill the viewport without competing with public navigation.
12+
*/
13+
export function PublicChrome() {
14+
const pathname = usePathname();
15+
// Match the `/admin` segment boundary so hypothetical sibling routes like
16+
// `/administrator` or `/admin-tools` keep the public chrome.
17+
if (pathname === "/admin" || pathname?.startsWith("/admin/")) {
18+
return null;
19+
}
20+
return (
21+
<>
22+
<ResourceBanner />
23+
<Footer />
24+
</>
25+
);
26+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { render, screen } from "@testing-library/react";
2+
import { usePathname } from "next/navigation";
3+
import { beforeEach, describe, expect, it, type Mock } from "vitest";
4+
5+
import { PublicChrome } from "../PublicChrome";
6+
7+
// Renders real Footer + ResourceBanner; only their external deps
8+
// (next/link, next/image, next/navigation) are mocked globally in test/setup.ts,
9+
// so a regression inside either landmark would fail this file.
10+
11+
describe("PublicChrome", () => {
12+
beforeEach(() => {
13+
(usePathname as Mock).mockReturnValue("/");
14+
});
15+
16+
it("renders ResourceBanner + Footer on public routes", () => {
17+
render(<PublicChrome />);
18+
expect(
19+
screen.getByRole("region", { name: /ressources et aide/i }),
20+
).toBeInTheDocument();
21+
expect(screen.getByRole("contentinfo")).toBeInTheDocument();
22+
});
23+
24+
it("renders nothing on /admin", () => {
25+
(usePathname as Mock).mockReturnValue("/admin");
26+
const { container } = render(<PublicChrome />);
27+
expect(container).toBeEmptyDOMElement();
28+
});
29+
30+
it("renders nothing on nested /admin/* routes", () => {
31+
(usePathname as Mock).mockReturnValue("/admin/declarations");
32+
const { container } = render(<PublicChrome />);
33+
expect(container).toBeEmptyDOMElement();
34+
});
35+
36+
it("renders chrome on non-admin nested routes", () => {
37+
(usePathname as Mock).mockReturnValue("/mon-espace/declarations");
38+
render(<PublicChrome />);
39+
expect(
40+
screen.getByRole("region", { name: /ressources et aide/i }),
41+
).toBeInTheDocument();
42+
expect(screen.getByRole("contentinfo")).toBeInTheDocument();
43+
});
44+
45+
it("renders chrome on sibling routes whose name merely starts with 'admin'", () => {
46+
(usePathname as Mock).mockReturnValue("/administrator");
47+
render(<PublicChrome />);
48+
expect(
49+
screen.getByRole("region", { name: /ressources et aide/i }),
50+
).toBeInTheDocument();
51+
expect(screen.getByRole("contentinfo")).toBeInTheDocument();
52+
});
53+
});

packages/app/src/modules/layout/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ export { Breadcrumb } from "./Breadcrumb";
22
export { Footer } from "./Footer";
33
export { Header } from "./Header";
44
export { ImpersonateBanner } from "./ImpersonateBanner";
5+
export { PublicChrome } from "./PublicChrome";
56
export { ResourceBanner } from "./ResourceBanner";
67
export { DsfrPictogram } from "./shared/DsfrPictogram";
78
export { NewTabNotice } from "./shared/NewTabNotice";

0 commit comments

Comments
 (0)