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 && (