diff --git a/package.json b/package.json index 056c01f..4e9b12b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@openstax/assessment-components", - "version": "1.1.0", + "version": "1.2.0", "license": "MIT", "source": "./src/index.ts", "types": "./dist/src/index.d.ts", diff --git a/src/components/Exercise.spec.tsx b/src/components/Exercise.spec.tsx index b3530a0..aff0bcd 100644 --- a/src/components/Exercise.spec.tsx +++ b/src/components/Exercise.spec.tsx @@ -142,6 +142,7 @@ describe('Exercise', () => { numberOfQuestions: 1, scrollToQuestion: 1, hasMultipleAttempts: false, + hasFeedback: true, onAnswerChange: () => null, onAnswerSave: () => null, onNextStep: () => null, diff --git a/src/components/Exercise.stories.tsx b/src/components/Exercise.stories.tsx index d1aeb86..ca6f65f 100644 --- a/src/components/Exercise.stories.tsx +++ b/src/components/Exercise.stories.tsx @@ -130,6 +130,7 @@ const exerciseWithQuestionStatesProps = (): ExerciseWithQuestionStatesProps => { apiIsPending: false } }, + hasFeedback: true, }}; type TextResizerValue = -2 | -1 | 0 | 1 | 2 | 3; @@ -181,6 +182,34 @@ export const Default = () => { ) }; +export const DefaultWithoutFeedback = () => { + const [selectedAnswerId, setSelectedAnswerId] = useState(0); + const [apiIsPending, setApiIsPending] = useState(false) + const [isCompleted, setIsCompleted] = useState(false) + const props = exerciseWithQuestionStatesProps(); + props.hasFeedback = false; + props.questionStates['1'].answer_id = selectedAnswerId; + props.questionStates['1'].apiIsPending = apiIsPending; + props.questionStates['1'].is_completed = isCompleted; + props.questionStates['1'].canAnswer = !isCompleted; + return ( + & { id: number, question_id: number }) => { + setSelectedAnswerId(a.id) + }} + onAnswerSave={() => { + setApiIsPending(true); + setTimeout(() => { + setApiIsPending(false) + setIsCompleted(true) + }, 1000) + }} + onNextStep={(idx) => console.log(`Next step: ${idx}`)} + /> + ) +}; + export const DeprecatedStepData = () => ; export const CompleteWithFeedback = () => { @@ -210,6 +239,34 @@ export const CompleteWithFeedback = () => { return ; }; +export const CompleteWithoutFeedback = () => { + const props: ExerciseWithQuestionStatesProps = { + ...exerciseWithQuestionStatesProps(), + + questionStates: { + '1': { + available_points: '1.0', + is_completed: true, + answer_id_order: ['1', '2'], + answer_id: 1, + free_response: 'Free response', + feedback_html: '', + correct_answer_id: '', + correct_answer_feedback_html: '', + attempts_remaining: 0, + attempt_number: 1, + incorrectAnswerId: 0, + canAnswer: false, + needsSaved: false, + apiIsPending: false + } + }, + hasFeedback: false, + }; + + return ; +}; + export const IncorrectWithFeedbackAndSolution = () => { const props: ExerciseWithQuestionStatesProps = { ...exerciseWithQuestionStatesProps() }; props.questionStates = { diff --git a/src/components/Exercise.tsx b/src/components/Exercise.tsx index 2df0332..0bf6995 100644 --- a/src/components/Exercise.tsx +++ b/src/components/Exercise.tsx @@ -138,6 +138,7 @@ export interface ExerciseBaseProps { * - A topic icon linking to the relevant textbook location */ exerciseIcons?: ExerciseIcons; + hasFeedback?: boolean; } export interface ExerciseWithStepDataProps extends ExerciseBaseProps { diff --git a/src/components/ExerciseQuestion.spec.tsx b/src/components/ExerciseQuestion.spec.tsx index 248a64d..0910149 100644 --- a/src/components/ExerciseQuestion.spec.tsx +++ b/src/components/ExerciseQuestion.spec.tsx @@ -50,6 +50,7 @@ describe('ExerciseQuestion', () => { displaySolution: false, available_points: '1.0', exercise_uid: '', + hasFeedback: true, } }); @@ -125,6 +126,21 @@ describe('ExerciseQuestion', () => { expect(tree).toMatchSnapshot(); }); + it('renders Submit & continue button', () => { + const tree = renderer.create( + + ).toJSON(); + expect(tree).toMatchSnapshot(); + }); + it('renders continue button (unused?)', () => { const tree = renderer.create( { expect(mockFn).toHaveBeenCalledWith(0); }); + + it('passes question index on submit button click when there is not feedback', () => { + const mockFn = jest.fn(); + + // This combination of props should never happen: `is_completed` should not + // be true at the same time as `needsSaved`. This combination allows the + // test to work correctly without simulating waiting for api calls + const tree = renderer.create( + + ); + renderer.act(() => { + tree.root.findByType(SaveButton).props.onClick(); + }); + + expect(mockFn).toHaveBeenCalledWith(0); + }); }); diff --git a/src/components/ExerciseQuestion.stories.tsx b/src/components/ExerciseQuestion.stories.tsx index 8360861..0a92d2e 100644 --- a/src/components/ExerciseQuestion.stories.tsx +++ b/src/components/ExerciseQuestion.stories.tsx @@ -51,7 +51,8 @@ const props = { needsSaved: true, canUpdateCurrentStep: true, attempt_number: 0, - apiIsPending: false + apiIsPending: false, + hasFeedback: false }; export const Default = () => ; diff --git a/src/components/ExerciseQuestion.tsx b/src/components/ExerciseQuestion.tsx index 6148917..a4117eb 100644 --- a/src/components/ExerciseQuestion.tsx +++ b/src/components/ExerciseQuestion.tsx @@ -37,6 +37,7 @@ export interface ExerciseQuestionProps { free_response?: string; show_all_feedback?: boolean; tableFeedbackEnabled?: boolean; + hasFeedback?: ExerciseBaseProps['hasFeedback']; } const AttemptsRemaining = ({ count }: { count: number }) => { @@ -56,7 +57,7 @@ const PublishedComments = ({ published_comments }: { published_comments?: string } export const SaveButton = (props: { - disabled: boolean, isWaiting: boolean, attempt_number: number + disabled: boolean, isWaiting: boolean, attempt_number: number, willContinue: boolean } & React.ComponentPropsWithoutRef<'button'>) => ( ); @@ -93,9 +96,18 @@ export const ExerciseQuestion = React.forwardRef((props: ExerciseQuestionProps, is_completed, correct_answer_id, incorrectAnswerId, choicesEnabled, questionNumber, answer_id, hasMultipleAttempts, attempts_remaining, published_comments, detailedSolution, canAnswer, needsSaved, attempt_number, apiIsPending, onAnswerSave, onNextStep, canUpdateCurrentStep, - displaySolution, available_points, free_response, show_all_feedback, tableFeedbackEnabled + displaySolution, available_points, free_response, show_all_feedback, tableFeedbackEnabled, + hasFeedback } = props; + const [shouldContinue, setShouldContinue] = React.useState(false) + React.useEffect(() => { + if (shouldContinue && is_completed) { + onNextStep(questionNumber - 1); + setShouldContinue(false); + } + }, [onNextStep, questionNumber, shouldContinue, is_completed]); + return (
Detailed solution:
)}
- {canAnswer && needsSaved ? + {(canAnswer && needsSaved) || shouldContinue ? onAnswerSave(numberfyId(question.id))} + onClick={() => { + onAnswerSave(numberfyId(question.id)); + if (!hasFeedback) { + setShouldContinue(true); + } + }} + willContinue={!hasFeedback} /> : onNextStep(questionNumber - 1)} canUpdateCurrentStep={canUpdateCurrentStep} />}
diff --git a/src/components/__snapshots__/ExerciseQuestion.spec.tsx.snap b/src/components/__snapshots__/ExerciseQuestion.spec.tsx.snap index c1112bd..fde358f 100644 --- a/src/components/__snapshots__/ExerciseQuestion.spec.tsx.snap +++ b/src/components/__snapshots__/ExerciseQuestion.spec.tsx.snap @@ -470,6 +470,165 @@ exports[`ExerciseQuestion renders Save button 1`] = ` `; +exports[`ExerciseQuestion renders Submit & continue button 1`] = ` +
+
+
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+
+
+
+ + Points: + 1.0 + + + +
+
+ +
+
+
+
+`; + exports[`ExerciseQuestion renders all attempts remaining 1`] = `