Remove support for <img > tag, tests passing

This commit is contained in:
C. Fuhrman 2024-09-15 00:34:41 -04:00
parent 3527643164
commit 6f43720649
20 changed files with 404 additions and 318 deletions

View file

@ -2,5 +2,4 @@ import { GIFTQuestion } from 'gift-pegjs';
export interface QuestionType { export interface QuestionType {
question: GIFTQuestion; question: GIFTQuestion;
image: string;
} }

View file

@ -1,38 +1,42 @@
/*//QuestionType.test.tsx //QuestionType.test.tsx
import { GIFTQuestion } from 'gift-pegjs'; import { GIFTQuestion } from 'gift-pegjs';
import { QuestionType } from '../../Types/QuestionType'; import { QuestionType } from '../../Types/QuestionType';
const sampleStem = 'Sample question stem';
const options = ['Option A', 'Option B'];
const sampleFormat = 'plain';
const sampleType = 'MC';
const sampleTitle = 'Sample Question';
const mockQuestion: GIFTQuestion = { const mockQuestion: GIFTQuestion = {
id: '1', id: '1',
type: 'MC', type: sampleType,
stem: { format: 'plain', text: 'Sample Question' }, stem: { format: sampleFormat, text: sampleStem },
title: 'Sample Question', title: sampleTitle,
hasEmbeddedAnswers: false, hasEmbeddedAnswers: false,
globalFeedback: null, globalFeedback: null,
choices: [ choices: [
{ text: { format: 'plain', text: 'Option A' }, isCorrect: true, weight: 1, feedback: null }, { text: { format: sampleFormat, text: options[0] }, isCorrect: true, weight: 1, feedback: null },
{ text: { format: 'plain', text: 'Option B' }, isCorrect: false, weight: 0, feedback: null }, { text: { format: sampleFormat, text: options[1] }, isCorrect: false, weight: 0, feedback: null },
], ],
}; };
const mockQuestionType: QuestionType = { const mockQuestionType: QuestionType = {
question: mockQuestion, question: mockQuestion,
image: 'sample-image-url',
}; };
describe('QuestionType', () => { describe('QuestionType', () => {
test('has the expected structure', () => { test('has the expected structure', () => {
expect(mockQuestionType).toEqual(expect.objectContaining({ expect(mockQuestionType).toEqual(expect.objectContaining({
question: expect.any(Object), question: expect.any(Object),
image: expect.any(String),
})); }));
expect(mockQuestionType.question).toEqual(expect.objectContaining({ expect(mockQuestionType.question).toEqual(expect.objectContaining({
id: expect.any(String), id: expect.any(String),
type: expect.any(String), type: expect.any(String),
stem: expect.objectContaining({ stem: expect.objectContaining({
format: expect.any(String), format: sampleFormat,
text: expect.any(String), text: sampleStem,
}), }),
title: expect.any(String), title: expect.any(String),
hasEmbeddedAnswers: expect.any(Boolean), hasEmbeddedAnswers: expect.any(Boolean),
@ -40,4 +44,4 @@ describe('QuestionType', () => {
choices: expect.any(Array), choices: expect.any(Array),
})); }));
}); });
});*/ });

View file

@ -1,4 +1,4 @@
/*//QuizType.test.tsx //QuizType.test.tsx
import { QuizType } from "../../Types/QuizType"; import { QuizType } from "../../Types/QuizType";
export function isQuizValid(quiz: QuizType): boolean { export function isQuizValid(quiz: QuizType): boolean {
return quiz.title.length > 0 && quiz.content.length > 0; return quiz.title.length > 0 && quiz.content.length > 0;
@ -8,6 +8,10 @@ describe('isQuizValid function', () => {
it('returns true for a valid quiz', () => { it('returns true for a valid quiz', () => {
const validQuiz: QuizType = { const validQuiz: QuizType = {
_id: '1', _id: '1',
folderId: 'test',
userId: 'user',
created_at: new Date('2021-10-01'),
updated_at: new Date('2021-10-02'),
title: 'Sample Quiz', title: 'Sample Quiz',
content: ['Question 1', 'Question 2'], content: ['Question 1', 'Question 2'],
}; };
@ -19,7 +23,11 @@ describe('isQuizValid function', () => {
it('returns false for an invalid quiz with an empty title', () => { it('returns false for an invalid quiz with an empty title', () => {
const invalidQuiz: QuizType = { const invalidQuiz: QuizType = {
_id: '2', _id: '2',
folderId: 'test',
userId: 'user',
title: '', title: '',
created_at: new Date('2021-10-01'),
updated_at: new Date('2021-10-02'),
content: ['Question 1', 'Question 2'], content: ['Question 1', 'Question 2'],
}; };
@ -29,12 +37,16 @@ describe('isQuizValid function', () => {
it('returns false for an invalid quiz with no questions', () => { it('returns false for an invalid quiz with no questions', () => {
const invalidQuiz: QuizType = { const invalidQuiz: QuizType = {
_id: '3', _id: '2',
title: 'Sample Quiz', folderId: 'test',
userId: 'user',
title: 'sample',
created_at: new Date('2021-10-01'),
updated_at: new Date('2021-10-02'),
content: [], content: [],
}; };
const result = isQuizValid(invalidQuiz); const result = isQuizValid(invalidQuiz);
expect(result).toBe(false); expect(result).toBe(false);
}); });
});*/ });

View file

@ -26,25 +26,25 @@ describe('GIFTTemplatePreview Component', () => {
const previewContainer = screen.getByTestId('preview-container'); const previewContainer = screen.getByTestId('preview-container');
expect(previewContainer).toBeInTheDocument(); expect(previewContainer).toBeInTheDocument();
}); });
it('renders images correctly', () => { // it('renders images correctly', () => {
const questions = [ // const questions = [
'Question 1', // 'Question 1',
'<img src="image1.jpg" alt="Image 1">', // '<img src="image1.jpg" alt="Image 1">',
'Question 2', // 'Question 2',
'<img src="image2.jpg" alt="Image 2">', // '<img src="image2.jpg" alt="Image 2">',
]; // ];
const { getByAltText } = render(<GIFTTemplatePreview questions={questions} />); // const { getByAltText } = render(<GIFTTemplatePreview questions={questions} />);
const image1 = getByAltText('Image 1'); // const image1 = getByAltText('Image 1');
const image2 = getByAltText('Image 2'); // const image2 = getByAltText('Image 2');
expect(image1).toBeInTheDocument(); // expect(image1).toBeInTheDocument();
expect(image2).toBeInTheDocument(); // expect(image2).toBeInTheDocument();
}); // });
it('renders non-images correctly', () => { // it('renders non-images correctly', () => {
const questions = ['Question 1', 'Question 2']; // const questions = ['Question 1', 'Question 2'];
const { queryByAltText } = render(<GIFTTemplatePreview questions={questions} />); // const { queryByAltText } = render(<GIFTTemplatePreview questions={questions} />);
const image1 = queryByAltText('Image 1'); // const image1 = queryByAltText('Image 1');
const image2 = queryByAltText('Image 2'); // const image2 = queryByAltText('Image 2');
expect(image1).toBeNull(); // expect(image1).toBeNull();
expect(image2).toBeNull(); // expect(image2).toBeNull();
}); // });
}); });

View file

@ -1,10 +1,11 @@
/*// ShortAnswerQuestion.test.tsx // ShortAnswerQuestion.test.tsx
import { render, screen, fireEvent } from '@testing-library/react'; import { render, screen, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom'; import '@testing-library/jest-dom';
import ShortAnswerQuestion from '../../../../components/Questions/ShortAnswerQuestion/ShortAnswerQuestion'; import ShortAnswerQuestion from '../../../../components/Questions/ShortAnswerQuestion/ShortAnswerQuestion';
describe('ShortAnswerQuestion Component', () => { describe('ShortAnswerQuestion Component', () => {
const mockHandleSubmitAnswer = jest.fn(); const mockHandleSubmitAnswer = jest.fn();
const sampleStem = 'Sample question stem';
const sampleProps = { const sampleProps = {
questionTitle: 'Sample Question', questionTitle: 'Sample Question',
@ -34,14 +35,12 @@ describe('ShortAnswerQuestion Component', () => {
}; };
beforeEach(() => { beforeEach(() => {
render(<ShortAnswerQuestion questionContent={{text: '', format: 'plain'}} {...sampleProps} />); render(<ShortAnswerQuestion questionContent={{text: sampleStem, format: 'plain'}} {...sampleProps} />);
}); });
it('renders correctly', () => { it('renders correctly', () => {
expect(screen.getByText('Sample Question')).toBeInTheDocument(); expect(screen.getByText(sampleStem)).toBeInTheDocument();
expect(screen.getByTestId('text-input')).toBeInTheDocument(); expect(screen.getByTestId('text-input')).toBeInTheDocument();
expect(screen.getByText('Répondre')).toBeInTheDocument(); expect(screen.getByText('Répondre')).toBeInTheDocument();
}); });
@ -77,4 +76,4 @@ describe('ShortAnswerQuestion Component', () => {
expect(mockHandleSubmitAnswer).toHaveBeenCalledWith('User Input'); expect(mockHandleSubmitAnswer).toHaveBeenCalledWith('User Input');
}); });
});*/ });

View file

@ -1,10 +1,11 @@
/*// TrueFalseQuestion.test.tsx // TrueFalseQuestion.test.tsx
import { render, fireEvent, screen } from '@testing-library/react'; import { render, fireEvent, screen } from '@testing-library/react';
import '@testing-library/jest-dom'; import '@testing-library/jest-dom';
import TrueFalseQuestion from '../../../../components/Questions/TrueFalseQuestion/TrueFalseQuestion'; import TrueFalseQuestion from '../../../../components/Questions/TrueFalseQuestion/TrueFalseQuestion';
describe('TrueFalseQuestion Component', () => { describe('TrueFalseQuestion Component', () => {
const mockHandleSubmitAnswer = jest.fn(); const mockHandleSubmitAnswer = jest.fn();
const sampleStem = 'Sample question stem';
const sampleProps = { const sampleProps = {
questionTitle: 'Sample True/False Question', questionTitle: 'Sample True/False Question',
@ -14,15 +15,13 @@ describe('TrueFalseQuestion Component', () => {
}; };
beforeEach(() => { beforeEach(() => {
render(<TrueFalseQuestion questionContent={{text: '', format: 'plain'}} {...sampleProps} />); render(<TrueFalseQuestion questionContent={{text: sampleStem, format: 'plain'}} {...sampleProps} />);
}); });
it('renders correctly', () => { it('renders correctly', () => {
expect(screen.getByText('Sample True/False Question')).toBeInTheDocument(); expect(screen.getByText(sampleStem)).toBeInTheDocument();
expect(screen.getByText('Vrai')).toBeInTheDocument(); expect(screen.getByText('Vrai')).toBeInTheDocument();
expect(screen.getByText('Faux')).toBeInTheDocument(); expect(screen.getByText('Faux')).toBeInTheDocument();
expect(screen.getByText('Répondre')).toBeInTheDocument(); expect(screen.getByText('Répondre')).toBeInTheDocument();
}); });
@ -61,4 +60,4 @@ describe('TrueFalseQuestion Component', () => {
expect(mockHandleSubmitAnswer).toHaveBeenCalledWith(false); expect(mockHandleSubmitAnswer).toHaveBeenCalledWith(false);
}); });
});*/ });

View file

@ -1,126 +1,141 @@
import { render, screen, fireEvent } from '@testing-library/react'; import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import '@testing-library/jest-dom'; import '@testing-library/jest-dom';
import { MemoryRouter } from 'react-router-dom';
import { QuestionType } from '../../../../Types/QuestionType'; import { QuestionType } from '../../../../Types/QuestionType';
import StudentModeQuiz from '../../../../components/StudentModeQuiz/StudentModeQuiz'; import StudentModeQuiz from '../../../../components/StudentModeQuiz/StudentModeQuiz';
const mockQuestions: QuestionType[] = [
{
question: {
id: '1',
type: 'MC',
stem: { format: 'plain', text: 'Sample Question 1' },
title: 'Sample Question 1',
hasEmbeddedAnswers: false,
globalFeedback: null,
choices: [
{ text: { format: 'plain', text: 'Option A' }, isCorrect: true, weight: 1, feedback: null },
{ text: { format: 'plain', text: 'Option B' }, isCorrect: false, weight: 0, feedback: null },
],
},
},
{
question: {
id: '2',
type: 'TF',
stem: { format: 'plain', text: 'Sample Question 2' },
isTrue: true,
incorrectFeedback: null,
correctFeedback: null,
title: 'Question 2',
hasEmbeddedAnswers: false,
globalFeedback: null,
},
},
];
const mockSubmitAnswer = jest.fn();
const mockDisconnectWebSocket = jest.fn();
describe('StudentModeQuiz', () => { describe('StudentModeQuiz', () => {
const mockQuestions: QuestionType[] = [
{
question: {
id: '1',
type: 'MC',
stem: { format: 'plain', text: 'Sample Question 1' },
title: 'Sample Question 1',
hasEmbeddedAnswers: false,
globalFeedback: null,
choices: [
{ text: { format: 'plain', text: 'Option A' }, isCorrect: true, weight: 1, feedback: null },
{ text: { format: 'plain', text: 'Option B' }, isCorrect: false, weight: 0, feedback: null },
],
},
image: '<img src="sample-image-url" alt="Sample Image" />',
},
{
question: {
id: '2',
type: 'TF',
stem: { format: 'plain', text: 'Sample Question 2' },
isTrue: true,
incorrectFeedback: null,
correctFeedback: null,
title: 'Question 2',
hasEmbeddedAnswers: false,
globalFeedback: null,
},
image: 'sample-image-url-2',
},
];
const mockSubmitAnswer = jest.fn();
const mockDisconnectWebSocket = jest.fn();
test('renders the initial question', async () => { test('renders the initial question', async () => {
render( render(
<StudentModeQuiz <MemoryRouter>
questions={mockQuestions} <StudentModeQuiz
submitAnswer={mockSubmitAnswer} questions={mockQuestions}
disconnectWebSocket={mockDisconnectWebSocket} submitAnswer={mockSubmitAnswer}
/> disconnectWebSocket={mockDisconnectWebSocket}
/>
</MemoryRouter>
); );
expect(screen.getByText('Sample Question 1')).toBeInTheDocument(); // wait for the question to be rendered
expect(screen.getByText('Option A')).toBeInTheDocument(); await waitFor(() => {
expect(screen.getByText('Option B')).toBeInTheDocument(); expect(screen.getByText('Sample Question 1')).toBeInTheDocument();
expect(screen.getByText('Déconnexion')).toBeInTheDocument(); expect(screen.getByText('Option A')).toBeInTheDocument();
expect(screen.getByText('Option B')).toBeInTheDocument();
expect(screen.getByText('Quitter')).toBeInTheDocument();
});
}); });
test('handles answer submission text', () => { test('handles answer submission text', async () => {
render( render(
<StudentModeQuiz <MemoryRouter>
<StudentModeQuiz
questions={mockQuestions} questions={mockQuestions}
submitAnswer={mockSubmitAnswer} submitAnswer={mockSubmitAnswer}
disconnectWebSocket={mockDisconnectWebSocket} disconnectWebSocket={mockDisconnectWebSocket}
/> />
</MemoryRouter>
); );
fireEvent.click(screen.getByText('Option A')); fireEvent.click(screen.getByText('Option A'));
fireEvent.click(screen.getByText('Répondre')); fireEvent.click(screen.getByText('Répondre'));
expect(mockSubmitAnswer).toHaveBeenCalledWith('Option A', '1'); await waitFor(() => {
expect(mockSubmitAnswer).toHaveBeenCalledWith('Option A', '1');
});
}); });
test('handles disconnect button click', () => { test('handles quit button click', async () => {
render( render(
<StudentModeQuiz <MemoryRouter>
questions={mockQuestions} <StudentModeQuiz
submitAnswer={mockSubmitAnswer} questions={mockQuestions}
disconnectWebSocket={mockDisconnectWebSocket} submitAnswer={mockSubmitAnswer}
/> disconnectWebSocket={mockDisconnectWebSocket}
); />
fireEvent.click(screen.getByText('Déconnexion')); </MemoryRouter>);
fireEvent.click(screen.getByText('Quitter'));
expect(mockDisconnectWebSocket).toHaveBeenCalled(); await waitFor(() => {
expect(mockDisconnectWebSocket).toHaveBeenCalled();
});
}); });
test('navigates to the next question', () => { test('navigates to the next question', async () => {
render( render(
<StudentModeQuiz <MemoryRouter>
questions={mockQuestions} <StudentModeQuiz
submitAnswer={mockSubmitAnswer} questions={mockQuestions}
disconnectWebSocket={mockDisconnectWebSocket} submitAnswer={mockSubmitAnswer}
/> disconnectWebSocket={mockDisconnectWebSocket}
); />
</MemoryRouter>);
fireEvent.click(screen.getByText('Option A')); fireEvent.click(screen.getByText('Option A'));
fireEvent.click(screen.getByText('Répondre')); fireEvent.click(screen.getByText('Répondre'));
fireEvent.click(screen.getByText('Question suivante')); fireEvent.click(screen.getByText('Question suivante'));
await waitFor(() => {
const sampleQuestionElements = screen.queryAllByText(/Sample question 2/i);
expect(sampleQuestionElements.length).toBeGreaterThan(0);
expect(screen.getByText('V')).toBeInTheDocument();
});
expect(screen.getByText('Sample Question 2')).toBeInTheDocument();
expect(screen.getByText('T')).toBeInTheDocument();
}); });
test('navigates to the previous question', () => { test('navigates to the previous question', async () => {
render( render(
<StudentModeQuiz <MemoryRouter>
questions={mockQuestions} <StudentModeQuiz
submitAnswer={mockSubmitAnswer} questions={mockQuestions}
disconnectWebSocket={mockDisconnectWebSocket} submitAnswer={mockSubmitAnswer}
/> disconnectWebSocket={mockDisconnectWebSocket}
); />
</MemoryRouter>);
fireEvent.click(screen.getByText('Option A')); fireEvent.click(screen.getByText('Option A'));
fireEvent.click(screen.getByText('Répondre')); fireEvent.click(screen.getByText('Répondre'));
fireEvent.click(screen.getByText('Question précédente')); fireEvent.click(screen.getByText('Question précédente'));
await waitFor(() => {
expect(screen.getByText('Sample Question 1')).toBeInTheDocument(); expect(screen.getByText('Sample Question 1')).toBeInTheDocument();
expect(screen.getByText('Option B')).toBeInTheDocument(); expect(screen.getByText('Option B')).toBeInTheDocument();
});
}); });
}); });

View file

@ -25,7 +25,7 @@ describe('TeacherModeQuiz', () => {
beforeEach(() => { beforeEach(() => {
render( render(
<TeacherModeQuiz <TeacherModeQuiz
questionInfos={{ question: mockQuestion, image: 'sample-image-url' }} questionInfos={{ question: mockQuestion }}
submitAnswer={mockSubmitAnswer} submitAnswer={mockSubmitAnswer}
disconnectWebSocket={mockDisconnectWebSocket} disconnectWebSocket={mockDisconnectWebSocket}
/> />

View file

@ -33,7 +33,7 @@ describe('QuizForm Component', () => {
}); });
test('renders QuizForm for a new quiz', async () => { test('renders QuizForm for a new quiz', async () => {
render( const { container } = render(
<MemoryRouter initialEntries={['/teacher/editor-quiz']}> <MemoryRouter initialEntries={['/teacher/editor-quiz']}>
<QuizForm /> <QuizForm />
</MemoryRouter> </MemoryRouter>
@ -41,8 +41,9 @@ describe('QuizForm Component', () => {
expect(screen.getByText(/Éditeur de quiz/i)).toBeInTheDocument(); expect(screen.getByText(/Éditeur de quiz/i)).toBeInTheDocument();
const editorTextArea = screen.getByRole('textbox'); // find the 'editor' text area
fireEvent.change(editorTextArea, { target: { value: 'Sample question?' } }); const editorTextArea = container.querySelector('textarea.editor');
fireEvent.change(editorTextArea!, { target: { value: 'Sample question?' } });
await waitFor(() => { await waitFor(() => {
const sampleQuestionElements = screen.queryAllByText(/Sample question\?/i); const sampleQuestionElements = screen.queryAllByText(/Sample question\?/i);

View file

@ -1,39 +1,39 @@
import { QuestionService } from "../../services/QuestionService"; // import { QuestionService } from "../../services/QuestionService";
describe('QuestionService', () => { describe.skip('QuestionService', () => {
describe('getImage', () => { // describe('getImage', () => {
it('should return empty string for text without image tag', () => { // it('should return empty string for text without image tag', () => {
const text = 'This is a sample text without an image tag.'; // const text = 'This is a sample text without an image tag.';
const imageUrl = QuestionService.getImage(text); // const imageUrl = QuestionService.getImage(text);
expect(imageUrl).toBe(''); // expect(imageUrl).toBe('');
}); // });
it('should return the image tag from the text', () => { // it('should return the image tag from the text', () => {
const text = 'This is a sample text with an <img src="image.jpg" alt="Sample Image" /> tag.'; // const text = 'This is a sample text with an <img src="image.jpg" alt="Sample Image" /> tag.';
const imageUrl = QuestionService.getImage(text); // const imageUrl = QuestionService.getImage(text);
expect(imageUrl).toBe('<img src="image.jpg" alt="Sample Image" />'); // expect(imageUrl).toBe('<img src="image.jpg" alt="Sample Image" />');
}); // });
}); // });
describe('getImageSource', () => { // describe('getImageSource', () => {
it('should return the image source from the image tag in the text', () => { // it('should return the image source from the image tag in the text', () => {
const text = '<img src="image.jpg" alt="Sample Image" />'; // const text = '<img src="image.jpg" alt="Sample Image" />';
const imageUrl = QuestionService.getImageSource(text); // const imageUrl = QuestionService.getImageSource(text);
expect(imageUrl).toBe('src="image.jpg" alt="Sample Image" /'); // expect(imageUrl).toBe('src="image.jpg" alt="Sample Image" /');
}); // });
}); // });
describe('ignoreImgTags', () => { // describe('ignoreImgTags', () => {
it('should return the same text if it does not contain an image tag', () => { // it('should return the same text if it does not contain an image tag', () => {
const text = 'This is a sample text without an image tag.'; // const text = 'This is a sample text without an image tag.';
const result = QuestionService.ignoreImgTags(text); // const result = QuestionService.ignoreImgTags(text);
expect(result).toBe(text); // expect(result).toBe(text);
}); // });
it('should remove the image tag from the text', () => { // it('should remove the image tag from the text', () => {
const text = 'This is a sample text with an <img src="image.jpg" alt="Sample Image" /> tag.'; // const text = 'This is a sample text with an <img src="image.jpg" alt="Sample Image" /> tag.';
const result = QuestionService.ignoreImgTags(text); // const result = QuestionService.ignoreImgTags(text);
expect(result).toBe('This is a sample text with an tag.'); // expect(result).toBe('This is a sample text with an tag.');
}); // });
}); // });
}); });

View file

@ -20,32 +20,31 @@ const GIFTTemplatePreview: React.FC<GIFTTemplatePreviewProps> = ({
useEffect(() => { useEffect(() => {
try { try {
let previewHTML = ''; let previewHTML = '';
questions.forEach((item) => { questions.forEach((giftQuestion) => {
const isImage = item.includes('<img'); // const isImage = item.includes('<img');
if (isImage) { // if (isImage) {
const imageUrlMatch = item.match(/<img[^>]+>/i); // const imageUrlMatch = item.match(/<img[^>]+>/i);
if (imageUrlMatch) { // if (imageUrlMatch) {
let imageUrl = imageUrlMatch[0]; // let imageUrl = imageUrlMatch[0];
imageUrl = imageUrl.replace('img', 'img style="width:10vw;" src='); // imageUrl = imageUrl.replace('img', 'img style="width:10vw;" src=');
item = item.replace(imageUrlMatch[0], ''); // item = item.replace(imageUrlMatch[0], '');
previewHTML += `${imageUrl}`; // previewHTML += `${imageUrl}`;
} // }
} // }
try { try {
const parsedItem = parse(item); const question = parse(giftQuestion);
previewHTML += Template(parsedItem[0], { previewHTML += Template(question[0], {
preview: true, preview: true,
theme: 'light' theme: 'light'
}); });
} catch (error) { } catch (error) {
if (error instanceof Error) { if (error instanceof Error) {
previewHTML += ErrorTemplate(item + '\n' + error.message); previewHTML += ErrorTemplate(giftQuestion + '\n' + error.message);
} else { } else {
previewHTML += ErrorTemplate(item + '\n' + 'Erreur inconnue'); previewHTML += ErrorTemplate(giftQuestion + '\n' + 'Erreur inconnue');
} }
} }
previewHTML += '';
}); });
if (hideAnswers) { if (hideAnswers) {

View file

@ -16,23 +16,15 @@ function formatLatex(text: string): string {
); );
} }
function escapeHTML(text: string) {
return text
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
}
export default function TextType({ text }: TextTypeOptions): string { export default function TextType({ text }: TextTypeOptions): string {
const formatText = formatLatex(escapeHTML(text.text.trim())); const formatText = formatLatex(text.text.trim());
switch (text.format) { switch (text.format) {
case 'moodle': case 'moodle':
case 'plain': case 'plain':
return formatText.replace(/(?:\r\n|\r|\n)/g, '<br>'); return formatText.replace(/(?:\r\n|\r|\n)/g, '<br>');
case 'html': case 'html':
// Strip outer paragraph tags (not a great approach with regex)
return formatText.replace(/(^<p>)(.*?)(<\/p>)$/gm, '$2'); return formatText.replace(/(^<p>)(.*?)(<\/p>)$/gm, '$2');
case 'markdown': case 'markdown':
return ( return (
@ -42,6 +34,6 @@ export default function TextType({ text }: TextTypeOptions): string {
.replace(/(^<p>)(.*?)(<\/p>)$/gm, '$2') .replace(/(^<p>)(.*?)(<\/p>)$/gm, '$2')
); );
default: default:
return ``; throw new Error(`Unsupported text format: ${text.format}`);
} }
} }

View file

@ -45,7 +45,7 @@ export default function Title({ type, title }: TitleOptions): string {
${ ${
title !== null title !== null
? `<span style="${QuestionTitle}">${title}</span>` ? `<span style="${QuestionTitle}">${title}</span>`
: `<span style="${OptionalTitle}">Titre optionnel...</span>` : `<span style="${OptionalTitle}"><em>(Sans titre)</em></span>`
} }
</span> </span>
<span style="${QuestionTypeContainer} margin-bottom: 10px;"> <span style="${QuestionTypeContainer} margin-bottom: 10px;">

View file

@ -9,12 +9,12 @@ import TrueFalse from './TrueFalse';
import Error from './Error'; import Error from './Error';
import { import {
GIFTQuestion, GIFTQuestion,
Category as CategoryType, // Category as CategoryType,
Description as DescriptionType, // Description as DescriptionType,
MultipleChoice as MultipleChoiceType, MultipleChoice as MultipleChoiceType,
Numerical as NumericalType, Numerical as NumericalType,
ShortAnswer as ShortAnswerType, ShortAnswer as ShortAnswerType,
Essay as EssayType, // Essay as EssayType,
TrueFalse as TrueFalseType, TrueFalse as TrueFalseType,
Matching as MatchingType, Matching as MatchingType,
DisplayOptions DisplayOptions
@ -29,12 +29,13 @@ export default function Template(
Object.assign(state, options); Object.assign(state, options);
switch (type) { switch (type) {
case 'Category': // Category, Description, Essay are not supported?
return Category({ ...(keys as CategoryType) }); // case 'Category':
case 'Description': // return Category({ ...(keys as CategoryType) });
return Description({ // case 'Description':
...(keys as DescriptionType) // return Description({
}); // ...(keys as DescriptionType)
// });
case 'MC': case 'MC':
return MultipleChoice({ return MultipleChoice({
...(keys as MultipleChoiceType) ...(keys as MultipleChoiceType)
@ -45,13 +46,15 @@ export default function Template(
return ShortAnswer({ return ShortAnswer({
...(keys as ShortAnswerType) ...(keys as ShortAnswerType)
}); });
case 'Essay': // case 'Essay':
return Essay({ ...(keys as EssayType) }); // return Essay({ ...(keys as EssayType) });
case 'TF': case 'TF':
return TrueFalse({ ...(keys as TrueFalseType) }); return TrueFalse({ ...(keys as TrueFalseType) });
case 'Matching': case 'Matching':
return Matching({ ...(keys as MatchingType) }); return Matching({ ...(keys as MatchingType) });
default: default:
// TODO: throw error for unsupported question types?
// throw new Error(`Unsupported question type: ${type}`);
return ``; return ``;
} }
} }

View file

@ -4,7 +4,7 @@ import QuestionComponent from '../Questions/Question';
import '../../pages/Student/JoinRoom/joinRoom.css'; import '../../pages/Student/JoinRoom/joinRoom.css';
import { QuestionType } from '../../Types/QuestionType'; import { QuestionType } from '../../Types/QuestionType';
import { QuestionService } from '../../services/QuestionService'; // import { QuestionService } from '../../services/QuestionService';
import { Button } from '@mui/material'; import { Button } from '@mui/material';
import QuestionNavigation from '../QuestionNavigation/QuestionNavigation'; import QuestionNavigation from '../QuestionNavigation/QuestionNavigation';
import { ChevronLeft, ChevronRight } from '@mui/icons-material'; import { ChevronLeft, ChevronRight } from '@mui/icons-material';
@ -23,16 +23,14 @@ const StudentModeQuiz: React.FC<StudentModeQuizProps> = ({
}) => { }) => {
const [questionInfos, setQuestion] = useState<QuestionType>(questions[0]); const [questionInfos, setQuestion] = useState<QuestionType>(questions[0]);
const [isAnswerSubmitted, setIsAnswerSubmitted] = useState(false); const [isAnswerSubmitted, setIsAnswerSubmitted] = useState(false);
const [imageUrl, setImageUrl] = useState(''); // const [imageUrl, setImageUrl] = useState('');
const previousQuestion = () => { const previousQuestion = () => {
setQuestion(questions[Number(questionInfos.question?.id) - 2]); setQuestion(questions[Number(questionInfos.question?.id) - 2]);
setIsAnswerSubmitted(false); setIsAnswerSubmitted(false);
}; };
useEffect(() => { useEffect(() => {}, [questionInfos]);
setImageUrl(QuestionService.getImageSource(questionInfos.image));
}, [questionInfos]);
const nextQuestion = () => { const nextQuestion = () => {
setQuestion(questions[Number(questionInfos.question?.id)]); setQuestion(questions[Number(questionInfos.question?.id)]);
@ -68,7 +66,7 @@ const StudentModeQuiz: React.FC<StudentModeQuizProps> = ({
handleOnSubmitAnswer={handleOnSubmitAnswer} handleOnSubmitAnswer={handleOnSubmitAnswer}
question={questionInfos.question} question={questionInfos.question}
showAnswer={isAnswerSubmitted} showAnswer={isAnswerSubmitted}
imageUrl={imageUrl} // imageUrl={imageUrl}
/> />
<div className="center-h-align mt-1/2"> <div className="center-h-align mt-1/2">
<div className="w-12"> <div className="w-12">

View file

@ -5,7 +5,7 @@ import QuestionComponent from '../Questions/Question';
import '../../pages/Student/JoinRoom/joinRoom.css'; import '../../pages/Student/JoinRoom/joinRoom.css';
import { QuestionType } from '../../Types/QuestionType'; import { QuestionType } from '../../Types/QuestionType';
import { QuestionService } from '../../services/QuestionService'; // import { QuestionService } from '../../services/QuestionService';
import DisconnectButton from '../../components/DisconnectButton/DisconnectButton'; import DisconnectButton from '../../components/DisconnectButton/DisconnectButton';
interface TeacherModeQuizProps { interface TeacherModeQuizProps {
@ -20,11 +20,10 @@ const TeacherModeQuiz: React.FC<TeacherModeQuizProps> = ({
disconnectWebSocket disconnectWebSocket
}) => { }) => {
const [isAnswerSubmitted, setIsAnswerSubmitted] = useState(false); const [isAnswerSubmitted, setIsAnswerSubmitted] = useState(false);
const [imageUrl, setImageUrl] = useState(''); // const [imageUrl, setImageUrl] = useState('');
useEffect(() => { useEffect(() => {
setIsAnswerSubmitted(false); setIsAnswerSubmitted(false);
setImageUrl(QuestionService.getImageSource(questionInfos.image));
}, [questionInfos]); }, [questionInfos]);
const handleOnSubmitAnswer = (answer: string | number | boolean) => { const handleOnSubmitAnswer = (answer: string | number | boolean) => {
@ -55,7 +54,7 @@ const TeacherModeQuiz: React.FC<TeacherModeQuizProps> = ({
</div> </div>
) : ( ) : (
<QuestionComponent <QuestionComponent
imageUrl={imageUrl} // imageUrl={imageUrl}
handleOnSubmitAnswer={handleOnSubmitAnswer} handleOnSubmitAnswer={handleOnSubmitAnswer}
question={questionInfos.question} question={questionInfos.question}
/> />

View file

@ -6,7 +6,7 @@ import { parse } from 'gift-pegjs';
import Template from '../../../components/GiftTemplate/templates'; import Template from '../../../components/GiftTemplate/templates';
import { QuizType } from '../../../Types/QuizType'; import { QuizType } from '../../../Types/QuizType';
import { FolderType } from '../../../Types/FolderType'; import { FolderType } from '../../../Types/FolderType';
import { QuestionService } from '../../../services/QuestionService'; // import { QuestionService } from '../../../services/QuestionService';
import ApiService from '../../../services/ApiService'; import ApiService from '../../../services/ApiService';
import './dashboard.css'; import './dashboard.css';
@ -178,7 +178,7 @@ const Dashboard: React.FC = () => {
// Otherwise the quiz is invalid // Otherwise the quiz is invalid
for (let i = 0; i < questions.length; i++) { for (let i = 0; i < questions.length; i++) {
try { try {
questions[i] = QuestionService.ignoreImgTags(questions[i]); // questions[i] = QuestionService.ignoreImgTags(questions[i]);
const parsedItem = parse(questions[i]); const parsedItem = parse(questions[i]);
Template(parsedItem[0]); Template(parsedItem[0]);
} catch (error) { } catch (error) {

View file

@ -5,7 +5,7 @@ import { Socket } from 'socket.io-client';
import { parse } from 'gift-pegjs'; import { parse } from 'gift-pegjs';
import { QuestionType } from '../../../Types/QuestionType'; import { QuestionType } from '../../../Types/QuestionType';
import LiveResultsComponent from '../../../components/LiveResults/LiveResults'; import LiveResultsComponent from '../../../components/LiveResults/LiveResults';
import { QuestionService } from '../../../services/QuestionService'; // import { QuestionService } from '../../../services/QuestionService';
import webSocketService from '../../../services/WebsocketService'; import webSocketService from '../../../services/WebsocketService';
import { QuizType } from '../../../Types/QuizType'; import { QuizType } from '../../../Types/QuizType';
@ -160,10 +160,7 @@ const ManageRoom: React.FC = () => {
const parsedQuestions = [] as QuestionType[]; const parsedQuestions = [] as QuestionType[];
quizQuestionArray.forEach((question, index) => { quizQuestionArray.forEach((question, index) => {
const imageTag = QuestionService.getImage(question); parsedQuestions.push({ question: parse(question)[0] });
const imageUrl = QuestionService.getImageSource(imageTag);
question = QuestionService.ignoreImgTags(question);
parsedQuestions.push({ question: parse(question)[0], image: imageUrl });
parsedQuestions[index].question.id = (index + 1).toString(); parsedQuestions[index].question.id = (index + 1).toString();
}); });
if (parsedQuestions.length === 0) return null; if (parsedQuestions.length === 0) return null;
@ -285,7 +282,6 @@ const ManageRoom: React.FC = () => {
{currentQuestion && ( {currentQuestion && (
<Question <Question
imageUrl={currentQuestion?.image}
showAnswer={false} showAnswer={false}
question={currentQuestion?.question} question={currentQuestion?.question}
/> />

View file

@ -1,25 +1,3 @@
export class QuestionService { export class QuestionService {
static getImage(text: string) {
const imageUrlMatch = text.match(/<img[^>]+>/i);
if (imageUrlMatch) {
return imageUrlMatch[0];
}
return '';
}
static getImageSource = (text: string): string => {
let imageUrl = text.replace('<img ', '');
imageUrl = imageUrl.replace('>', '');
return imageUrl;
};
static ignoreImgTags(text: string): string {
if (text.includes('<img')) {
const imageUrlMatch = text.match(/<img[^>]+>/i);
if (imageUrlMatch) {
text = text.replace(imageUrlMatch[0], '');
}
}
return text;
}
} }

236
server/package-lock.json generated
View file

@ -1612,9 +1612,9 @@
} }
}, },
"node_modules/body-parser": { "node_modules/body-parser": {
"version": "1.20.2", "version": "1.20.3",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
"integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
"dependencies": { "dependencies": {
"bytes": "3.1.2", "bytes": "3.1.2",
"content-type": "~1.0.5", "content-type": "~1.0.5",
@ -1624,7 +1624,7 @@
"http-errors": "2.0.0", "http-errors": "2.0.0",
"iconv-lite": "0.4.24", "iconv-lite": "0.4.24",
"on-finished": "2.4.1", "on-finished": "2.4.1",
"qs": "6.11.0", "qs": "6.13.0",
"raw-body": "2.5.2", "raw-body": "2.5.2",
"type-is": "~1.6.18", "type-is": "~1.6.18",
"unpipe": "1.0.0" "unpipe": "1.0.0"
@ -1734,12 +1734,18 @@
} }
}, },
"node_modules/call-bind": { "node_modules/call-bind": {
"version": "1.0.2", "version": "1.0.7",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
"dependencies": { "dependencies": {
"function-bind": "^1.1.1", "es-define-property": "^1.0.0",
"get-intrinsic": "^1.0.2" "es-errors": "^1.3.0",
"function-bind": "^1.1.2",
"get-intrinsic": "^1.2.4",
"set-function-length": "^1.2.1"
},
"engines": {
"node": ">= 0.4"
}, },
"funding": { "funding": {
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
@ -2140,6 +2146,22 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/define-data-property": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
"dependencies": {
"es-define-property": "^1.0.0",
"es-errors": "^1.3.0",
"gopd": "^1.0.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/delayed-stream": { "node_modules/delayed-stream": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
@ -2255,9 +2277,9 @@
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
}, },
"node_modules/encodeurl": { "node_modules/encodeurl": {
"version": "1.0.2", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
"engines": { "engines": {
"node": ">= 0.8" "node": ">= 0.8"
} }
@ -2361,6 +2383,25 @@
"is-arrayish": "^0.2.1" "is-arrayish": "^0.2.1"
} }
}, },
"node_modules/es-define-property": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
"integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
"dependencies": {
"get-intrinsic": "^1.2.4"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-errors": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/escalade": { "node_modules/escalade": {
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
@ -2454,36 +2495,36 @@
} }
}, },
"node_modules/express": { "node_modules/express": {
"version": "4.19.2", "version": "4.21.0",
"resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz",
"integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==",
"dependencies": { "dependencies": {
"accepts": "~1.3.8", "accepts": "~1.3.8",
"array-flatten": "1.1.1", "array-flatten": "1.1.1",
"body-parser": "1.20.2", "body-parser": "1.20.3",
"content-disposition": "0.5.4", "content-disposition": "0.5.4",
"content-type": "~1.0.4", "content-type": "~1.0.4",
"cookie": "0.6.0", "cookie": "0.6.0",
"cookie-signature": "1.0.6", "cookie-signature": "1.0.6",
"debug": "2.6.9", "debug": "2.6.9",
"depd": "2.0.0", "depd": "2.0.0",
"encodeurl": "~1.0.2", "encodeurl": "~2.0.0",
"escape-html": "~1.0.3", "escape-html": "~1.0.3",
"etag": "~1.8.1", "etag": "~1.8.1",
"finalhandler": "1.2.0", "finalhandler": "1.3.1",
"fresh": "0.5.2", "fresh": "0.5.2",
"http-errors": "2.0.0", "http-errors": "2.0.0",
"merge-descriptors": "1.0.1", "merge-descriptors": "1.0.3",
"methods": "~1.1.2", "methods": "~1.1.2",
"on-finished": "2.4.1", "on-finished": "2.4.1",
"parseurl": "~1.3.3", "parseurl": "~1.3.3",
"path-to-regexp": "0.1.7", "path-to-regexp": "0.1.10",
"proxy-addr": "~2.0.7", "proxy-addr": "~2.0.7",
"qs": "6.11.0", "qs": "6.13.0",
"range-parser": "~1.2.1", "range-parser": "~1.2.1",
"safe-buffer": "5.2.1", "safe-buffer": "5.2.1",
"send": "0.18.0", "send": "0.19.0",
"serve-static": "1.15.0", "serve-static": "1.16.2",
"setprototypeof": "1.2.0", "setprototypeof": "1.2.0",
"statuses": "2.0.1", "statuses": "2.0.1",
"type-is": "~1.6.18", "type-is": "~1.6.18",
@ -2528,12 +2569,12 @@
} }
}, },
"node_modules/finalhandler": { "node_modules/finalhandler": {
"version": "1.2.0", "version": "1.3.1",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
"integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
"dependencies": { "dependencies": {
"debug": "2.6.9", "debug": "2.6.9",
"encodeurl": "~1.0.2", "encodeurl": "~2.0.0",
"escape-html": "~1.0.3", "escape-html": "~1.0.3",
"on-finished": "2.4.1", "on-finished": "2.4.1",
"parseurl": "~1.3.3", "parseurl": "~1.3.3",
@ -2689,14 +2730,18 @@
} }
}, },
"node_modules/get-intrinsic": { "node_modules/get-intrinsic": {
"version": "1.2.1", "version": "1.2.4",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
"integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
"dependencies": { "dependencies": {
"function-bind": "^1.1.1", "es-errors": "^1.3.0",
"has": "^1.0.3", "function-bind": "^1.1.2",
"has-proto": "^1.0.1", "has-proto": "^1.0.1",
"has-symbols": "^1.0.3" "has-symbols": "^1.0.3",
"hasown": "^2.0.0"
},
"engines": {
"node": ">= 0.4"
}, },
"funding": { "funding": {
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
@ -2763,20 +2808,23 @@
"node": ">=4" "node": ">=4"
} }
}, },
"node_modules/gopd": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
"integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
"dependencies": {
"get-intrinsic": "^1.1.3"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/graceful-fs": { "node_modules/graceful-fs": {
"version": "4.2.11", "version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"dev": true "dev": true
}, },
"node_modules/has": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.4.tgz",
"integrity": "sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==",
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/has-flag": { "node_modules/has-flag": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
@ -2786,10 +2834,21 @@
"node": ">=4" "node": ">=4"
} }
}, },
"node_modules/has-property-descriptors": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
"integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
"dependencies": {
"es-define-property": "^1.0.0"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-proto": { "node_modules/has-proto": {
"version": "1.0.1", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
"integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==",
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
}, },
@ -2817,7 +2876,6 @@
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz",
"integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==",
"dev": true,
"dependencies": { "dependencies": {
"function-bind": "^1.1.2" "function-bind": "^1.1.2"
}, },
@ -4046,9 +4104,12 @@
"integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==" "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg=="
}, },
"node_modules/merge-descriptors": { "node_modules/merge-descriptors": {
"version": "1.0.1", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
"integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
}, },
"node_modules/merge-stream": { "node_modules/merge-stream": {
"version": "2.0.0", "version": "2.0.0",
@ -4440,9 +4501,12 @@
} }
}, },
"node_modules/object-inspect": { "node_modules/object-inspect": {
"version": "1.13.0", "version": "1.13.2",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.0.tgz", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz",
"integrity": "sha512-HQ4J+ic8hKrgIt3mqk6cVOVrW2ozL4KdvHlqpBv9vDYWx9ysAgENAdvy4FoGF+KFdhR7nQTNm5J0ctAeOwn+3g==", "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==",
"engines": {
"node": ">= 0.4"
},
"funding": { "funding": {
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
@ -4576,9 +4640,9 @@
"dev": true "dev": true
}, },
"node_modules/path-to-regexp": { "node_modules/path-to-regexp": {
"version": "0.1.7", "version": "0.1.10",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz",
"integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w=="
}, },
"node_modules/picocolors": { "node_modules/picocolors": {
"version": "1.0.0", "version": "1.0.0",
@ -4706,11 +4770,11 @@
] ]
}, },
"node_modules/qs": { "node_modules/qs": {
"version": "6.11.0", "version": "6.13.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
"integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
"dependencies": { "dependencies": {
"side-channel": "^1.0.4" "side-channel": "^1.0.6"
}, },
"engines": { "engines": {
"node": ">=0.6" "node": ">=0.6"
@ -4881,9 +4945,9 @@
} }
}, },
"node_modules/send": { "node_modules/send": {
"version": "0.18.0", "version": "0.19.0",
"resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
"integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
"dependencies": { "dependencies": {
"debug": "2.6.9", "debug": "2.6.9",
"depd": "2.0.0", "depd": "2.0.0",
@ -4903,20 +4967,28 @@
"node": ">= 0.8.0" "node": ">= 0.8.0"
} }
}, },
"node_modules/send/node_modules/encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/send/node_modules/ms": { "node_modules/send/node_modules/ms": {
"version": "2.1.3", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
}, },
"node_modules/serve-static": { "node_modules/serve-static": {
"version": "1.15.0", "version": "1.16.2",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
"integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
"dependencies": { "dependencies": {
"encodeurl": "~1.0.2", "encodeurl": "~2.0.0",
"escape-html": "~1.0.3", "escape-html": "~1.0.3",
"parseurl": "~1.3.3", "parseurl": "~1.3.3",
"send": "0.18.0" "send": "0.19.0"
}, },
"engines": { "engines": {
"node": ">= 0.8.0" "node": ">= 0.8.0"
@ -4927,6 +4999,22 @@
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="
}, },
"node_modules/set-function-length": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
"integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
"dependencies": {
"define-data-property": "^1.1.4",
"es-errors": "^1.3.0",
"function-bind": "^1.1.2",
"get-intrinsic": "^1.2.4",
"gopd": "^1.0.1",
"has-property-descriptors": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/setprototypeof": { "node_modules/setprototypeof": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
@ -4954,13 +5042,17 @@
} }
}, },
"node_modules/side-channel": { "node_modules/side-channel": {
"version": "1.0.4", "version": "1.0.6",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==",
"dependencies": { "dependencies": {
"call-bind": "^1.0.0", "call-bind": "^1.0.7",
"get-intrinsic": "^1.0.2", "es-errors": "^1.3.0",
"object-inspect": "^1.9.0" "get-intrinsic": "^1.2.4",
"object-inspect": "^1.13.1"
},
"engines": {
"node": ">= 0.4"
}, },
"funding": { "funding": {
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"