learn and share notes on atproto (wip) 🦉
malfestio.stormlightlabs.org/
readability
solid
axum
atproto
srs
1import type { ReviewCard } from "$lib/model";
2import { cleanup, fireEvent, render, screen } from "@solidjs/testing-library";
3import { afterEach, describe, expect, it, vi } from "vitest";
4import { StudySession } from "../StudySession";
5
6vi.mock(
7 "$lib/api",
8 () => ({ api: { submitReview: vi.fn().mockResolvedValue({ ok: true, json: () => Promise.resolve({}) }) } }),
9);
10
11const mockCards: ReviewCard[] = [{
12 review_id: "review-1",
13 card_id: "card-1",
14 deck_id: "deck-1",
15 deck_title: "Test Deck",
16 front: "What is 2+2?",
17 back: "4",
18 media_url: undefined,
19 hints: [],
20 due_at: new Date().toISOString(),
21}, {
22 review_id: "review-2",
23 card_id: "card-2",
24 deck_id: "deck-1",
25 deck_title: "Test Deck",
26 front: "What is the capital of France?",
27 back: "Paris",
28 media_url: undefined,
29 hints: ["It's a famous city"],
30 due_at: new Date().toISOString(),
31}];
32
33describe("StudySession", () => {
34 afterEach(cleanup);
35
36 it("renders card front initially", () => {
37 const onComplete = vi.fn();
38 const onExit = vi.fn();
39
40 render(() => <StudySession cards={mockCards} onComplete={onComplete} onExit={onExit} />);
41
42 expect(screen.getByText("What is 2+2?")).toBeInTheDocument();
43 expect(screen.getByText("Press Space or click to reveal")).toBeInTheDocument();
44 });
45
46 it("shows progress indicator", () => {
47 const onComplete = vi.fn();
48 const onExit = vi.fn();
49
50 render(() => <StudySession cards={mockCards} onComplete={onComplete} onExit={onExit} />);
51
52 expect(screen.getByText(/Card 1 of 2/i)).toBeInTheDocument();
53 });
54
55 it("flips card on click", async () => {
56 const onComplete = vi.fn();
57 const onExit = vi.fn();
58
59 render(() => <StudySession cards={mockCards} onComplete={onComplete} onExit={onExit} />);
60
61 expect(screen.getByText("What is 2+2?")).toBeInTheDocument();
62
63 const cardElement = screen.getByText("What is 2+2?").closest("div[class*='cursor-pointer']");
64 if (cardElement) {
65 fireEvent.click(cardElement);
66 }
67
68 expect(await screen.findByText("How well did you know this?")).toBeInTheDocument();
69 });
70
71 it("flips back to front on second click", async () => {
72 const onComplete = vi.fn();
73 const onExit = vi.fn();
74
75 render(() => <StudySession cards={mockCards} onComplete={onComplete} onExit={onExit} />);
76
77 const cardElement = screen.getByText("What is 2+2?").closest("div[class*='cursor-pointer']");
78 if (cardElement) fireEvent.click(cardElement);
79
80 expect(await screen.findByText("How well did you know this?")).toBeInTheDocument();
81
82 if (cardElement) fireEvent.click(cardElement);
83 expect(await screen.findByText("Press Space or click to reveal")).toBeInTheDocument();
84 expect(screen.queryByText("How well did you know this?")).not.toBeInTheDocument();
85 });
86
87 it("shows keyboard hints conditionally", async () => {
88 const onComplete = vi.fn();
89 const onExit = vi.fn();
90
91 render(() => <StudySession cards={mockCards} onComplete={onComplete} onExit={onExit} />);
92
93 expect(screen.getByText("Space: Flip")).toBeInTheDocument();
94 expect(screen.getByText("Esc: Exit")).toBeInTheDocument();
95
96 // Initially hidden
97 expect(screen.queryByText("1-6: Grade")).not.toBeInTheDocument();
98 expect(screen.queryByText("E: Edit")).not.toBeInTheDocument();
99
100 // Flip card
101 const cardElement = screen.getByText("What is 2+2?").closest("div[class*='cursor-pointer']");
102 if (cardElement) fireEvent.click(cardElement);
103
104 // Now visible
105 expect(await screen.findByText("1-6: Grade")).toBeInTheDocument();
106 expect(screen.getByText("E: Edit")).toBeInTheDocument();
107 });
108
109 it("calls onExit when exit button is clicked", () => {
110 const onComplete = vi.fn();
111 const onExit = vi.fn();
112
113 render(() => <StudySession cards={mockCards} onComplete={onComplete} onExit={onExit} />);
114
115 const exitButton = screen.getByRole("button", { name: /✕ Exit/i });
116 fireEvent.click(exitButton);
117
118 expect(onExit).toHaveBeenCalled();
119 });
120});