L’explication dit que le tableau et chaque élément sont nouveaux, donc modifier le résultat devrait laisser l’entrée intacte. Pourtant, quand l’appelant passe un label en majuscules, l’original change aussi. La copie superficielle a conservé la référence à l’objet imbriqué.

Téléchargez le laboratoire d’aperçu des colis. Son code, ses données et son explication volontairement incorrecte ont été créés pour ce tutoriel. Utilisez Node.js 22 ou ultérieur. Aucune installation, aucun identifiant ni accès réseau n’est nécessaire.

Garder l’affirmation près de la source

Vérifiez si modifier le label d’un parcel copié change aussi l’entrée. Lisez ensemble la fonction de copie et son appelant.

Ouvrez le dossier extrait parcel-preview-lab dans Yenpo. Avec Command-P (Aller au fichier…), ouvrez claims.md, src/copy-parcels.mjs et src/preview.mjs dans des onglets. Confrontez l’affirmation à la copie et à son appelant. Rechercher dans tous les fichiers permet de trouver row.label.text ; inspectez la condition uppercase autour plutôt que de prendre une ligne trouvée pour la preuve que chaque appel modifie l’entrée.

Cette lecture ne nécessite ni connexion à un modèle ni serveur de langage. Téléchargez Yenpo pour Mac pour ouvrir le même exemple, ou consultez l’aide à la recherche de fichiers et de texte si vous perdez le fil. Exécutez vous-même l’observateur et les vérifications ci-dessous dans Terminal, puis consignez leurs preuves.

Décomposer l’explication en affirmations

Voici l’explication pédagogique à vérifier :

copyParcels crée un nouveau tableau et un nouvel objet pour chaque colis. Ses étiquettes sont des copies indépendantes, donc preview peut les mettre en majuscules sans modifier l’entrée.

La première phrase contient deux affirmations. La seconde ajoute une affirmation de propriété des objets et une conclusion comportementale. Juger tout le paragraphe « correct ou incorrect » ferait perdre les parties utiles avec l’erreur.

ID Affirmation Qu’est-ce qui l’établirait ou la réfuterait ?
C1 Le tableau renvoyé est nouveau. Comparer les identités des tableaux d’entrée et de sortie.
C2 Chaque objet colis externe est nouveau. Comparer les identités des colis correspondants.
C3 Chaque étiquette imbriquée est indépendante. Comparer les identités des étiquettes, pas seulement leur texte.
C4 Mettre l’aperçu en majuscules préserve l’entrée. Atteindre cette branche et inspecter ensuite l’entrée de l’appelant.
C5 Toute mutation de l’entrée est un défaut métier. Trouver une exigence ou un contrat de propriété ; le code seul n’en fournit pas.

C5 est une conclusion possible du lecteur, pas une partie de l’explication citée. Séparez-la pour que réfuter C4 ne tranche pas implicitement l’exigence produit.

Le périmètre est un tableau dense d’objets colis ordinaires et mutables, chacun avec un objet simple label et une chaîne text. Le laboratoire ne couvre ni getters, ni proxies, ni instances de classe, ni entrées-sorties externes. Gardez ce périmètre près de l’explication au lieu de généraliser l’exécution à toute valeur JavaScript.

Suivre la référence au-delà de la fonction

Ouvrez src/copy-parcels.mjs :

export function copyParcels(parcels) {
  return parcels.map((parcel) => ({ ...parcel }));
}

Le littéral objet crée un nouvel objet externe à chaque appel du callback. Il copie la valeur de la propriété label, qui est une référence vers un objet. Il ne crée pas de nouvelle étiquette imbriquée. MDN décrit cette distinction dans la syntaxe de décomposition et la copie superficielle.

Ouvrez maintenant l’appelant réel, src/preview.mjs :

import { copyParcels } from './copy-parcels.mjs';

export function preview(parcels, { uppercase = false } = {}) {
  const rows = copyParcels(parcels);
  if (uppercase) {
    for (const row of rows) {
      row.label.text = row.label.text.toUpperCase();
    }
  }
  return rows;
}

L’écriture importante vise row.label.text. Réaffecter row.id toucherait le nouvel objet externe ; affecter via row.label atteint l’objet imbriqué partagé. Pour la fixture, la structure des références juste après la copie est :

input ──> original array ──> original parcel ──┐
                                             ├──> one label object
rows  ──> new array ──────> new parcel ────────┘

Trouver cette affectation ne suffit pas à expliquer une exécution précise. La branche par défaut la saute. Pour tester C4, fournissez uppercase: true et un texte dont la forme en majuscules diffère de la valeur actuelle.

Exécuter la branche qui peut contredire l’explication

Décompressez le téléchargement, entrez dans parcel-preview-lab et exécutez :

node observe.mjs

L’observateur crée une fixture neuve pour chaque branche. Il enregistre le texte initial comme chaîne avant d’appeler preview, puis affiche les identités et les valeurs. Voici les résultats :

Observation uppercase: false uppercase: true
Même tableau ? false false
Même colis externe ? false false
Même étiquette imbriquée ? true true
Texte enregistré avant l’appel fragile fragile
Texte d’entrée après l’appel fragile FRAGILE
Texte de l’aperçu après l’appel fragile FRAGILE

C’est un contre-exemple atteignable à C4 : l’appelant fournit l’entrée, choisit la branche des majuscules et peut observer la modification de son propre texte imbriqué. C1 et C2 restent vraies. C3 est déjà réfutée par l’identité partagée de l’étiquette, y compris dans la branche sans affectation.

Une étiquette déjà en majuscules est une sonde moins forte : l’écriture a lieu, mais la valeur avant et après est identique. Un tableau vide n’entre jamais dans la boucle. Ces entrées peuvent passer une vérification « le texte n’a pas changé » sans établir une propriété indépendante. Les tests de preuve comprennent ces deux contrôles.

Ne pas confondre valeurs égales et objets indépendants

Avant l’écriture, cette assertion passe :

assert.deepEqual(rows, input);

Cela indique que la structure et les valeurs comparées correspondent. Cela ne prouve pas que les objets imbriqués correspondants ont des identités distinctes. Ces trois assertions passent aussi simultanément :

assert.notStrictEqual(rows, input);
assert.notStrictEqual(rows[0], input[0]);
assert.strictEqual(rows[0].label, input[0].label);

Le laboratoire importe assert depuis node:assert/strict. Consultez la référence des assertions Node pour la sémantique des comparaisons. Conservez les deux types de preuves : les identités décrivent le partage, les valeurs avant et après montrent la conséquence d’une écriture atteignable.

N’utilisez pas const before = input comme état « avant » sauvegardé. Cette variable pointerait toujours vers le même tableau et pourrait aussi montrer la valeur modifiée. L’observateur enregistre la chaîne primitive beforeText avant l’appel. Une valeur capturée et une référence vers un objet mutable répondent à des questions différentes.

Transformer le résultat en explication révisée

Exécutez ces commandes séparément ; l’échec de la première est volontairement attendu :

node --test checks/original-claims.test.mjs
node --test checks/evidence.test.mjs

L’exécution des affirmations initiales donne deux tests réussis et deux échecs, avec un code de sortie non nul. C1 et C2 passent ; les assertions d’étiquettes indépendantes et d’entrée préservée échouent. L’exécution des preuves donne six tests réussis et un code zéro. La documentation du lanceur de tests Node décrit leur exécution. Aucune source applicative ne change entre les deux : les assertions expriment des affirmations différentes sur le même programme.

Consignez le verdict et sa limite, pas seulement un résultat vert ou rouge :

ID Verdict Preuve et limite
C1, C2 Étayées dans le périmètre Nouveau tableau et nouveaux colis externes : map, le littéral objet et les identités concordent.
C3 Réfutée Les étiquettes imbriquées sont partagées : entrée et sortie détiennent la même référence.
C4 Réfutée Les majuscules modifient l’entrée : fragile devient FRAGILE dans la branche vraie.
C5 Indéterminée Aucune violation d’exigence établie : aucun contrat de propriété n’a été fourni.

Voici une explication corrigée :

copyParcels crée un nouveau tableau et de nouveaux objets colis externes tout en conservant chaque référence d’étiquette imbriquée. Avec uppercase: false, preview n’effectue aucune affectation de texte. Avec uppercase: true, il écrit via l’étiquette partagée. Pour la fixture fragile, l’entrée de l’appelant devient FRAGILE. L’acceptabilité de cette mutation dépend du contrat de propriété.

Le ZIP contient ce relevé dans claims.md, l’observateur, les deux fichiers de tests et un manifeste d’empreintes.

Rapporter les preuves à la tâche de lecture

Gardez la version du code, l’explication vérifiée et les entrées et sorties du test. Demandez ensuite : « Les deux labels partagent un objet et le passage en majuscules modifie l’original. Réécris l’explication en tenant compte de ces deux points. » Vérifiez la nouvelle réponse avec le même code.

Le lecteur de code IA de Yenpo permet de parcourir les sources locales, de poser des questions ciblées et d’ouvrir les références en suivant les appelants. Exécutez le laboratoire et vos vérifications dans Terminal ou vos outils de test habituels ; Yenpo n’exécute pas de commandes shell ni de tests à votre place.

Si la prochaine décision est de changer ce comportement, établissez d’abord l’exigence. Pour un correctif concret, poursuivez avec la revue du code généré par IA à l’aide d’un contre-exemple. Pour enregistrer un comportement inconnu avant un refactoring, utilisez des tests de caractérisation.