File size: 4,288 Bytes
a0ebf39 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 | import { describe, it, expect, beforeEach, vi } from 'vitest';
const store: Record<string, string> = {};
const localStorageStub = {
getItem: (k: string) => (k in store ? store[k] : null),
setItem: (k: string, v: string) => {
store[k] = String(v);
},
removeItem: (k: string) => {
delete store[k];
},
clear: () => {
for (const k of Object.keys(store)) delete store[k];
},
key: (i: number) => Object.keys(store)[i] ?? null,
get length() {
return Object.keys(store).length;
},
};
vi.stubGlobal('localStorage', localStorageStub);
vi.stubGlobal('window', { localStorage: localStorageStub });
import {
ANSWERS_KEY_PREFIX,
DRAFT_KEY_PREFIX,
RESULTS_KEY_PREFIX,
clearAllForScene,
clearSubmitted,
readAnswersForSummary,
readSubmittedState,
writeSubmittedAnswers,
writeSubmittedResults,
} from '@/lib/quiz/persistence';
import type { QuestionResult } from '@/lib/quiz/grading';
describe('quiz persistence', () => {
beforeEach(() => {
localStorageStub.clear();
});
it('readSubmittedState returns null when nothing is stored', () => {
expect(readSubmittedState('s1')).toBeNull();
});
it('returns answering state when only answers are stored', () => {
writeSubmittedAnswers('s1', { q1: 'a' });
expect(readSubmittedState('s1')).toEqual({ kind: 'answering', answers: { q1: 'a' } });
});
it('returns reviewing state when both answers and results are stored', () => {
const results: QuestionResult[] = [
{ questionId: 'q1', correct: true, status: 'correct', earned: 1 },
];
writeSubmittedAnswers('s1', { q1: 'a' });
writeSubmittedResults('s1', results);
expect(readSubmittedState('s1')).toEqual({
kind: 'reviewing',
answers: { q1: 'a' },
results,
});
});
it('falls back to answering when results array is empty', () => {
writeSubmittedAnswers('s1', { q1: 'a' });
writeSubmittedResults('s1', []);
expect(readSubmittedState('s1')).toEqual({ kind: 'answering', answers: { q1: 'a' } });
});
it('returns null on corrupt answers JSON', () => {
localStorageStub.setItem(ANSWERS_KEY_PREFIX + 's1', '{invalid');
expect(readSubmittedState('s1')).toBeNull();
});
it('clearSubmitted wipes answers + results but leaves draft intact', () => {
localStorageStub.setItem(DRAFT_KEY_PREFIX + 's1', JSON.stringify({ q1: 'b' }));
writeSubmittedAnswers('s1', { q1: 'a' });
writeSubmittedResults('s1', [
{ questionId: 'q1', correct: true, status: 'correct', earned: 1 },
]);
clearSubmitted('s1');
expect(readSubmittedState('s1')).toBeNull();
expect(localStorageStub.getItem(DRAFT_KEY_PREFIX + 's1')).not.toBeNull();
});
it('clearAllForScene wipes all three keys for a single scene', () => {
localStorageStub.setItem(DRAFT_KEY_PREFIX + 's1', '{}');
writeSubmittedAnswers('s1', { q1: 'a' });
writeSubmittedResults('s1', [
{ questionId: 'q1', correct: true, status: 'correct', earned: 1 },
]);
// unrelated scene should not be touched
writeSubmittedAnswers('s2', { q1: 'z' });
clearAllForScene('s1');
expect(localStorageStub.getItem(DRAFT_KEY_PREFIX + 's1')).toBeNull();
expect(localStorageStub.getItem(ANSWERS_KEY_PREFIX + 's1')).toBeNull();
expect(localStorageStub.getItem(RESULTS_KEY_PREFIX + 's1')).toBeNull();
expect(localStorageStub.getItem(ANSWERS_KEY_PREFIX + 's2')).not.toBeNull();
});
it('readAnswersForSummary prefers submitted over draft', () => {
localStorageStub.setItem(DRAFT_KEY_PREFIX + 's1', JSON.stringify({ q1: 'draft' }));
writeSubmittedAnswers('s1', { q1: 'submitted' });
expect(readAnswersForSummary('s1')).toEqual({ q1: 'submitted' });
});
it('readAnswersForSummary falls back to draft when no submission', () => {
localStorageStub.setItem(DRAFT_KEY_PREFIX + 's1', JSON.stringify({ q1: 'draft' }));
expect(readAnswersForSummary('s1')).toEqual({ q1: 'draft' });
});
it('readAnswersForSummary returns empty object when nothing is stored', () => {
expect(readAnswersForSummary('s1')).toEqual({});
});
it('tolerates corrupt draft JSON when no submission exists', () => {
localStorageStub.setItem(DRAFT_KEY_PREFIX + 's1', '{corrupt');
expect(readAnswersForSummary('s1')).toEqual({});
});
});
|