From 65c0adfd769b9ef11b897c96a3634c61120055b8 Mon Sep 17 00:00:00 2001 From: nsfisis Date: Mon, 8 Dec 2025 00:18:03 +0900 Subject: feat(client): redesign frontend with TailwindCSS v4 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace inline styles with TailwindCSS, implementing a cohesive Japanese-inspired design system with custom colors (cream, teal primary), typography (Fraunces, DM Sans), and animations. Update all pages and components with consistent styling, improve accessibility by adding aria-hidden to decorative SVGs, and configure Biome for Tailwind CSS syntax support. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- src/client/pages/HomePage.test.tsx | 65 ++++++++++++++++++++++---------------- 1 file changed, 37 insertions(+), 28 deletions(-) (limited to 'src/client/pages/HomePage.test.tsx') diff --git a/src/client/pages/HomePage.test.tsx b/src/client/pages/HomePage.test.tsx index 18c2e76..5b8489a 100644 --- a/src/client/pages/HomePage.test.tsx +++ b/src/client/pages/HomePage.test.tsx @@ -137,7 +137,8 @@ describe("HomePage", () => { renderWithProviders(); - expect(screen.getByText("Loading decks...")).toBeDefined(); + // Loading state shows spinner (svg with animate-spin class) + expect(document.querySelector(".animate-spin")).toBeDefined(); }); it("displays empty state when no decks exist", async () => { @@ -151,10 +152,10 @@ describe("HomePage", () => { renderWithProviders(); await waitFor(() => { - expect(screen.getByText("You don't have any decks yet.")).toBeDefined(); + expect(screen.getByText("No decks yet")).toBeDefined(); }); expect( - screen.getByText("Create your first deck to start learning!"), + screen.getByText("Create your first deck to start learning"), ).toBeDefined(); }); @@ -255,7 +256,7 @@ describe("HomePage", () => { renderWithProviders(); await waitFor(() => { - expect(screen.queryByText("Loading decks...")).toBeNull(); + expect(screen.getByText("No decks yet")).toBeDefined(); }); await user.click(screen.getByRole("button", { name: "Logout" })); @@ -290,11 +291,11 @@ describe("HomePage", () => { ).toBeDefined(); }); - // The deck item should only contain the heading, no description paragraph - const deckItem = screen + // The deck card should only contain the heading, no description paragraph + const deckCard = screen .getByRole("heading", { name: "No Description Deck" }) - .closest("li"); - expect(deckItem?.querySelectorAll("p").length).toBe(0); + .closest("div[class*='bg-white']"); + expect(deckCard?.querySelectorAll("p").length).toBe(0); }); it("passes auth header when fetching decks", async () => { @@ -315,7 +316,7 @@ describe("HomePage", () => { }); describe("Create Deck", () => { - it("shows Create Deck button", async () => { + it("shows New Deck button", async () => { vi.mocked(apiClient.rpc.api.decks.$get).mockResolvedValue( mockResponse({ ok: true, @@ -326,13 +327,13 @@ describe("HomePage", () => { renderWithProviders(); await waitFor(() => { - expect(screen.queryByText("Loading decks...")).toBeNull(); + expect(screen.getByText("No decks yet")).toBeDefined(); }); - expect(screen.getByRole("button", { name: "Create Deck" })).toBeDefined(); + expect(screen.getByRole("button", { name: /New Deck/i })).toBeDefined(); }); - it("opens modal when Create Deck button is clicked", async () => { + it("opens modal when New Deck button is clicked", async () => { const user = userEvent.setup(); vi.mocked(apiClient.rpc.api.decks.$get).mockResolvedValue( mockResponse({ @@ -344,10 +345,10 @@ describe("HomePage", () => { renderWithProviders(); await waitFor(() => { - expect(screen.queryByText("Loading decks...")).toBeNull(); + expect(screen.getByText("No decks yet")).toBeDefined(); }); - await user.click(screen.getByRole("button", { name: "Create Deck" })); + await user.click(screen.getByRole("button", { name: /New Deck/i })); expect(screen.getByRole("dialog")).toBeDefined(); expect( @@ -367,10 +368,10 @@ describe("HomePage", () => { renderWithProviders(); await waitFor(() => { - expect(screen.queryByText("Loading decks...")).toBeNull(); + expect(screen.getByText("No decks yet")).toBeDefined(); }); - await user.click(screen.getByRole("button", { name: "Create Deck" })); + await user.click(screen.getByRole("button", { name: /New Deck/i })); expect(screen.getByRole("dialog")).toBeDefined(); await user.click(screen.getByRole("button", { name: "Cancel" })); @@ -413,11 +414,11 @@ describe("HomePage", () => { renderWithProviders(); await waitFor(() => { - expect(screen.queryByText("Loading decks...")).toBeNull(); + expect(screen.getByText("No decks yet")).toBeDefined(); }); // Open modal - await user.click(screen.getByRole("button", { name: "Create Deck" })); + await user.click(screen.getByRole("button", { name: /New Deck/i })); // Fill in form await user.type(screen.getByLabelText("Name"), "New Deck"); @@ -427,7 +428,7 @@ describe("HomePage", () => { ); // Submit - await user.click(screen.getByRole("button", { name: "Create" })); + await user.click(screen.getByRole("button", { name: "Create Deck" })); // Modal should close await waitFor(() => { @@ -462,7 +463,7 @@ describe("HomePage", () => { ).toBeDefined(); }); - const editButtons = screen.getAllByRole("button", { name: "Edit" }); + const editButtons = screen.getAllByRole("button", { name: "Edit deck" }); expect(editButtons.length).toBe(2); }); @@ -483,7 +484,7 @@ describe("HomePage", () => { ).toBeDefined(); }); - const editButtons = screen.getAllByRole("button", { name: "Edit" }); + const editButtons = screen.getAllByRole("button", { name: "Edit deck" }); await user.click(editButtons.at(0) as HTMLElement); expect(screen.getByRole("dialog")).toBeDefined(); @@ -511,7 +512,7 @@ describe("HomePage", () => { ).toBeDefined(); }); - const editButtons = screen.getAllByRole("button", { name: "Edit" }); + const editButtons = screen.getAllByRole("button", { name: "Edit deck" }); await user.click(editButtons.at(0) as HTMLElement); expect(screen.getByRole("dialog")).toBeDefined(); @@ -556,7 +557,7 @@ describe("HomePage", () => { }); // Click Edit on first deck - const editButtons = screen.getAllByRole("button", { name: "Edit" }); + const editButtons = screen.getAllByRole("button", { name: "Edit deck" }); await user.click(editButtons.at(0) as HTMLElement); // Update name @@ -565,7 +566,7 @@ describe("HomePage", () => { await user.type(nameInput, "Updated Japanese"); // Save - await user.click(screen.getByRole("button", { name: "Save" })); + await user.click(screen.getByRole("button", { name: "Save Changes" })); // Modal should close await waitFor(() => { @@ -601,7 +602,9 @@ describe("HomePage", () => { ).toBeDefined(); }); - const deleteButtons = screen.getAllByRole("button", { name: "Delete" }); + const deleteButtons = screen.getAllByRole("button", { + name: "Delete deck", + }); expect(deleteButtons.length).toBe(2); }); @@ -622,7 +625,9 @@ describe("HomePage", () => { ).toBeDefined(); }); - const deleteButtons = screen.getAllByRole("button", { name: "Delete" }); + const deleteButtons = screen.getAllByRole("button", { + name: "Delete deck", + }); await user.click(deleteButtons.at(0) as HTMLElement); expect(screen.getByRole("dialog")).toBeDefined(); @@ -651,7 +656,9 @@ describe("HomePage", () => { ).toBeDefined(); }); - const deleteButtons = screen.getAllByRole("button", { name: "Delete" }); + const deleteButtons = screen.getAllByRole("button", { + name: "Delete deck", + }); await user.click(deleteButtons.at(0) as HTMLElement); expect(screen.getByRole("dialog")).toBeDefined(); @@ -692,7 +699,9 @@ describe("HomePage", () => { }); // Click Delete on first deck - const deleteButtons = screen.getAllByRole("button", { name: "Delete" }); + const deleteButtons = screen.getAllByRole("button", { + name: "Delete deck", + }); await user.click(deleteButtons.at(0) as HTMLElement); // Wait for modal to appear -- cgit v1.2.3-70-g09d2