diff --git a/react/src/quiz/quiz-result.tsx b/react/src/quiz/quiz-result.tsx index eeab595f..75b5af90 100644 --- a/react/src/quiz/quiz-result.tsx +++ b/react/src/quiz/quiz-result.tsx @@ -113,12 +113,7 @@ export function QuizResult(props: QuizResultProps): ReactNode { : undefined }
- { - // TODO stats for infinite quiz - props.quizDescriptor.kind === 'custom' || props.quizDescriptor.kind === 'infinite' - ? undefined - : - } +
Details (spoilers, don't share!)
@@ -477,7 +472,7 @@ export function GenericQuizResultRow(props: GenericQuizResultRowProps): ReactNod {props.getStat('a')} - + {comparison} diff --git a/react/src/quiz/quiz-statistics.tsx b/react/src/quiz/quiz-statistics.tsx index 4c2620e0..fcf31a41 100644 --- a/react/src/quiz/quiz-statistics.tsx +++ b/react/src/quiz/quiz-statistics.tsx @@ -2,16 +2,32 @@ import React, { ReactNode } from 'react' import { useColors, useJuxtastatColors } from '../page_template/colors' -import { QuizDescriptorWithTime, QuizHistory } from './quiz' -import { parseTimeIdentifier } from './statistics' +import { QuizDescriptor, QuizDescriptorWithTime, QuizHistory } from './quiz' +import { getInfiniteQuizzes, parseTimeIdentifier } from './statistics' -interface QuizStatisticsProps { - // this kind of statistic only really applies to daily/weekly quizzes - quiz: QuizDescriptorWithTime - wholeHistory: QuizHistory +export function QuizStatistics( + props: { + quiz: QuizDescriptor + wholeHistory: QuizHistory + }, +): ReactNode | undefined { + switch (props.quiz.kind) { + case 'juxtastat': + case 'retrostat': + return + case 'infinite': + return + case 'custom': + return undefined + } } -export function QuizStatistics(props: QuizStatisticsProps): ReactNode { +export function QuizStatisticsForTimedStatistics( + props: { + quiz: QuizDescriptorWithTime + wholeHistory: QuizHistory + }, +): ReactNode { const colors = useColors() const history = (i: number): QuizHistory[string] | undefined => { switch (props.quiz.kind) { @@ -153,3 +169,31 @@ export function DisplayedStat({ number, name, additionalClass, color }: { number ) } + +export function QuizStatisticsForInfinite( + props: { + quiz: QuizDescriptor & { kind: 'infinite' } + wholeHistory: QuizHistory + }, +): ReactNode | undefined { + const [seedVersions, keys] = getInfiniteQuizzes(props.wholeHistory) + const numCorrects = keys.map( + key => props.wholeHistory[key].correct_pattern.reduce((partialSum: number, a) => partialSum + (a ? 1 : 0), 0), + ) + + // sort indices by numCorrects + let sortedIndices = Array.from(Array(numCorrects.length).keys()) + sortedIndices.sort((a, b) => numCorrects[a] - numCorrects[b]) + // take first 5 + sortedIndices = sortedIndices.slice(0, 5) + if (sortedIndices.length === 0) { + return undefined + } + + const sortedSeedVersions = sortedIndices.map(i => seedVersions[i]) + const sortedNumCorrects = sortedIndices.map(i => numCorrects[i]) + + console.log(sortedSeedVersions) + console.log(sortedNumCorrects) + return undefined +} diff --git a/react/src/quiz/statistics.ts b/react/src/quiz/statistics.ts index f83d6ca0..8c54a55b 100644 --- a/react/src/quiz/statistics.ts +++ b/react/src/quiz/statistics.ts @@ -50,7 +50,7 @@ async function reportToServerGeneric(wholeHistory: QuizHistory, endpointLatest: return false } -function getInfiniteQuizzes(wholeHistory: QuizHistory): [[string, number][], string[]] { +export function getInfiniteQuizzes(wholeHistory: QuizHistory): [[string, number][], string[]] { const seedVersions: [string, number][] = [] const keys: string[] = [] for (const day of Object.keys(wholeHistory)) { diff --git a/react/test/quiz_infinite_test.ts b/react/test/quiz_infinite_test.ts index 2a0c8843..021204af 100644 --- a/react/test/quiz_infinite_test.ts +++ b/react/test/quiz_infinite_test.ts @@ -33,44 +33,94 @@ async function correctIncorrect(t: TestController): Promise { const localStorageDefault = { persistent_id: '000000000000007', secure_id: '00000003' } -async function completeCorrectAnswerSequence(t: TestController, alreadyKnownAnswers: string[]): Promise { +const seedStr = 'deadbeef00' + +// async function completeCorrectAnswerSequence(t: TestController, alreadyKnownAnswers: string[]): Promise { +// // console.log(await sampleRandomQuestion(seedStr, 0)) +// await t.eval(() => { +// localStorage.clear() +// for (const key of Object.keys(localStorageDefault)) { +// localStorage.setItem(key, localStorageDefault[key]) +// } +// }, { dependencies: { localStorageDefault } }) +// await t.wait(100) +// await safeReload(t) +// await waitForQuizLoading(t) +// await clickButtons(t, alreadyKnownAnswers) +// while (await isQuestionPage(t)) { +// await clickButton(t, 'a') +// await t.wait(500) +// } +// // check that the first n characters match the already known answers +// const text = await correctIncorrect(t) +// for (let i = 0; i < alreadyKnownAnswers.length; i++) { +// if (!text[i]) { +// throw new Error('alreadyKnownAnswers is incorrect') +// } +// } +// const correctAnswers: string[] = [...alreadyKnownAnswers] +// for (let i = alreadyKnownAnswers.length; i < text.length; i++) { +// if (text[i]) { +// correctAnswers.push('a') +// } +// else { +// correctAnswers.push('b') +// } +// } +// // check that the prefixes match + +// return correctAnswers +// } + +const correctAnswerSequences = new Map() + +quizFixture( + 'collect correct answers', + `${target}/quiz.html`, + localStorageDefault, + ``, + 'desktop', +) + +const version = 0 + +test('collect correct answers', async (t) => { + const seed = 'deadbeef00' + // set localStorage such that I_{seedStr}_{version} has had 30 questions answered await t.eval(() => { - localStorage.clear() - for (const key of Object.keys(localStorageDefault)) { - localStorage.setItem(key, localStorageDefault[key]) - } - }, { dependencies: { localStorageDefault } }) - await t.wait(100) + localStorage.quiz_history = JSON.stringify({ + [`I_${seed}_${version}`]: { + // false so the quiz ends + correct_pattern: Array(30).fill(false), + choices: Array(30).fill('a'), + }, + }) + }, { dependencies: { seed, version } }) + await t.navigateTo(`${target}/quiz.html#mode=infinite&seed=${seed}&v=${version}`) await safeReload(t) - await waitForQuizLoading(t) - await clickButtons(t, alreadyKnownAnswers) - while (await isQuestionPage(t)) { - await clickButton(t, 'a') - await t.wait(500) - } - // check that the first n characters match the already known answers - const text = await correctIncorrect(t) - for (let i = 0; i < alreadyKnownAnswers.length; i++) { - if (!text[i]) { - throw new Error('alreadyKnownAnswers is incorrect') - } - } - const correctAnswers: string[] = [...alreadyKnownAnswers] - for (let i = alreadyKnownAnswers.length; i < text.length; i++) { - if (text[i]) { + // Get all quiz_result_symbol elements and the text therein + const symbols = Selector('.quiz_result_comparison_symbol') + const symbolsCount = await symbols.count + const correctAnswers: string[] = [] + for (let i = 0; i < symbolsCount; i++) { + const symbol = symbols.nth(i) + const text = await symbol.innerText + console.log(text) + if (text === '>') { correctAnswers.push('a') } - else { + else if (text === '<') { correctAnswers.push('b') } + else { + throw new Error(`unexpected text ${text} in ${await symbol.textContent}`) + } } - // check that the prefixes match - - return correctAnswers -} + correctAnswerSequences.set(seed, correctAnswers) +}) const seed = 0xdeadbeef00 -const param = '#mode=infinite&seed=deadbeef00&v=0' +const param = `#mode=infinite&seed=${seedStr}&v=${version}` quizFixture( 'generate link', `${target}/quiz.html${param}`, @@ -79,21 +129,22 @@ quizFixture( 'desktop', ) -let correctAnswerSequence: string[] +// let correctAnswerSequence: string[] -test('formulates correct sequence', async (t) => { - // returns if quiztext exists as a class - let alreadyKnownAnswers: string[] = [] - while (true) { - alreadyKnownAnswers = await completeCorrectAnswerSequence(t, alreadyKnownAnswers) - if (alreadyKnownAnswers.length >= 30) { - break - } - } - correctAnswerSequence = alreadyKnownAnswers -}) +// test('formulates correct sequence', async (t) => { +// // returns if quiztext exists as a class +// let alreadyKnownAnswers: string[] = [] +// while (true) { +// alreadyKnownAnswers = await completeCorrectAnswerSequence(t, alreadyKnownAnswers) +// if (alreadyKnownAnswers.length >= 30) { +// break +// } +// } +// correctAnswerSequence = alreadyKnownAnswers +// }) async function provideAnswers(t: TestController, start: number, isCorrect: boolean[]): Promise { + const correctAnswerSequence = correctAnswerSequences.get(seedStr)! for (let i = start; i < start + isCorrect.length; i++) { await clickButton(t, isCorrect[i - start] === (correctAnswerSequence[i] === 'a') ? 'a' : 'b') await t.wait(500) @@ -184,3 +235,7 @@ test('19-correct', async (t) => { // low bit order first: 1111,1111 1111,1111 1111,0000 000[0,0000] This becomes FF FF 0F 00 await t.expect(await juxtastatInfiniteTable()).eql(`7|${seed}|FFFF0F00|20|27\n`) }) + +// test('do-not-report-partial', async (t) => { +// await provideAnswers(t, 0, [false, true, true, true, true]) +// await t.navigateTo(`${target}/quiz.html#mode=infinite&seed=deadbeef01&v=0`)