From 8a7a4447183e95bd3a51ff3bd69102844624b48c Mon Sep 17 00:00:00 2001 From: "C. Fuhrman" Date: Fri, 24 Jan 2025 14:51:58 -0500 Subject: [PATCH] baby steps (getting tests running) --- .../MultipleChoiceQuestionDisplay.test.tsx | 10 +- .../NumericalQuestionDisplay.test.tsx} | 47 +++++++--- .../components/Questions/Question.test.tsx | 93 +++++++------------ .../ShortAnswerQuestion.test.tsx | 48 ++++------ client/src/__tests__/smoke-test.test.ts | 30 ++++++ .../NumericalQuestionDisplay.tsx} | 61 ++++++------ .../components/Questions/QuestionDisplay.tsx | 41 +++----- .../ShortAnswerQuestionDisplay.tsx} | 41 ++++---- .../TrueFalseQuestion/TrueFalseQuestion.tsx | 4 +- 9 files changed, 181 insertions(+), 194 deletions(-) rename client/src/__tests__/components/Questions/{NumericalQuestion/NumericalQuestion.test.tsx => NumericalQuestionDisplay/NumericalQuestionDisplay.test.tsx} (55%) create mode 100644 client/src/__tests__/smoke-test.test.ts rename client/src/components/Questions/{NumericalQuestion/NumericalQuestion.tsx => NumericalQuestionDisplay/NumericalQuestionDisplay.tsx} (54%) rename client/src/components/Questions/{ShortAnswerQuestion/ShortAnswerQuestion.tsx => ShortAnswerQuestionDisplay/ShortAnswerQuestionDisplay.tsx} (60%) diff --git a/client/src/__tests__/components/Questions/MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay.test.tsx b/client/src/__tests__/components/Questions/MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay.test.tsx index 0e68c39..be6c8e9 100644 --- a/client/src/__tests__/components/Questions/MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay.test.tsx +++ b/client/src/__tests__/components/Questions/MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay.test.tsx @@ -17,15 +17,19 @@ const question = questions[0]; describe('MultipleChoiceQuestionDisplay', () => { const mockHandleOnSubmitAnswer = jest.fn(); + const sampleProps = { + question: question, + handleOnSubmitAnswer: mockHandleOnSubmitAnswer, + showAnswer: false + }; + const choices = question.choices; beforeEach(() => { render( ); }); diff --git a/client/src/__tests__/components/Questions/NumericalQuestion/NumericalQuestion.test.tsx b/client/src/__tests__/components/Questions/NumericalQuestionDisplay/NumericalQuestionDisplay.test.tsx similarity index 55% rename from client/src/__tests__/components/Questions/NumericalQuestion/NumericalQuestion.test.tsx rename to client/src/__tests__/components/Questions/NumericalQuestionDisplay/NumericalQuestionDisplay.test.tsx index e307ed8..50741d8 100644 --- a/client/src/__tests__/components/Questions/NumericalQuestion/NumericalQuestion.test.tsx +++ b/client/src/__tests__/components/Questions/NumericalQuestionDisplay/NumericalQuestionDisplay.test.tsx @@ -2,29 +2,48 @@ import React from 'react'; import { render, screen, fireEvent } from '@testing-library/react'; import '@testing-library/jest-dom'; -import NumericalQuestion from 'src/components/Questions/NumericalQuestion/NumericalQuestion'; +import NumericalQuestionDisplay from 'src/components/Questions/NumericalQuestionDisplay/NumericalQuestionDisplay'; +import { NumericalQuestion, parse, ParsedGIFTQuestion } from 'gift-pegjs'; +import { MemoryRouter } from 'react-router-dom'; + +const questions = parse( + ` + ::Sample Question 1:: Question stem + { + #5..10 + }` +) as ParsedGIFTQuestion[]; + +const question = questions[0] as NumericalQuestion; + +describe('NumericalQuestion parse', () => { + const q = questions[0]; + + it('The question is Numerical', () => { + expect(q.type).toBe('Numerical'); + }); +}); describe('NumericalQuestion Component', () => { - const mockHandleSubmitAnswer = jest.fn(); - const sampleStem = 'Sample question stem'; + const mockHandleOnSubmitAnswer = jest.fn(); const sampleProps = { - questionTitle: 'Sample Question', - correctAnswers: { - numberHigh: 10, - numberLow: 5, - type: 'high-low' - }, - handleOnSubmitAnswer: mockHandleSubmitAnswer, + question: question, + handleOnSubmitAnswer: mockHandleOnSubmitAnswer, showAnswer: false }; beforeEach(() => { - render(); + render( + + + ); }); it('renders correctly', () => { - expect(screen.getByText(sampleStem)).toBeInTheDocument(); + expect(screen.getByText(question.formattedStem.text)).toBeInTheDocument(); expect(screen.getByTestId('number-input')).toBeInTheDocument(); expect(screen.getByText('Répondre')).toBeInTheDocument(); }); @@ -48,7 +67,7 @@ describe('NumericalQuestion Component', () => { fireEvent.click(submitButton); - expect(mockHandleSubmitAnswer).not.toHaveBeenCalled(); + expect(mockHandleOnSubmitAnswer).not.toHaveBeenCalled(); }); it('submits answer correctly', () => { @@ -59,6 +78,6 @@ describe('NumericalQuestion Component', () => { fireEvent.click(submitButton); - expect(mockHandleSubmitAnswer).toHaveBeenCalledWith(7); + expect(mockHandleOnSubmitAnswer).toHaveBeenCalledWith(7); }); }); diff --git a/client/src/__tests__/components/Questions/Question.test.tsx b/client/src/__tests__/components/Questions/Question.test.tsx index 465ae7a..dc88b53 100644 --- a/client/src/__tests__/components/Questions/Question.test.tsx +++ b/client/src/__tests__/components/Questions/Question.test.tsx @@ -1,71 +1,40 @@ // Question.test.tsx import React from 'react'; -import { render, screen, fireEvent } from '@testing-library/react'; +import { render, screen, fireEvent, within } from '@testing-library/react'; import '@testing-library/jest-dom'; -import Questions from 'src/components/Questions/QuestionDisplay'; -import { GIFTQuestion } from 'gift-pegjs'; +import QuestionDisplay from 'src/components/Questions/QuestionDisplay'; +import { parse, Question } from 'gift-pegjs'; -// describe('Questions Component', () => { const mockHandleSubmitAnswer = jest.fn(); - const sampleTrueFalseQuestion: GIFTQuestion = { - type: 'TF', - stem: { format: 'plain', text: 'Sample True/False Question' }, - isTrue: true, - falseFeedback: null, - trueFeedback: null, - title: 'True/False Question', - hasEmbeddedAnswers: false, - globalFeedback: null, + const sampleTrueFalseQuestion = + parse('::Sample True/False Question:: Sample True/False Question {T}')[0]; + + const sampleMultipleChoiceQuestion = + parse('::Sample Multiple Choice Question:: Sample Multiple Choice Question {=Choice 1 ~Choice 2}')[0]; + + const sampleNumericalQuestion = + parse('::Sample Numerical Question:: Sample Numerical Question {#5..10}')[0]; + + const sampleShortAnswerQuestion = + parse('::Sample Short Answer Question:: Sample Short Answer Question {=Correct Answer =Another Answer}')[0]; + + const sampleProps = { + handleOnSubmitAnswer: mockHandleSubmitAnswer, + showAnswer: false }; - const sampleMultipleChoiceQuestion: GIFTQuestion = { - type: 'MC', - stem: { format: 'plain', text: 'Sample Multiple Choice Question' }, - title: 'Multiple Choice Question', - hasEmbeddedAnswers: false, - globalFeedback: null, - choices: [ - { feedback: null, isCorrect: true, text: { format: 'plain', text: 'Choice 1' }, weight: 1 }, - { feedback: null, isCorrect: false, text: { format: 'plain', text: 'Choice 2' }, weight: 0 }, - ], + const renderComponent = (question: Question) => { + render(); }; - const sampleNumericalQuestion: GIFTQuestion = { - type: 'Numerical', - stem: { format: 'plain', text: 'Sample Numerical Question' }, - title: 'Numerical Question', - hasEmbeddedAnswers: false, - globalFeedback: null, - choices: { numberHigh: 10, numberLow: 5, type: 'high-low' }, - }; - - const sampleShortAnswerQuestion: GIFTQuestion = { - type: 'Short', - stem: { format: 'plain', text: 'Sample short answer question' }, - title: 'Short Answer Question Title', - hasEmbeddedAnswers: false, - globalFeedback: null, - choices: [ - { - feedback: { format: 'html', text: 'Correct answer feedback' }, - isCorrect: true, - text: { format: 'html', text: 'Correct Answer' }, - weight: 1, - }, - { - feedback: { format: 'html', text: 'Incorrect answer feedback' }, - isCorrect: false, - text: { format: 'html', text: 'Incorrect Answer' }, - weight: 0, - }, - ], - }; - - const renderComponent = (question: GIFTQuestion) => { - render(); - }; + it('parsed questions correctly', () => { + expect(sampleTrueFalseQuestion.type).toBe('TF'); + expect(sampleMultipleChoiceQuestion.type).toBe('MC'); + expect(sampleNumericalQuestion.type).toBe('Numerical'); + expect(sampleShortAnswerQuestion.type).toBe('Short'); + }); it('renders correctly for True/False question', () => { renderComponent(sampleTrueFalseQuestion); @@ -120,15 +89,19 @@ describe('Questions Component', () => { it('renders correctly for Short Answer question', () => { renderComponent(sampleShortAnswerQuestion); - expect(screen.getByText('Sample short answer question')).toBeInTheDocument(); - expect(screen.getByTestId('text-input')).toBeInTheDocument(); + expect(screen.getByText('Sample Short Answer Question')).toBeInTheDocument(); + const container = screen.getByLabelText('short-answer-input'); + const inputElement = within(container).getByRole('textbox') as HTMLInputElement; + expect(inputElement).toBeInTheDocument(); expect(screen.getByText('Répondre')).toBeInTheDocument(); }); it('handles input and submission for Short Answer question', () => { renderComponent(sampleShortAnswerQuestion); - const inputElement = screen.getByTestId('text-input') as HTMLInputElement; + const container = screen.getByLabelText('short-answer-input'); + const inputElement = within(container).getByRole('textbox') as HTMLInputElement; + fireEvent.change(inputElement, { target: { value: 'User Input' } }); const submitButton = screen.getByText('Répondre'); diff --git a/client/src/__tests__/components/Questions/ShortAnswerQuestion/ShortAnswerQuestion.test.tsx b/client/src/__tests__/components/Questions/ShortAnswerQuestion/ShortAnswerQuestion.test.tsx index 351f732..709e9d5 100644 --- a/client/src/__tests__/components/Questions/ShortAnswerQuestion/ShortAnswerQuestion.test.tsx +++ b/client/src/__tests__/components/Questions/ShortAnswerQuestion/ShortAnswerQuestion.test.tsx @@ -1,54 +1,35 @@ // ShortAnswerQuestion.test.tsx import React from 'react'; -import { render, screen, fireEvent } from '@testing-library/react'; +import { render, screen, fireEvent, within } from '@testing-library/react'; import '@testing-library/jest-dom'; -import ShortAnswerQuestion from 'src/components/Questions/ShortAnswerQuestion/ShortAnswerQuestion'; +import ShortAnswerQuestionDisplay from 'src/components/Questions/ShortAnswerQuestionDisplay/ShortAnswerQuestionDisplay'; +import { parse, ShortAnswerQuestion } from 'gift-pegjs'; describe('ShortAnswerQuestion Component', () => { const mockHandleSubmitAnswer = jest.fn(); - const sampleStem = 'Sample question stem'; + const question = + parse('::Sample Short Answer Question:: Sample Short Answer Question {=Correct Answer ~Incorrect Answer}')[0] as ShortAnswerQuestion; const sampleProps = { - questionTitle: 'Sample Question', - choices: [ - { - id: '1', - feedback: { - format: 'text', - text: 'Correct answer feedback' - }, - isCorrect: true, - text: { - format: 'text', - text: 'Correct Answer' - } - }, - { - id: '2', - feedback: null, - isCorrect: false, - text: { - format: 'text', - text: 'Incorrect Answer' - } - } - ], handleOnSubmitAnswer: mockHandleSubmitAnswer, showAnswer: false }; beforeEach(() => { - render(); + render(); }); it('renders correctly', () => { - expect(screen.getByText(sampleStem)).toBeInTheDocument(); - expect(screen.getByTestId('text-input')).toBeInTheDocument(); + expect(screen.getByText(question.formattedStem.text)).toBeInTheDocument(); + const container = screen.getByLabelText('short-answer-input'); + const inputElement = within(container).getByRole('textbox') as HTMLInputElement; + expect(inputElement).toBeInTheDocument(); expect(screen.getByText('Répondre')).toBeInTheDocument(); }); it('handles input change correctly', () => { - const inputElement = screen.getByTestId('text-input') as HTMLInputElement; + const container = screen.getByLabelText('short-answer-input'); + const inputElement = within(container).getByRole('textbox') as HTMLInputElement; fireEvent.change(inputElement, { target: { value: 'User Input' } }); @@ -70,7 +51,10 @@ describe('ShortAnswerQuestion Component', () => { }); it('submits answer correctly', () => { - const inputElement = screen.getByTestId('text-input') as HTMLInputElement; + const container = screen.getByLabelText('short-answer-input'); + const inputElement = within(container).getByRole('textbox') as HTMLInputElement; + + // const inputElement = screen.getByRole('textbox', { name: 'short-answer-input'}) as HTMLInputElement; const submitButton = screen.getByText('Répondre'); fireEvent.change(inputElement, { target: { value: 'User Input' } }); diff --git a/client/src/__tests__/smoke-test.test.ts b/client/src/__tests__/smoke-test.test.ts new file mode 100644 index 0000000..1331d7e --- /dev/null +++ b/client/src/__tests__/smoke-test.test.ts @@ -0,0 +1,30 @@ +import { parse, NumericalQuestion, SimpleNumericalAnswer, } from "gift-pegjs"; +import { isSimpleNumericalAnswer } from "gift-pegjs/typeGuards"; + +describe('Numerical Question Tests', () => { + // ::Ulysses birthdate::When was Ulysses S. Grant born? {#1822} + it('should produce a valid Question object for a Numerical question with Title', () => { + const input = ` + ::Ulysses birthdate::When was Ulysses S. Grant born? {#1822} + `; + const result = parse(input); + + // Type assertion to ensure result matches the Question interface + const question = result[0]; + + // Example assertions to check specific properties + expect(question).toHaveProperty('type', 'Numerical'); + const numericalQuestion = question as NumericalQuestion; + expect(numericalQuestion.title).toBe('Ulysses birthdate'); + expect(numericalQuestion.formattedStem.text).toBe('When was Ulysses S. Grant born?'); + expect(numericalQuestion.choices).toBeDefined(); + expect(numericalQuestion.choices).toHaveLength(1); + + const choice = numericalQuestion.choices[0]; + expect(isSimpleNumericalAnswer(choice)).toBe(true); + const c = choice as SimpleNumericalAnswer; + expect(c.type).toBe('simple'); + expect(c.number).toBe(1822); + + }); +}); diff --git a/client/src/components/Questions/NumericalQuestion/NumericalQuestion.tsx b/client/src/components/Questions/NumericalQuestionDisplay/NumericalQuestionDisplay.tsx similarity index 54% rename from client/src/components/Questions/NumericalQuestion/NumericalQuestion.tsx rename to client/src/components/Questions/NumericalQuestionDisplay/NumericalQuestionDisplay.tsx index 488fb2a..acf0869 100644 --- a/client/src/components/Questions/NumericalQuestion/NumericalQuestion.tsx +++ b/client/src/components/Questions/NumericalQuestionDisplay/NumericalQuestionDisplay.tsx @@ -3,71 +3,70 @@ import React, { useState } from 'react'; import '../questionStyle.css'; import { Button, TextField } from '@mui/material'; import { textType } from '../../GiftTemplate/templates/TextType'; -import { TextFormat, NumericalAnswer, isHighLowNumericalAnswer, isMultipleNumericalAnswer, isRangeNumericalAnswer, isSimpleNumericalAnswer, SimpleNumericalAnswer, RangeNumericalAnswer, HighLowNumericalAnswer } from 'gift-pegjs'; +import { NumericalQuestion, SimpleNumericalAnswer, RangeNumericalAnswer, HighLowNumericalAnswer } from 'gift-pegjs'; +import { isSimpleNumericalAnswer, isRangeNumericalAnswer, isHighLowNumericalAnswer, isMultipleNumericalAnswer } from 'gift-pegjs/typeGuards'; import DOMPurify from 'dompurify'; -// type CorrectAnswer = { -// numberHigh?: number; -// numberLow?: number; -// number?: number; -// type: string; -// }; - interface Props { - questionContent: TextFormat; - correctAnswers: NumericalAnswer; - globalFeedback?: string | undefined; + question: NumericalQuestion; handleOnSubmitAnswer?: (answer: number) => void; showAnswer?: boolean; } -const NumericalQuestion: React.FC = (props) => { - const { questionContent, correctAnswers, showAnswer, handleOnSubmitAnswer, globalFeedback } = +const NumericalQuestionDisplay: React.FC = (props) => { + const { question, showAnswer, handleOnSubmitAnswer } = props; const [answer, setAnswer] = useState(); - let correctAnswer= ''; + const correctAnswers = question.choices; + let correctAnswer = ''; - if (isSimpleNumericalAnswer(correctAnswers)) { - correctAnswer = `${(correctAnswers as SimpleNumericalAnswer).number}`; - } else if (isRangeNumericalAnswer(correctAnswers)) { - const choice = correctAnswers as RangeNumericalAnswer; + //const isSingleAnswer = correctAnswers.length === 1; + + if (isSimpleNumericalAnswer(correctAnswers[0])) { + correctAnswer = `${(correctAnswers[0] as SimpleNumericalAnswer).number}`; + } else if (isRangeNumericalAnswer(correctAnswers[0])) { + const choice = correctAnswers[0] as RangeNumericalAnswer; correctAnswer = `Entre ${choice.number - choice.range} et ${choice.number + choice.range}`; - } else if (isHighLowNumericalAnswer(correctAnswers)) { - const choice = correctAnswers as HighLowNumericalAnswer; + } else if (isHighLowNumericalAnswer(correctAnswers[0])) { + const choice = correctAnswers[0] as HighLowNumericalAnswer; correctAnswer = `Entre ${choice.numberLow} et ${choice.numberHigh}`; - } else if (isMultipleNumericalAnswer(correctAnswers)) { + } else if (isMultipleNumericalAnswer(correctAnswers[0])) { correctAnswer = `MultipleNumericalAnswer is not supported yet`; - } else { + } else { throw new Error('Unknown numerical answer type'); - } - + } + return (
-
+
{showAnswer ? ( <>
{correctAnswer}
- {globalFeedback &&
{globalFeedback}
} + {question.formattedGlobalFeedback &&
+
+
} ) : ( <>
) => { setAnswer(e.target.valueAsNumber); }} inputProps={{ 'data-testid': 'number-input' }} />
- {globalFeedback && showAnswer && ( -
{globalFeedback}
+ {question.formattedGlobalFeedback && showAnswer && ( +
+
+
)} {handleOnSubmitAnswer && (