{
  "assessmentTests": {
    "javascript_test": {
      "name": "Test JavaScript",
      "desc": "30 questions de scénario sur la syntaxe fondamentale et la coercition de type, les fonctions/portée/fermetures et this, les objets/tableaux et les structures de données, et la boucle d'événements/async/idiomes de prototype — découvrez si votre JavaScript correspond à ce qu'une annonce d'emploi entend par maîtrise de JavaScript.",
      "recommendation": "Votre profil de compétences JavaScript",
      "results": {
        "beginner": {
          "name": "Débutant",
          "desc": "Vous pouvez écrire des fonctions et des objets qui fonctionnent, mais les questions que vous avez manquées se regroupent autour de la coercition de type et de la sémantique des références plutôt que la syntaxe — == convertissant silencieusement les types avant la comparaison, une assignation de tableau qui partage un objet plutôt que de le copier, pourquoi NaN n'est jamais égal à lui-même. Ce n'est pas une question de mal comprendre JavaScript ; ce sont les règles spécifiques qui trébuchent les personnes qui ont appris le langage en écrivant du code qui fonctionnait plutôt qu'en apprenant comment les valeurs et les références se comportent réellement. Cela importe au travail car c'est là où le code s'exécute sans erreur et fait quand même la mauvaise chose.",
          "recommendation": "Commencez par trois choses, dans cet ordre : pourquoi == force les types avant la comparaison tandis que === ne le fait pas, pourquoi assigner un tableau ou un objet à une nouvelle variable copie la référence plutôt que la valeur, et comment var est hissé et initialisé à undefined tandis que let et const restent dans la zone morte temporelle jusqu'à ce que leur déclaration s'exécute. Le guide JavaScript de MDN couvre tous les trois avec des exemples exécutables."
        },
        "intermediate": {
          "name": "Intermédiaire",
          "desc": "Vous gérez confortablement le code d'application quotidien — fonctions, méthodes de tableau, flux de contrôle simple — et ne seriez pas ralenti par les travaux de maintenance routinière. L'écart entre ici et Avancé est surtout dans ce qui se passe quand les règles de portée et de timing de JavaScript interagissent : une var capturée dans les rappels d'une boucle qui lisent tous la même valeur finale, un this qui cesse silencieusement de pointer vers l'objet une fois qu'une méthode est détachée de celui-ci, une copie spread qui semble profonde mais ne copie que le niveau supérieur. Ce sont les types de bugs qui passent une lecture rapide et ne se manifestent que dans une condition d'exécution spécifique.",
          "recommendation": "Concentrez-vous sur la façon dont la portée et this se résolvent au moment de l'appel plutôt qu'au moment de la définition : pourquoi une boucle for basée sur var transmet chaque rappel la même valeur de compteur finale tandis que let ne le fait pas, comment this est déterminé par la façon dont une fonction est appelée plutôt que d'où elle est écrite, et pourquoi une copie spread ou Object.assign ne copie que le niveau supérieur d'un objet. Ensuite la boucle d'événements, car c'est ce qui trébuche les personnes qui connaissent déjà les promises individuellement."
        },
        "advanced": {
          "name": "Avancé",
          "desc": "C'est le niveau que la plupart des annonces d'emploi entendent par \"JavaScript fort\". Vous lisez une chaîne de filter/map/reduce et pouvez prédire exactement ce qu'elle retourne sans muter le tableau d'origine, vous savez pourquoi un console.log synchrone bat toujours une microtask qui bat toujours un setTimeout, et vous recourez à Promise.all en sachant qu'elle rejette l'instant où n'importe quelle promesse le fait plutôt que d'attendre le reste. Ce qui sépare cette bande du sommet est le côté prototype et modèle d'objet du travail : comment la chaîne de prototype résout une méthode héritée par rapport à hasOwnProperty, et ce qu'un champ de classe privé cache vraiment.",
          "recommendation": "Allez plus loin dans les parties qui protègent l'état que d'autre code touche aussi : comment la chaîne de prototype de JavaScript résout les recherches de méthode qui ne sont pas sur l'instance elle-même, ce qu'un champ privé #fields de classe cache vraiment de l'accès extérieur, et comment JSON.stringify traite undefined et les fonctions différemment à l'intérieur d'un objet simple par rapport à l'intérieur d'un tableau. Le matériel de MDN sur la boucle d'événements et la chaîne de prototype est l'étape naturelle suivante pour les deux."
        },
        "expert": {
          "name": "Expert",
          "desc": "Vous avez marqué au sommet de chaque section — syntaxe fondamentale, types et coercition ; fonctions, portée, fermetures et this ; objets, tableaux et structures de données ; et async, boucle d'événements, prototypes et idiomes. Pratiquement, cela signifie que vous pouvez vous voir remettre une fonction inconnue et expliquer pourquoi elle retourne ce qu'elle retourne, pas seulement ce que la syntaxe dit qu'elle devrait faire, ce qui est la compétence plus difficile et plus précieuse. À ce niveau, le langage lui-même est rarement le facteur limitant ; la limite est généralement le flux de contrôle async ou la forme des données en dessous.",
          "recommendation": "Les retours sont maintenant dans la conception et le diagnostic : raisonner sur une condition de course entre Promise.all et Promise.race plutôt que de réciter leurs définitions, choisir quand une fermeture est le bon outil pour l'état privé par rapport à quand un champ privé de classe l'est, et lire un bug de commande microtask-vs-macrotask dans les journaux de production plutôt que dans un quiz. Si vous êtes dépistée pour un rôle, décrivez un bug de timing que vous avez trouvé — une variable de fermeture obsolète ou un rappel async hors d'ordre — plutôt que de nommer les fonctionnalités de JavaScript ; cela démontre le raisonnement, pas seulement le vocabulaire."
        }
      },
      "questions": [
        {
          "question": "console.log(x);\nvar x = 5;\nQu'est-ce que cela imprime?",
          "options": [
            {
              "icon": "",
              "label": "5 — les déclarations var s'exécutent immédiatement, avant tout autre code"
            },
            {
              "icon": "",
              "label": "ReferenceError: x is not defined"
            },
            {
              "icon": "",
              "label": "undefined — les déclarations var sont hissées au sommet de leur portée et initialisées à undefined, mais l'assignation s'exécute sur place, donc avant qu'elle ne s'exécute la variable existe mais contient undefined"
            },
            {
              "icon": "",
              "label": "null"
            }
          ]
        },
        {
          "question": "console.log(y);\nlet y = 5;\nQue se passe-t-il?",
          "options": [
            {
              "icon": "",
              "label": "Elle lève ReferenceError: Cannot access 'y' before initialization — let est hissé au sommet de sa portée mais reste dans la zone morte temporelle jusqu'à ce que la ligne de déclaration s'exécute réellement"
            },
            {
              "icon": "",
              "label": "Elle imprime undefined, comme var le ferait"
            },
            {
              "icon": "",
              "label": "Elle imprime 5"
            },
            {
              "icon": "",
              "label": "Elle lève une SyntaxError au moment de l'analyse"
            }
          ]
        },
        {
          "question": "const arr = [1, 2, 3];\narr.push(4);\nconsole.log(arr.length);\nQu'est-ce que cela imprime?",
          "options": [
            {
              "icon": "",
              "label": "4 — const empêche uniquement de réassigner la liaison arr elle-même, pas de muter l'objet tableau vers lequel il pointe, donc push fonctionne quand même"
            },
            {
              "icon": "",
              "label": "Elle lève une TypeError, parce que arr est const"
            },
            {
              "icon": "",
              "label": "3 — push est bloqué sur un tableau const"
            },
            {
              "icon": "",
              "label": "undefined"
            }
          ]
        },
        {
          "question": "console.log('' == 0);\nconsole.log('' === 0);\nQu'est-ce que les deux lignes impriment?",
          "options": [
            {
              "icon": "",
              "label": "true puis true"
            },
            {
              "icon": "",
              "label": "false puis false"
            },
            {
              "icon": "",
              "label": "true puis false — == force la chaîne vide à 0 avant la comparaison, tandis que === compare le type et la valeur sans coercition, et une chaîne n'est jamais strictement égale à un nombre"
            },
            {
              "icon": "",
              "label": "false puis true"
            }
          ]
        },
        {
          "question": "console.log(NaN === NaN);\nconsole.log(Number.isNaN(NaN));\nQu'est-ce que les deux lignes impriment?",
          "options": [
            {
              "icon": "",
              "label": "true puis true"
            },
            {
              "icon": "",
              "label": "true puis false"
            },
            {
              "icon": "",
              "label": "false puis false"
            },
            {
              "icon": "",
              "label": "false puis true — NaN est la seule valeur en JavaScript qui n'est pas égale à elle-même sous ===, mais Number.isNaN() la détecte correctement quand même"
            }
          ]
        },
        {
          "question": "console.log(typeof null);\nQu'est-ce que cela imprime?",
          "options": [
            {
              "icon": "",
              "label": "\"null\""
            },
            {
              "icon": "",
              "label": "\"object\" — un bug de longue date du langage: typeof null retourne \"object\" même si null est une primitive, et cela a été conservé pour la compatibilité ascendante depuis"
            },
            {
              "icon": "",
              "label": "\"undefined\""
            },
            {
              "icon": "",
              "label": "\"boolean\""
            }
          ]
        },
        {
          "question": "Lequel des éléments suivants est truthy quand utilisé dans une condition if?",
          "options": [
            {
              "icon": "",
              "label": "0"
            },
            {
              "icon": "",
              "label": "\"\" (une chaîne vide)"
            },
            {
              "icon": "",
              "label": "null"
            },
            {
              "icon": "",
              "label": "\"0\" — une chaîne non-vide est toujours truthy en JavaScript, même quand son contenu est lui-même la chaîne \"0\""
            }
          ]
        },
        {
          "question": "foo();\nfunction foo() { console.log('a'); }\nbar();\nvar bar = function() { console.log('b'); };\nQue se passe-t-il?",
          "options": [
            {
              "icon": "",
              "label": "Les deux appels impriment, \"a\" puis \"b\""
            },
            {
              "icon": "",
              "label": "foo() imprime \"a\"; bar() lève TypeError: bar is not a function — les déclarations de fonction sont entièrement hissées avec leur corps, mais une expression de fonction assignée à var ne hisse que le var lui-même (comme undefined), pas l'assignation"
            },
            {
              "icon": "",
              "label": "Les deux appels lèvent ReferenceError"
            },
            {
              "icon": "",
              "label": "foo() lève; bar() imprime \"b\""
            }
          ]
        },
        {
          "question": "function makeCounter() {\n  let count = 0;\n  return function() { count++; return count; };\n}\nconst counter = makeCounter();\nconsole.log(counter());\nconsole.log(counter());\nQu'est-ce que les deux lignes impriment?",
          "options": [
            {
              "icon": "",
              "label": "1 puis 1"
            },
            {
              "icon": "",
              "label": "0 puis 1"
            },
            {
              "icon": "",
              "label": "undefined puis undefined"
            },
            {
              "icon": "",
              "label": "1 puis 2 — la fonction retournée se ferme sur la même variable count de la portée de makeCounter, et cette variable persiste entre les appels plutôt que de se réinitialiser"
            }
          ]
        },
        {
          "question": "for (var i = 0; i < 3; i++) {\n  setTimeout(() => console.log(i), 0);\n}\nQu'est-ce que cela imprime?",
          "options": [
            {
              "icon": "",
              "label": "0, 1, 2"
            },
            {
              "icon": "",
              "label": "3, 3, 3 — var est fonction-scoped, pas block-scoped, donc tous les trois rappels se ferment sur le même i, qui a déjà fini la boucle et égal 3 au moment où n'importe lequel d'eux s'exécute réellement"
            },
            {
              "icon": "",
              "label": "0, 0, 0"
            },
            {
              "icon": "",
              "label": "undefined, undefined, undefined"
            }
          ]
        },
        {
          "question": "for (let i = 0; i < 3; i++) {\n  setTimeout(() => console.log(i), 0);\n}\nQu'est-ce que cela imprime?",
          "options": [
            {
              "icon": "",
              "label": "3, 3, 3"
            },
            {
              "icon": "",
              "label": "0, 0, 0"
            },
            {
              "icon": "",
              "label": "undefined, undefined, undefined"
            },
            {
              "icon": "",
              "label": "0, 1, 2 — let crée une liaison fraîche de i pour chaque itération de boucle, donc chaque rappel se ferme sur sa propre copie séparée plutôt qu'une variable partagée"
            }
          ]
        },
        {
          "question": "function greet() { return `Hi, ${this.name}`; }\nconst person = { name: 'Ana' };\nconsole.log(greet.call(person));\nQu'est-ce que cela imprime?",
          "options": [
            {
              "icon": "",
              "label": "\"Hi, Ana\" — Function.prototype.call invoque la fonction avec this explicitement défini à son premier argument, donc this.name se résout contre person"
            },
            {
              "icon": "",
              "label": "\"Hi, undefined\" — call() fonctionne uniquement sur les fonctions qui sont déjà des méthodes d'un objet"
            },
            {
              "icon": "",
              "label": "Elle lève une TypeError, parce que greet n'est pas une méthode de person"
            },
            {
              "icon": "",
              "label": "\"Hi, ${this.name}\" — call() n'évalue pas le littéral de template"
            }
          ]
        },
        {
          "question": "const timer = {\n  seconds: 0,\n  start: function() {\n    setInterval(() => { this.seconds++; }, 1000);\n  },\n};\nPourquoi les gens préfèrent-ils la fonction fléchée à function() { this.seconds++ } à l'intérieur de setInterval ici?",
          "options": [
            {
              "icon": "",
              "label": "Les fonctions fléchées n'ont pas leur propre this — elles capturent this lexicalement de la fonction start englobante, qui est timer (puisque start a été appelée comme timer.start()), donc this.seconds fait correctement référence à timer.seconds à chaque tick"
            },
            {
              "icon": "",
              "label": "Les fonctions fléchées s'exécutent plus rapidement que les fonctions régulières au moment de l'exécution"
            },
            {
              "icon": "",
              "label": "Les fonctions fléchées lient automatiquement this à l'objet global, qui se trouve être égal à timer"
            },
            {
              "icon": "",
              "label": "Les fonctions fléchées convertissent automatiquement this en une variable globale"
            }
          ]
        },
        {
          "question": "function multiply(a, b) { return a * b; }\nconst double = multiply.bind(null, 2);\nconsole.log(double(5));\nQu'est-ce que cela imprime?",
          "options": [
            {
              "icon": "",
              "label": "Elle lève une erreur, parce que bind exige les deux arguments à l'avance"
            },
            {
              "icon": "",
              "label": "10 — bind retourne une nouvelle fonction avec this et tous les arguments de tête pré-définis ; appeler double(5) fournit l'argument restant b=5, donc multiply(2, 5) s'exécute"
            },
            {
              "icon": "",
              "label": "NaN"
            },
            {
              "icon": "",
              "label": "7, parce que bind ajoute ses arguments à l'appel plutôt que de les substituer"
            }
          ]
        },
        {
          "question": "function outer() {\n  let x = 10;\n  function inner() {\n    let x = 20;\n    console.log(x);\n  }\n  inner();\n  console.log(x);\n}\nouter();\nQu'est-ce que les deux appels console.log impriment?",
          "options": [
            {
              "icon": "",
              "label": "20 puis 20"
            },
            {
              "icon": "",
              "label": "20 puis 10 — le propre let x de inner occulte le x d'outer uniquement à l'intérieur de la portée de inner ; le propre x d'outer n'est jamais touché"
            },
            {
              "icon": "",
              "label": "10 puis 10"
            },
            {
              "icon": "",
              "label": "Elle lève une ReferenceError, parce que x est déclaré deux fois"
            }
          ]
        },
        {
          "question": "const a = [1, 2, 3];\nconst b = a;\nb.push(4);\nconsole.log(a.length);\nQu'est-ce que cela imprime?",
          "options": [
            {
              "icon": "",
              "label": "3 — b est une copie séparée de a"
            },
            {
              "icon": "",
              "label": "undefined"
            },
            {
              "icon": "",
              "label": "Elle lève une TypeError, parce que a est const"
            },
            {
              "icon": "",
              "label": "4 — les tableaux sont des objets, donc const b = a copie la référence, pas le tableau; muter b via push mute aussi le même tableau exact que a pointe"
            }
          ]
        },
        {
          "question": "const original = { name: 'Ana', tags: ['x', 'y'] };\nconst copy = { ...original };\ncopy.name = 'Bea';\ncopy.tags.push('z');\nconsole.log(original.name, original.tags.length);\nQu'est-ce que cela imprime?",
          "options": [
            {
              "icon": "",
              "label": "'Ana' 2 — spread clone-t profondément chaque objet et tableau imbriqué"
            },
            {
              "icon": "",
              "label": "'Ana' 3 — spread est seulement une copie superficielle : une propriété primitive comme name est copiée par valeur, donc réassigner copy.name ne touche jamais original, mais un tableau imbriqué comme tags est copié par référence, donc pousser dans copy.tags mute le même tableau exact que original.tags pointe"
            },
            {
              "icon": "",
              "label": "'Bea' 3 — spread copie tout par référence, donc les deux les modifications au niveau supérieur et imbriquées reviennent à original"
            },
            {
              "icon": "",
              "label": "'Bea' 2"
            }
          ]
        },
        {
          "question": "const { a: x = 10, b: y = 20 } = { a: 5 };\nconsole.log(x, y);\nQu'est-ce que cela imprime?",
          "options": [
            {
              "icon": "",
              "label": "undefined undefined"
            },
            {
              "icon": "",
              "label": "5 undefined"
            },
            {
              "icon": "",
              "label": "10 20"
            },
            {
              "icon": "",
              "label": "5 20 — a est renommé à x et est présent, donc il garde sa valeur 5 ; b est renommé à y et est absent, donc il revient à sa valeur par défaut 20"
            }
          ]
        },
        {
          "question": "const [first, ...rest] = [1, 2, 3, 4];\nconsole.log(rest);\nQu'est-ce que cela imprime?",
          "options": [
            {
              "icon": "",
              "label": "[1, 2, 3, 4]"
            },
            {
              "icon": "",
              "label": "[2, 3, 4] — rest rassemble chaque élément restant après first dans un tout nouveau tableau"
            },
            {
              "icon": "",
              "label": "2"
            },
            {
              "icon": "",
              "label": "[1]"
            }
          ]
        },
        {
          "question": "const nums = [10, 1, 2];\nnums.sort();\nconsole.log(nums);\nQu'est-ce que cela imprime?",
          "options": [
            {
              "icon": "",
              "label": "[1, 2, 10] — sort ordonne toujours les nombres numériquement en ordre croissant par défaut"
            },
            {
              "icon": "",
              "label": "[10, 1, 2] — sort ne mute pas le tableau d'origine"
            },
            {
              "icon": "",
              "label": "[1, 10, 2] — le comparateur par défaut d'Array.prototype.sort convertit les éléments en chaînes et les compare lexicographiquement, donc \"10\" se trie avant \"2\" ; sort mute aussi le tableau en place plutôt que de retourner un nouveau"
            },
            {
              "icon": "",
              "label": "[2, 1, 10]"
            }
          ]
        },
        {
          "question": "const user = { profile: { age: 0 } };\nconsole.log(user.profile?.age ?? 'unknown');\nconsole.log(user.address?.city ?? 'unknown');\nQu'est-ce que les deux lignes impriment?",
          "options": [
            {
              "icon": "",
              "label": "'unknown' puis 'unknown' — ?? traite 0 comme manquant, de la même façon que || le fait"
            },
            {
              "icon": "",
              "label": "0 puis 'unknown' — ?? ne revient que sur null ou undefined, pas sur d'autres valeurs falsy comme 0, donc age (0) est conservé tel quel ; user.address est undefined, donc ?. court-circuite toute la chaîne à undefined et ?? fournit alors 'unknown'"
            },
            {
              "icon": "",
              "label": "0 puis undefined"
            },
            {
              "icon": "",
              "label": "'unknown' puis 0"
            }
          ]
        },
        {
          "question": "const obj = { a: undefined, b: function(){}, c: NaN, d: [undefined, function(){}, 1] };\nconsole.log(JSON.stringify(obj));\nQu'est-ce que cela imprime?",
          "options": [
            {
              "icon": "",
              "label": "'{\"a\":null,\"b\":null,\"c\":null,\"d\":[null,null,1]}' — chaque valeur non prise en charge devient null partout, dans les objets et les tableaux"
            },
            {
              "icon": "",
              "label": "'{\"a\":undefined,\"b\":undefined,\"c\":NaN,\"d\":[undefined,null,1]}'"
            },
            {
              "icon": "",
              "label": "'{\"c\":null,\"d\":[null,null,1]}' — les propriétés d'objet dont la valeur est undefined ou une fonction sont omises entièrement, et NaN se sérialise en null, mais à l'intérieur d'un tableau ces mêmes valeurs non prises en charge deviennent null au lieu d'être supprimées, car supprimer un élément de tableau décalerait chaque index ultérieur"
            },
            {
              "icon": "",
              "label": "Elle lève une TypeError, parce que NaN ne peut pas être sérialisé en JSON"
            }
          ]
        },
        {
          "question": "console.log('1');\nsetTimeout(() => console.log('2'), 0);\nPromise.resolve().then(() => console.log('3'));\nconsole.log('4');\nDans quel ordre ceux-ci s'impriment-ils?",
          "options": [
            {
              "icon": "",
              "label": "1, 2, 3, 4"
            },
            {
              "icon": "",
              "label": "1, 4, 2, 3"
            },
            {
              "icon": "",
              "label": "1, 4, 3, 2 — le code synchrone s'exécute d'abord (donc 1 et 4 s'impriment immédiatement), puis la file d'attente des microtasks (rappels Promise) se vide complètement avant que la boucle d'événements ne passe à la prochaine macrotask, donc 3 s'imprime avant le rappel setTimeout de 2"
            },
            {
              "icon": "",
              "label": "1, 3, 4, 2"
            }
          ]
        },
        {
          "question": "async function foo() {\n  console.log('a');\n  await null;\n  console.log('b');\n}\nconsole.log('start');\nfoo();\nconsole.log('end');\nDans quel ordre ceux-ci s'impriment-ils?",
          "options": [
            {
              "icon": "",
              "label": "start, a, end, b — foo() s'exécute de manière synchrone jusqu'à son premier await, donc 'a' se connecte immédiatement quand foo() est appelée ; l'await suspend alors foo et rend le contrôle à l'appelant, donc 'end' se connecte avant le 'b' repris, qui s'exécute plus tard comme une microtask"
            },
            {
              "icon": "",
              "label": "start, a, b, end"
            },
            {
              "icon": "",
              "label": "start, end, a, b"
            },
            {
              "icon": "",
              "label": "a, start, end, b"
            }
          ]
        },
        {
          "question": "Promise.all([\n  Promise.resolve(1),\n  Promise.reject('err'),\n  Promise.resolve(3),\n]).then(r => console.log('ok', r)).catch(e => console.log('fail', e));\nQu'est-ce que cela imprime?",
          "options": [
            {
              "icon": "",
              "label": "'fail err' — Promise.all rejette dès qu'une de ses promesses d'entrée rejette, en ignorant les résultats des autres, donc le gestionnaire catch s'exécute avec la raison de ce rejet"
            },
            {
              "icon": "",
              "label": "'ok' [1, 3]"
            },
            {
              "icon": "",
              "label": "'ok' [1, undefined, 3]"
            },
            {
              "icon": "",
              "label": "Elle lève de manière synchrone et aucun gestionnaire ne s'exécute jamais"
            }
          ]
        },
        {
          "question": "const fast = new Promise(res => setTimeout(() => res('fast'), 10));\nconst slow = new Promise(res => setTimeout(() => res('slow'), 100));\nPromise.race([fast, slow]).then(v => console.log(v));\nQu'est-ce que cela imprime?",
          "options": [
            {
              "icon": "",
              "label": "'slow'"
            },
            {
              "icon": "",
              "label": "['fast', 'slow']"
            },
            {
              "icon": "",
              "label": "'fast' — Promise.race se règle dès que la première de ses promesses d'entrée se règle, et le minuteur de 10ms se résout bien avant celui de 100ms"
            },
            {
              "icon": "",
              "label": "Elle lève une erreur, parce que race exige exactement deux promesses"
            }
          ]
        },
        {
          "question": "function Animal(name) { this.name = name; }\nAnimal.prototype.speak = function() { return `${this.name} makes a sound`; };\nconst dog = new Animal('Rex');\nconsole.log(dog.speak());\nconsole.log(dog.hasOwnProperty('speak'));\nQu'est-ce que les deux lignes impriment?",
          "options": [
            {
              "icon": "",
              "label": "'Rex makes a sound' puis true"
            },
            {
              "icon": "",
              "label": "undefined puis false"
            },
            {
              "icon": "",
              "label": "Elle lève une TypeError, parce que dog n'a pas sa propre méthode speak"
            },
            {
              "icon": "",
              "label": "'Rex makes a sound' puis false — speak est défini sur Animal.prototype, pas sur l'instance dog elle-même ; dog la trouve via la chaîne de prototype quand elle est appelée, mais hasOwnProperty vérifie uniquement les propriétés propres de l'instance, pas n'importe quoi d'hérité"
            }
          ]
        },
        {
          "question": "class Counter {\n  #count = 0;\n  increment() { this.#count++; return this.#count; }\n}\nconst c = new Counter();\nconsole.log(c.increment());\nconsole.log(c.count);\nQu'est-ce que les deux lignes impriment?",
          "options": [
            {
              "icon": "",
              "label": "1 puis undefined — #count est un champ de classe privé, lisible et inscriptible uniquement à partir des propres méthodes de la classe ; c.count (sans le #) est une propriété complètement différente et ordinaire qui n'a jamais été définie, donc elle est undefined"
            },
            {
              "icon": "",
              "label": "1 puis 0"
            },
            {
              "icon": "",
              "label": "1 puis 1"
            },
            {
              "icon": "",
              "label": "Elle lève une SyntaxError lors de l'accès à c.count"
            }
          ]
        },
        {
          "question": "const items = ['a', 'b'];\nconst label = `Items: ${items.length > 0 ? items.join(', ') : 'none'}`;\nconsole.log(label);\nQu'est-ce que cela imprime?",
          "options": [
            {
              "icon": "",
              "label": "'Items: ${items.length > 0 ? items.join(\\', \\') : \\'none\\'}'"
            },
            {
              "icon": "",
              "label": "'Items: none'"
            },
            {
              "icon": "",
              "label": "'Items: a, b' — les littéraux de template évaluent toute expression placée à l'intérieur de ${...}, y compris un ternaire complet, et interpolent son résultat directement dans la chaîne"
            },
            {
              "icon": "",
              "label": "Elle lève une SyntaxError, parce que les ternaires ne sont pas autorisés à l'intérieur des littéraux de template"
            }
          ]
        },
        {
          "question": "const nums = [1, 2, 3, 4, 5];\nconst result = nums.filter(n => n % 2 === 0).map(n => n * 10).reduce((sum, n) => sum + n, 0);\nconsole.log(result, nums);\nQu'est-ce que cela imprime?",
          "options": [
            {
              "icon": "",
              "label": "60 [1, 2, 3, 4, 5] — filter, map et reduce retournent chacun un nouveau tableau ou une valeur sans muter le tableau d'origine, donc nums est inchangé tandis que result est la somme réduite finale (2 et 4, doublés à 20 et 40, additionnés à 60)"
            },
            {
              "icon": "",
              "label": "60 [2, 4, 20, 40]"
            },
            {
              "icon": "",
              "label": "150 [1, 2, 3, 4, 5]"
            },
            {
              "icon": "",
              "label": "60 [20, 40]"
            }
          ]
        }
      ],
      "optionOrderVersion": "50fc0eba818f2d72"
    }
  }
}
