Add import/export
This commit is contained in:
parent
02ecca7598
commit
667c490537
9 changed files with 2261 additions and 54 deletions
539
tests/components/ImportQuizzesModal.test.tsx
Normal file
539
tests/components/ImportQuizzesModal.test.tsx
Normal file
|
|
@ -0,0 +1,539 @@
|
|||
import React from 'react';
|
||||
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { ImportQuizzesModal } from '../../components/ImportQuizzesModal';
|
||||
import type { ExportedQuiz, QuizExportFile } from '../../types';
|
||||
|
||||
const createMockExportFile = (quizzes: ExportedQuiz[] = []): QuizExportFile => ({
|
||||
version: 1,
|
||||
exportedAt: '2024-01-15T10:00:00.000Z',
|
||||
quizzes,
|
||||
});
|
||||
|
||||
const createMockQuiz = (overrides?: Partial<ExportedQuiz>): ExportedQuiz => ({
|
||||
title: 'Test Quiz',
|
||||
source: 'manual',
|
||||
questions: [
|
||||
{
|
||||
id: 'q1',
|
||||
text: 'What is 2+2?',
|
||||
timeLimit: 20,
|
||||
options: [
|
||||
{ text: '3', isCorrect: false, shape: 'triangle', color: 'red' },
|
||||
{ text: '4', isCorrect: true, shape: 'diamond', color: 'blue' },
|
||||
],
|
||||
},
|
||||
],
|
||||
...overrides,
|
||||
});
|
||||
|
||||
describe('ImportQuizzesModal', () => {
|
||||
const defaultProps = {
|
||||
isOpen: true,
|
||||
onClose: vi.fn(),
|
||||
onImport: vi.fn(),
|
||||
parseFile: vi.fn(),
|
||||
importing: false,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('rendering', () => {
|
||||
it('renders nothing when isOpen is false', () => {
|
||||
render(<ImportQuizzesModal {...defaultProps} isOpen={false} />);
|
||||
expect(screen.queryByText('Import Quizzes')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders modal when isOpen is true', () => {
|
||||
render(<ImportQuizzesModal {...defaultProps} />);
|
||||
expect(screen.getByText('Import Quizzes')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows upload step by default', () => {
|
||||
render(<ImportQuizzesModal {...defaultProps} />);
|
||||
expect(screen.getByText(/Select a Kaboot export file/)).toBeInTheDocument();
|
||||
expect(screen.getByText(/Drag & drop or click to select/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows file type hint', () => {
|
||||
render(<ImportQuizzesModal {...defaultProps} />);
|
||||
expect(screen.getByText(/Accepts .json export files/)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('file upload - happy path', () => {
|
||||
it('parses file on drop and shows quiz selection', async () => {
|
||||
const mockExport = createMockExportFile([createMockQuiz()]);
|
||||
defaultProps.parseFile.mockResolvedValueOnce(mockExport);
|
||||
|
||||
render(<ImportQuizzesModal {...defaultProps} />);
|
||||
|
||||
const dropZone = screen.getByText(/Drag & drop or click to select/).closest('div')!;
|
||||
const file = new File([JSON.stringify(mockExport)], 'export.json', { type: 'application/json' });
|
||||
|
||||
await fireEvent.drop(dropZone, {
|
||||
dataTransfer: { files: [file] },
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Test Quiz')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('shows all quizzes from export file', async () => {
|
||||
const mockExport = createMockExportFile([
|
||||
createMockQuiz({ title: 'Quiz 1' }),
|
||||
createMockQuiz({ title: 'Quiz 2' }),
|
||||
createMockQuiz({ title: 'Quiz 3' }),
|
||||
]);
|
||||
defaultProps.parseFile.mockResolvedValueOnce(mockExport);
|
||||
|
||||
render(<ImportQuizzesModal {...defaultProps} />);
|
||||
|
||||
const dropZone = screen.getByText(/Drag & drop or click to select/).closest('div')!;
|
||||
const file = new File([JSON.stringify(mockExport)], 'export.json', { type: 'application/json' });
|
||||
|
||||
await fireEvent.drop(dropZone, { dataTransfer: { files: [file] } });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Quiz 1')).toBeInTheDocument();
|
||||
expect(screen.getByText('Quiz 2')).toBeInTheDocument();
|
||||
expect(screen.getByText('Quiz 3')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('selects all quizzes by default', async () => {
|
||||
const mockExport = createMockExportFile([
|
||||
createMockQuiz({ title: 'Quiz 1' }),
|
||||
createMockQuiz({ title: 'Quiz 2' }),
|
||||
]);
|
||||
defaultProps.parseFile.mockResolvedValueOnce(mockExport);
|
||||
|
||||
render(<ImportQuizzesModal {...defaultProps} />);
|
||||
|
||||
const dropZone = screen.getByText(/Drag & drop or click to select/).closest('div')!;
|
||||
const file = new File([JSON.stringify(mockExport)], 'export.json', { type: 'application/json' });
|
||||
|
||||
await fireEvent.drop(dropZone, { dataTransfer: { files: [file] } });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('2 of 2 selected')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('shows export date', async () => {
|
||||
const mockExport = createMockExportFile([createMockQuiz()]);
|
||||
defaultProps.parseFile.mockResolvedValueOnce(mockExport);
|
||||
|
||||
render(<ImportQuizzesModal {...defaultProps} />);
|
||||
|
||||
const dropZone = screen.getByText(/Drag & drop or click to select/).closest('div')!;
|
||||
const file = new File([JSON.stringify(mockExport)], 'export.json', { type: 'application/json' });
|
||||
|
||||
await fireEvent.drop(dropZone, { dataTransfer: { files: [file] } });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/Exported/)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('shows AI badge for ai_generated quizzes', async () => {
|
||||
const mockExport = createMockExportFile([
|
||||
createMockQuiz({ title: 'AI Quiz', source: 'ai_generated', aiTopic: 'Science' }),
|
||||
]);
|
||||
defaultProps.parseFile.mockResolvedValueOnce(mockExport);
|
||||
|
||||
render(<ImportQuizzesModal {...defaultProps} />);
|
||||
|
||||
const dropZone = screen.getByText(/Drag & drop or click to select/).closest('div')!;
|
||||
const file = new File([JSON.stringify(mockExport)], 'export.json', { type: 'application/json' });
|
||||
|
||||
await fireEvent.drop(dropZone, { dataTransfer: { files: [file] } });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('AI')).toBeInTheDocument();
|
||||
expect(screen.getByText(/Science/)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('shows Manual badge for manual quizzes', async () => {
|
||||
const mockExport = createMockExportFile([
|
||||
createMockQuiz({ title: 'Manual Quiz', source: 'manual' }),
|
||||
]);
|
||||
defaultProps.parseFile.mockResolvedValueOnce(mockExport);
|
||||
|
||||
render(<ImportQuizzesModal {...defaultProps} />);
|
||||
|
||||
const dropZone = screen.getByText(/Drag & drop or click to select/).closest('div')!;
|
||||
const file = new File([JSON.stringify(mockExport)], 'export.json', { type: 'application/json' });
|
||||
|
||||
await fireEvent.drop(dropZone, { dataTransfer: { files: [file] } });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Manual')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('file upload - unhappy path', () => {
|
||||
it('shows error for non-JSON files', async () => {
|
||||
render(<ImportQuizzesModal {...defaultProps} />);
|
||||
|
||||
const dropZone = screen.getByText(/Drag & drop or click to select/).closest('div')!;
|
||||
const file = new File(['content'], 'export.txt', { type: 'text/plain' });
|
||||
|
||||
await fireEvent.drop(dropZone, { dataTransfer: { files: [file] } });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Please select a JSON file')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('shows error when parseFile throws', async () => {
|
||||
defaultProps.parseFile.mockRejectedValueOnce(new Error('Invalid export file format'));
|
||||
|
||||
render(<ImportQuizzesModal {...defaultProps} />);
|
||||
|
||||
const dropZone = screen.getByText(/Drag & drop or click to select/).closest('div')!;
|
||||
const file = new File(['{}'], 'export.json', { type: 'application/json' });
|
||||
|
||||
await fireEvent.drop(dropZone, { dataTransfer: { files: [file] } });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Invalid export file format')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('shows generic error for unknown parse failures', async () => {
|
||||
defaultProps.parseFile.mockRejectedValueOnce('unknown error');
|
||||
|
||||
render(<ImportQuizzesModal {...defaultProps} />);
|
||||
|
||||
const dropZone = screen.getByText(/Drag & drop or click to select/).closest('div')!;
|
||||
const file = new File(['{}'], 'export.json', { type: 'application/json' });
|
||||
|
||||
await fireEvent.drop(dropZone, { dataTransfer: { files: [file] } });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Failed to parse file')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('handles empty file drop event', async () => {
|
||||
render(<ImportQuizzesModal {...defaultProps} />);
|
||||
|
||||
const dropZone = screen.getByText(/Drag & drop or click to select/).closest('div')!;
|
||||
|
||||
await fireEvent.drop(dropZone, { dataTransfer: { files: [] } });
|
||||
|
||||
expect(defaultProps.parseFile).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('quiz selection', () => {
|
||||
const setupWithQuizzes = async (quizzes: ExportedQuiz[] = [createMockQuiz()]) => {
|
||||
const mockExport = createMockExportFile(quizzes);
|
||||
defaultProps.parseFile.mockResolvedValueOnce(mockExport);
|
||||
|
||||
render(<ImportQuizzesModal {...defaultProps} />);
|
||||
|
||||
const dropZone = screen.getByText(/Drag & drop or click to select/).closest('div')!;
|
||||
const file = new File([JSON.stringify(mockExport)], 'export.json', { type: 'application/json' });
|
||||
|
||||
await fireEvent.drop(dropZone, { dataTransfer: { files: [file] } });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(quizzes[0].title)).toBeInTheDocument();
|
||||
});
|
||||
};
|
||||
|
||||
it('toggles quiz selection on click', async () => {
|
||||
await setupWithQuizzes([createMockQuiz({ title: 'Toggle Test' })]);
|
||||
|
||||
const quizCard = screen.getByText('Toggle Test').closest('[class*="cursor-pointer"]')!;
|
||||
|
||||
expect(screen.getByText('1 of 1 selected')).toBeInTheDocument();
|
||||
|
||||
await fireEvent.click(quizCard);
|
||||
|
||||
expect(screen.getByText('0 of 1 selected')).toBeInTheDocument();
|
||||
|
||||
await fireEvent.click(quizCard);
|
||||
|
||||
expect(screen.getByText('1 of 1 selected')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('select all button selects all quizzes', async () => {
|
||||
await setupWithQuizzes([
|
||||
createMockQuiz({ title: 'Quiz 1' }),
|
||||
createMockQuiz({ title: 'Quiz 2' }),
|
||||
]);
|
||||
|
||||
const quiz1Card = screen.getByText('Quiz 1').closest('[class*="cursor-pointer"]')!;
|
||||
await fireEvent.click(quiz1Card);
|
||||
|
||||
expect(screen.getByText('1 of 2 selected')).toBeInTheDocument();
|
||||
|
||||
await fireEvent.click(screen.getByText('Select All'));
|
||||
|
||||
expect(screen.getByText('2 of 2 selected')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('deselect all when all selected', async () => {
|
||||
await setupWithQuizzes([
|
||||
createMockQuiz({ title: 'Quiz 1' }),
|
||||
createMockQuiz({ title: 'Quiz 2' }),
|
||||
]);
|
||||
|
||||
expect(screen.getByText('2 of 2 selected')).toBeInTheDocument();
|
||||
expect(screen.getByText('Deselect All')).toBeInTheDocument();
|
||||
|
||||
await fireEvent.click(screen.getByText('Deselect All'));
|
||||
|
||||
expect(screen.getByText('0 of 2 selected')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('back button returns to upload step', async () => {
|
||||
await setupWithQuizzes();
|
||||
|
||||
await fireEvent.click(screen.getByText('Back'));
|
||||
|
||||
expect(screen.getByText(/Drag & drop or click to select/)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('import action', () => {
|
||||
const setupWithQuizzes = async (quizzes: ExportedQuiz[] = [createMockQuiz()]) => {
|
||||
const mockExport = createMockExportFile(quizzes);
|
||||
defaultProps.parseFile.mockResolvedValueOnce(mockExport);
|
||||
|
||||
render(<ImportQuizzesModal {...defaultProps} />);
|
||||
|
||||
const dropZone = screen.getByText(/Drag & drop or click to select/).closest('div')!;
|
||||
const file = new File([JSON.stringify(mockExport)], 'export.json', { type: 'application/json' });
|
||||
|
||||
await fireEvent.drop(dropZone, { dataTransfer: { files: [file] } });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(quizzes[0].title)).toBeInTheDocument();
|
||||
});
|
||||
};
|
||||
|
||||
it('calls onImport with selected quizzes', async () => {
|
||||
const quizzes = [
|
||||
createMockQuiz({ title: 'Quiz 1' }),
|
||||
createMockQuiz({ title: 'Quiz 2' }),
|
||||
];
|
||||
await setupWithQuizzes(quizzes);
|
||||
|
||||
const quiz1Card = screen.getByText('Quiz 1').closest('[class*="cursor-pointer"]')!;
|
||||
await fireEvent.click(quiz1Card);
|
||||
|
||||
await fireEvent.click(screen.getByText(/Import 1 Quiz/));
|
||||
|
||||
expect(defaultProps.onImport).toHaveBeenCalledWith([quizzes[1]]);
|
||||
});
|
||||
|
||||
it('calls onImport with all selected quizzes', async () => {
|
||||
const quizzes = [
|
||||
createMockQuiz({ title: 'Quiz 1' }),
|
||||
createMockQuiz({ title: 'Quiz 2' }),
|
||||
];
|
||||
await setupWithQuizzes(quizzes);
|
||||
|
||||
await fireEvent.click(screen.getByText(/Import 2 Quizzes/));
|
||||
|
||||
expect(defaultProps.onImport).toHaveBeenCalledWith(quizzes);
|
||||
});
|
||||
|
||||
it('disables import button when no quizzes selected', async () => {
|
||||
await setupWithQuizzes([createMockQuiz({ title: 'Quiz 1' })]);
|
||||
|
||||
const quizCard = screen.getByText('Quiz 1').closest('[class*="cursor-pointer"]')!;
|
||||
await fireEvent.click(quizCard);
|
||||
|
||||
const importButton = screen.getByRole('button', { name: /Import 0 Quiz/ });
|
||||
expect(importButton).toBeDisabled();
|
||||
});
|
||||
|
||||
it('closes modal after successful import', async () => {
|
||||
defaultProps.onImport.mockResolvedValueOnce(undefined);
|
||||
await setupWithQuizzes();
|
||||
|
||||
await fireEvent.click(screen.getByText(/Import 1 Quiz/));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(defaultProps.onClose).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('shows importing state', async () => {
|
||||
const quizzes = [createMockQuiz()];
|
||||
const mockExport = createMockExportFile(quizzes);
|
||||
defaultProps.parseFile.mockResolvedValueOnce(mockExport);
|
||||
|
||||
const { rerender } = render(<ImportQuizzesModal {...defaultProps} />);
|
||||
|
||||
const dropZone = screen.getByText(/Drag & drop or click to select/).closest('div')!;
|
||||
const file = new File([JSON.stringify(mockExport)], 'export.json', { type: 'application/json' });
|
||||
|
||||
await fireEvent.drop(dropZone, { dataTransfer: { files: [file] } });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(quizzes[0].title)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
rerender(<ImportQuizzesModal {...defaultProps} importing={true} />);
|
||||
|
||||
expect(screen.getByText('Importing...')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('disables quiz selection while importing', async () => {
|
||||
const quizzes = [createMockQuiz()];
|
||||
const mockExport = createMockExportFile(quizzes);
|
||||
defaultProps.parseFile.mockResolvedValueOnce(mockExport);
|
||||
|
||||
const { rerender } = render(<ImportQuizzesModal {...defaultProps} />);
|
||||
|
||||
const dropZone = screen.getByText(/Drag & drop or click to select/).closest('div')!;
|
||||
const file = new File([JSON.stringify(mockExport)], 'export.json', { type: 'application/json' });
|
||||
|
||||
await fireEvent.drop(dropZone, { dataTransfer: { files: [file] } });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(quizzes[0].title)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
rerender(<ImportQuizzesModal {...defaultProps} importing={true} />);
|
||||
|
||||
const quizCard = screen.getByText(quizzes[0].title).closest('[class*="cursor-pointer"]')!;
|
||||
expect(quizCard).toHaveClass('cursor-not-allowed');
|
||||
});
|
||||
});
|
||||
|
||||
describe('modal interactions', () => {
|
||||
it('calls onClose when X button clicked', async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<ImportQuizzesModal {...defaultProps} />);
|
||||
|
||||
const closeButtons = screen.getAllByRole('button');
|
||||
const xButton = closeButtons.find(btn => btn.querySelector('svg'));
|
||||
|
||||
if (xButton) {
|
||||
await user.click(xButton);
|
||||
expect(defaultProps.onClose).toHaveBeenCalled();
|
||||
}
|
||||
});
|
||||
|
||||
it('calls onClose when backdrop clicked', async () => {
|
||||
render(<ImportQuizzesModal {...defaultProps} />);
|
||||
|
||||
const backdrop = document.querySelector('.fixed.inset-0');
|
||||
expect(backdrop).toBeInTheDocument();
|
||||
fireEvent.click(backdrop!);
|
||||
|
||||
expect(defaultProps.onClose).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('does not close when modal content clicked', async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<ImportQuizzesModal {...defaultProps} />);
|
||||
|
||||
await user.click(screen.getByText('Import Quizzes'));
|
||||
|
||||
expect(defaultProps.onClose).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('resets state when closed via onClose', async () => {
|
||||
const mockExport = createMockExportFile([createMockQuiz()]);
|
||||
const mockOnClose = vi.fn();
|
||||
defaultProps.parseFile.mockResolvedValue(mockExport);
|
||||
|
||||
render(<ImportQuizzesModal {...defaultProps} onClose={mockOnClose} />);
|
||||
|
||||
const dropZone = screen.getByText(/Drag & drop or click to select/).closest('div')!;
|
||||
const file = new File([JSON.stringify(mockExport)], 'export.json', { type: 'application/json' });
|
||||
|
||||
await fireEvent.drop(dropZone, { dataTransfer: { files: [file] } });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Test Quiz')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
await fireEvent.click(screen.getByText('Back'));
|
||||
|
||||
expect(screen.getByText(/Drag & drop or click to select/)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('drag and drop visual feedback', () => {
|
||||
it('shows visual feedback on drag over', async () => {
|
||||
render(<ImportQuizzesModal {...defaultProps} />);
|
||||
|
||||
const dropZone = screen.getByText(/Drag & drop or click to select/).closest('div')!;
|
||||
|
||||
fireEvent.dragOver(dropZone);
|
||||
|
||||
expect(screen.getByText('Drop file here')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('removes visual feedback on drag leave', async () => {
|
||||
render(<ImportQuizzesModal {...defaultProps} />);
|
||||
|
||||
const dropZone = screen.getByText(/Drag & drop or click to select/).closest('div')!;
|
||||
|
||||
fireEvent.dragOver(dropZone);
|
||||
expect(screen.getByText('Drop file here')).toBeInTheDocument();
|
||||
|
||||
fireEvent.dragLeave(dropZone);
|
||||
expect(screen.getByText(/Drag & drop or click to select/)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('question count display', () => {
|
||||
it('shows singular for 1 question', async () => {
|
||||
const mockExport = createMockExportFile([
|
||||
createMockQuiz({ title: 'Single Q Quiz' }),
|
||||
]);
|
||||
defaultProps.parseFile.mockResolvedValueOnce(mockExport);
|
||||
|
||||
render(<ImportQuizzesModal {...defaultProps} />);
|
||||
|
||||
const dropZone = screen.getByText(/Drag & drop or click to select/).closest('div')!;
|
||||
const file = new File([JSON.stringify(mockExport)], 'export.json', { type: 'application/json' });
|
||||
|
||||
await fireEvent.drop(dropZone, { dataTransfer: { files: [file] } });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('1 question')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('shows plural for multiple questions', async () => {
|
||||
const mockExport = createMockExportFile([
|
||||
createMockQuiz({
|
||||
title: 'Multi Q Quiz',
|
||||
questions: [
|
||||
{ id: 'q1', text: 'Q1', timeLimit: 20, options: [{ text: 'A', isCorrect: true, shape: 'triangle', color: 'red' }, { text: 'B', isCorrect: false, shape: 'diamond', color: 'blue' }] },
|
||||
{ id: 'q2', text: 'Q2', timeLimit: 20, options: [{ text: 'A', isCorrect: true, shape: 'triangle', color: 'red' }, { text: 'B', isCorrect: false, shape: 'diamond', color: 'blue' }] },
|
||||
],
|
||||
}),
|
||||
]);
|
||||
defaultProps.parseFile.mockResolvedValueOnce(mockExport);
|
||||
|
||||
render(<ImportQuizzesModal {...defaultProps} />);
|
||||
|
||||
const dropZone = screen.getByText(/Drag & drop or click to select/).closest('div')!;
|
||||
const file = new File([JSON.stringify(mockExport)], 'export.json', { type: 'application/json' });
|
||||
|
||||
await fireEvent.drop(dropZone, { dataTransfer: { files: [file] } });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('2 questions')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue