Lancer, vérifier, écrire
Un tuto se propose de deux façons — le bouton « ? » de la page et le bouton des fiches de documentation —, se vérifie par un test de bout en bout qui le suit comme un administrateur, et s’écrit en quelques étapes. Ce que le test a trouvé au premier lancement, et la recette d’un nouveau tuto.
Précédemment. Le moteur garde l’étape en cours dans l’onglet, fait passer les étapes de page en page, et ne laisse agir que la cible, à la souris comme au clavier.
Deux portes d’entrée 🚪#
Bouton | Où | Ce qu’il propose |
|---|---|---|
« ? » (TourLauncher) | En bas à gauche de toute page qui a des tutos. | Les tutos de cette page, pour ce visiteur. |
« Lancer le tuto » (TourStartButton) | Dans une fiche du guide du site ou de la documentation des administrateurs, par le composant MDX <TourStartButton id="…" />. | Un tuto précis : il conduit à sa page de départ. |
Un parcours (TourList) | En tête du guide et de la documentation des administrateurs, par <TourList title="…" ids="a,b,c" />. | Des tutos dans l’ordre où on les suit, numérotés, chacun cliquable, avec un lien vers sa fiche. |
Showing entries1to3entries
Les deux s’appelaient TourLauncher : le premier dans TourLauncher.tsx, le second dans TourStartButton.tsx, exporté sous le même nom. Deux composants différents sous un même nom, c’est une recherche dans le code qui trouve l’un en cherchant l’autre ; la relecture a donné au second le nom de son fichier.
TourLauncher.tsx — le bouton « ? »
Clique une étape pour mettre en évidence la zone correspondante dans le fichier complet.
// ⚠ Fichier introuvable : apps/web/src/features/tours/TourLauncher.tsx// Il a été déplacé ou supprimé depuis la rédaction de ce chapitre.Dans une fiche, le bouton est affiché à tous ceux qui lisent la documentation. À un visiteur qui n’a pas le rôle requis, il ne propose pas un tuto qui échouerait sur une page refusée : il est désactivé, et dit pourquoi (« Réservé aux administrateurs connectés »). Un identifiant inconnu affiche « Tuto introuvable » — ce que la garde empêche désormais d’atteindre (chapitre 1). Le droit de suivre (canFollow) et la raison d’un refus (unavailableReason) viennent du registre : le bouton « ? », celui des fiches et les parcours répondent la même chose au même visiteur.
Un parcours, numéroté#
Une fiche par tuto, c’est une fiche à trouver. L’accueil du guide et celui de la documentation des administrateurs proposent donc les tutos par parcours : « Parcours membre », « Le catalogue et le programme »… Chaque parcours est une liste numérotée, dans l’ordre où l’on suit les tutos, et chaque ligne se clique pour lancer le sien :
Le numéro est aussi dans le nom accessible du bouton (« 3. Créer un stage ») : un lecteur d’écran annonce l’ordre que l’œil voit. Un tuto hors du rôle du visiteur reste dans la liste, grisé, avec sa raison, plutôt que de disparaître — le parcours garde ses numéros. Et « La fiche » vient du champ doc du tuto, que la garde vérifie : un parcours ne renvoie jamais vers une page absente.
Un tuto se vérifie en le suivant#
Le tuto lui-même a son test de bout en bout (partie « Qualité », chapitre « Tester de bout en bout ») : un administrateur le lance depuis le bouton « ? » et le suit jusqu’au bout, comme on le ferait.
e2e/tutoriel.spec.ts
Clique une étape pour mettre en évidence la zone correspondante dans le fichier complet.
// ⚠ Fichier introuvable : e2e/tutoriel.spec.ts// Il a été déplacé ou supprimé depuis la rédaction de ce chapitre.Un second test vérifie qu’Échap interrompt le tuto. Les deux passent ; le premier n’est pas passé du premier coup.
Les tutos des visiteurs et des membres ont leur propre fichier, e2e/tutos-parcours.spec.ts : un tuto lancé depuis une fiche du guide (le bouton conduit à la page de départ), un autre qui traverse deux pages, et deux comportements qu’on ne constate qu’en marchant. D’abord, une étape facultative s’efface vraiment : un membre sans aucun document arrive à la dernière bulle sans passer par celles de la lecture.
Ce test a été vérifié comme une garde : optional retiré de l’étape « Ouvrez un document », il échoue — la bulle finale n’arrive jamais, le tuto attend une cible qui n’existera pas. Ensuite, un tuto n’envoie rien : le test force un clic sur le bouton « Envoyer » montré par la dernière étape (force: true clique au centre de l’élément, sans vérifier qu’il est accessible), et constate que le message de confirmation n’apparaît pas — le panneau posé sur la cible a reçu le clic.
Le clavier a son test aussi : Tab ne conduit jamais au bouton montré (le curseur reste dans la bulle), et Entrée dans l’adresse du mot de passe oublié n’envoie pas le lien. Ce dernier a été vérifié en cassant : la garde du submit désactivée (chapitre 2), Entrée envoie le lien, et le test échoue.
Les tutos d’administration ont le leur, e2e/tutos-admin.spec.ts, sur le même principe. Le clic forcé y vise « Publier » d’une page éditoriale (la page reste en brouillon), et « Créer » d’un utilisateur (aucun compte, aucun e-mail). Une demande est envoyée par l’API au nom d’un membre, puis l’administrateur la suit dans le tuto. Le test de la publication a été vérifié en cassant : l’étape passée de next à click, le clic forcé publie la page, et le test échoue. Les tutos qui parcourent un formulaire — discipline, cours, stage, évènement — sont suivis jusqu’à leur dernière bulle, et « Ajouter des séances » de la liste des stages jusqu’à l’édition de celui des données de test.
Ce que le test a trouvé#
À son premier lancement, le test s’est arrêté à l’étape 2 : la page de création d’une actualité ne s’affichait pas du tout (« This page couldn’t load »). Ce lancement-là tournait sur la machine de la relecture, sans Docker, où la connexion à la base passait par un module de remplacement. La console du navigateur désignait ce module — le backend de connexion à la base, exécuté… dans le navigateur.
La chaîne était la suivante. Le formulaire d’actualité est un composant client ; il importe le constructeur de pages depuis l’entrée @features/page-builder. Cette entrée exportait aussi PageRenderer, le composant serveur qui affiche une page : il lit la base par Prisma et résout les médias. Importer l’entrée, c’était importer PageRenderer, donc le backend, dans le code envoyé au navigateur de chaque formulaire d’administration.
En production, et dans les tests lancés sur un poste avec Docker, le client Prisma dispose d’une version pour navigateur : elle se construit sans erreur, et ne lève une erreur qu’à la première requête, que personne ne fait. Rien ne cassait, rien ne se voyait — mais chaque formulaire chargeait du code serveur qui n’avait rien à y faire. Le module de remplacement, lui, n’avait pas de version pour navigateur : il a rendu visible ce qui était là depuis longtemps.
// ⚠ Fichier introuvable : apps/web/src/features/page-builder/index.ts// Il a été déplacé ou supprimé depuis la rédaction de ce chapitre.PageRenderer s’importe désormais par son propre chemin, que seuls des composants serveur utilisent. Et il le dit lui-même :
// ⚠ Fichier introuvable : apps/web/src/features/page-builder/PageRenderer.tsx// Il a été déplacé ou supprimé depuis la rédaction de ce chapitre.import "server-only" ne fait rien à l’exécution. Mais si un composant client l’importe, le build échoue, avec le nom du fichier fautif. La règle « ce composant ne part jamais au navigateur » n’est plus une convention à retenir : c’est le compilateur qui la tient.
Un test de bout en bout trouve aussi ce qu’il ne cherche pas. Ce test vérifiait un tuto ; il a trouvé un composant serveur dans le code du navigateur — comme le premier lancement des tests de la partie « Qualité » avait trouvé le visionneur PDF qui faisait échouer le rendu serveur. Les deux problèmes étaient invisibles à l’écran.
Ce qu’écrire un tuto a trouvé#
Un tuto se rédige en lisant le code de chaque page qu’il traverse : la bulle doit dire ce que fait vraiment le bouton. En écrivant celui des stages, la question « où ajoute-t-on les dates ? » n’avait plus de réponse. Le composant des séances, SessionsManager, existait toujours, mais plus aucune page ne l’affichait : lors du déplacement des pages d’administration dans le groupe (admin), les éditions d’un stage et d’un évènement avaient été réécrites sans lui. Depuis, un stage ne pouvait plus recevoir de date — et sans séance à venir, il n’apparaît pas dans l’agenda.
Les deux pages d’édition ont retrouvé leur section Séances, branchée sur les procédures seminarSession.* et eventSession.*. Un test de bout en bout, e2e/seances.spec.ts, ajoute une séance au stage des données de test et vérifie qu’elle survit au rechargement : un composant qui disparaît d’une page ne se voit ni au typecheck ni au lint, seulement en suivant le parcours.
Écrire un nouveau tuto#
Étape | Ce qu’on fait | Ce qui le vérifie |
|---|---|---|
1. Choisir le parcours | Une tâche d’un public précis, qui se fait en quelques pages, et qui ne publie, n’envoie ni ne supprime rien : le tuto montre le bouton, l’utilisateur le clique. | La relecture. |
2. Poser les noms | Un data-tour sur chaque élément visé, nommé d’après la page et le rôle (« events.form.title »). | Rien encore : les noms ne servent à personne. |
3. Écrire la définition | Un fichier dans definitions/, typé Tour : public, pages, page de départ, étapes et ce qui les fait passer ; optional sur une étape dont la cible peut manquer. | TypeScript (une étape route sans match est refusée). |
4. L’enregistrer | L’importer dans tour.registry.ts. | check-tour-targets : définition enregistrée, cibles présentes. |
5. Le proposer dans une fiche | Sa fiche, avec <TourStartButton id="…" />, son chemin dans le champ doc du tuto, et sa place dans un parcours (<TourList ids="…" />). | check-tour-targets : l’identifiant existe, la fiche aussi. |
6. Le suivre | Un test de bout en bout qui le lance et le suit jusqu’au bout. | pnpm e2e. |
Showing entries1to6entries
Un tuto vieillit comme le code qu’il traverse. La garde surveille ses cibles ; le test, son déroulement. Une étape dont le texte ne correspond plus à l’écran (un bouton renommé « Enregistrer ») ne se voit qu’à la relecture : un tuto se relit quand sa page change.
Récapitulatif#
Question | Réponse du projet |
|---|---|
Comment un tuto est-il proposé ? | Par le bouton « ? » de sa page (filtré par public), ou par un bouton dans une fiche de documentation. |
Que fait le bouton d’une fiche pour un visiteur sans le rôle ? | Il est désactivé et dit pourquoi, plutôt que de lancer un tuto qui échouerait. |
Comment vérifier qu’un tuto se suit ? | Un test de bout en bout qui le lance et le suit, étape par étape, comme un utilisateur. |
Qu’a trouvé ce test ? | Le backend dans le code envoyé au navigateur des formulaires, par l’entrée du constructeur de pages. |
Comment l’éviter à l’avenir ? | import "server-only" dans les composants serveur : le build échoue si un composant client les importe. |
Comment écrire un nouveau tuto ? | Des data-tour, une définition, le registre, un bouton de fiche, un test — chaque étape a sa vérification. |
Showing entries1to6entries
Fin du parcours#
Cette étude de cas clôt la documentation du développeur. Le moteur des tutoriels a traversé presque toutes ses parties : des données typées et une union discriminée, un état hors de React lu sans écart d’hydratation, un composant serveur tenu hors du navigateur, l’accessibilité au clavier, une garde maison, un test de bout en bout. C’est la méthode de tout le projet, appliquée à une seule fonctionnalité : décrire en données, vérifier par un outil, et ne pas croire qu’une chose marche avant de l’avoir vue marcher.