mirror of
https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir.git
synced 2025-08-11 21:23:54 -04:00
Merge remote-tracking branch 'test/main' into PFEH25-merge-it2main
This commit is contained in:
commit
68f301b972
7 changed files with 167 additions and 44 deletions
1
LICENSE
1
LICENSE
|
|
@ -2,6 +2,7 @@ MIT License
|
||||||
|
|
||||||
Copyright (c) 2023 ETS-PFE004-Plateforme-sondage-minitest
|
Copyright (c) 2023 ETS-PFE004-Plateforme-sondage-minitest
|
||||||
Copyright (c) 2024 Louis-Antoine Caron, Mathieu Roy, Mélanie St-Hilaire, Samy Waddah
|
Copyright (c) 2024 Louis-Antoine Caron, Mathieu Roy, Mélanie St-Hilaire, Samy Waddah
|
||||||
|
Copyright (c) 2024 Gabriel Moisan-Matte, Mathieu Sévigny-Lavallée, Jerry Kwok Hiu Fung, Bruno Roesner, Florent Serres
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
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 { act } from 'react';
|
import { act } from 'react';
|
||||||
|
|
@ -17,21 +17,30 @@ const question = questions[0];
|
||||||
|
|
||||||
describe('MultipleChoiceQuestionDisplay', () => {
|
describe('MultipleChoiceQuestionDisplay', () => {
|
||||||
const mockHandleOnSubmitAnswer = jest.fn();
|
const mockHandleOnSubmitAnswer = jest.fn();
|
||||||
const sampleProps = {
|
|
||||||
question: question,
|
const TestWrapper = ({ showAnswer }: { showAnswer: boolean }) => {
|
||||||
handleOnSubmitAnswer: mockHandleOnSubmitAnswer,
|
const [showAnswerState, setShowAnswerState] = useState(showAnswer);
|
||||||
showAnswer: false
|
|
||||||
|
const handleOnSubmitAnswer = (answer: string) => {
|
||||||
|
mockHandleOnSubmitAnswer(answer);
|
||||||
|
setShowAnswerState(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MemoryRouter>
|
||||||
|
<MultipleChoiceQuestionDisplay
|
||||||
|
question={question}
|
||||||
|
handleOnSubmitAnswer={handleOnSubmitAnswer}
|
||||||
|
showAnswer={showAnswerState}
|
||||||
|
/>
|
||||||
|
</MemoryRouter>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const choices = question.choices;
|
const choices = question.choices;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
render(
|
render(<TestWrapper showAnswer={false} />);
|
||||||
<MemoryRouter>
|
|
||||||
<MultipleChoiceQuestionDisplay
|
|
||||||
{...sampleProps}
|
|
||||||
/>
|
|
||||||
</MemoryRouter>);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('renders the question and choices', () => {
|
test('renders the question and choices', () => {
|
||||||
|
|
@ -55,7 +64,6 @@ describe('MultipleChoiceQuestionDisplay', () => {
|
||||||
act(() => {
|
act(() => {
|
||||||
fireEvent.click(choiceButton);
|
fireEvent.click(choiceButton);
|
||||||
});
|
});
|
||||||
|
|
||||||
const submitButton = screen.getByText('Répondre');
|
const submitButton = screen.getByText('Répondre');
|
||||||
act(() => {
|
act(() => {
|
||||||
fireEvent.click(submitButton);
|
fireEvent.click(submitButton);
|
||||||
|
|
@ -63,4 +71,51 @@ describe('MultipleChoiceQuestionDisplay', () => {
|
||||||
|
|
||||||
expect(mockHandleOnSubmitAnswer).toHaveBeenCalledWith('Choice 1');
|
expect(mockHandleOnSubmitAnswer).toHaveBeenCalledWith('Choice 1');
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
it('should show ✅ next to the correct answer and ❌ next to the wrong answers when showAnswer is true', async () => {
|
||||||
|
const choiceButton = screen.getByText('Choice 1').closest('button');
|
||||||
|
if (!choiceButton) throw new Error('Choice button not found');
|
||||||
|
|
||||||
|
// Click on choiceButton
|
||||||
|
act(() => {
|
||||||
|
fireEvent.click(choiceButton);
|
||||||
|
});
|
||||||
|
|
||||||
|
const button = screen.getByText("Répondre");
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
fireEvent.click(button);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Wait for the DOM to update
|
||||||
|
const correctAnswer = screen.getByText("Choice 1").closest('button');
|
||||||
|
expect(correctAnswer).toBeInTheDocument();
|
||||||
|
expect(correctAnswer?.textContent).toContain('✅');
|
||||||
|
|
||||||
|
const wrongAnswer1 = screen.getByText("Choice 2").closest('button');
|
||||||
|
expect(wrongAnswer1).toBeInTheDocument();
|
||||||
|
expect(wrongAnswer1?.textContent).toContain('❌');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not show ✅ or ❌ when repondre button is not clicked', async () => {
|
||||||
|
const choiceButton = screen.getByText('Choice 1').closest('button');
|
||||||
|
if (!choiceButton) throw new Error('Choice button not found');
|
||||||
|
|
||||||
|
// Click on choiceButton
|
||||||
|
act(() => {
|
||||||
|
fireEvent.click(choiceButton);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check for correct answer
|
||||||
|
const correctAnswer = screen.getByText("Choice 1").closest('button');
|
||||||
|
expect(correctAnswer).toBeInTheDocument();
|
||||||
|
expect(correctAnswer?.textContent).not.toContain('✅');
|
||||||
|
|
||||||
|
// Check for wrong answers
|
||||||
|
const wrongAnswer1 = screen.getByText("Choice 2");
|
||||||
|
expect(wrongAnswer1).toBeInTheDocument();
|
||||||
|
expect(wrongAnswer1?.textContent).not.toContain('❌');
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
// TrueFalseQuestion.test.tsx
|
// TrueFalseQuestion.test.tsx
|
||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
import { render, fireEvent, screen, act } from '@testing-library/react';
|
import { render, fireEvent, screen, act } from '@testing-library/react';
|
||||||
import '@testing-library/jest-dom';
|
import '@testing-library/jest-dom';
|
||||||
import { MemoryRouter } from 'react-router-dom';
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
|
|
@ -12,17 +12,28 @@ describe('TrueFalseQuestion Component', () => {
|
||||||
const trueFalseQuestion =
|
const trueFalseQuestion =
|
||||||
parse(`${sampleStem}{T}`)[0] as TrueFalseQuestion;
|
parse(`${sampleStem}{T}`)[0] as TrueFalseQuestion;
|
||||||
|
|
||||||
const sampleProps = {
|
|
||||||
question: trueFalseQuestion,
|
const TestWrapper = ({ showAnswer }: { showAnswer: boolean }) => {
|
||||||
handleOnSubmitAnswer: mockHandleSubmitAnswer,
|
const [showAnswerState, setShowAnswerState] = useState(showAnswer);
|
||||||
showAnswer: false
|
|
||||||
|
const handleOnSubmitAnswer = (answer: boolean) => {
|
||||||
|
mockHandleSubmitAnswer(answer);
|
||||||
|
setShowAnswerState(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MemoryRouter>
|
||||||
|
<TrueFalseQuestionDisplay
|
||||||
|
question={trueFalseQuestion}
|
||||||
|
handleOnSubmitAnswer={handleOnSubmitAnswer}
|
||||||
|
showAnswer={showAnswerState}
|
||||||
|
/>
|
||||||
|
</MemoryRouter>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
render(
|
render(<TestWrapper showAnswer={false} />);
|
||||||
<MemoryRouter>
|
|
||||||
<TrueFalseQuestionDisplay {...sampleProps} />
|
|
||||||
</MemoryRouter>);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders correctly', () => {
|
it('renders correctly', () => {
|
||||||
|
|
@ -73,4 +84,50 @@ describe('TrueFalseQuestion Component', () => {
|
||||||
|
|
||||||
expect(mockHandleSubmitAnswer).toHaveBeenCalledWith(false);
|
expect(mockHandleSubmitAnswer).toHaveBeenCalledWith(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should show ✅ next to the correct answer and ❌ next to the wrong answers when showAnswer is true', async () => {
|
||||||
|
const choiceButton = screen.getByText('Vrai').closest('button');
|
||||||
|
if (!choiceButton) throw new Error('T button not found');
|
||||||
|
|
||||||
|
// Click on choiceButton
|
||||||
|
act(() => {
|
||||||
|
fireEvent.click(choiceButton);
|
||||||
|
});
|
||||||
|
|
||||||
|
const button = screen.getByText("Répondre");
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
fireEvent.click(button);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Wait for the DOM to update
|
||||||
|
const correctAnswer = screen.getByText("Vrai").closest('button');
|
||||||
|
expect(correctAnswer).toBeInTheDocument();
|
||||||
|
expect(correctAnswer?.textContent).toContain('✅');
|
||||||
|
|
||||||
|
const wrongAnswer1 = screen.getByText("Faux").closest('button');
|
||||||
|
expect(wrongAnswer1).toBeInTheDocument();
|
||||||
|
expect(wrongAnswer1?.textContent).toContain('❌');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not show ✅ or ❌ when repondre button is not clicked', async () => {
|
||||||
|
const choiceButton = screen.getByText('Vrai').closest('button');
|
||||||
|
if (!choiceButton) throw new Error('Choice button not found');
|
||||||
|
|
||||||
|
// Click on choiceButton
|
||||||
|
act(() => {
|
||||||
|
fireEvent.click(choiceButton);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check for correct answer
|
||||||
|
const correctAnswer = screen.getByText("Vrai").closest('button');
|
||||||
|
expect(correctAnswer).toBeInTheDocument();
|
||||||
|
expect(correctAnswer?.textContent).not.toContain('✅');
|
||||||
|
|
||||||
|
// Check for wrong answers
|
||||||
|
const wrongAnswer1 = screen.getByText("Faux");
|
||||||
|
expect(wrongAnswer1).toBeInTheDocument();
|
||||||
|
expect(wrongAnswer1?.textContent).not.toContain('❌');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -153,7 +153,7 @@ const GiftCheatSheet: React.FC = () => {
|
||||||
</pre>
|
</pre>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="question-type">
|
<div className="question-type" id="images-section">
|
||||||
<h4> 9. Images </h4>
|
<h4> 9. Images </h4>
|
||||||
<p>Il est possible d'insérer une image dans une question, une réponse (choix multiple) et dans une rétroaction. D'abord, <strong>le format de l'élément doit être [markdown]</strong>. Ensuite utilisez la syntaxe suivante :</p>
|
<p>Il est possible d'insérer une image dans une question, une réponse (choix multiple) et dans une rétroaction. D'abord, <strong>le format de l'élément doit être [markdown]</strong>. Ensuite utilisez la syntaxe suivante :</p>
|
||||||
<pre>
|
<pre>
|
||||||
|
|
@ -185,8 +185,7 @@ const GiftCheatSheet: React.FC = () => {
|
||||||
Attention: l'ancienne fonctionnalité avec les balises <code>{'<img>'}</code> n'est plus
|
Attention: l'ancienne fonctionnalité avec les balises <code>{'<img>'}</code> n'est plus
|
||||||
supportée.
|
supportée.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="question-type">
|
<div className="question-type">
|
||||||
<h4> 10. Informations supplémentaires </h4>
|
<h4> 10. Informations supplémentaires </h4>
|
||||||
<p>
|
<p>
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ interface Props {
|
||||||
}
|
}
|
||||||
|
|
||||||
const MultipleChoiceQuestionDisplay: React.FC<Props> = (props) => {
|
const MultipleChoiceQuestionDisplay: React.FC<Props> = (props) => {
|
||||||
|
|
||||||
const { question, showAnswer, handleOnSubmitAnswer } = props;
|
const { question, showAnswer, handleOnSubmitAnswer } = props;
|
||||||
const [answer, setAnswer] = useState<string>();
|
const [answer, setAnswer] = useState<string>();
|
||||||
|
|
||||||
|
|
@ -24,7 +23,6 @@ const MultipleChoiceQuestionDisplay: React.FC<Props> = (props) => {
|
||||||
setAnswer(choice);
|
setAnswer(choice);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const alpha = Array.from(Array(26)).map((_e, i) => i + 65);
|
const alpha = Array.from(Array(26)).map((_e, i) => i + 65);
|
||||||
const alphabet = alpha.map((x) => String.fromCharCode(x));
|
const alphabet = alpha.map((x) => String.fromCharCode(x));
|
||||||
return (
|
return (
|
||||||
|
|
@ -37,25 +35,23 @@ const MultipleChoiceQuestionDisplay: React.FC<Props> = (props) => {
|
||||||
const selected = answer === choice.formattedText.text ? 'selected' : '';
|
const selected = answer === choice.formattedText.text ? 'selected' : '';
|
||||||
return (
|
return (
|
||||||
<div key={choice.formattedText.text + i} className="choice-container">
|
<div key={choice.formattedText.text + i} className="choice-container">
|
||||||
<Button
|
<Button
|
||||||
variant="text"
|
variant="text"
|
||||||
className="button-wrapper"
|
className="button-wrapper"
|
||||||
onClick={() => !showAnswer && handleOnClickAnswer(choice.formattedText.text)}
|
onClick={() => !showAnswer && handleOnClickAnswer(choice.formattedText.text)}>
|
||||||
>
|
{showAnswer? (<div> {(choice.isCorrect ? '✅' : '❌')}</div>)
|
||||||
{choice.formattedFeedback === null &&
|
:``}
|
||||||
showAnswer &&
|
|
||||||
(choice.isCorrect ? '✅' : '❌')}
|
|
||||||
<div className={`circle ${selected}`}>{alphabet[i]}</div>
|
<div className={`circle ${selected}`}>{alphabet[i]}</div>
|
||||||
<div className={`answer-text ${selected}`}>
|
<div className={`answer-text ${selected}`}>
|
||||||
<div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(choice.formattedText) }} />
|
<div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(choice.formattedText) }} />
|
||||||
</div>
|
</div>
|
||||||
</Button>
|
{choice.formattedFeedback && showAnswer && (
|
||||||
{choice.formattedFeedback && showAnswer && (
|
|
||||||
<div className="feedback-container mb-1 mt-1/2">
|
<div className="feedback-container mb-1 mt-1/2">
|
||||||
{choice.isCorrect ? '✅' : '❌'}
|
|
||||||
<div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(choice.formattedFeedback) }} />
|
<div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(choice.formattedFeedback) }} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
</Button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@ const TrueFalseQuestionDisplay: React.FC<Props> = (props) => {
|
||||||
|
|
||||||
const selectedTrue = answer ? 'selected' : '';
|
const selectedTrue = answer ? 'selected' : '';
|
||||||
const selectedFalse = answer !== undefined && !answer ? 'selected' : '';
|
const selectedFalse = answer !== undefined && !answer ? 'selected' : '';
|
||||||
const correctAnswer = question.isTrue === answer;
|
|
||||||
return (
|
return (
|
||||||
<div className="question-container">
|
<div className="question-container">
|
||||||
<div className="question content">
|
<div className="question content">
|
||||||
|
|
@ -34,7 +33,7 @@ const TrueFalseQuestionDisplay: React.FC<Props> = (props) => {
|
||||||
onClick={() => !showAnswer && setAnswer(true)}
|
onClick={() => !showAnswer && setAnswer(true)}
|
||||||
fullWidth
|
fullWidth
|
||||||
>
|
>
|
||||||
{showAnswer && (correctAnswer ? '✅' : '❌')}
|
{showAnswer? (<div> {(question.isTrue ? '✅' : '❌')}</div>):``}
|
||||||
<div className={`circle ${selectedTrue}`}>V</div>
|
<div className={`circle ${selectedTrue}`}>V</div>
|
||||||
<div className={`answer-text ${selectedTrue}`}>Vrai</div>
|
<div className={`answer-text ${selectedTrue}`}>Vrai</div>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -43,7 +42,7 @@ const TrueFalseQuestionDisplay: React.FC<Props> = (props) => {
|
||||||
onClick={() => !showAnswer && setAnswer(false)}
|
onClick={() => !showAnswer && setAnswer(false)}
|
||||||
fullWidth
|
fullWidth
|
||||||
>
|
>
|
||||||
{showAnswer && (!correctAnswer ? '✅' : '❌')}
|
{showAnswer? (<div> {(!question.isTrue ? '✅' : '❌')}</div>):``}
|
||||||
<div className={`circle ${selectedFalse}`}>F</div>
|
<div className={`circle ${selectedFalse}`}>F</div>
|
||||||
<div className={`answer-text ${selectedFalse}`}>Faux</div>
|
<div className={`answer-text ${selectedFalse}`}>Faux</div>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,14 @@ const QuizForm: React.FC = () => {
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const scrollToImagesSection = (event: { preventDefault: () => void; }) => {
|
||||||
|
event.preventDefault();
|
||||||
|
const section = document.getElementById('images-section');
|
||||||
|
if (section) {
|
||||||
|
section.scrollIntoView({ behavior: 'smooth' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
const userFolders = await ApiService.getUserFolders();
|
const userFolders = await ApiService.getUserFolders();
|
||||||
|
|
@ -277,10 +285,18 @@ const QuizForm: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h4>Mes images :</h4>
|
<h4>Mes images :</h4>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div>(Cliquez sur un lien pour le copier)</div>
|
<div>
|
||||||
<ul>
|
<div style={{ display: "inline" }}>(Voir section </div>
|
||||||
|
<a href="#images-section"style={{ textDecoration: "none" }} onClick={scrollToImagesSection}>
|
||||||
|
<u><em><h4 style={{ display: "inline" }}> 9. Images </h4></em></u>
|
||||||
|
</a>
|
||||||
|
<div style={{ display: "inline" }}> ci-dessous</div>
|
||||||
|
<div style={{ display: "inline" }}>)</div>
|
||||||
|
<br />
|
||||||
|
<em> - Cliquez sur un lien pour le copier</em>
|
||||||
|
</div>
|
||||||
|
<ul>
|
||||||
{imageLinks.map((link, index) => {
|
{imageLinks.map((link, index) => {
|
||||||
const imgTag = `} "texte de l'infobulle")`;
|
const imgTag = `} "texte de l'infobulle")`;
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue