feat: auth by default, theme toggle, CoderPad startup, review fixes
- Require a session on every route; public routes opt out with @allow_anonymous - Split password hashing and pepper loading into passwords.py - Add a system/light/dark theme toggle backed by light-dark() colors - Ignore stale 401s from an earlier session, PATCH only changed book fields, and block overlapping journal-entry saves - Add bin/start and CoderPad Vite server settings for the pad's start/restart - Rewrite README as a mise onboarding guide; expand .gitignore - Include review-round fixes and tests
This commit is contained in:
@@ -79,13 +79,7 @@ describe("BookForm", () => {
|
||||
expect(calls).toContainEqual({
|
||||
method: "PATCH",
|
||||
path: "/api/books/7",
|
||||
body: {
|
||||
title: "Dune Messiah",
|
||||
author: "Frank Herbert",
|
||||
genre_id: 5,
|
||||
total_pages: 380,
|
||||
current_page: 142,
|
||||
},
|
||||
body: { title: "Dune Messiah" },
|
||||
});
|
||||
});
|
||||
|
||||
@@ -104,4 +98,41 @@ describe("BookForm", () => {
|
||||
await user.click(screen.getByRole("button", { name: "Retry" }));
|
||||
expect(await screen.findByLabelText("Title")).toHaveValue("Dune");
|
||||
});
|
||||
|
||||
it("saving without changes returns to the book without a request", async () => {
|
||||
const calls = signedIn({
|
||||
"GET /api/books/7": () => [200, dune],
|
||||
"GET /api/books/7/notes": () => [200, []],
|
||||
});
|
||||
const user = renderApp("/books/7/edit");
|
||||
await screen.findByDisplayValue("Dune");
|
||||
await user.click(screen.getByRole("button", { name: "Save changes" }));
|
||||
|
||||
await screen.findByRole("heading", { name: "Dune", level: 1 });
|
||||
expect(calls.some((call) => call.method === "PATCH")).toBe(false);
|
||||
});
|
||||
|
||||
it("preserves progress saved in another session when only the title changes", async () => {
|
||||
let saved = { ...dune };
|
||||
signedIn({
|
||||
"GET /api/books/7": () => [200, { ...saved }],
|
||||
"GET /api/books/7/notes": () => [200, []],
|
||||
"PATCH /api/books/7": ({ body }) => {
|
||||
saved = { ...saved, ...(body as object) };
|
||||
return [200, saved];
|
||||
},
|
||||
});
|
||||
const user = renderApp("/books/7/edit");
|
||||
const title = await screen.findByDisplayValue("Dune");
|
||||
await screen.findByRole("option", { name: "Science Fiction" });
|
||||
|
||||
// Another session saves progress after this form loads its snapshot.
|
||||
saved = { ...saved, current_page: 300 };
|
||||
await user.clear(title);
|
||||
await user.type(title, "Dune Messiah");
|
||||
await user.click(screen.getByRole("button", { name: "Save changes" }));
|
||||
|
||||
await screen.findByRole("heading", { name: "Dune Messiah", level: 1 });
|
||||
expect(screen.getByRole("progressbar")).toHaveAttribute("value", "300");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user