94 lines
4.2 KiB
TypeScript
94 lines
4.2 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
import type { QuizContent } from '@/lib/types/stage';
|
|
import { addQuestion, updateQuestion } from '@/components/edit/surfaces/quiz/quiz-edit-ops';
|
|
|
|
// Mock the canonical stage store so we can assert write-through.
|
|
const updateScene = vi.fn();
|
|
vi.mock('@/lib/store/stage', () => ({
|
|
useStageStore: { getState: () => ({ updateScene }) },
|
|
}));
|
|
|
|
const { useQuizEditSession } = await import('@/components/edit/surfaces/quiz/quiz-edit-session');
|
|
|
|
function makeContent(): QuizContent {
|
|
return {
|
|
type: 'quiz',
|
|
questions: [{ id: 'q1', type: 'single', question: 'Q?', options: [], answer: [], points: 1 }],
|
|
};
|
|
}
|
|
|
|
describe('useQuizEditSession (auto-save to stage store)', () => {
|
|
beforeEach(() => {
|
|
useQuizEditSession.getState().end();
|
|
updateScene.mockClear();
|
|
});
|
|
|
|
it('seed adopts a baseline without touching the stage store', () => {
|
|
useQuizEditSession.getState().seed('scene-1', makeContent());
|
|
const { sceneId, history } = useQuizEditSession.getState();
|
|
expect(sceneId).toBe('scene-1');
|
|
expect(history?.past).toEqual([]);
|
|
expect(history?.present.questions[0].id).toBe('q1');
|
|
expect(updateScene).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('commit advances history one step, writes through, and produces a NEW content ref', () => {
|
|
useQuizEditSession.getState().seed('scene-1', makeContent());
|
|
const before = useQuizEditSession.getState().history!.present;
|
|
useQuizEditSession.getState().commit(addQuestion(before, 'multiple', 'q2'));
|
|
const { history } = useQuizEditSession.getState();
|
|
expect(history?.past).toHaveLength(1);
|
|
expect(history?.present).not.toBe(before); // ref change → surfaceStateEqual fires
|
|
expect(history?.present.questions.map((q) => q.id)).toEqual(['q1', 'q2']);
|
|
expect(updateScene).toHaveBeenCalledWith(
|
|
'scene-1',
|
|
expect.objectContaining({ content: history!.present }),
|
|
);
|
|
});
|
|
|
|
it('commitText with the same key coalesces into one undo step (every keystroke writes through)', () => {
|
|
useQuizEditSession.getState().seed('scene-1', makeContent());
|
|
const s = () => useQuizEditSession.getState();
|
|
s().commitText(updateQuestion(s().history!.present, 'q1', { question: 'C' }), 'q1:question');
|
|
s().commitText(updateQuestion(s().history!.present, 'q1', { question: 'Ca' }), 'q1:question');
|
|
s().commitText(updateQuestion(s().history!.present, 'q1', { question: 'Cap' }), 'q1:question');
|
|
expect(s().history?.past).toHaveLength(1); // one burst → one step
|
|
expect(s().history?.present.questions[0].question).toBe('Cap');
|
|
expect(updateScene).toHaveBeenCalledTimes(3); // but all three persisted
|
|
});
|
|
|
|
it('commitText with a different key starts a new undo step', () => {
|
|
useQuizEditSession.getState().seed('scene-1', makeContent());
|
|
const s = () => useQuizEditSession.getState();
|
|
s().commitText(updateQuestion(s().history!.present, 'q1', { question: 'X' }), 'q1:question');
|
|
s().commitText(updateQuestion(s().history!.present, 'q1', { analysis: 'Y' }), 'q1:analysis');
|
|
expect(s().history?.past).toHaveLength(2);
|
|
});
|
|
|
|
it('a discrete commit closes the current text burst', () => {
|
|
useQuizEditSession.getState().seed('scene-1', makeContent());
|
|
const s = () => useQuizEditSession.getState();
|
|
s().commitText(updateQuestion(s().history!.present, 'q1', { question: 'X' }), 'q1:question');
|
|
s().commit(addQuestion(s().history!.present, 'single', 'q2'));
|
|
// Another text edit with the SAME key must NOT merge into the pre-commit burst.
|
|
s().commitText(updateQuestion(s().history!.present, 'q1', { question: 'XY' }), 'q1:question');
|
|
expect(s().history?.past).toHaveLength(3);
|
|
});
|
|
|
|
it('undo / redo write the restored snapshot through to the stage store', () => {
|
|
useQuizEditSession.getState().seed('scene-1', makeContent());
|
|
const s = () => useQuizEditSession.getState();
|
|
const base = s().history!.present;
|
|
s().commit(addQuestion(base, 'single', 'q2'));
|
|
updateScene.mockClear();
|
|
s().undo();
|
|
expect(s().history?.present).toBe(base);
|
|
expect(updateScene).toHaveBeenLastCalledWith(
|
|
'scene-1',
|
|
expect.objectContaining({ content: base }),
|
|
);
|
|
s().redo();
|
|
expect(s().history?.present.questions.map((q) => q.id)).toEqual(['q1', 'q2']);
|
|
});
|
|
});
|