diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 09e1e92..4d0da86 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,6 +1,7 @@ import { SerwistProvider } from '@serwist/turbopack/react' import type { Metadata, Viewport } from 'next' import type { ReactNode } from 'react' +import { AppHeader } from '@/components/app-header' import './globals.css' const APP_NAME = 'flashcards' @@ -55,6 +56,7 @@ export default function RootLayout({ children }: { children: ReactNode }) { + {children} diff --git a/src/app/page.test.tsx b/src/app/page.test.tsx index 05be353..33899b8 100644 --- a/src/app/page.test.tsx +++ b/src/app/page.test.tsx @@ -3,8 +3,18 @@ import { describe, expect, it } from 'vitest' import Home from './page' describe('Home', () => { - it('affiche le contenu minimal', () => { + it('affiche un titre et un lien vers le jeu', () => { render() - expect(screen.getByRole('heading', { level: 1, name: 'Hello world' })).toBeInTheDocument() + expect( + screen.getByRole('heading', { level: 1, name: /R\u00e9viser tes cartes/ }), + ).toBeInTheDocument() + const playLink = screen.getByRole('link', { name: /Jouer/ }) + expect(playLink).toHaveAttribute('href', '/play') + }) + + it('affiche un lien vers la gestion des cartes', () => { + render() + const cardsLink = screen.getByRole('link', { name: /G\u00e9rer les cartes/ }) + expect(cardsLink).toHaveAttribute('href', '/cards') }) }) diff --git a/src/app/page.tsx b/src/app/page.tsx index cd1e753..195bcff 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,7 +1,35 @@ +import Link from 'next/link' + export default function Home() { return ( -
-

Hello world

+
+

+ Réviser tes cartes, une carte à la fois. +

+

+ Tire une carte au hasard, révèle la réponse quand tu es prêt, ou choisis un thème pour + cibler ta révision. +

+
+ + Jouer + + Tirage aléatoire d'une carte, par thème ou globalement. + + + + Gérer les cartes + + Créer, modifier, consulter et supprimer tes cartes. + + +
) } diff --git a/src/app/play/page.test.tsx b/src/app/play/page.test.tsx new file mode 100644 index 0000000..76e4fcc --- /dev/null +++ b/src/app/play/page.test.tsx @@ -0,0 +1,22 @@ +import { render, screen } from '@testing-library/react' +import { describe, expect, it, vi } from 'vitest' +import PlayPage from './page' + +vi.mock('@/lib/api-client', () => ({ + ApiClientError: class extends Error { + status = 0 + }, + cardsApi: { themes: vi.fn(), random: vi.fn() }, +})) + +import { cardsApi } from '@/lib/api-client' + +describe('PlayPage', () => { + it('monte le jeu avec le sélecteur de thème et le tirage (F-01 à F-04, F-10)', async () => { + vi.mocked(cardsApi.themes).mockResolvedValue(['git']) + render() + expect(screen.getByRole('button', { name: 'Tirer une carte' })).toBeInTheDocument() + expect(screen.getByLabelText('Th\u00e8me')).toBeInTheDocument() + await screen.findByRole('option', { name: 'git' }) + }) +}) diff --git a/src/app/play/page.tsx b/src/app/play/page.tsx new file mode 100644 index 0000000..b440d6e --- /dev/null +++ b/src/app/play/page.tsx @@ -0,0 +1,14 @@ +import type { Metadata } from 'next' +import { PlayView } from '@/components/play-view' + +export const metadata: Metadata = { + title: 'Jouer', +} + +export default function PlayPage() { + return ( +
+ +
+ ) +} diff --git a/src/components/app-header.test.tsx b/src/components/app-header.test.tsx new file mode 100644 index 0000000..442fb63 --- /dev/null +++ b/src/components/app-header.test.tsx @@ -0,0 +1,15 @@ +import { render, screen } from '@testing-library/react' +import { describe, expect, it } from 'vitest' +import { AppHeader } from './app-header' + +describe('AppHeader', () => { + it('affiche la marque et les liens de navigation', () => { + render() + expect(screen.getByRole('link', { name: 'Flashcards' })).toHaveAttribute('href', '/') + expect(screen.getByRole('link', { name: 'Jouer' })).toHaveAttribute('href', '/play') + expect(screen.getByRole('link', { name: 'G\u00e9rer les cartes' })).toHaveAttribute( + 'href', + '/cards', + ) + }) +}) diff --git a/src/components/app-header.tsx b/src/components/app-header.tsx new file mode 100644 index 0000000..784461a --- /dev/null +++ b/src/components/app-header.tsx @@ -0,0 +1,25 @@ +import Link from 'next/link' + +export function AppHeader() { + return ( +
+ +
+ ) +} diff --git a/src/components/play-view.test.tsx b/src/components/play-view.test.tsx index 87f3708..461b453 100644 --- a/src/components/play-view.test.tsx +++ b/src/components/play-view.test.tsx @@ -1,6 +1,6 @@ import { render, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import { beforeEach, describe, expect, it, vi } from 'vitest' +import { beforeEach, expect, it, vi } from 'vitest' import { PlayView } from './play-view' vi.mock('@/lib/api-client', () => { @@ -42,11 +42,15 @@ it('tire une carte au hasard globalement (F-01) puis masque la réponse (F-02, F await userEvent.click(await screen.findByRole('button', { name: 'Tirer une carte' })) expect(await screen.findByText('Cest quoi un rebase ?')).toBeInTheDocument() expect(cardsApi.random).toHaveBeenCalledWith(undefined) - expect(screen.queryByText(/R\u00e9appliquer des commits sur une autre base/)).not.toBeInTheDocument() + expect( + screen.queryByText(/R\u00e9appliquer des commits sur une autre base/), + ).not.toBeInTheDocument() await userEvent.click(screen.getByRole('button', { name: 'Voir la réponse' })) expect(screen.getByText(/R\u00e9appliquer des commits sur une autre base/)).toBeInTheDocument() await userEvent.click(screen.getByRole('button', { name: 'Masquer la réponse' })) - expect(screen.queryByText(/R\u00e9appliquer des commits sur une autre base/)).not.toBeInTheDocument() + expect( + screen.queryByText(/R\u00e9appliquer des commits sur une autre base/), + ).not.toBeInTheDocument() }) it('tire uniquement dans le thème choisi (F-04) et gère un tirage vide (F-10 erreur)', async () => { @@ -59,7 +63,10 @@ it('tire uniquement dans le thème choisi (F-04) et gère un tirage vide (F-10 e }) it('affiche un message si le thème ne contient aucune carte', async () => { - vi.mocked(cardsApi.random).mockRejectedValue(new ApiClientError(404, 'NotFoundError', 'Aucune carte')) + vi.mocked(cardsApi.themes).mockResolvedValue(['git', 'javascript', 'vide']) + vi.mocked(cardsApi.random).mockRejectedValue( + new ApiClientError(404, 'NotFoundError', 'Aucune carte'), + ) render() await userEvent.selectOptions(await screen.findByLabelText('Thème'), 'vide') await userEvent.click(screen.getByRole('button', { name: 'Tirer une carte' })) diff --git a/src/components/play-view.tsx b/src/components/play-view.tsx index f5fb264..fc52c6e 100644 --- a/src/components/play-view.tsx +++ b/src/components/play-view.tsx @@ -2,7 +2,7 @@ import { useCallback, useEffect, useState } from 'react' import { ApiClientError, cardsApi } from '@/lib/api-client' -import type { Card, CardInput } from '@/domain/card' +import type { Card } from '@/domain/card' const EMPTY_THEME = '' @@ -40,8 +40,7 @@ export function PlayView() { if (e instanceof ApiClientError) { setError(e.message) setCard(null) - } - else { + } else { setError('Impossible de tirer une carte') setCard(null) } @@ -82,7 +81,9 @@ export function PlayView() { {error && ( -

{error}

+

+ {error} +

)} {card && (