{
  "assessmentTests": {
    "javascript_test": {
      "name": "JavaScript-test",
      "desc": "30 scenariobaserte spørsmål om kjernebegreper og typetilpasning, funksjoner/omfang/lukninger og this, objekter/tabeller og datastrukturer, og event loop/async/prototype-idiomer — finn ut om din JavaScript-kompetanse stemmer med det som menes med JavaScript-ferdigheter i jobbannonsene.",
      "recommendation": "Din JavaScript-kompetanseprofil",
      "results": {
        "beginner": {
          "name": "Begynner",
          "desc": "Du kan skrive fungerende funksjoner og objekter, men spørsmålene du mistet klynger seg rundt typetilpasning og referansesemantikk heller enn syntaks — == som stilletiende konverterer typer før sammenligning, en tabell-tilordning som deler ett objekt heller enn å kopiere det, hvorfor NaN aldri er lik seg selv. Dette handler ikke om å være dårlig til JavaScript; disse er de spesifikke reglene som forvirrer mennesker som lærte språket ved å skrive kode som tilfeldigvis fungerte heller enn ved å lære hvordan verdier og referanser faktisk oppfører seg. De betyr noe på jobben fordi hver og en er der koden kjører uten feil og likevel gjør feil ting.",
          "recommendation": "Start med tre ting, i denne rekkefølgen: hvorfor == tilpasser typer før sammenligning mens === ikke gjør det, hvorfor å tilordne en tabell eller objekt til en ny variabel kopierer referansen i stedet for verdien, og hvordan var er løftet og initialisert til undefined mens let og const blir i den tidsmessige død-sonen til deres deklarasjon kjører. MDNs JavaScript-guide dekker alle tre med kjørbare eksempler."
        },
        "intermediate": {
          "name": "Intermediær",
          "desc": "Du håndterer hverdagskode komfortabelt — funksjoner, tabell-metoder, rett kontrollflyt — og ville ikke blitt bremset av rutinefeaturearbeid. Gapet mellom her og Avansert handler mest om hva som skjer når JavaScripts omfangs- og timingeregler samspiller: en var som er fanget inne i en løkkes callbacks som alle leser den samme sluttverdi, en this som stille sluttet å peke på objektet når en metode ble koplet fra det, en spread-kopi som ser dyp ut men bare kopierer ett nivå. Det er den slags feil som passerer en rask gjennomlesing og bare dukker opp under en spesifikk kjøretidsbetingelse.",
          "recommendation": "Fokuser på hvordan omfang og this faktisk blir løst på anropstidspunktet heller enn på definisjonstidspunktet: hvorfor en var-basert for-løkke gir hver callback den samme siste tellerverdien mens let ikke gjør det, hvordan this bestemmes av hvordan en funksjon blir kalt heller enn hvor den er skrevet, og hvorfor en spread eller Object.assign bare kopierer toppnivået av et objekt. Deretter event loop, siden det er det som forvirrer mennesker som allerede kjenner promises individuelt."
        },
        "advanced": {
          "name": "Avansert",
          "desc": "Dette er nivået de fleste jobbannonsene mener med «sterk JavaScript». Du leser en kjede av filter/map/reduce og kan forutsi nøyaktig hva den returnerer uten å mutere den opprinnelige tabellen, du vet hvorfor en synkron console.log alltid slår en microtask som alltid slår en setTimeout, og du griper etter Promise.all vel vitende om at det avviser øyeblikkelig hvis noen promise avviser i stedet for å vente på resten. Det som skiller dette bandet fra toppen er prototype- og objekt-modellsiden av arbeidet: hvordan prototype-kjeden løser en arvet metode kontra hasOwnProperty, og hva et privat klassefelt faktisk skjuler.",
          "recommendation": "Push inn i delene som beskytter tilstand annen kode også berører: hvordan JavaScripts prototype-kjede løser metodeoppslaginger som ikke er på instansen selv, hva et klasses private #fields faktisk skjuler fra ekstern tilgang, og hvordan JSON.stringify behandler undefined og funksjoner forskjellig inne i et vanlig objekt kontra inne i en tabell. MDNs materiale om event loop og prototype-kjeden er det naturlige neste steget for begge."
        },
        "expert": {
          "name": "Ekspert",
          "desc": "Du skåret på toppen av hver seksjon — kjernebegreper, typer og tilpasning; funksjoner, omfang, lukninger og this; objekter, tabeller og datastrukturer; og async, event loop, prototyper og idiomer. Praktisk betyr det at du kan få overlevert en fremmed funksjon og forklare hvorfor den returnerer det den gjør, ikke bare det syntaksen sier den burde gjøre, som er den vanskeligere og mer verdifulle ferdigheten. På dette nivået er språket selv sjelden den begrensende faktoren; grensen er vanligvis async-kontrollflyt eller formen på dataene under den.",
          "recommendation": "Avkastningen er nå innenfor utforming og diagnose: resonnering om et race condition mellom Promise.all og Promise.race heller enn å resitere deres definisjoner, valg av når en lukking er riktig verktøy for privat tilstand kontra når et klasses private felt er, og lesing av en microtask-kontra-macrotask rekkefølgefeil i produksjonspørringer heller enn i en quiz. Hvis du blir screenet for en rolle, beskriv en timingebugg du fant — en foreldet lukningsvariabel eller en utenfor rekkefølge async-callback — heller enn å navngi JavaScript-funksjoner; det demonstrerer resonnering, ikke bare ordforråd."
        }
      },
      "questions": [
        {
          "question": "console.log(x);\nvar x = 5;\nHva skrives ut?",
          "options": [
            {"icon": "","label": "5 — var-deklarasjoner kjører umiddelbart, før annen kode"},
            {"icon": "","label": "ReferenceError: x is not defined"},
            {"icon": "","label": "undefined — var-deklarasjoner løftes til toppen av sitt omfang og initialiseres til undefined, men tilordningen kjører på plass, så før den kjører eksisterer variabelen men inneholder undefined"},
            {"icon": "","label": "null"}
          ]
        },
        {
          "question": "console.log(y);\nlet y = 5;\nHva skjer?",
          "options": [
            {"icon": "","label": "Det kaster ReferenceError: Cannot access 'y' before initialization — let løftes til toppen av sitt omfang men blir i den tidsmessige død-sonen til deklarasjonslinjen faktisk kjører"},
            {"icon": "","label": "Det skriver ut undefined, det samme som var ville gjort"},
            {"icon": "","label": "Det skriver ut 5"},
            {"icon": "","label": "Det kaster en SyntaxError ved parse-tid"}
          ]
        },
        {
          "question": "const arr = [1, 2, 3];\narr.push(4);\nconsole.log(arr.length);\nHva skrives ut?",
          "options": [
            {"icon": "","label": "4 — const hindrer bare omtildeling av arr-bindingen selv, ikke mutasjon av tabell-objektet den peker på, så push virker fortsatt"},
            {"icon": "","label": "Det kaster en TypeError, fordi arr er const"},
            {"icon": "","label": "3 — push er blokkert på en const-tabell"},
            {"icon": "","label": "undefined"}
          ]
        },
        {
          "question": "console.log('' == 0);\nconsole.log('' === 0);\nHva skriver de to linjene ut?",
          "options": [
            {"icon": "","label": "true og true"},
            {"icon": "","label": "false og false"},
            {"icon": "","label": "true deretter false — == tilpasser den tomme strengen til 0 før sammenligning, mens === sammenligner type og verdi uten tilpasning, og en streng er aldri strengt lik et tall"},
            {"icon": "","label": "false deretter true"}
          ]
        },
        {
          "question": "console.log(NaN === NaN);\nconsole.log(Number.isNaN(NaN));\nHva skriver de to linjene ut?",
          "options": [
            {"icon": "","label": "true og true"},
            {"icon": "","label": "true og false"},
            {"icon": "","label": "false og false"},
            {"icon": "","label": "false deretter true — NaN er den eneste verdien i JavaScript som ikke er lik seg selv under ===, men Number.isNaN() detekterer det likevel riktig"}
          ]
        },
        {
          "question": "console.log(typeof null);\nHva skrives ut?",
          "options": [
            {"icon": "","label": "\"null\""},
            {"icon": "","label": "\"object\" — en langvarig bug i språket: typeof null returnerer \"object\" selv om null er en primitive, og det har blitt beholdt for bakoverkompatibilitet siden den gang"},
            {"icon": "","label": "\"undefined\""},
            {"icon": "","label": "\"boolean\""}
          ]
        },
        {
          "question": "Hvilket av følgende er sannferdig når det brukes i en if-betingelse?",
          "options": [
            {"icon": "","label": "0"},
            {"icon": "","label": "\"\" (en tom streng)"},
            {"icon": "","label": "null"},
            {"icon": "","label": "\"0\" — en ikke-tom streng er alltid sannferdig i JavaScript, selv når innholdet er selv strengen \"0\""}
          ]
        },
        {
          "question": "foo();\nfunction foo() { console.log('a'); }\nbar();\nvar bar = function() { console.log('b'); };\nHva skjer?",
          "options": [
            {"icon": "","label": "Begge anropene skriver ut, \"a\" deretter \"b\""},
            {"icon": "","label": "foo() skriver ut \"a\"; bar() kaster TypeError: bar is not a function — funksjonsdeklarasjoner løftes helt med kroppen sin, men en var-tildelt funksjonsuttrykk løfter bare var selv (som undefined), ikke tilordningen"},
            {"icon": "","label": "Begge anropene kaster ReferenceError"},
            {"icon": "","label": "foo() kaster; bar() skriver ut \"b\""}
          ]
        },
        {
          "question": "function makeCounter() {\n  let count = 0;\n  return function() { count++; return count; };\n}\nconst counter = makeCounter();\nconsole.log(counter());\nconsole.log(counter());\nHva skriver de to linjene ut?",
          "options": [
            {"icon": "","label": "1 deretter 1"},
            {"icon": "","label": "0 deretter 1"},
            {"icon": "","label": "undefined deretter undefined"},
            {"icon": "","label": "1 deretter 2 — den returnerte funksjonen lukker seg rundt samme count-variabel fra makeCounter sitt omfang, og den variabelen vedvarer mellom anrop i stedet for å tilbakestilles"}
          ]
        },
        {
          "question": "for (var i = 0; i < 3; i++) {\n  setTimeout(() => console.log(i), 0);\n}\nHva skrives ut?",
          "options": [
            {"icon": "","label": "0, 1, 2"},
            {"icon": "","label": "3, 3, 3 — var er funksjonsomfang, ikke blokkomfang, så alle tre callbacks lukker seg rundt samme i, som allerede har fullført løkken og tilsvarer 3 når noen av dem faktisk kjører"},
            {"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}\nHva skrives ut?",
          "options": [
            {"icon": "","label": "3, 3, 3"},
            {"icon": "","label": "0, 0, 0"},
            {"icon": "","label": "undefined, undefined, undefined"},
            {"icon": "","label": "0, 1, 2 — let opprett en frisk binding av i for hver løkkeitatasjon, så hver callback lukker seg rundt sin egen separate kopi i stedet for en delt variabel"}
          ]
        },
        {
          "question": "function greet() { return `Hi, ${this.name}`; }\nconst person = { name: 'Ana' };\nconsole.log(greet.call(person));\nHva skrives ut?",
          "options": [
            {"icon": "","label": "\"Hi, Ana\" — Function.prototype.call påkaller funksjonen med this eksplisitt satt til sitt første argument, så this.name løses mot person"},
            {"icon": "","label": "\"Hi, undefined\" — call() virker bare på funksjoner som allerede er metoder på et objekt"},
            {"icon": "","label": "Det kaster en TypeError, fordi greet ikke er en metode på person"},
            {"icon": "","label": "\"Hi, ${this.name}\" — call() evaluerer ikke template literalen"}
          ]
        },
        {
          "question": "const timer = {\n  seconds: 0,\n  start: function() {\n    setInterval(() => { this.seconds++; }, 1000);\n  },\n};\nHvorfor foretrekker folk arrow-funksjonen over function() { this.seconds++ } inne i setInterval her?",
          "options": [
            {"icon": "","label": "Arrow-funksjoner har ikke sin egen this — de fanger this leksikalsk fra den omsluttende start-funksjonen, som er timer (siden start ble kalt som timer.start()), så this.seconds henviser riktig til timer.seconds på hver tikk"},
            {"icon": "","label": "Arrow-funksjoner kjører raskere enn vanlige funksjoner ved kjøretid"},
            {"icon": "","label": "Arrow-funksjoner binder automatisk this til det globale objektet, som tilfeldigvis tilsvarer timer"},
            {"icon": "","label": "Arrow-funksjoner konverterer this til en global variabel automatisk"}
          ]
        },
        {
          "question": "function multiply(a, b) { return a * b; }\nconst double = multiply.bind(null, 2);\nconsole.log(double(5));\nHva skrives ut?",
          "options": [
            {"icon": "","label": "Det kaster en feil, fordi bind krever begge argumenter på forhånd"},
            {"icon": "","label": "10 — bind returnerer en ny funksjon med this og alle ledende argumenter forhåndsinnstilt; å kalle double(5) leverer det gjenstående argumentet b=5, så multiply(2, 5) kjører"},
            {"icon": "","label": "NaN"},
            {"icon": "","label": "7, fordi bind legger til sine argumenter til anropet heller enn å erstatte dem"}
          ]
        },
        {
          "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();\nHva skriver de to console.log anropene ut?",
          "options": [
            {"icon": "","label": "20 deretter 20"},
            {"icon": "","label": "20 deretter 10 — inner sin egen let x skygger outer sin x bare inne i inner sitt omfang; outer sin egen x blir aldri rørt"},
            {"icon": "","label": "10 deretter 10"},
            {"icon": "","label": "Det kaster en ReferenceError, fordi x er deklarert to ganger"}
          ]
        },
        {
          "question": "const a = [1, 2, 3];\nconst b = a;\nb.push(4);\nconsole.log(a.length);\nHva skrives ut?",
          "options": [
            {"icon": "","label": "3 — b er en egen kopi av a"},
            {"icon": "","label": "undefined"},
            {"icon": "","label": "Det kaster en TypeError, fordi a er const"},
            {"icon": "","label": "4 — tabeller er objekter, så const b = a kopierer referansen, ikke tabellen; mutasjon av b gjennom push muterer også den nøyaktig samme tabellen a peker på"}
          ]
        },
        {
          "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);\nHva skrives ut?",
          "options": [
            {"icon": "","label": "'Ana' 2 — spread gjør dypkopi av hvert nestet objekt og tabell"},
            {"icon": "","label": "'Ana' 3 — spread er bare en grunn kopi: en primitiv eienskap som name blir kopiert etter verdi, så omtildeling av copy.name berører aldri original, men en nestet tabell som tags blir kopiert etter referanse, så pushing inn i copy.tags muterer den nøyaktig samme tabellen original.tags peker på"},
            {"icon": "","label": "'Bea' 3 — spread kopierer alt etter referanse, så både toppnivå og nestete endringer løper tilbake til original"},
            {"icon": "","label": "'Bea' 2"}
          ]
        },
        {
          "question": "const { a: x = 10, b: y = 20 } = { a: 5 };\nconsole.log(x, y);\nHva skrives ut?",
          "options": [
            {"icon": "","label": "undefined undefined"},
            {"icon": "","label": "5 undefined"},
            {"icon": "","label": "10 20"},
            {"icon": "","label": "5 20 — a blir omdøpt til x og er til stede, så det beholder sin verdi 5; b blir omdøpt til y og er fraværende, så det faller tilbake til sin standard 20"}
          ]
        },
        {
          "question": "const [first, ...rest] = [1, 2, 3, 4];\nconsole.log(rest);\nHva skrives ut?",
          "options": [
            {"icon": "","label": "[1, 2, 3, 4]"},
            {"icon": "","label": "[2, 3, 4] — rest samler hvert gjenværende element etter first inn i en brand ny tabell"},
            {"icon": "","label": "2"},
            {"icon": "","label": "[1]"}
          ]
        },
        {
          "question": "const nums = [10, 1, 2];\nnums.sort();\nconsole.log(nums);\nHva skrives ut?",
          "options": [
            {"icon": "","label": "[1, 2, 10] — sort ordner alltid tall numerisk i stigende rekkefølge som standard"},
            {"icon": "","label": "[10, 1, 2] — sort muterer ikke den opprinnelige tabellen"},
            {"icon": "","label": "[1, 10, 2] — Array.prototype.sort sin standardsammenlikner konverterer elementer til strenger og sammenligner dem leksikografisk, så \"10\" sorteres før \"2\"; sort muterer også tabellen på plass heller enn å returnere en ny"},
            {"icon": "","label": "[2, 1, 10]"}
          ]
        },
        {
          "question": "const user = { profile: { age: 0 } };\nconsole.log(user.profile?.age ?? 'unknown');\nconsole.log(user.address?.city ?? 'unknown');\nHva skriver de to linjene ut?",
          "options": [
            {"icon": "","label": "'unknown' deretter 'unknown' — ?? behandler 0 som manglende, det samme som || gjør"},
            {"icon": "","label": "0 deretter 'unknown' — ?? faller bare tilbake på null eller undefined, ikke andre falsy verdier som 0, så age (0) holdes som-er; user.address er undefined, så ?. kortslutter hele kjeden til undefined og ?? leverer deretter 'unknown'"},
            {"icon": "","label": "0 deretter undefined"},
            {"icon": "","label": "'unknown' deretter 0"}
          ]
        },
        {
          "question": "const obj = { a: undefined, b: function(){}, c: NaN, d: [undefined, function(){}, 1] };\nconsole.log(JSON.stringify(obj));\nHva skrives ut?",
          "options": [
            {"icon": "","label": "'{\"a\":null,\"b\":null,\"c\":null,\"d\":[null,null,1]}' — hver uunderstøttet verdi blir null overalt, i objekter og tabeller"},
            {"icon": "","label": "'{\"a\":undefined,\"b\":undefined,\"c\":NaN,\"d\":[undefined,null,1]}'"},
            {"icon": "","label": "'{\"c\":null,\"d\":[null,null,1]}' — objektegenskaper hvis verdi er undefined eller en funksjon er fullstendig utelatt, og NaN blir serielt til null, men inne i en tabell blir de samme uunderstøttede verdiene null i stedet for å bli fjernet, fordi fjerning av et tabellelement ville skiftet hvert senere indeks"},
            {"icon": "","label": "Det kaster en TypeError, fordi NaN ikke kan serialiseres til JSON"}
          ]
        },
        {
          "question": "console.log('1');\nsetTimeout(() => console.log('2'), 0);\nPromise.resolve().then(() => console.log('3'));\nconsole.log('4');\nI hvilken rekkefølge skrives disse ut?",
          "options": [
            {"icon": "","label": "1, 2, 3, 4"},
            {"icon": "","label": "1, 4, 2, 3"},
            {"icon": "","label": "1, 4, 3, 2 — synkron kode kjører først (så 1 og 4 skrives ut umiddelbart), deretter dreneres microtask-køen helt før event loop går over til neste macrotask, så 3 skrives ut før setTimeout-callbackens 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');\nI hvilken rekkefølge skrives disse ut?",
          "options": [
            {"icon": "","label": "start, a, end, b — foo() kjører synkront opp til sin første await, så 'a' logges umiddelbart når foo() kalles; await suspenderer deretter foo og returnerer kontroll til kalleren, så 'end' logges før den gjenopptatte 'b', som kjører senere som en 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));\nHva skrives ut?",
          "options": [
            {"icon": "","label": "'fail err' — Promise.all avviser så snart noen av dens inndatapromises avviser, og ignorerer resultatene av de andre, så catch-behandler kjører med den avvisningens grunn"},
            {"icon": "","label": "'ok' [1, 3]"},
            {"icon": "","label": "'ok' [1, undefined, 3]"},
            {"icon": "","label": "Det kaster synkront og ingen behandler kjører noensinne"}
          ]
        },
        {
          "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));\nHva skrives ut?",
          "options": [
            {"icon": "","label": "'slow'"},
            {"icon": "","label": "['fast', 'slow']"},
            {"icon": "","label": "'fast' — Promise.race blir oppgjort så snart den første av dens inndatapromises blir oppgjort, og 10ms-timeren løses godt før 100ms-en"},
            {"icon": "","label": "Det kaster en feil, fordi race krever nøyaktig to 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'));\nHva skriver de to linjene ut?",
          "options": [
            {"icon": "","label": "'Rex makes a sound' deretter true"},
            {"icon": "","label": "undefined deretter false"},
            {"icon": "","label": "Det kaster en TypeError, fordi dog ikke har sin egen speak-metode"},
            {"icon": "","label": "'Rex makes a sound' deretter false — speak er definert på Animal.prototype, ikke på dog-instansen selv; dog finner det gjennom prototype-kjeden når det kalles, men hasOwnProperty sjekker bare instansens egne egenskaper, ikke noe arvet"}
          ]
        },
        {
          "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);\nHva skriver de to linjene ut?",
          "options": [
            {"icon": "","label": "1 deretter undefined — #count er et privat klassefelt, lesbar og skrivbar bare fra innsiden av klassens egne metoder; c.count (uten #) er en helt annen, ordinær eienskap som aldri ble satt, så den er undefined"},
            {"icon": "","label": "1 deretter 0"},
            {"icon": "","label": "1 deretter 1"},
            {"icon": "","label": "Det kaster en SyntaxError når tilgang til c.count"}
          ]
        },
        {
          "question": "const items = ['a', 'b'];\nconst label = `Items: ${items.length > 0 ? items.join(', ') : 'none'}`;\nconsole.log(label);\nHva skrives ut?",
          "options": [
            {"icon": "","label": "'Items: ${items.length > 0 ? items.join(\\', \\') : \\'none\\'}' "},
            {"icon": "","label": "'Items: none'"},
            {"icon": "","label": "'Items: a, b' — template literals evaluerer enhver uttrykk plassert inne i ${...}, inkludert en fullstendig ternær, og interpolerer resultatet direkte inn i strengen"},
            {"icon": "","label": "Det kaster en SyntaxError, fordi ternærer ikke er tillatt inne i 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);\nHva skrives ut?",
          "options": [
            {"icon": "","label": "60 [1, 2, 3, 4, 5] — filter, map og reduce returnerer hver en ny tabell eller verdi uten å mutere den opprinnelige tabellen, så nums er uendret mens result er den endelige reduserte summen (2 og 4, doblet til 20 og 40, summert til 60)"},
            {"icon": "","label": "60 [2, 4, 20, 40]"},
            {"icon": "","label": "150 [1, 2, 3, 4, 5]"},
            {"icon": "","label": "60 [20, 40]"}
          ]
        }
      ],
      "optionOrderVersion": "50fc0eba818f2d72"
    }
  }
}
