Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions components/navigation/NavbarWrapper.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import { NO_NAVBAR_PAGES } from "@/lib/navbar";
import { isNoNavbarPage } from "@/lib/navbar";
import { usePathname } from "next/navigation";
import React from "react";

Expand All @@ -13,7 +13,7 @@ export default function NavbarWrapper({
}) {
const pathname = usePathname();

if (NO_NAVBAR_PAGES.includes(pathname)) {
if (isNoNavbarPage(pathname)) {
return <>{noNavbarChildren}</>;
}

Expand Down
16 changes: 16 additions & 0 deletions lib/navbar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export const NAVBAR_ITEMS: NavbarItem[] = [
{ label: "Events", href: "/events", icon: "calendar" },
];

// Entries ending in "/" also match every path under them (see matchesPage).
export const NO_NAVBAR_PAGES = [
ORGANIZATION_NOT_FOUND_PATH,
"/login",
Expand All @@ -56,4 +57,19 @@ export const NO_NAVBAR_PAGES = [
"/forgotpassword",
];

/**
* Whether `pathname` is one of `pages`. An entry ending in "/" matches any
* path under it: "/forms/" matches "/forms/abc" but not "/forms" or
* "/formsabc". Other entries match only the exact path.
*/
export function matchesPage(pathname: string, pages: readonly string[]) {
return pages.some((page) =>
page.endsWith("/") ? pathname.startsWith(page) : pathname === page,
);
}

export function isNoNavbarPage(pathname: string) {
return matchesPage(pathname, NO_NAVBAR_PAGES);
}

export const MEMBERSHIP_REDIRECT_EXCLUDED_PAGES = NO_NAVBAR_PAGES;
10 changes: 10 additions & 0 deletions styles/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,16 @@
transparent
);

/* Theme tokens that will follow each org's theme (#300). They hold today's
values, so using them changes nothing yet. */
--color-page-bg: #ffffff;
--color-page-text: var(--color-grey-text-strong);
/* Text on brand-colored surfaces, e.g. a primary button's label. */
--color-brand-foreground: #ffffff;
--font-display: var(--font-heading);
/* Corner radius for inputs and buttons (Tailwind's `rounded`). */
--radius-control: 0.25rem;

--text-desktop-display: 48px;
--text-desktop-heading-1: 40px;
--text-desktop-heading-2: 32px;
Expand Down
32 changes: 32 additions & 0 deletions tests/unit/components/navigation/NavbarWrapper.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import { cleanup, render, screen } from "@testing-library/react";
import NavbarWrapper from "@/components/navigation/NavbarWrapper";

const pathname = vi.hoisted(() => ({ current: "/" }));

vi.mock("next/navigation", () => ({
usePathname: () => pathname.current,
}));

function renderAt(path: string) {
pathname.current = path;
render(
<NavbarWrapper noNavbarChildren={<p>without navbar</p>}>
<p>with navbar</p>
</NavbarWrapper>,
);
}

describe("NavbarWrapper", () => {
afterEach(cleanup);

it("renders the navbar layout on regular pages", () => {
renderAt("/events");
expect(screen.getByText("with navbar")).toBeTruthy();
});

it("renders without the navbar on excluded pages", () => {
renderAt("/login");
expect(screen.getByText("without navbar")).toBeTruthy();
});
Comment thread
shaply marked this conversation as resolved.
});
34 changes: 34 additions & 0 deletions tests/unit/lib/navbar.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// @vitest-environment node
import { describe, expect, it } from "vitest";
import { isNoNavbarPage, matchesPage, NO_NAVBAR_PAGES } from "@/lib/navbar";

describe(matchesPage, () => {
const pages = ["/login", "/forms/"];

it("matches any path under an entry ending in /", () => {
expect(matchesPage("/forms/abc", pages)).toBe(true);
expect(matchesPage("/forms/camp-application-2027/edit", pages)).toBe(true);
});

it("doesn't match a path that only shares the prefix's letters", () => {
expect(matchesPage("/formsabc", pages)).toBe(false);
expect(matchesPage("/forms", pages)).toBe(false);
});

it("matches other entries exactly", () => {
expect(matchesPage("/login", pages)).toBe(true);
expect(matchesPage("/login/help", pages)).toBe(false);
expect(matchesPage("/loginx", pages)).toBe(false);
});
});

describe(isNoNavbarPage, () => {
it.each(NO_NAVBAR_PAGES)("hides the navbar on %s", (page) => {
expect(isNoNavbarPage(page)).toBe(true);
});

it("shows the navbar elsewhere", () => {
expect(isNoNavbarPage("/")).toBe(false);
expect(isNoNavbarPage("/events")).toBe(false);
});
});
Loading