{
  "assessmentTests": {
    "javascript_test": {
      "name": "Test JavaScript",
      "desc": "30 pytań scenariuszowych dotyczących podstawowej składni i konwersji typów, funkcji/zakresu/zamknięć i this, obiektów/tablic i struktur danych, oraz event loop/async/idiomów prototypu — odkryj, czy Twoje umiejętności JavaScript odpowiadają temu, co oznaczają ogłoszenia o pracę przez kompetencję JavaScript.",
      "recommendation": "Twój profil kompetencji JavaScript",
      "results": {
        "beginner": {
          "name": "Początkujący",
          "desc": "Potrafisz pisać działające funkcje i obiekty, ale pytania, które przegapiłeś skupiają się wokół konwersji typów i semantyki referencji, a nie składni — == cicho konwertujące typy przed porównywaniem, przypisanie tablicy, które dzieli jeden obiekt zamiast go kopiować, dlaczego NaN nigdy nie jest równe sobie. To nie chodzi o bycie złym w JavaScript; to są określone zasady, które mylą ludzi, którzy nauczyli się języka pisząc kod, który przypadkowo działał, zamiast nauczyć się jak wartości i referencje faktycznie się zachowują. Mają znaczenie w pracy, ponieważ każdy z nich to miejsce, gdzie kod działa bez błędu i nadal robi coś złego.",
          "recommendation": "Zacznij od trzech rzeczy, w tej kolejności: dlaczego == konwertuje typy przed porównywaniem, podczas gdy === tego nie robi, dlaczego przypisanie tablicy lub obiektu do nowej zmiennej kopiuje referencję, a nie wartość, oraz jak var jest podnoszony i inicjalizowany na undefined, podczas gdy let i const pozostają w czasowej strefie martwej, aż do ich deklaracji faktycznie się wykonuje. Przewodnik JavaScript MDN obejmuje wszystkie trzy z działającymi przykładami."
        },
        "intermediate": {
          "name": "Pośredni",
          "desc": "Komfortowo obsługujesz codzienny kod aplikacyjny — funkcje, metody tablicy, prosta kontrola przepływu — i nie byłbyś spowolniony przez rutynową pracę nad funkcjami. Luka między tutaj a Zaawansowanym jest głównie tym, co się dzieje, gdy zasady zakresu i czasu JavaScript współdziałają: var przechwycony wewnątrz callbacks pętli, które wszystkie czytają tę samą wartość końcową, this, które cicho przestaje wskazywać na obiekt, gdy metoda zostaje odłączona od niego, kopia spread, która wygląda głębokozo, ale kopiuje tylko jeden poziom. To są rodzaje błędów, które przechodzą szybkie przejrzenie i pojawiają się tylko w określonych warunkach runtime.",
          "recommendation": "Skoncentruj się na tym, jak zakres i this faktycznie rozwiązują się w momencie wywołania, a nie w momencie definicji: dlaczego pętla for oparta na var daje każdemu callback tej samej ostatecznej wartości licznika, podczas gdy let tego nie robi, jak this jest określone przez sposób, w jaki funkcja jest wywoływana, a nie gdzie jest napisana, oraz dlaczego spread lub Object.assign kopią tylko najwyższy poziom obiektu. Następnie event loop, ponieważ to jest to, co myli ludzi, którzy już znają promises indywidualnie."
        },
        "advanced": {
          "name": "Zaawansowany",
          "desc": "To jest poziom, jaki większość ogłoszeń o pracę rozumie przez \"silny JavaScript\". Czytasz łańcuch filter/map/reduce i potrafisz dokładnie przewidzieć, co zwraca bez mutacji oryginalnej tablicy, wiesz, dlaczego synchroniczny console.log zawsze pokonuje microtask, który zawsze pokonuje setTimeout, i sięgasz po Promise.all wiedząc, że odrzuca w chwili, gdy którykolwiek z promises odrzuca, zamiast czekać na resztę. To, co oddziela ten band od góry, to strona prototypu i modelu obiektu pracy: jak łańcuch prototypów rozwiązuje dziedziczoną metodę w stosunku do hasOwnProperty, i co prywatne pole klasy faktycznie ukrywa.",
          "recommendation": "Wkrocz w części, które chronią stan, który dotyka również inny kod: jak łańcuch prototypów JavaScript rozwiązuje wyszukiwania metod, które nie są w samej instancji, co prywatne #fields klasy faktycznie ukrywają przed dostępem zewnętrznym, oraz jak JSON.stringify traktuje undefined i funkcje różnie wewnątrz zwykłego obiektu w stosunku do wewnątrz tablicy. Materiał MDN dotyczący event loop i łańcucha prototypów to naturalny następny krok dla obu."
        },
        "expert": {
          "name": "Ekspert",
          "desc": "Osiągnąłeś wynik na szczycie każdej sekcji — podstawowa składnia, typy i konwersja; funkcje, zakres, zamknięcia i this; obiekty, tablice i struktury danych; oraz async, event loop, prototypy i idiomy. Praktycznie oznacza to, że możesz otrzymać funkcję kogoś innego i wyjaśnić, dlaczego zwraca to, co zwraca, a nie tylko to, co składnia mówi, że powinna robić, co jest trudniejszą i bardziej cenną umiejętnością. Na tym poziomie sam język rzadko jest czynnikiem ograniczającym; limit to zwykle kontrola przepływu async lub kształt danych poniżej.",
          "recommendation": "Zwroty są teraz w projekcie i diagnozie: rozumowanie dotyczące wyścigu między Promise.all a Promise.race, a nie recytowanie ich definicji, wybór kiedy zamknięcie jest właściwym narzędziem do stanu prywatnego w stosunku do kiedy jest to prywatne pole klasy, i czytanie błędu kolejności microtask-versus-macrotask w dziennikach produkcji, a nie w quizie. Jeśli przechodzisz badanie na rolę, opisz błąd czasu, który znalazłeś — stałą zmienną zamknięcia lub async callback w złej kolejności — zamiast nazwania funkcji JavaScript; to demonstruje rozumowanie, a nie tylko słownictwo."
        }
      },
      "questions": [
        {
          "question": "console.log(x);\nvar x = 5;\nCo się drukuje?",
          "options": [
            {"icon": "","label": "5 — deklaracje var są wykonywane natychmiast, przed każdym innym kodem"},
            {"icon": "","label": "ReferenceError: x is not defined"},
            {"icon": "","label": "undefined — deklaracje var są podniesione na szczyt ich zakresu i inicjalizowane na undefined, ale przypisanie jest wykonywane na miejscu, więc zanim się wykonuje, zmienna istnieje, ale zawiera undefined"},
            {"icon": "","label": "null"}
          ]
        },
        {
          "question": "console.log(y);\nlet y = 5;\nCo się dzieje?",
          "options": [
            {"icon": "","label": "Wyrzuca ReferenceError: Cannot access 'y' before initialization — let jest podniesiony na szczyt jego zakresu, ale pozostaje w czasowej strefie martwej, aż linia deklaracji faktycznie się wykonuje"},
            {"icon": "","label": "Drukuje undefined, to samo co zrobiłby var"},
            {"icon": "","label": "Drukuje 5"},
            {"icon": "","label": "Wyrzuca SyntaxError w momencie parsowania"}
          ]
        },
        {
          "question": "const arr = [1, 2, 3];\narr.push(4);\nconsole.log(arr.length);\nCo się drukuje?",
          "options": [
            {"icon": "","label": "4 — const tylko zapobiega ponownym przypisom powiązania arr, nie mutacji obiektu tablicy, na którą wskazuje, więc push nadal działa"},
            {"icon": "","label": "Wyrzuca TypeError, ponieważ arr jest const"},
            {"icon": "","label": "3 — push jest zablokowany na tablicy const"},
            {"icon": "","label": "undefined"}
          ]
        },
        {
          "question": "console.log('' == 0);\nconsole.log('' === 0);\nCo drukują te dwie linie?",
          "options": [
            {"icon": "","label": "true a potem true"},
            {"icon": "","label": "false a potem false"},
            {"icon": "","label": "true a potem false — == konwertuje pusty string na 0 przed porównywaniem, podczas gdy === porównuje typ i wartość bez konwersji, a string nigdy nie jest ściśle równy liczbie"},
            {"icon": "","label": "false a potem true"}
          ]
        },
        {
          "question": "console.log(NaN === NaN);\nconsole.log(Number.isNaN(NaN));\nCo drukują te dwie linie?",
          "options": [
            {"icon": "","label": "true a potem true"},
            {"icon": "","label": "true a potem false"},
            {"icon": "","label": "false a potem false"},
            {"icon": "","label": "false a potem true — NaN to jedyna wartość w JavaScript, która nie jest równa sobie pod ===, ale Number.isNaN() prawidłowo to wykrywa"}
          ]
        },
        {
          "question": "console.log(typeof null);\nCo się drukuje?",
          "options": [
            {"icon": "","label": "\"null\""},
            {"icon": "","label": "\"object\" — długotrwały błąd w języku: typeof null zwraca \"object\", chociaż null jest prymitywem, i został zachowany ze względu na kompatybilność wsteczną od tamtych czasów"},
            {"icon": "","label": "\"undefined\""},
            {"icon": "","label": "\"boolean\""}
          ]
        },
        {
          "question": "Które z poniższych jest prawdziwe w warunku if?",
          "options": [
            {"icon": "","label": "0"},
            {"icon": "","label": "\"\" (pusty string)"},
            {"icon": "","label": "null"},
            {"icon": "","label": "\"0\" — niepusty string jest zawsze prawdziwy w JavaScript, nawet gdy zawartość jest sam string \"0\""}
          ]
        },
        {
          "question": "foo();\nfunction foo() { console.log('a'); }\nbar();\nvar bar = function() { console.log('b'); };\nCo się dzieje?",
          "options": [
            {"icon": "","label": "Oba wywołania drukują, \"a\" a potem \"b\""},
            {"icon": "","label": "foo() drukuje \"a\"; bar() wyrzuca TypeError: bar is not a function — deklaracje funkcji są w pełni podniesione z ich ciałem, ale wyrażenie funkcji przypisane do var podnosi tylko sam var (jako undefined), a nie przypisanie"},
            {"icon": "","label": "Oba wywołania wyrzucają ReferenceError"},
            {"icon": "","label": "foo() wyrzuca; bar() drukuje \"b\""}
          ]
        },
        {
          "question": "function makeCounter() {\n  let count = 0;\n  return function() { count++; return count; };\n}\nconst counter = makeCounter();\nconsole.log(counter());\nconsole.log(counter());\nCo drukują te dwie linie?",
          "options": [
            {"icon": "","label": "1 a potem 1"},
            {"icon": "","label": "0 a potem 1"},
            {"icon": "","label": "undefined a potem undefined"},
            {"icon": "","label": "1 a potem 2 — zwrócona funkcja zamyka się nad tą samą zmienną count z zakresu makeCounter, i ta zmienna utrzymuje się między wywołaniami zamiast być resetowana"}
          ]
        },
        {
          "question": "for (var i = 0; i < 3; i++) {\n  setTimeout(() => console.log(i), 0);\n}\nCo się drukuje?",
          "options": [
            {"icon": "","label": "0, 1, 2"},
            {"icon": "","label": "3, 3, 3 — var ma zasięg funkcji, nie bloku, więc wszystkie trzy callbacki zamykają się nad tym samym i, które już ukończyło pętlę i równa się 3, gdy którykolwiek z nich faktycznie się wykonuje"},
            {"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}\nCo się drukuje?",
          "options": [
            {"icon": "","label": "3, 3, 3"},
            {"icon": "","label": "0, 0, 0"},
            {"icon": "","label": "undefined, undefined, undefined"},
            {"icon": "","label": "0, 1, 2 — let tworzy świeże powiązanie i dla każdej iteracji pętli, więc każdy callback zamyka się nad swoją własną oddzielną kopią, a nie wspólną zmienną"}
          ]
        },
        {
          "question": "function greet() { return `Hi, ${this.name}`; }\nconst person = { name: 'Ana' };\nconsole.log(greet.call(person));\nCo się drukuje?",
          "options": [
            {"icon": "","label": "\"Hi, Ana\" — Function.prototype.call wywołuje funkcję z this jawnie ustawionym na jej pierwszy argument, więc this.name jest rozwiązywane względem person"},
            {"icon": "","label": "\"Hi, undefined\" — call() działa tylko na funkcjach, które są już metodami obiektu"},
            {"icon": "","label": "Wyrzuca TypeError, ponieważ greet nie jest metodą person"},
            {"icon": "","label": "\"Hi, ${this.name}\" — call() nie ocenia template literal"}
          ]
        },
        {
          "question": "const timer = {\n  seconds: 0,\n  start: function() {\n    setInterval(() => { this.seconds++; }, 1000);\n  },\n};\nDlaczego ludzie wolą funkcję strzałkową nad function() { this.seconds++ } wewnątrz setInterval tutaj?",
          "options": [
            {"icon": "","label": "Funkcje strzałkowe nie mają własnego this — przechwytują this leksykalnie z otaczającej funkcji start, którą jest timer (ponieważ start został wywołany jako timer.start()), więc this.seconds prawidłowo wskazuje na timer.seconds przy każdym ticku"},
            {"icon": "","label": "Funkcje strzałkowe działają szybciej niż zwykłe funkcje w runtime"},
            {"icon": "","label": "Funkcje strzałkowe automatycznie wiążą this z obiektem globalnym, który przypadkowo równa się timer"},
            {"icon": "","label": "Funkcje strzałkowe automatycznie konwertują this na zmienną globalną"}
          ]
        },
        {
          "question": "function multiply(a, b) { return a * b; }\nconst double = multiply.bind(null, 2);\nconsole.log(double(5));\nCo się drukuje?",
          "options": [
            {"icon": "","label": "Wyrzuca błąd, ponieważ bind wymaga obu argumentów z góry"},
            {"icon": "","label": "10 — bind zwraca nową funkcję z this i wszystkimi argumentami wiodącymi wstępnie ustawionym; wywołanie double(5) dostarcza pozostały argument b=5, więc multiply(2, 5) zostaje wykonane"},
            {"icon": "","label": "NaN"},
            {"icon": "","label": "7, ponieważ bind dodaje swoje argumenty do wywołania zamiast je zastępować"}
          ]
        },
        {
          "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();\nCo drukują te dwa wywołania console.log?",
          "options": [
            {"icon": "","label": "20 a potem 20"},
            {"icon": "","label": "20 a potem 10 — własne let x z inner przysłania x z outer tylko wewnątrz zakresu inner; własne x z outer nigdy nie jest dotykane"},
            {"icon": "","label": "10 a potem 10"},
            {"icon": "","label": "Wyrzuca ReferenceError, ponieważ x jest zadeklarowany dwa razy"}
          ]
        },
        {
          "question": "const a = [1, 2, 3];\nconst b = a;\nb.push(4);\nconsole.log(a.length);\nCo się drukuje?",
          "options": [
            {"icon": "","label": "3 — b to oddzielna kopia a"},
            {"icon": "","label": "undefined"},
            {"icon": "","label": "Wyrzuca TypeError, ponieważ a jest const"},
            {"icon": "","label": "4 — tablice to obiekty, więc const b = a kopiuje referencję, a nie tablicę; mutacja b przez push również mutuje dokładnie tę samą tablicę, na którą wskazuje a"}
          ]
        },
        {
          "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);\nCo się drukuje?",
          "options": [
            {"icon": "","label": "'Ana' 2 — spread wykonuje głęboką kopię każdego zagnieżdżonego obiektu i tablicy"},
            {"icon": "","label": "'Ana' 3 — spread jest tylko płytką kopią: prymitywna właściwość taka jak name jest kopiowana według wartości, więc ponowne przypisanie copy.name nigdy nie dotyka original, ale zagnieżdżona tablica taka jak tags jest kopiowana według referencji, więc push do copy.tags mutuje dokładnie tę samą tablicę, na którą wskazuje original.tags"},
            {"icon": "","label": "'Bea' 3 — spread kopiuje wszystko według referencji, więc zarówno zmiany na najwyższym poziomie, jak i zagnieżdżone zmiany wracają do original"},
            {"icon": "","label": "'Bea' 2"}
          ]
        },
        {
          "question": "const { a: x = 10, b: y = 20 } = { a: 5 };\nconsole.log(x, y);\nCo się drukuje?",
          "options": [
            {"icon": "","label": "undefined undefined"},
            {"icon": "","label": "5 undefined"},
            {"icon": "","label": "10 20"},
            {"icon": "","label": "5 20 — a jest przemianowana na x i jest obecna, więc zachowuje swoją wartość 5; b jest przemianowana na y i jest nieobecna, więc powraca do jej domyślnie 20"}
          ]
        },
        {
          "question": "const [first, ...rest] = [1, 2, 3, 4];\nconsole.log(rest);\nCo się drukuje?",
          "options": [
            {"icon": "","label": "[1, 2, 3, 4]"},
            {"icon": "","label": "[2, 3, 4] — rest zbiera każdy pozostały element po first w zupełnie nową tablicę"},
            {"icon": "","label": "2"},
            {"icon": "","label": "[1]"}
          ]
        },
        {
          "question": "const nums = [10, 1, 2];\nnums.sort();\nconsole.log(nums);\nCo się drukuje?",
          "options": [
            {"icon": "","label": "[1, 2, 10] — sort zawsze porządkuje liczby numerycznie w porządku rosnącym domyślnie"},
            {"icon": "","label": "[10, 1, 2] — sort nie mutuje oryginalnej tablicy"},
            {"icon": "","label": "[1, 10, 2] — komparator domyślny Array.prototype.sort konwertuje elementy na strings i porównuje je leksykograficznie, więc \"10\" jest sortowana przed \"2\"; sort również mutuje tablicę na miejscu zamiast zwracać nową"},
            {"icon": "","label": "[2, 1, 10]"}
          ]
        },
        {
          "question": "const user = { profile: { age: 0 } };\nconsole.log(user.profile?.age ?? 'unknown');\nconsole.log(user.address?.city ?? 'unknown');\nCo drukują te dwie linie?",
          "options": [
            {"icon": "","label": "'unknown' a potem 'unknown' — ?? traktuje 0 jako brakujące, to samo co || robi"},
            {"icon": "","label": "0 a potem 'unknown' — ?? wraca tylko do null lub undefined, a nie do innych fałszywych wartości takich jak 0, więc age (0) jest trzymane bez zmian; user.address jest undefined, więc ?. zwarcie całego łańcucha do undefined i ?? następnie dostarcza 'unknown'"},
            {"icon": "","label": "0 a potem undefined"},
            {"icon": "","label": "'unknown' a potem 0"}
          ]
        },
        {
          "question": "const obj = { a: undefined, b: function(){}, c: NaN, d: [undefined, function(){}, 1] };\nconsole.log(JSON.stringify(obj));\nCo się drukuje?",
          "options": [
            {"icon": "","label": "'{\"a\":null,\"b\":null,\"c\":null,\"d\":[null,null,1]}' — każda nieobsługiwana wartość staje się null wszędzie, w obiektach i tablicach"},
            {"icon": "","label": "'{\"a\":undefined,\"b\":undefined,\"c\":NaN,\"d\":[undefined,null,1]}'"},
            {"icon": "","label": "'{\"c\":null,\"d\":[null,null,1]}' — właściwości obiektu, których wartość jest undefined lub funkcją, są całkowicie pomijane, a NaN jest serializowane na null, ale wewnątrz tablicy te same nieobsługiwane wartości stają się null zamiast być usuwane, ponieważ usunięcie elementu tablicy przesunęłoby każdy późniejszy indeks"},
            {"icon": "","label": "Wyrzuca TypeError, ponieważ NaN nie może być serializowany do JSON"}
          ]
        },
        {
          "question": "console.log('1');\nsetTimeout(() => console.log('2'), 0);\nPromise.resolve().then(() => console.log('3'));\nconsole.log('4');\nW jakiej kolejności się drukują?",
          "options": [
            {"icon": "","label": "1, 2, 3, 4"},
            {"icon": "","label": "1, 4, 2, 3"},
            {"icon": "","label": "1, 4, 3, 2 — kod synchroniczny jest wykonywany najpierw (więc 1 i 4 drukują się natychmiast), następnie kolejka microtask jest całkowicie opróżniana, zanim event loop przejdzie do następnego macrotask, więc 3 drukuje się przed 2 z setTimeout callback"},
            {"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');\nW jakiej kolejności się drukują?",
          "options": [
            {"icon": "","label": "start, a, end, b — foo() jest wykonywana synchronicznie do jej pierwszego await, więc 'a' drukuje się natychmiast, gdy foo() jest wywoływana; await następnie wstrzymuje foo i zwraca kontrolę do dzwoniącego, więc 'end' drukuje się przed wznowionym 'b', który działa później jako 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));\nCo się drukuje?",
          "options": [
            {"icon": "","label": "'fail err' — Promise.all odrzuca, gdy tylko jeden z jego wejściowych promises odrzuci, ignorując wyniki pozostałych, więc catch handler działa z powodu odrzucenia"},
            {"icon": "","label": "'ok' [1, 3]"},
            {"icon": "","label": "'ok' [1, undefined, 3]"},
            {"icon": "","label": "Wyrzuca synchronicznie i żaden handler nigdy się nie wykonuje"}
          ]
        },
        {
          "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));\nCo się drukuje?",
          "options": [
            {"icon": "","label": "'slow'"},
            {"icon": "","label": "['fast', 'slow']"},
            {"icon": "","label": "'fast' — Promise.race się osadza, gdy tylko pierwsza z jego wejściowych promises się osadzi, a timer 10ms rozwiązuje się zdecydowanie przed 100ms"},
            {"icon": "","label": "Wyrzuca błąd, ponieważ race wymaga dokładnie dwóch promises"}
          ]
        },
        {
          "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'));\nCo drukują te dwie linie?",
          "options": [
            {"icon": "","label": "'Rex makes a sound' a potem true"},
            {"icon": "","label": "undefined a potem false"},
            {"icon": "","label": "Wyrzuca TypeError, ponieważ dog nie ma własnej metody speak"},
            {"icon": "","label": "'Rex makes a sound' a potem false — speak jest zdefiniowana na Animal.prototype, a nie na samej instancji dog; dog znajduje ją przez łańcuch prototypów, gdy jest wywoływana, ale hasOwnProperty sprawdza tylko własne właściwości instancji, a nie coś dziedziczonego"}
          ]
        },
        {
          "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);\nCo drukują te dwie linie?",
          "options": [
            {"icon": "","label": "1 a potem undefined — #count to prywatne pole klasy, czytelne i zapisywalne tylko z wnętrza własnych metod klasy; c.count (bez #) to całkowicie inna, zwykła właściwość, która nigdy nie została ustawiona, więc jest undefined"},
            {"icon": "","label": "1 a potem 0"},
            {"icon": "","label": "1 a potem 1"},
            {"icon": "","label": "Wyrzuca SyntaxError podczas uzyskiwania dostępu do c.count"}
          ]
        },
        {
          "question": "const items = ['a', 'b'];\nconst label = `Items: ${items.length > 0 ? items.join(', ') : 'none'}`;\nconsole.log(label);\nCo się drukuje?",
          "options": [
            {"icon": "","label": "'Items: ${items.length > 0 ? items.join(\\', \\') : \\'none\\'}'"},
            {"icon": "","label": "'Items: none'"},
            {"icon": "","label": "'Items: a, b' — template literals ewaluują każde wyrażenie umieszczone w ${...}, w tym pełny operator ternary, i interpolują wynik bezpośrednio do ciągu"},
            {"icon": "","label": "Wyrzuca SyntaxError, ponieważ operatory ternary nie są dozwolone w template literals"}
          ]
        },
        {
          "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);\nCo się drukuje?",
          "options": [
            {"icon": "","label": "60 [1, 2, 3, 4, 5] — filter, map i reduce każdy zwraca nową tablicę lub wartość bez mutacji oryginalnej tablicy, więc nums nie zmienia się, podczas gdy result jest ostateczną zredukowaną sumą (2 i 4, podwojone do 20 i 40, zsumowane do 60)"},
            {"icon": "","label": "60 [2, 4, 20, 40]"},
            {"icon": "","label": "150 [1, 2, 3, 4, 5]"},
            {"icon": "","label": "60 [20, 40]"}
          ]
        }
      ],
      "optionOrderVersion": "50fc0eba818f2d72"
    }
  }
}
