From 0e5b92a6fa3edb8b53deeebe5bcedd2de3683ff5 Mon Sep 17 00:00:00 2001 From: Rachel Stiansen Date: Mon, 7 Sep 2026 11:31:17 +0200 Subject: [PATCH 1/2] feat: add BookJumpList component and tests (#14) --- src/components/BookJumpList.test.tsx | 55 +++++++++++++++++++ src/components/BookJumpList.tsx | 24 ++++++++ .../__snapshots__/BookJumpList.test.tsx.snap | 30 ++++++++++ 3 files changed, 109 insertions(+) create mode 100644 src/components/BookJumpList.test.tsx create mode 100644 src/components/BookJumpList.tsx create mode 100644 src/components/__snapshots__/BookJumpList.test.tsx.snap diff --git a/src/components/BookJumpList.test.tsx b/src/components/BookJumpList.test.tsx new file mode 100644 index 0000000..02bbb3d --- /dev/null +++ b/src/components/BookJumpList.test.tsx @@ -0,0 +1,55 @@ +import { fireEvent, render, screen } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; +import { BookJumpList } from "./BookJumpList"; + +// Inline fixture data keeps the tests independent of the API and network. +const titles = ["The Hobbit", "Dune", "Pride and Prejudice"]; + +describe("BookJumpList", () => { + it('renders a native select associated with the label "Jump to book"', () => { + // Render the component with controlled test props. + render(); + + // Find the select by its accessible role and label. + const select = screen.getByRole("combobox", { name: "Jump to book" }); + + // Verify that the accessible control is a native HTML select element. + expect(select.tagName).toBe("SELECT"); + }); + + it("renders one option per title", () => { + render(); + + // Verify that every fixture title is rendered as an option in the same order. + expect(screen.getAllByRole("option")).toHaveLength(titles.length); + expect(screen.getAllByRole("option").map((option) => option.textContent)).toEqual(titles); + }); + + it("selects the option at currentIndex", () => { + render(); + + // currentIndex 1 should select the second title, "Dune". + expect(screen.getByRole("option", { name: "Dune" })).toHaveProperty("selected", true); + }); + + it("calls onSelect with the selected zero-based index", () => { + // Create a mock function so we can inspect how onSelect is called. + const onSelect = vi.fn(); + render(); + + // Simulate the user selecting the option whose value is index 2. + fireEvent.change(screen.getByRole("combobox", { name: "Jump to book" }), { + target: { value: "2" }, + }); + + // Verify that the component reports the selected index to its parent. + expect(onSelect).toHaveBeenCalledWith(2); + }); + + it("matches the rendered snapshot", () => { + const { container } = render(); + + // Detect unexpected changes to the component's rendered HTML structure. + expect(container).toMatchSnapshot(); + }); +}); diff --git a/src/components/BookJumpList.tsx b/src/components/BookJumpList.tsx new file mode 100644 index 0000000..66d20e5 --- /dev/null +++ b/src/components/BookJumpList.tsx @@ -0,0 +1,24 @@ +interface BookJumpListProps { + titles: string[]; // Titles available for direct navigation. + currentIndex: number; // Zero-based index of the currently selected book. + onSelect: (index: number) => void; // Notifies the parent when the user selects another book. +} + +export function BookJumpList({ titles, currentIndex, onSelect }: BookJumpListProps) { + return ( + <> + + + + ); +} diff --git a/src/components/__snapshots__/BookJumpList.test.tsx.snap b/src/components/__snapshots__/BookJumpList.test.tsx.snap new file mode 100644 index 0000000..2e4d329 --- /dev/null +++ b/src/components/__snapshots__/BookJumpList.test.tsx.snap @@ -0,0 +1,30 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`BookJumpList > matches the rendered snapshot 1`] = ` +
+ + +
+`; From e8ef4f2452f9cf786da2b8ee845afb2f6ddff98d Mon Sep 17 00:00:00 2001 From: Rachel Stiansen Date: Mon, 7 Sep 2026 15:56:26 +0200 Subject: [PATCH 2/2] style: add responsive BookJumpList styling (#14) --- src/components/BookJumpList.css | 26 +++++++++++++ src/components/BookJumpList.test.tsx | 6 ++- src/components/BookJumpList.tsx | 28 +++++++------ .../__snapshots__/BookJumpList.test.tsx.snap | 39 ++++++++++--------- 4 files changed, 66 insertions(+), 33 deletions(-) create mode 100644 src/components/BookJumpList.css diff --git a/src/components/BookJumpList.css b/src/components/BookJumpList.css new file mode 100644 index 0000000..0e54246 --- /dev/null +++ b/src/components/BookJumpList.css @@ -0,0 +1,26 @@ +.book-jump-list { + display: flex; + flex-direction: column; + gap: 0.5rem; + width: 100%; + max-width: 24rem; + color: #243b53; + font-weight: 600; +} + +.book-jump-list select { + width: 100%; + min-height: 2.75rem; + padding: 0.625rem 0.75rem; + border: 1px solid #d5dde5; + border-radius: 0.5rem; + background-color: #ffffff; + color: #1f2933; + font: inherit; + font-weight: 400; +} + +.book-jump-list select:focus-visible { + outline: 0.1875rem solid #2c3e50; + outline-offset: 0.125rem; +} diff --git a/src/components/BookJumpList.test.tsx b/src/components/BookJumpList.test.tsx index 02bbb3d..0128c75 100644 --- a/src/components/BookJumpList.test.tsx +++ b/src/components/BookJumpList.test.tsx @@ -42,12 +42,14 @@ describe("BookJumpList", () => { target: { value: "2" }, }); - // Verify that the component reports the selected index to its parent. + // Verify that the component reports the selected index to its parent. expect(onSelect).toHaveBeenCalledWith(2); }); it("matches the rendered snapshot", () => { - const { container } = render(); + const { container } = render( + , + ); // Detect unexpected changes to the component's rendered HTML structure. expect(container).toMatchSnapshot(); diff --git a/src/components/BookJumpList.tsx b/src/components/BookJumpList.tsx index 66d20e5..b923a2c 100644 --- a/src/components/BookJumpList.tsx +++ b/src/components/BookJumpList.tsx @@ -1,3 +1,5 @@ +import "./BookJumpList.css"; + interface BookJumpListProps { titles: string[]; // Titles available for direct navigation. currentIndex: number; // Zero-based index of the currently selected book. @@ -7,18 +9,20 @@ interface BookJumpListProps { export function BookJumpList({ titles, currentIndex, onSelect }: BookJumpListProps) { return ( <> - - + ); } diff --git a/src/components/__snapshots__/BookJumpList.test.tsx.snap b/src/components/__snapshots__/BookJumpList.test.tsx.snap index 2e4d329..e6b93a5 100644 --- a/src/components/__snapshots__/BookJumpList.test.tsx.snap +++ b/src/components/__snapshots__/BookJumpList.test.tsx.snap @@ -3,28 +3,29 @@ exports[`BookJumpList > matches the rendered snapshot 1`] = `
- + + + + +
`;