freedzhost.cloud

JavaScript module 4 : tester, structurer, livrer

Niveau DébutantTemps estimé 17 hÀ lire avant JavaScript module 3 : l'asynchrone et Node.jsUn mot inconnu ? Lexique

Dix-sept séances pour transformer un savoir en savoir-faire : organiser le code en modules, le tester avec le lanceur de tests intégré à Node, le déboguer méthodiquement, puis tout assembler dans le projet final — un outil en ligne de commande qui interroge l'API météo Open-Meteo, résume la réponse, et arrive testé et documenté. Méthode : 20 minutes de théorie, 30 de pratique, 10 de synthèse.

Jour 43 — Les modules ESM : export et import

Objectif : découper un programme en fichiers qui s'importent, avec la syntaxe moderne.

Théorie (20 min)

Un fichier par sujet, et des portes explicites entre eux : c'est le système de modules. La version moderne s'appelle ESM (ECMAScript Modules) : export ouvre une porte, import passe par la porte. Elle fonctionne dans un fichier .mjs, ou en .js avec "type": "module" dans le package.json (jour 38).

// maths.mjs
export function double(n) {
  return n * 2;
}
export const PI = 3.14159;
export default function triple(n) {   // un seul default par fichier
  return n * 3;
}

// app.mjs
import triple, { double, PI } from "./maths.mjs";
console.log(double(21), triple(7));

Trois règles pratiques. (1) L'import nommé reprend le nom exact de l'export, entre accolades ; l'export default est importé sous le nom que VOUS choisissez. (2) Le chemin est relatif au fichier qui importe, et l'extension est obligatoire en Node (./maths.mjs, pas ./maths). (3) L'import est statique : il se fait en haut du fichier, une fois ; le module importé n'est exécuté qu'une seule fois même si dix fichiers l'importent.

Pourquoi c'est une frontière et pas un confort : un module cache tout ce qu'il n'exporte pas. Le reste du programme ne peut dépendre que des portes — changez l'intérieur, les portes tiennent, rien ne casse ailleurs. C'est la base de tout le découpage en équipe et en tests : on teste un module par ses exports.

Dans import tout, { chose } from "./m.mjs", tout désigne…

  1. l'export default du module
  2. tous les exports réunis
  3. l'export nommé "tout"
Réponse

La partie hors accolades est toujours l'export default ; les accolades sont les exports nommés.

Pratique (30 min)

  1. Créez maths.mjs et app.mjs du cours, lancez node app.mjs : 42 et 21.
  2. Ajoutez à maths.mjs une fonction NON exportée interne() utilisée par double. Tentez de l'importer dans app.mjs : lisez l'erreur, elle dit que la porte n'existe pas. Critère de réussite : l'erreur est comprise et notée, le double fonctionne toujours.
  3. Oubliez l'extension dans l'import ("./maths") : lisez l'erreur ERR_MODULE_NOT_FOUND, réparez.
  4. Migrez un de vos scripts du module 3 : sortez la fonction attendre dans delai.mjs, exportez-la, importez-la.
  5. Trois fichiers qui s'importent en chaîne (a importe b, b importe c) : constatez que tout se résout au lancement, une fois.

Tips agent : « découpe ce script de 200 lignes en trois modules : données, calcul, affichage ; liste-moi les exports de chacun AVANT d'écrire » : exigez le plan des portes d'abord. Piège : l'agent peut mélanger export default et nommés au hasard. Preuve : chaque import ne voit que ce que le plan annonçait, et le programme produit la même sortie.

Synthèse (10 min)

Trois lignes : export nommé vs default, le chemin relatif et son extension, ce que le module cache. Sans notes : écrivez un module utilitaire à deux exports et le fichier qui les importe.

Jour 44 — Structurer un projet en fichiers

Objectif : adopter une structure de dossiers simple et durable pour un outil Node.

Théorie (20 min)

Un projet n'est pas un gros fichier : c'est une arborescence dont chaque étage a un rôle. Pour un outil en ligne de commande, la structure qui suffit largement :

meteo/
├── package.json        # carte d'identité, "type": "module"
├── README.md           # mode d'emploi, en premier
├── src/
│   ├── index.js        # l'entrée : lit les arguments, orchestre
│   ├── api.js          # tout ce qui parle au réseau
│   └── resume.js       # tout ce qui calcule (pur, testable)
└── test/
    └── resume.test.js  # les tests, à côté de leur sujet

Le principe organisateur : séparer ce qui parle au monde de ce qui calcule. api.js et index.js touchent au réseau, aux fichiers, à la console : ce sont les effets. resume.js prend des données et rend un résultat, sans rien lire ni écrire : c'est le cœur pur. Les tests s'écrivent sur le cœur pur — pas besoin de simuler le réseau pour tester un calcul. Cette frontière est LA décision d'architecture du module ; le jour 49 la creuse.

Deux conventions de plus : le point d'entrée fait le moins possible (lire argv, appeler, afficher, exit code) — on l'appelle parfois « la coquille » ; et le README se rédige tôt, car y écrire « comment on s'en sert » révèle les défauts de conception avant le code. Vous verrez : un outil dur à expliquer est un outil mal conçu.

Pratique (30 min)

  1. Créez l'arborescence du cours dans un nouveau dossier meteo : npm init -y, "type": "module", les quatre fichiers vides, le README avec trois lignes d'intention.
  2. Dans src/resume.js, écrivez une première fonction pure : export function etiqueter(temp) { return temp < 0 ? "gel" : temp < 15 ? "frais" : temp < 25 ? "doux" : "chaud"; }
  3. Dans src/index.js, importez etiqueter et affichez-la pour une valeur passée en argument (process.argv, Number() — les réflexes du jour 37).
  4. Critère de réussite : node src/index.js 18 affiche « doux ». L'architecture tourne, le projet final la remplira.
  5. Dans le carnet : pour chaque fichier de la structure, une ligne « ce fichier a le droit de… / il n'a pas le droit de… ».

Tips agent : « voici ma structure de dossiers et le rôle de chaque fichier ; mon index.js fait aussi du calcul — propose le découpage minimal pour le sortir » : l'agent est bon en redistribution. Piège : il peut créer six dossiers pour trois fonctions — refusez la sur-structure. Preuve : la commande d'entrée produit la même sortie après découpage.

Synthèse (10 min)

Trois lignes : effets vs cœur pur, la coquille d'entrée, pourquoi le README vient tôt. Sans notes : dessinez l'arborescence idéale d'un petit CLI de mémoire.

Jour 45 — node:test : votre premier test

Objectif : écrire et lancer un test automatisé avec le module intégré de Node.

Théorie (20 min)

Un test est un programme qui vérifie un autre programme : il appelle votre fonction sur des entrées choisies et compare le résultat à ce qui est attendu. La valeur est durable : chaque fois que vous modifierez le code, une commande rejouera TOUS les tests en une seconde — la peur de casser disparaît, et c'est elle qui rendait le code intouchable.

Node embarque son lanceur de tests depuis la v18 : node:test plus node:assert. Rien à installer :

// test/resume.test.js
import { test } from "node:test";
import assert from "node:assert/strict";
import { etiqueter } from "../src/resume.js";

test("etiqueter classe les températures", () => {
  assert.equal(etiqueter(-3), "gel");
  assert.equal(etiqueter(10), "frais");
  assert.equal(etiqueter(20), "doux");
  assert.equal(etiqueter(30), "chaud");
});

On lance avec node --test à la racine du projet : il trouve les fichiers *.test.js tout seul. Anatomie d'un test : un nom qui dit le COMPORTEMENT attendu (pas « test 1 »), des appels de la fonction sur des cas choisis, et des assertions. Les cas qui font vivre un test : le cas normal, les frontières (0, 15 et 25 pour etiqueter : pile aux changements de catégorie), et le cas absurde. Un test qui ne peut jamais échouer ne protège rien.

Pratique (30 min)

  1. Créez le fichier de test du cours dans votre projet meteo, lancez node --test. Critère de réussite : la sortie annonce 1 test passé, 0 échoué.
  2. Lisez la sortie en détail : le nom du test, les compteurs pass/fail en bas. Retenez où se lit le verdict.
  3. Ajoutez les tests de frontière : etiqueter(0), etiqueter(15), etiqueter(25). Ils doivent passer — vérifiez sur la fonction que les seuils sont bien inclus dans la catégorie attendue.
  4. Provoquez un échec volontaire : changez un attendu en "torride", relancez, LISEZ le rapport d'échec (il montre attendu vs reçu), réparez.
  5. Écrivez le test d'une deuxième fonction de resume.js : export function resumerTemperatures(liste) qui rend { min, max, moyenne }. Cas normal + tableau à un élément.

Tips agent : « écris les tests de cette fonction : cas normal, frontières, cas absurde » — l'agent est un bon pourvoyeur de cas. Piège : il peut écrire des tests qui passent toujours (assertion sur une évidence). Preuve : cassez la fonction exprès, le test DOIT devenir rouge ; sinon il ne prouvait rien.

Synthèse (10 min)

Trois lignes : ce qu'un test vérifie, node --test et ses conventions de nommage, les trois familles de cas. Sans notes : écrivez le test de fizzbuzz du jour 12.

Jour 46 — assert : égal, profond, erreurs

Objectif : choisir la bonne assertion : equal, deepEqual, throws, et le mode strict.

Théorie (20 min)

node:assert/strict (le /strict est important : il impose l'égalité stricte partout) offre quatre assertions qui couvrent l'essentiel :

  • assert.equal(a, b) : égalité stricte de valeurs simples (nombres, chaînes, booléens). Pour les décimaux calculés, préférez une marge : assert.ok(Math.abs(moyenne - 10.56) < 0.01) — souvenez-vous de 0.1 + 0.2 (jour 4).
  • assert.deepEqual(a, b) : compare objets et tableaux contenu par contenu. Deux objets construits séparément ne sont jamais ===, mais leurs contenus peuvent être égaux : assert.deepEqual(resumerTemperatures([10, 20]), { min: 10, max: 20, moyenne: 15 }).
  • assert.throws(fn) : vérifie qu'une fonction lève une erreur — le code qui refuse les mauvaises entrées se teste aussi. assert.throws(() => convertir("abc")). Un second argument filtre : /pas un nombre/ impose le message.
  • assert.ok(x) : x est truthy — pour tout ce qui n'entre pas dans les trois autres.

La règle de rédaction : une assertion dit QUOI, et le message d'échec doit vous suffire à retrouver le pourquoi sans rouvrir le test — choisissez des données parlantes (etiqueter(0) vaut mieux que etiqueter(3.7) pour une frontière). Et un test ne teste qu'une idée : trois assertions sur le même comportement, oui ; trois comportements dans un test, non — quand il échoue, vous voulez savoir lequel des trois a cédé.

Pour vérifier que compter(["a", "a"]) rend le bon objet, il faut…

  1. assert.equal(resultat, { a: 2 })
  2. assert.deepEqual(resultat, { a: 2 })
Réponse

deepEqual : equal compare les références d'objets, toujours différentes ; deepEqual compare les contenus.

Pratique (30 min)

  1. Reprenez votre compter du jour 19 dans un module, écrivez son test avec deepEqual sur trois cas, dont le tableau vide (résultat {}).
  2. Testez la marge de flottants : une fonction moyenne, un résultat attendu à 0.01 près avec assert.ok et Math.abs.
  3. Testez l'erreur : votre convertir du jour 21, avec assert.throws(() => convertir("abc"), /pas un nombre/).
  4. Critère de réussite : node --test tout vert ; puis cassez chaque fonction une par une et regardez CHAQUE test devenir rouge avant de réparer — la preuve que les tests voient quelque chose.
  5. Dans le carnet : equal vs deepEqual en une phrase chacun, avec un exemple de votre code.

Tips agent : « ces tests utilisent equal sur des objets — corrige en deepEqual et explique quand equal suffit » : un agent connaît la distinction. Piège : il peut remplacer par deepStrictEqual ou autre variante sans dire pourquoi — demandez la règle, pas juste le patch. Preuve : un test qui échoue sur un objet presque égal (clé en trop) le prouve.

Synthèse (10 min)

Trois lignes : les quatre assertions et leur terrain, la marge pour les flottants, une idée par test. Sans notes : testez une fonction qui rend un tableau, et une qui lève.

Jour 47 — Tester du code asynchrone

Objectif : tester des fonctions async, leurs résultats et leurs rejets.

Théorie (20 min)

Un test async est simplement un test dont la fonction est async : le lanceur attend la promesse qu'elle renvoie.

import { test } from "node:test";
import assert from "node:assert/strict";

test("attendre résout après le délai", async () => {
  const debut = Date.now();
  await attendre(50);
  assert.ok(Date.now() - debut >= 50);
});

test("getJSON rejette sur un serveur injoignable", async () => {
  await assert.rejects(() => getJSON("http://localhost:1/jamais"));
});

Deux règles. (1) await partout : un test async qui oublie d'attendre sa promesse se termine « vert » AVANT que le code ait fini — le pire test qui existe, il prétend vérifier sans regarder. Le symptôme : le test passe même quand le code est cassé. (2) Les rejets se vérifient avec assert.rejects, le frère asynchrone de throws : await assert.rejects(fn, /message/).

La question qui vient tout de suite : « comment tester du code qui appelle une vraie API ? » Réponse : on ne le fait pas dans les tests unitaires. Le réseau est lent, capricieux, et changeant — un test qui dépend d'Internet échoue dans le train. La solution est l'architecture du jour 44 : le réseau est isolé dans api.js, mince et sans logique ; tout ce qui calcule est pur et se teste sur des données FIXES écrites dans le test (une réponse d'API enregistrée un jour, rejouée pour toujours). Tester le vrai réseau a un nom — test d'intégration — et se fait à part, en connaissance de cause.

Pratique (30 min)

  1. Écrivez le test de attendre du cours (reprenez la fonction du jour 31 dans un module). Vert au premier essai.
  2. Écrivez une fausse fonction async function chercherVille(nom) qui rend une promesse d'objet { nom, latitude, longitude } depuis un petit dictionnaire en dur, et rejette si la ville est inconnue. Testez les deux chemins : résultat avec await + deepEqual, rejet avec assert.rejects.
  3. Critère de réussite : cassez volontairement le dictionnaire : le test du résultat devient rouge. Réparez.
  4. Le piège du test qui ne regarde pas : écrivez un test async SANS await sur une promesse rejetée ; constatez qu'il passe (ou signale un rejet non géré) — notez le réflexe : « un test async sans await est suspect ».
  5. Préparez le projet : enregistrez dans test/fixture-meteo.json une vraie réponse Open-Meteo réduite (courant + 3 jours de prévisions quotidiennes), récupérée avec votre script du jour 35. C'est votre donnée de test pour les jours 52-53.

Tips agent : « écris les tests de cette fonction async, y compris le rejet » : l'agent sait faire. Piège : il peut tester en appelant le VRAI réseau — exigez des données fixes. Preuve : coupez le wifi, les tests doivent rester verts.

Synthèse (10 min)

Trois lignes : await obligatoire dans un test async, assert.rejects, pourquoi le réseau reste hors des tests. Sans notes : écrivez le test d'une fonction async qui lève.

Jour 48 — Déboguer : du console.log au débogueur

Objectif : choisir le bon outil de débogage selon la taille du problème.

Théorie (20 min)

Déboguer, ce n'est pas relire le code en espérant : c'est réduire l'écart entre ce que vous croyiez et ce qui se passe, mesure après mesure. Trois outils, trois tailles de problème :

  • Le console.log chirurgical : pour 90 % des cas. Pas « ça marche pas » disséminé partout : une hypothèse précise (« je crois que cette valeur est undefined ici »), un log juste avant, une conclusion. La règle : un log = une hypothèse. Et console.log retire ses propres erreurs quand on l'oublie — pensez à les enlever.
  • L'instruction debugger; : posée dans le code, elle suspend l'exécution quand un inspecteur est attaché. Sous Node : node --inspect-brk app.js, puis ouvrez chrome://inspect dans un navigateur Chromium : pas-à-pas, valeurs des variables à chaque instant, points d'arrêt posés au clic. Pour les nœuds de logique qui résistent au log.
  • Le test qui reproduit (jours 45-47) : un bug isolé en test ne revient jamais. Le débogage qui finit bien s'achève par un test rouge devenu vert.

La méthode, toujours la même : (1) reproduire le bug de façon fiable — si vous ne savez pas le déclencher à coup sûr, commencez par là ; (2) formuler UNE hypothèse ; (3) la tester avec un log ou un point d'arrêt ; (4) corriger, vérifier, écrire le test. Une hypothèse à la fois : celui qui change trois choses à la fois ne saura jamais laquelle a réparé — et a peut-être cassé autre chose.

Pratique (30 min)

  1. Semez un bug : dans votre scores.js du jour 26, écrivez le comparateur du tri à l'envers. Exécutez, constatez, puis déboguez UNIQUEMENT avec deux console.log d'hypothèse. Notez la démarche au carnet.
  2. Essayez le débogueur : ajoutez debugger; dans une fonction, lancez node --inspect-brk, ouvrez chrome://inspect, avancez en pas-à-pas et inspectez deux variables. Critère de réussite : vous avez vu une valeur changer pas à pas.
  3. Le test réparateur : écrivez le test qui aurait attrapé le bug du tri. Il est rouge avec le bug, vert sans. Rangez-le.
  4. Exercice chronométré : un ami imaginaire vous envoie « ma moyenne donne NaN ». Trois causes plausibles : liste vide, un élément non numérique, division par le mauvais nombre. Écrivez les trois reproductions, trouvez laquelle correspond à son symptôme.

Tips agent : « ce test échoue, voici le code et la sortie : propose trois hypothèses classées par probabilité, et le log qui trancherait » : un agent est fort en hypothèses ordonnées. Piège : il corrigera d'autorité si vous ne l'interdisez pas — vous voulez le diagnostic d'abord. Preuve : l'hypothèse confirmée par le log exécuté chez vous.

Synthèse (10 min)

Trois lignes : les trois outils et leur taille, la règle une-hypothèse-un-log, le test qui clôt un bug. Sans notes : racontez la méthode en quatre étapes à votre carnet.

Jour 49 — Écrire du code testable : séparer calcul et effets

Objectif : reconnaître et réparer le code impossible à tester sans le casser.

Théorie (20 min)

Certaines fonctions se testent en deux lignes ; d'autres exigent Internet, un fichier, et une prière. La différence n'est pas le talent du testeur, c'est la structure du code. Une fonction pure rend toujours le même résultat pour les mêmes entrées et ne touche à rien d'extérieur : elle se teste gratuitement. Une fonction à effets (réseau, fichiers, console, hasard, horloge) dépend du monde : elle se teste difficilement.

// difficile à tester : calcul et effet mélangés
async function afficherMeteo(ville) {
  const donnees = await getJSON(urlDe(ville));
  const texte = `Il fait ${donnees.current.temperature_2m} °C`;
  console.log(texte);
}

// testable : le calcul sort, l'effet reste à l'entrée
function formaterMeteo(donnees) {
  return `Il fait ${donnees.current.temperature_2m} °C`;
}
async function afficherMeteo(ville) {
  console.log(formaterMeteo(await getJSON(urlDe(ville))));
}

Le patron a un nom : coquille et cœur. Le cœur (formaterMeteo, vos pipelines du module 2) est pur, abondant, et couvert de tests. La coquille (index.js, api.js) est mince : elle lit argv, appelle l'API, passe les données au cœur, affiche le résultat — si mince qu'on la vérifie à l'œil. Quand une fonction est « impossible à tester », la question n'est pas « comment simuler le monde » mais « qu'est-ce qui calcule ici, et pourquoi n'est-il pas sorti ? ». Le refactoring qui extrait le calcul s'appelle… extraire une fonction : vous savez faire depuis le jour 12.

Pratique (30 min)

  1. Prenez votre script météo du jour 35 : tout est dans une seule fonction main. Extrayez formaterMeteo(donnees) (pure) et laissez main en coquille. Même sortie à l'exécution.
  2. Écrivez le test de formaterMeteo sur votre fixture du jour 47 — trois assertions sur le texte rendu.
  3. Critère de réussite : le test passe SANS réseau (coupez le wifi pour en être sûr) et le script produit le même affichage qu'avant le découpage.
  4. Repérez dans vos anciens scripts une fonction qui mélange console.log et calcul : extrayez le calcul, testez-le.
  5. Casse à discuter au carnet : votre formaterMeteo utilise-t-il l'heure actuelle ou un hasard ? Si oui, passez-les EN PARAMÈTRE — même le temps et le hasard deviennent testables quand ils entrent par la porte.

Tips agent : « sépare cette fonction en cœur pur et coquille ; ne change aucun comportement » : refactoring mécanique que l'agent fait bien si vous interdisez toute autre retouche. Piège : l'agent renomme et « améliore » au passage — exigez le diff minimal. Preuve : sortie identique, et le cœur passe un test sans réseau.

Synthèse (10 min)

Trois lignes : pur vs effets, coquille et cœur, le temps et le hasard en paramètres. Sans notes : transformez une fonction qui affiche en fonction qui rend, plus l'appelant qui affiche.

Jour 50 — Projet final 1/6 : le cahier des charges du CLI météo

Objectif : fixer par écrit ce que l'outil fera — avant d'écrire la moindre ligne.

Théorie (20 min)

Le projet final : un outil en ligne de commande qui donne la météo résumée d'une ville. Deux appels réseau (géocodage du nom de ville, puis prévisions), un résumé calculé, un affichage propre, des tests, un README. Six séances, chacune livrable.

Pourquoi un cahier des charges pour un outil si petit ? Parce que les projets qui dérivent dérivent dès la première heure, pas à la fin. Votre cahier tient en huit lignes :

  • Usage : node src/index.js <ville> [--jours N] — N entre 1 et 7, défaut 3.
  • Sortie : ville, pays, puis pour chaque jour : date, températures min/max, libellé du temps (pluie, neige…). Le tout aligné et lisible.
  • APIs : géocodage https://geocoding-api.open-meteo.com/v1/search?name=…&count=1 puis prévisions https://api.open-meteo.com/v1/forecast?…&daily=temperature_2m_max,temperature_2m_min,weathercode&timezone=auto — gratuites, sans clé.
  • Erreurs : ville introuvable, réseau en panne, argument manquant → message clair en français, exit 1.
  • Tests : tout le calcul (formatage, résumé, libellés) testé sur fixture ; le réseau hors des tests.
  • Livrable : code découpé en modules, tests verts, README qui permet à un inconnu d'utiliser l'outil.

Notez ce que le cahier NE contient pas : pas de couleurs, pas de mode interactif, pas de cache. Un projet fini bat un projet ambitieux : chaque « ce serait bien » va sur une liste « plus tard », pas dans le code.

Pratique (30 min)

  1. Écrivez le cahier des charges en haut de votre README.md, avec VOS mots et vos choix (langue des messages, nombre de jours par défaut).
  2. Explorez les deux APIs à la main avec curl ou le navigateur : géocodez votre ville, notez la structure (results[0].latitude, .longitude, .country) ; appelez la prévision, notez que daily contient des tableaux PARALLÈLES (daily.time[i] va avec daily.temperature_2m_max[i]).
  3. Critère de réussite : vous avez dans le carnet les deux URL complètes qui fonctionnent pour votre ville, et le chemin exact de chaque donnée utile.
  4. Constituez la fixture : sauvegardez une vraie réponse de prévision (3 jours suffisent) dans test/fixture-meteo.json.
  5. Listez les modules : api.js (les deux appels), resume.js (libellés, mise en forme — pur), index.js (coquille). Un export par fonction prévue, écrit en commentaire.

Tips agent : « relis ce cahier des charges et liste les cas limites que j'ai oubliés » : l'agent est un bon reliseur de spécifications. Piège : il proposera dix fonctionnalités en plus — elles vont dans « plus tard », pas dans le cahier. Preuve : chaque cas limite ajouté a sa réponse écrite (message exact, exit code).

Synthèse (10 min)

Trois lignes : ce que le cahier fixe, ce qu'il refuse, la structure des réponses des deux APIs. Sans notes : récitez l'usage exact de votre outil comme si vous le présentiez.

Jour 51 — Projet final 2/6 : interroger Open-Meteo

Objectif : écrire api.js : géocodage, prévisions, erreurs propres.

Théorie (20 min)

Aujourd'hui, la coquille réseau. Deux fonctions, chacune courte, chacune avec ses erreurs :

import { URLSearchParams } from "node:url";

export async function geocoder(ville) {
  const params = new URLSearchParams({ name: ville, count: "1" });
  const donnees = await getJSON(`https://geocoding-api.open-meteo.com/v1/search?${params}`);
  if (!donnees.results || donnees.results.length === 0) {
    throw new Error(`ville introuvable : ${ville}`);
  }
  const { latitude, longitude, name, country } = donnees.results[0];
  return { latitude, longitude, name, country };
}

export async function previsions({ latitude, longitude }, jours) {
  const params = new URLSearchParams({
    latitude: String(latitude),
    longitude: String(longitude),
    daily: "temperature_2m_max,temperature_2m_min,weathercode",
    forecast_days: String(jours),
    timezone: "auto"
  });
  return getJSON(`https://api.open-meteo.com/v1/forecast?${params}`);
}

Et votre getJSON du jour 36 (test de ok, AbortController à 5 s) mutualisé en haut du même fichier. Trois points d'attention : la déstructuration en sortie de geocoder (jour 20) ne fait passer que ce qui sert — l'aval ne dépend plus de la forme de l'API ; les paramètres passent TOUS par URLSearchParams, jamais collés à la main (un nom de ville avec un espace casserait l'URL) ; et chaque fonction dit clairement ce qui peut la faire échouer.

api.js reste non testé par node:test — c'est la coquille réseau. On la vérifie à la main, une fois, sur des cas réels : c'est le prix admis de l'architecture coquille/cœur.

Pratique (30 min)

  1. Écrivez api.js complet (getJSON + geocoder + previsions). Testez geocoder à la main dans un script jetable : votre ville, puis "Zzzzville" pour l'erreur introuvable.
  2. Testez previsions sur les coordonnées de votre ville : la réponse contient daily.time de la bonne longueur selon --jours.
  3. Critère de réussite : les trois cas (ville connue, ville absurde, wifi coupé) donnent chacun un message ou une erreur compréhensible — pas de pile brute.
  4. Vérifiez le piège URLSearchParams : géocodez une ville à espaces (« Le Mans »). Sans encodage propre, ça cassait ; là, ça passe.
  5. Notez au carnet le contrat exact : ce que geocoder rend (quatre champs), ce que previsions rend (la réponse brute).

Tips agent : « relis mon api.js : y a-t-il un paramètre non encodé, un cas d'erreur silencieux ? » : relecture ciblée, deux questions seulement. Piège : l'agent peut réécrire getJSON « mieux » et casser votre timeout. Preuve : les trois cas manuels passent après toute retouche.

Synthèse (10 min)

Trois lignes : le contrat de geocoder, pourquoi URLSearchParams partout, pourquoi api.js échappe aux tests unitaires. Sans notes : réécrivez geocoder de mémoire.

Jour 52 — Projet final 3/6 : résumer les données

Objectif : écrire resume.js, le cœur pur : libellés météo et mise en forme du rapport.

Théorie (20 min)

Le cœur pur prend la réponse de l'API et rend du texte. Deux problèmes à résoudre proprement. Le premier : les weathercodes d'Open-Meteo sont des nombres (0 = ciel clair, 61 = pluie faible, 95 = orage…) : une table de correspondance objet fait l'affaire, avec un défaut pour les codes inconnus — une donnée externe n'est jamais exhaustive.

const LIBELLES = {
  0: "ciel clair", 1: "plutôt clair", 2: "partiellement nuageux", 3: "couvert",
  45: "brouillard", 48: "brouillard givrant",
  51: "bruine légère", 61: "pluie faible", 63: "pluie", 65: "pluie forte",
  71: "neige faible", 80: "averses", 95: "orage"
};

export function libelle(code) {
  return LIBELLES[code] ?? `code ${code}`;
}

Le deuxième : les tableaux parallèles. daily.time[i], daily.temperature_2m_max[i] et daily.temperature_2m_min[i] décrivent le même jour : on « zippe » par l'index avec map sur time, puis on compose le rapport avec map + join("\n") — votre module 2, en mission réelle. La date se formate avec Intl (jour 23) :

const fmt = new Intl.DateTimeFormat("fr-FR", { weekday: "short", day: "numeric", month: "short" });

export function rapporter(reponse) {
  return reponse.daily.time.map((jour, i) => {
    const date = fmt.format(new Date(jour));
    const min = Math.round(reponse.daily.temperature_2m_min[i]);
    const max = Math.round(reponse.daily.temperature_2m_max[i]);
    return `${date} : ${min}° / ${max}°, ${libelle(reponse.daily.weathercode[i])}`;
  }).join("\n");
}

Pur de bout en bout : même réponse en entrée, même texte en sortie, zéro réseau, zéro console. Demain, ce fichier devient le plus testé du projet.

Pratique (30 min)

  1. Écrivez resume.js (LIBELLES, libelle, rapporter). Le nombre de codes météo de la table est votre choix — couvrez les quinze du cours au moins.
  2. Essayez rapporter sur votre fixture dans un script jetable : trois lignes de rapport, dates en français, températures arrondies.
  3. Critère de réussite : le rapport affiché correspond aux données de la fixture, vérifié jour par jour à la main ; et un weathercode absent de la table affiche « code 99 » sans planter.
  4. Ajoutez export function entete(lieu) qui rend « Météo pour {name}, {country} » — la ligne de titre de demain.
  5. Relisez resume.js à voix haute : s'il contient le moindre fetch, console.log ou new Date() sans argument… le new Date(jour) avec argument est pur, lui. Vérifiez la distinction au carnet.

Tips agent : « voici la table des weathercodes WMO, complète mon objet LIBELLES » : avec la table fournie, l'agent est fiable. Piège : sans la table, il invente des libellés plausibles mais faux. Preuve : deux ou trois codes vérifiés dans la documentation Open-Meteo.

Synthèse (10 min)

Trois lignes : table de correspondance et défaut, zippage par index, ce qui rend rapporter pur. Sans notes : réécrivez libelle et le squelette de rapporter.

Jour 53 — Projet final 4/6 : les tests du projet

Objectif : couvrir le cœur du projet de tests qui passent — et qui peuvent échouer.

Théorie (20 min)

Jour de récolte : resume.js est pur, il se teste sans réseau, sur la fixture. La méthode de rédaction d'une suite de tests qui sert :

  1. Le chemin heureux : la fixture réelle donne le rapport attendu — écrivez l'attendu en le lisant dans la fixture, pas en copiant la sortie du programme (sinon vous testez que le programme se contredit… avec lui-même).
  2. Les frontières : un seul jour, des températures négatives, un code inconnu.
  3. La non-régression : chaque bug rencontré pendant le projet devient un test.
import { test } from "node:test";
import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import { libelle, rapporter } from "../src/resume.js";

const fixture = JSON.parse(await readFile(new URL("./fixture-meteo.json", import.meta.url), "utf8"));

test("rapporter rend une ligne par jour", () => {
  const lignes = rapporter(fixture).split("\n");
  assert.equal(lignes.length, fixture.daily.time.length);
});

test("libelle traduit et survit à l'inconnu", () => {
  assert.equal(libelle(0), "ciel clair");
  assert.equal(libelle(999), "code 999");
});

Le chargement de la fixture mérite une explication : import.meta.url est l'URL du fichier de test ; new URL("./fixture-meteo.json", import.meta.url) en déduit le chemin de la fixture relativement au test — fiable quel que soit le dossier de lancement (le piège du jour 39, désamorcé). Le top-level await est légal dans un module.

Pratique (30 min)

  1. Écrivez test/resume.test.js : les deux tests du cours, plus le test de contenu (une ligne contient « ° / » et un libellé connu de la fixture).
  2. Lancez node --test. Critère de réussite : tout est vert, wifi coupé.
  3. Ajoutez la frontière « un seul jour » : fabriquez une mini-réponse à la main dans le test, vérifiez la ligne unique.
  4. Faites le rituel de preuve : cassez libelle (renvoyez toujours "pluie"), regardez les tests rougir, réparez.
  5. Testez aussi entete du jour 52, et etiqueter si vous l'avez gardé. Comptez vos tests au carnet : six à dix, c'est une bonne suite pour ce projet.

Tips agent : « voici ma fixture et mon resume.js ; propose les tests manquants, en citant la ligne de la fixture qui justifie chaque attendu » : l'obliger à citer la donnée tue les attendus inventés. Piège : un test écrit à partir de la sortie du programme ne teste rien. Preuve : le rituel du code cassé exprès fait rougir chaque test.

Synthèse (10 min)

Trois lignes : les trois familles de tests, l'attendu lu dans la fixture, import.meta.url pour les chemins. Sans notes : écrivez un test de libelle complet.

Jour 54 — Projet final 5/6 : erreurs, --help et README

Objectif : finir la coquille : arguments, messages d'erreur, documentation d'usage.

Théorie (20 min)

La coquille index.js assemble : lire les arguments, appeler api.js, passer les données à resume.js, afficher, et traduire toute erreur en message humain + exit 1. Elle tient en un écran :

import { geocoder, previsions } from "./api.js";
import { entete, rapporter } from "./resume.js";

const USAGE = "usage : node src/index.js <ville> [--jours N]";

async function main() {
  const args = process.argv.slice(2);
  if (args.length === 0 || args.includes("--help")) {
    console.log(USAGE);
    process.exit(args.length === 0 ? 1 : 0);
  }
  const iJours = args.indexOf("--jours");
  const jours = iJours === -1 ? 3 : Number(args[iJours + 1]);
  if (!Number.isInteger(jours) || jours < 1 || jours > 7) {
    throw new Error("--jours attend un entier entre 1 et 7");
  }
  const ville = args.filter(a => !a.startsWith("--")).filter(a => a !== String(jours))[0];
  const lieu = await geocoder(ville);
  const reponse = await previsions(lieu, jours);
  console.log(entete(lieu));
  console.log(rapporter(reponse));
}

main().catch(err => {
  console.error(`erreur : ${err.message}`);
  process.exit(1);
});

Deux conventions d'outil bien élevé : --help répond l'usage et sort avec le code 0 (demander de l'aide n'est pas un échec ; l'argument manquant, si) ; et les erreurs partent sur console.error (la sortie d'erreur) pour ne pas se mélanger au résultat quand on redirige la sortie. Le README complète : une phrase de présentation, l'installation (Node LTS, rien d'autre), trois exemples d'usage, la section « comment ça marche » en trois lignes.

Pratique (30 min)

  1. Écrivez index.js complet. Lancez : votre ville, --jours 5, --help, sans argument, ville absurde, --jours 12. Six lancements, six comportements prévus.
  2. Critère de réussite : chaque erreur affiche UNE ligne claire et le code de sortie est 1 (vérifiez avec echo $?), chaque succès affiche le rapport.
  3. Testez la redirection : node src/index.js Lyon > meteo.txt — le fichier ne contient QUE le rapport, les erreurs iraient au terminal.
  4. Rédigez le README.md : présentation, prérequis, usage avec deux exemples réels (ville à un mot, ville à espaces), section « architecture » de trois lignes (api/resume/index), et comment lancer les tests.
  5. Le test de l'inconnu : donnez le README à relire — à vous-même dans deux jours, ou à un agent — et vérifiez qu'il suffit à utiliser l'outil.

Tips agent : « relis mon README et joue l'utilisateur : quelles questions restent sans réponse ? » : l'agent est un bon cobaye de documentation. Piège : il répondra aussi les questions « pour vous » — tout ce qui mérite d'être su entre dans le README. Preuve : la liste des questions qu'il a posées, et vos réponses intégrées.

Synthèse (10 min)

Trois lignes : le contrat exit 0/1 et --help, console.error vs console.log, le contenu minimal d'un README d'outil. Sans notes : récitez les six lancements de vérification.

Jour 55 — Projet final 6/6 : relecture et présentation

Objectif : relire son propre code comme celui d'un autre, et livrer.

Théorie (20 min)

La dernière compétence du projet n'est pas technique : c'est la relecture froide. Votre code, lu à tête reposée, comme s'il venait d'un collègue que vous ne voulez pas vexer mais pas tromper non plus. La grille de lecture d'un CLI de cette taille :

  • Chaque fonction : un nom qui dit vrai, un seul travail, moins d'un écran ?
  • Chaque fichier : à sa place dans le découpage coquille/cœur ?
  • Les erreurs : chaque throw a-t-il un message utile pour celui qui subira ?
  • Les morts : code commenté, fonction inutilisée, console.log de débogage — dehors.
  • Les noms : un inconnu comprend-il donnees, r, tmp ? Renommer est le refactoring le plus rentable qui existe.

Puis la présentation : trois minutes à voix haute (ou au carnet) — ce que fait l'outil, un lancement en direct, le plan des fichiers, une difficulté et comment vous l'avez résolue. Celui qui peut présenter son outil le possède ; celui qui bredouille a trouvé sa prochaine séance de travail.

Pratique (30 min)

  1. Relisez chaque fichier avec la grille du cours, crayon à la main. Chaque anomalie devient une correction immédiate ou une ligne « plus tard » au README.
  2. Refaites passer toute la suite : node --test. Critère de réussite : vert après relecture — les renommages n'ont rien cassé, les tests l'ont prouvé.
  3. Relancez les six scénarios du jour 54 après relecture : le comportement n'a pas bougé.
  4. Écrivez la présentation de trois minutes au carnet, chronométrez-vous à voix haute.
  5. Emballez : le dossier meteo contient src/, test/, README.md, package.json — rien de superflu. Vous avez livré un outil.

Tips agent : « relis src/ avec cette grille : [coller la grille]. Signale, ne corrige pas » : la relecture sans réécriture est le réglage qui rend l'agent utile ici. Piège : ses goûts ne sont pas les vôtres — vous tranchez chaque remarque, pas lui. Preuve : les tests et les six scénarios restent verts après application de CE que vous avez retenu.

Synthèse (10 min)

Trois lignes : la grille de relecture, ce que vous avez corrigé, ce que la présentation a révélé. Le projet est livré — quatre jours de consolidation et c'est un parcours bouclé.

Jour 56 — Deno, Bun, nvm : l'écosystème au-delà de Node

Objectif : situer les alternatives à Node et les gestionnaires de versions, sans rien réinstaller.

Théorie (20 min)

Node n'est pas seul, et vous lirez ces noms partout :

  • Deno (deno.com) : créé par l'auteur original de Node, corrige ses défauts de jeunesse — TypeScript sans configuration, sécurité par défaut (le réseau et les fichiers demandent une permission explicite), modules par URL. Un vrai candidat pour vos prochains outils personnels.
  • Bun (bun.sh) : le sprinter — démarrage et installation de paquets très rapides, boîte à outils intégrée (tests, bundler). Compatible Node pour l'essentiel.
  • Les gestionnaires de versions (nvm, fnm, ou le gestionnaire de paquets de votre système) : plusieurs versions de Node sur une machine, une par projet. Le jour où un projet exige Node 20 et un autre Node 24, vous saurez que c'est un problème résolu.

Le conseil honnête : ne changez rien maintenant. Node LTS couvre tout ce parcours et l'essentiel du monde du travail. Ces alternatives se retesteront quand vous aurez un besoin qu'elles résolvent — un outil en TypeScript, un projet qui démarre mille fois par jour. L'écosystème JavaScript bouge vite ; votre défense n'est pas de tout suivre, mais de savoir reconnaître un besoin réel d'une mode.

Pratique (30 min)

  1. Vérifiez votre installation : node --version, npm --version. Notez-les au carnet avec la date.
  2. Sans rien installer, exécutez un de vos scripts avec le mode « permission » en tête : listez ce qu'il touche (réseau ? fichiers ?) — c'est la question que Deno pose explicitement, et elle vaut déjà pour Node.
  3. Lisez les pages d'accueil de Deno et de Bun (5 min chacune) : notez une promesse de chacun, et ce que ça changerait pour votre CLI météo.
  4. Critère de réussite : vous pouvez répondre en deux phrases à « pourquoi Node et pas Deno pour ce parcours ? » — la réponse tient à la maturité de l'écosystème et à l'omniprésence, pas à un hasard.
  5. Au carnet : votre règle personnelle « quand est-ce que je change d'outil ? » en une phrase.

Tips agent : « mon outil a besoin de X ; Node LTS le fait-il nativement aujourd'hui ? » : la question qui désamorce 80 % des migrations inutiles. Piège : les agents adorent les outils neufs — exigez la comparaison avec votre besoin, pas la liste des nouveautés. Preuve : la page de doc officielle de la fonctionnalité.

Synthèse (10 min)

Trois lignes : Deno et ses permissions, Bun et sa vitesse, votre règle de migration. Sans notes : citez les trois familles d'outils du jour.

Jour 57 — Révisions guidées de tout le parcours

Objectif : refaire les gestes fondateurs des quatre modules, de mémoire.

Théorie (20 min)

Pas de notion nouvelle. Le parcours tient en dix gestes ; aujourd'hui vous les refaites tous, sans notes, dans la console ou Node. C'est la répétition générale avant l'examen blanc de demain.

  1. Déclarer let/const et choisir le bon (J3).
  2. Manipuler chaînes et gabarits (J5).
  3. Lire dans un objet imbriqué, point et crochets (J7).
  4. Écrire une fonction, forme classique et fléchée (J12).
  5. Écrire une closure fabrique (J16).
  6. Composer filter/map/reduce (J17-19, 25).
  7. Déstructurer avec défaut (J20).
  8. Prédire l'ordre synchrone / microtâche / tâche (J30).
  9. Écrire une fonction async avec try…catch et getJSON (J33, 36).
  10. Écrire un test node:test avec frontières (J45-46).

Chaque geste qui résiste renvoie à sa séance : relisez la théorie, refaites la pratique. Il reste deux jours pour les consolider — c'est exactement à ça qu'ils servent.

Pratique (30 min)

  1. Chronométrez les dix gestes : chacun dans la console ou un fichier jetable, de mémoire. Notez le temps de chacun au carnet.
  2. Critère de réussite : huit gestes sur dix réussis sans aide. En dessous, les gestes fautifs sont refaits demain avant l'examen.
  3. Pour les deux gestes les plus lents : refaites-les une deuxième fois immédiatement — la deuxième passe ancre la première.
  4. Relisez votre carnet de A à Z : surlignez les trois phrases que vous citez le plus souvent. Ce sont vos acquis ; le reste est en route.

Tips agent : « voici ma liste de dix gestes ; interroge-moi dessus, une question à la fois, et attends ma réponse avant de corriger » : l'agent examinateur, réglé sur la patience. Piège : il corrigera trop vite si vous ne le précisez pas. Preuve : vous avez produit chaque réponse AVANT de lire la sienne.

Synthèse (10 min)

Trois lignes : les gestes solides, les gestes à refaire demain, votre meilleur progrès du parcours. Demain : l'examen blanc, une heure, sans notes.

Jour 58 — L'examen blanc

Objectif : prouver l'ensemble du parcours en une heure, sans notes ni aide.

Théorie (20 min)

Une heure, chronométrée, sans carnet ni documentation ni IA. Quatre épreuves courtes qui couvrent les quatre modules. Ce n'est pas un piège : tout y est du déjà-fait. Si une épreuve bloque plus de dix minutes, notez-la et passez à la suivante — la liste des blocages EST le résultat de l'examen.

  1. Langage (15 min) : écrivez analyser(texte) qui rend { mots, plusLong, voyelles } : nombre de mots, le mot le plus long, le nombre de voyelles (a e i o u y). Testez-la à la main sur deux phrases.
  2. Fonctions (10 min) : écrivez apresDelai(valeur, ms) qui rend une promesse tenant avec la valeur après ms ; puis une fonction async qui l'utilise pour additionner deux valeurs reçues en décalé.
  3. Node (15 min) : un script stats.mjs qui lit un fichier JSON de nombres (créez-le) et affiche min, max, moyenne — avec message propre si le fichier manque.
  4. Tests (20 min) : la suite de tests d'analyser : cas normal, chaîne vide, mot unique, et un test qui aurait attrapé un oubli de la casse ("É" n'est pas dans votre liste de voyelles — votre choix de le traiter ou non, mais le test documente le choix).

Pratique (30 min)

  1. Réglez un minuteur à 60 minutes et faites les quatre épreuves à la suite. Silence, téléphone loin.
  2. Critère de réussite : les trois programmes s'exécutent correctement et la suite de tests est verte — dans l'heure, ou avec le temps supplémentaire noté.
  3. Après le minuteur, auto-correction au carnet : pour chaque épreuve, « fluide », « laborieux mais fait », « bloqué » — et la cause du blocage en une ligne.
  4. Chaque « bloqué » devient une séance à refaire cette semaine, AVANT de considérer le parcours terminé.

Tips agent : résistez à l'agent pendant l'examen — c'est le seul jour où il n'a pas sa place. APRÈS, en revanche : « voici mes quatre productions et mes auto-critiques ; qu'ai-je raté ? » est une excellente revue. Preuve : ses remarques ne comptent que si vous les reproduisez ou les réfutez en exécutant.

Synthèse (10 min)

Trois lignes : le score par épreuve, la cause de chaque blocage, les séances à refaire. Demain : le bilan, et la suite de votre vie avec JavaScript.

Jour 59 — Bilan du parcours et la suite

Objectif : fermer le parcours proprement et choisir la prochaine montagne.

Théorie (20 min)

Cinquante-neuf séances. Vous êtes parti d'une console vide ; vous finissez avec un outil en ligne de commande qui parle à une API réelle, structuré en modules, couvert de tests, documenté. Relisez la check-list de la page d'accueil du parcours et cochez honnêtement : chaque case cochée est un acquis, chaque case vide est un programme — pas un reproche.

Les suites naturelles, par goût :

  • Vers le web : vous savez le langage ; le DOM et les interfaces sont la prochaine étape — le parcours développement web du site est fait pour ça, et tout ce que vous avez appris s'y applique tel quel.
  • Vers les serveurs : votre mini-API du jour 41 tient la porte ouverte ; un framework (Express, Fastify) et une base de données sont les deux notions suivantes.
  • Vers un autre langage : le parcours Python se suit maintenant très vite — les idées (fonctions, tests, modules) sont les mêmes, seule la grammaire change.
  • Vers l'open source : votre CLI météo mérite un dépôt public, un README soigné, et peut-être ses premiers utilisateurs.

La dernière leçon est celle du premier jour : une heure par jour bat sept heures le dimanche. JavaScript ne se termine jamais — il se pratique. Choisissez un projet qui vous sert, et continuez.

Pratique (30 min)

  1. Cochez la check-list de la page d'accueil du parcours, en regardant votre dossier js/ comme preuve.
  2. Relisez votre carnet du jour 1 au jour 59 : il tient en main et raconte une progression — c'est le livrable caché du parcours.
  3. Critère de réussite finale : vous présentez le CLI météo (trois minutes, jour 55) à quelqu'un — un proche, un collègue, un enregistrement. À voix haute, pour de vrai.
  4. Écrivez au carnet la prochaine étape choisie, et la première séance concrète (quoi, quand, une heure).
  5. Rangez les « plus tard » du projet dans une liste visible : c'est votre backlog personnel, la matière des semaines qui viennent.

Tips agent : « voici mon CLI et son README ; rédige la description de dépôt (une phrase) et les trois mots-clés » : l'agent excelle à présenter ce qui existe. Piège : il survendra — votre outil fait trois choses, pas trente. Preuve : la phrase décrit exactement ce que node src/index.js Lyon produit.

Synthèse (10 min)

Trois lignes : ce que vous savez faire que vous ne saviez pas il y a 59 jours, la prochaine étape choisie, votre première séance planifiée. Le parcours est terminé ; la pratique commence. Bravo.

Utile ? Dites-nous ce qu'il faut mesurer ensuite.

Commentaires

Ni compte ni traceur : un commentaire est lu par un humain avant d'apparaître.