Files
openmaic/OpenMAIC/tests/edit/surfaces/quiz/quiz-edit-session.test.ts
2026-08-16 14:58:47 +08:00

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']);
});
});