diff --git a/src/App.test.tsx b/src/App.test.tsx index a613eef..9821139 100644 --- a/src/App.test.tsx +++ b/src/App.test.tsx @@ -1,6 +1,7 @@ import { beforeEach, describe, expect, it } from "vitest"; import { http, HttpResponse, delay } from "msw"; import { fireEvent, screen, waitFor } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; import App from "./App"; import { withProviders } from "./test/utils"; import { server } from "./test/server"; @@ -47,6 +48,50 @@ describe("App", () => { expect(await screen.findByText(/No books found/)).toBeInTheDocument(); }); + it("navigates to the next and previous book with the controls", async () => { + const user = userEvent.setup(); + withProviders(); + await screen.findByRole("heading", { name: "The Hobbit" }); + + await user.click(screen.getByRole("button", { name: "Show next book" })); + expect( + await screen.findByRole("heading", { name: "A Wizard of Earthsea" }), + ).toBeInTheDocument(); + expect(screen.getByText("2 / 3")).toBeInTheDocument(); + + await user.click(screen.getByRole("button", { name: "Show previous book" })); + expect(await screen.findByRole("heading", { name: "The Hobbit" })).toBeInTheDocument(); + expect(screen.getByText("1 / 3")).toBeInTheDocument(); + }); + + it("jumps directly to a book from the list", async () => { + const user = userEvent.setup(); + withProviders(); + await screen.findByRole("heading", { name: "The Hobbit" }); + + await user.selectOptions(screen.getByLabelText("Jump to book"), "2"); + expect( + await screen.findByRole("heading", { name: "The Left Hand of Darkness" }), + ).toBeInTheDocument(); + expect(screen.getByText("3 / 3")).toBeInTheDocument(); + }); + + it("does not refetch while navigating between books", async () => { + resetSearchRequestCount(); + const user = userEvent.setup(); + withProviders(); + await screen.findByRole("heading", { name: "The Hobbit" }); + + await user.click(screen.getByRole("button", { name: "Show next book" })); + await screen.findByRole("heading", { name: "A Wizard of Earthsea" }); + await user.click(screen.getByRole("button", { name: "Show previous book" })); + await screen.findByRole("heading", { name: "The Hobbit" }); + await user.selectOptions(screen.getByLabelText("Jump to book"), "2"); + await screen.findByRole("heading", { name: "The Left Hand of Darkness" }); + + expect(getSearchRequestCount()).toBe(1); + }); + it("fetches once more when the subject changes", async () => { withProviders(); await screen.findByRole("heading", { name: "The Hobbit" }); diff --git a/src/App.tsx b/src/App.tsx index f566c60..a7c2db0 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,7 @@ +import { useState } from "react"; import { BookCard } from "./components/BookCard"; +import { BookJumpList } from "./components/BookJumpList"; +import { NavigationControls } from "./components/NavigationControls"; import { SubjectFilter } from "./components/SubjectFilter"; import { useBooks } from "./hooks/useBooks"; import { usePreferences } from "./hooks/usePreferences"; @@ -7,7 +10,10 @@ import "./App.css"; function App() { const { subject, setSubject } = usePreferences(); const { books, isLoading, isError } = useBooks(subject); - const current = books[0]; + + const [index, setIndex] = useState(0); + const safeIndex = books.length > 0 ? Math.min(index, books.length - 1) : 0; + const current = books[safeIndex]; return (
@@ -25,6 +31,17 @@ function App() { {current && (
+ setIndex(Math.max(0, safeIndex - 1))} + onNext={() => setIndex(Math.min(books.length - 1, safeIndex + 1))} + /> + book.title)} + currentIndex={safeIndex} + onSelect={setIndex} + />
)} diff --git a/src/__snapshots__/App.test.tsx.snap b/src/__snapshots__/App.test.tsx.snap index 4c98b2e..10bad78 100644 --- a/src/__snapshots__/App.test.tsx.snap +++ b/src/__snapshots__/App.test.tsx.snap @@ -101,6 +101,57 @@ exports[`App > matches the snapshot 1`] = `
+ +