Skip to content

Commit 765fcd2

Browse files
Merge pull request #47 from Sleeping-Bear-Systems/make-page-auth-aware
Make navigation bar application aware
2 parents 5dd343c + ac232f7 commit 765fcd2

18 files changed

Lines changed: 415 additions & 51 deletions

src/features/about/aboutPage.test.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,26 @@
11
import { describe, expect, test } from "bun:test";
22
import type { AppVariables } from "@shared/appVariables.ts";
3+
import { pageRoutes } from "@shared/pageRoutes.ts";
34
import { Hono } from "hono";
45
import { aboutPage } from "./aboutPage.tsx";
56

67
describe("aboutPage", () => {
78
test("renders the about page HTML", async () => {
8-
const app = new Hono<{ Variables: AppVariables }>().route("/", aboutPage);
9+
const app = new Hono<{ Variables: AppVariables }>().route(
10+
pageRoutes.ABOUT,
11+
aboutPage,
12+
);
913

10-
const response = await app.fetch(new Request("http://localhost/"));
14+
const response = await app.fetch(
15+
new Request(`http://localhost${pageRoutes.ABOUT}`),
16+
);
1117
const html = await response.text();
1218

1319
expect(response.status).toBe(200);
1420
expect(html).toContain("<title>ToDo</title>");
1521
expect(html).toContain("<h1>About</h1>");
16-
expect(html).toContain('<a href="/">Home</a>');
22+
expect(html).toContain('<a href="/login">Login</a>');
23+
expect(html).not.toContain('<a href="/about">About</a>');
1724
expect(html).toContain("<h2>Powered By</h2>");
1825
expect(html).toContain("<th>Name</th>");
1926
expect(html).toContain("<th>Link</th>");

src/features/about/aboutPage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export const aboutPage = new Hono<{ Variables: AppVariables }>().get(
66
"/",
77
(c) => {
88
return c.html(
9-
<Page>
9+
<Page type="unauthenticated" currentPath={c.req.path}>
1010
<h1>About</h1>
1111
<h2>Powered By</h2>
1212
<table>

src/features/add-todo/addToDoPage.test.tsx

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@ describe("addToDoPage", () => {
1414
});
1515
const app = new Hono<{ Variables: AppVariables }>()
1616
.use("*", createAppConfigMiddleware(appConfig))
17-
.route("/", addToDoPage);
17+
.route(pageRoutes.ADD_TODO, addToDoPage);
1818

19-
const response = await app.fetch(new Request("http://localhost/"));
19+
const response = await app.fetch(
20+
new Request(`http://localhost${pageRoutes.ADD_TODO}`),
21+
);
2022

2123
expect(response.status).toBe(302);
2224
expect(response.headers.get("Location")).toBe(pageRoutes.LOGIN);
@@ -26,13 +28,17 @@ describe("addToDoPage", () => {
2628
const appConfig = createAppConfig({
2729
JWT_SECRET: "12345678901234567890123456789012",
2830
});
29-
const token = await sign({ sub: "admin" }, appConfig.jwt.secret, "HS256");
31+
const token = await sign(
32+
{ sub: "1234", preferred_username: "admin" },
33+
appConfig.jwt.secret,
34+
"HS256",
35+
);
3036
const app = new Hono<{ Variables: AppVariables }>()
3137
.use("*", createAppConfigMiddleware(appConfig))
32-
.route("/", addToDoPage);
38+
.route(pageRoutes.ADD_TODO, addToDoPage);
3339

3440
const response = await app.fetch(
35-
new Request("http://localhost/", {
41+
new Request(`http://localhost${pageRoutes.ADD_TODO}`, {
3642
headers: {
3743
Cookie: `${appConfig.jwt.cookieName}=${token}`,
3844
},
@@ -41,6 +47,12 @@ describe("addToDoPage", () => {
4147
const html = await response.text();
4248

4349
expect(response.status).toBe(200);
44-
expect(html).toContain("Add ToDo");
50+
expect(html).toContain("<title>ToDo</title>");
51+
expect(html).toContain("<h1>Add ToDo</h1>");
52+
expect(html).toContain('<a href="/">Home</a>');
53+
expect(html).not.toContain('<a href="/add-todo">Add</a>');
54+
expect(html).toContain('<a href="/about">About</a>');
55+
expect(html).toContain("<li>admin</li>");
56+
expect(html).toContain('<form action="/api/logout" method="post">');
4557
});
4658
});

src/features/add-todo/addToDoPage.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
import type { AppVariables } from "@shared/appVariables.ts";
22
import { Page } from "@shared/page.tsx";
33
import { pageJwtMiddleware } from "@shared/pageJwtMiddleware.ts";
4+
import { getUsernameFromJwtPayload } from "@shared/utility.ts";
45
import { Hono } from "hono";
56

67
export const addToDoPage = new Hono<{ Variables: AppVariables }>()
78
.use("/", pageJwtMiddleware)
89
.get("/", (c) => {
10+
const username = getUsernameFromJwtPayload(c.var.jwtPayload);
11+
912
return c.html(
10-
<Page>
13+
<Page type="authenticated" currentPath={c.req.path} username={username}>
1114
<h1>Add ToDo</h1>
1215
</Page>,
1316
);

src/features/home/homePage.test.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,11 @@ describe("homePage", () => {
2626
const appConfig = createAppConfig({
2727
JWT_SECRET: "12345678901234567890123456789012",
2828
});
29-
const token = await sign({ sub: "admin" }, appConfig.jwt.secret, "HS256");
29+
const token = await sign(
30+
{ sub: "1234", preferred_username: "admin" },
31+
appConfig.jwt.secret,
32+
"HS256",
33+
);
3034
const app = new Hono<{ Variables: AppVariables }>()
3135
.use("*", createAppConfigMiddleware(appConfig))
3236
.route("/", homePage);
@@ -43,6 +47,10 @@ describe("homePage", () => {
4347
expect(response.status).toBe(200);
4448
expect(html).toContain("<title>ToDo</title>");
4549
expect(html).toContain("<h1>Home</h1>");
50+
expect(html).not.toContain('<a href="/">Home</a>');
51+
expect(html).toContain('<a href="/add-todo">Add</a>');
4652
expect(html).toContain('<a href="/about">About</a>');
53+
expect(html).toContain("<li>admin</li>");
54+
expect(html).toContain('<form action="/api/logout" method="post">');
4755
});
4856
});

src/features/home/homePage.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import type { AppVariables } from "@shared/appVariables.ts";
22
import { Page } from "@shared/page.tsx";
33
import { pageJwtMiddleware } from "@shared/pageJwtMiddleware.ts";
4+
import { getUsernameFromJwtPayload } from "@shared/utility.ts";
45
import { Hono } from "hono";
56

67
export const homePage = new Hono<{ Variables: AppVariables }>()
78
.use("/", pageJwtMiddleware)
89
.get("/", (c) => {
10+
const username = getUsernameFromJwtPayload(c.var.jwtPayload);
911
return c.html(
10-
<Page>
12+
<Page type="authenticated" currentPath={c.req.path} username={username}>
1113
<h1>Home</h1>
1214
</Page>,
1315
);

src/features/login/loginPage.test.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
11
import { describe, expect, test } from "bun:test";
22
import { apiRoutes } from "@shared/apiRoutes.ts";
33
import type { AppVariables } from "@shared/appVariables.ts";
4+
import { pageRoutes } from "@shared/pageRoutes.ts";
45
import { Hono } from "hono";
56
import { loginPage } from "./loginPage.tsx";
67

78
describe("loginPage", () => {
89
test("renders the login page HTML", async () => {
9-
const app = new Hono<{ Variables: AppVariables }>().route("/", loginPage);
10+
const app = new Hono<{ Variables: AppVariables }>().route(
11+
pageRoutes.LOGIN,
12+
loginPage,
13+
);
1014

11-
const response = await app.fetch(new Request("http://localhost/"));
15+
const response = await app.fetch(
16+
new Request(`http://localhost${pageRoutes.LOGIN}`),
17+
);
1218
const html = await response.text();
1319

1420
expect(response.status).toBe(200);
@@ -25,6 +31,8 @@ describe("loginPage", () => {
2531
);
2632
expect(html).toContain('autocomplete="current-password"');
2733
expect(html).toContain('<button type="submit">Login</button>');
34+
expect(html).not.toContain('<a href="/login">Login</a>');
35+
expect(html).toContain('<a href="/about">About</a>');
2836
expect(html).toContain(apiRoutes.LOGIN);
2937
expect(html).toContain("contentType: &#39;form&#39;");
3038
});

src/features/login/loginPage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export const loginPage = new Hono<{ Variables: AppVariables }>().get(
77
"/",
88
(c) => {
99
return c.html(
10-
<Page>
10+
<Page type="unauthenticated" currentPath={c.req.path}>
1111
<h1>Login</h1>
1212
<form
1313
id="login"

src/shared/appVariables.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { JwtVariables } from "hono/jwt";
12
import type { Logger } from "winston";
23
import type { AppConfig } from "./appConfig.ts";
34
import type { Clock } from "./clock.ts";
@@ -6,4 +7,4 @@ export type AppVariables = {
67
clock: Clock;
78
appConfig: AppConfig;
89
logger: Logger;
9-
};
10+
} & JwtVariables;

src/shared/navigationBar.test.tsx

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { describe, expect, test } from "bun:test";
2+
import { NavigationBar } from "./navigationBar.tsx";
3+
4+
describe("NavigationBar", () => {
5+
test("renders authenticated navigation links, username, and logout action", () => {
6+
const html = NavigationBar({
7+
type: "authenticated",
8+
currentPath: "/add-todo",
9+
username: "test-user",
10+
});
11+
const htmlString = String(html);
12+
13+
expect(htmlString).toContain("<nav>");
14+
expect(htmlString).toContain('<a href="/">Home</a>');
15+
expect(htmlString).not.toContain('<a href="/add-todo">Add</a>');
16+
expect(htmlString).toContain('<a href="/about">About</a>');
17+
expect(htmlString).toContain("<li>test-user</li>");
18+
expect(htmlString).toContain('<form action="/api/logout" method="post">');
19+
expect(htmlString).toContain(
20+
'<button class="nav-link-button" type="submit">',
21+
);
22+
});
23+
24+
test("hides the authenticated current route link", () => {
25+
const html = NavigationBar({
26+
type: "authenticated",
27+
currentPath: "/",
28+
username: "test-user",
29+
});
30+
const htmlString = String(html);
31+
32+
expect(htmlString).not.toContain('<a href="/">Home</a>');
33+
expect(htmlString).toContain('<a href="/add-todo">Add</a>');
34+
expect(htmlString).toContain('<a href="/about">About</a>');
35+
expect(htmlString).toContain("<li>test-user</li>");
36+
expect(htmlString).toContain('<form action="/api/logout" method="post">');
37+
});
38+
39+
test("renders unauthenticated links", () => {
40+
const html = NavigationBar({ type: "unauthenticated", currentPath: "/" });
41+
const htmlString = String(html);
42+
43+
expect(htmlString).toContain('<a href="/login">Login</a>');
44+
expect(htmlString).toContain('<a href="/about">About</a>');
45+
expect(htmlString).not.toContain("Logout");
46+
});
47+
48+
test("hides the unauthenticated current route link", () => {
49+
const html = NavigationBar({
50+
type: "unauthenticated",
51+
currentPath: "/login",
52+
});
53+
const htmlString = String(html);
54+
55+
expect(htmlString).not.toContain('<a href="/login">Login</a>');
56+
expect(htmlString).toContain('<a href="/about">About</a>');
57+
});
58+
});

0 commit comments

Comments
 (0)