Skip to content

Commit e6720bd

Browse files
authored
Core 675 refactor exercise preview (#92)
* remove includeRemove logic from preview exercise card * print stories deleted by mistake * test description fixed * added new story for preview exercise, hide all feedback propertly * revert print delete * remove questionState prop, added feedback inside answers for stories purposes * is_completed added into ExercisePreview component * update snapshots
1 parent 4d43c21 commit e6720bd

File tree

7 files changed

+157
-152
lines changed

7 files changed

+157
-152
lines changed

src/components/Exercise.stories.tsx

Lines changed: 53 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ const exerciseWithStepDataProps: ExerciseWithStepDataProps = {
7777
canUpdateCurrentStep: false,
7878
};
7979

80-
const exerciseWithQuestionStatesProps = (uid?: string): ExerciseWithQuestionStatesProps => {
80+
const exerciseWithQuestionStatesProps = (uid?: string, correctness?: string): ExerciseWithQuestionStatesProps => {
8181
return {
8282
exercise: {
8383
uid: uid || '1@1',
@@ -104,11 +104,11 @@ const exerciseWithQuestionStatesProps = (uid?: string): ExerciseWithQuestionStat
104104
is_answer_order_important: false,
105105
answers: [{
106106
id: '1',
107-
correctness: undefined,
107+
correctness: correctness,
108108
content_html: 'True',
109109
}, {
110110
id: '2',
111-
correctness: undefined,
111+
correctness: correctness,
112112
content_html: 'False',
113113
}],
114114
}],
@@ -146,12 +146,6 @@ const exerciseWithQuestionStatesProps = (uid?: string): ExerciseWithQuestionStat
146146
}
147147
};
148148

149-
const exerciseWithOverlayProps = (overlayChildren: React.ReactNode) => {
150-
return {
151-
overlayChildren,
152-
};
153-
}
154-
155149
type TextResizerValue = -2 | -1 | 0 | 1 | 2 | 3;
156150
const textResizerScales = [0.75, 0.9, 1, 1.25, 1.5, 2];
157151
const textResizerValues: TextResizerValue[] = [-2, -1, 0, 1, 2, 3];
@@ -657,12 +651,12 @@ bitterness. The discriminant <span data-math='b^2 - 4ac'></span> could perhaps a
657651
answers: [
658652
{
659653
id: '1',
660-
correctness: undefined,
654+
correctness: '1.0',
661655
content_html: `<span data-math='\\sqrt[3]{\\text{Apple}}'></span>`,
662656
},
663657
{
664658
id: '2',
665-
correctness: undefined,
659+
correctness: '1.0',
666660
content_html: `<span data-math='\\frac{\\text{Banana}^{2}}{4}'></span>`,
667661
},
668662
],
@@ -677,12 +671,12 @@ bitterness. The discriminant <span data-math='b^2 - 4ac'></span> could perhaps a
677671
answers: [
678672
{
679673
id: '1',
680-
correctness: undefined,
674+
correctness: '1.0',
681675
content_html: `<span data-math='e^{\\text{Blue}}'></span>`,
682676
},
683677
{
684678
id: '2',
685-
correctness: undefined,
679+
correctness: '1.0',
686680
content_html: `<span data-math='\\frac{\\pi}{2} + \\text{Red}'></span>`,
687681
},
688682
],
@@ -731,12 +725,12 @@ export const PreviewCard = () => {
731725
const props1: ExerciseWithQuestionStatesProps = {
732726
...exerciseWithQuestionStatesProps(),
733727
questionStates: {
734-
'1': {
728+
'320733': {
735729
available_points: '1.0',
736730
is_completed: true,
737731
answer_id_order: ['1', '2', '3', '4'],
738732
answer_id: randomlyCorrectAnswer,
739-
free_response: '',
733+
free_response: 'Feedback info',
740734
feedback_html: '',
741735
correct_answer_id: randomlyCorrectAnswer.toString(),
742736
correct_answer_feedback_html:
@@ -804,18 +798,24 @@ export const PreviewCard = () => {
804798
{
805799
id: 832300,
806800
content_html: 'hypothalamus',
801+
correctness: undefined,
802+
feedback_html: 'Feedback response',
807803
},
808804
{
809805
id: 832303,
810806
content_html: 'medulla oblongata',
807+
correctness: '1.0',
808+
feedback_html: 'Feedback response',
811809
},
812810
{
813811
id: 832301,
814812
content_html: 'corpus callosum',
813+
correctness: undefined,
815814
},
816815
{
817816
id: 832302,
818817
content_html: 'cerebellum',
818+
correctness: undefined,
819819
},
820820
],
821821
hints: [],
@@ -828,9 +828,17 @@ export const PreviewCard = () => {
828828
},
829829
};
830830

831+
const [showFeedback, setShowFeedback] = React.useState<boolean>(false);
832+
831833
return (
832834
<TextResizerProvider>
833-
<Exercise {...props1} className='preview-card' previewMode />
835+
<button
836+
onClick={()=> setShowFeedback(prev => !prev)}>{`Turn ${showFeedback ? 'off': 'on'} feedback`}
837+
</button>
838+
<ExercisePreview
839+
exercise={props1.exercise}
840+
showAllFeedback={showFeedback}
841+
/>
834842
</TextResizerProvider>
835843
);
836844
};
@@ -839,7 +847,6 @@ export const OverlayCard = () => {
839847
const randomlyCorrectAnswer = Math.floor(Math.random() * 3) + 1;
840848
const props1: ExerciseWithQuestionStatesProps = {
841849
...exerciseWithQuestionStatesProps('1@123'),
842-
...exerciseWithOverlayProps(<button>Overlay</button>),
843850
questionStates: {
844851
'1': {
845852
available_points: '1.0',
@@ -938,17 +945,8 @@ export const OverlayCard = () => {
938945
},
939946
};
940947

941-
const [buttonVariant, setButtonVariant] = React.useState<'include' | 'remove'>('include');
942-
943948
const props2: ExerciseWithQuestionStatesProps = {
944-
...exerciseWithQuestionStatesProps('1@321'),
945-
...exerciseWithOverlayProps(
946-
<IncludeRemoveQuestion
947-
buttonVariant={buttonVariant}
948-
onIncludeHandler={() => setButtonVariant('remove')}
949-
onRemoveHandler={() => setButtonVariant('include')}
950-
/>
951-
),
949+
...exerciseWithQuestionStatesProps('1@321', '1.0'),
952950
questionStates: {
953951
'1': {
954952
available_points: '1.0',
@@ -976,29 +974,41 @@ export const OverlayCard = () => {
976974
const includeHandler = (exerciseUid: string) => setSelectedQuestions(previous => previous.concat(exerciseUid));
977975
const removeHandler = (exerciseUid: string) => setSelectedQuestions(previous => previous.filter((id) => id !== exerciseUid));
978976

977+
978+
const includeRemoveQuestionComponent1 =
979+
<IncludeRemoveQuestion
980+
buttonVariant={selectedQuestions.includes(props1.exercise.uid) ? 'remove' : 'include'}
981+
onIncludeHandler={() => includeHandler(props1.exercise.uid)}
982+
onRemoveHandler={() => removeHandler(props1.exercise.uid)}
983+
onClickDetails={() => setShowDetails1((previous) => !previous)}
984+
/>;
985+
986+
const includeRemoveQuestionComponent2 =
987+
<IncludeRemoveQuestion
988+
buttonVariant={selectedQuestions.includes(props2.exercise.uid) ? 'remove' : 'include'}
989+
onIncludeHandler={() => includeHandler(props2.exercise.uid)}
990+
onRemoveHandler={() => removeHandler(props2.exercise.uid)}
991+
onClickDetails={() => setShowDetails2((previous) => !previous)}
992+
/>;
993+
979994
return (
980995
<TextResizerProvider>
981996
<h2>Exercise cards</h2>
982-
<Exercise {...props1} className='preview-card' previewMode />
983-
<Exercise {...props2} className='preview-card' previewMode />
997+
<Exercise {...props1} overlayChildren={<button>Overlay</button>} className='preview-card' previewMode />
998+
<Exercise {...props2} overlayChildren={<button>Overlay</button>} className='preview-card' previewMode />
984999
<h2>Exercise Preview cards</h2>
9851000
{showDetails1 && <h2>Details 1!</h2>}
986-
<ExercisePreview
987-
selected={selectedQuestions.includes(props1.exercise.uid)}
988-
onIncludeHandler={()=> includeHandler(props1.exercise.uid)}
989-
onRemoveHandler={()=> removeHandler(props1.exercise.uid)}
990-
onClickDetails={()=> setShowDetails1((previous) => !previous)}
991-
enableOverlay
992-
exercise={props1.exercise}
1001+
<ExercisePreview
1002+
selected={selectedQuestions.includes(props1.exercise.uid)}
1003+
overlayChildren={includeRemoveQuestionComponent1}
1004+
exercise={props1.exercise}
9931005
/>
9941006
{showDetails2 && <h2>Details 2!</h2>}
995-
<ExercisePreview
996-
selected={selectedQuestions.includes(props2.exercise.uid)}
997-
onIncludeHandler={()=> includeHandler(props2.exercise.uid)}
998-
onRemoveHandler={()=> removeHandler(props2.exercise.uid)}
999-
onClickDetails={()=> setShowDetails2((previous) => !previous)}
1000-
enableOverlay
1001-
exercise={props2.exercise}
1007+
<ExercisePreview
1008+
selected={selectedQuestions.includes(props2.exercise.uid)}
1009+
overlayChildren={includeRemoveQuestionComponent2}
1010+
exercise={props2.exercise}
1011+
showAllFeedback
10021012
/>
10031013
</TextResizerProvider>
10041014
);

src/components/Exercise/styles.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,5 @@ export const exerciseStyles = css`
131131
font-weight: bold;
132132
font-size: 1.6rem;
133133
}
134-
135-
136134
}
137135
`;

src/components/ExercisePreview.spec.tsx

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,9 @@ describe('ExercisePreview', () => {
3333
is_answer_order_important: false,
3434
answers: [{
3535
id: '1',
36-
correctness: undefined,
36+
correctness: '1.0',
3737
content_html: 'True',
38+
feedback_html: 'Feedback',
3839
}, {
3940
id: '2',
4041
correctness: undefined,
@@ -45,22 +46,18 @@ describe('ExercisePreview', () => {
4546
});
4647

4748
it.each`
48-
enableOverlay | selected | description
49-
${true} | ${true} | ${'with overlay and selected true'}
50-
${true} | ${true} | ${'with overlay and selected false'}
51-
${false} | ${false} | ${'without overlay'}
52-
`('matches snapshot %description', ({ enableOverlay, selected }: { enableOverlay: boolean, selected: boolean }) => {
53-
const onIncludeMock = jest.fn();
54-
const onRemoveMock = jest.fn();
55-
const onDetailsMock = jest.fn();
49+
selected | description | overlay | showFeedback
50+
${true} | ${'with overlay and selected true'} | ${<button>Over</button>} | ${undefined}
51+
${true} | ${'with overlay and selected false'} | ${<button>Over</button>} | ${false}
52+
${false} | ${'without overlay'} | ${undefined} | ${true}
53+
`('matches snapshot $description', (
54+
{ selected, overlay, showFeedback }: { selected: boolean, overlay: JSX.Element, showFeedback: boolean }) => {
5655
const tree = renderer.create(
57-
<ExercisePreview
58-
exercise={exercise}
59-
enableOverlay={enableOverlay}
56+
<ExercisePreview
57+
exercise={exercise}
6058
selected={selected}
61-
onIncludeHandler={onIncludeMock}
62-
onRemoveHandler={onRemoveMock}
63-
onClickDetails={onDetailsMock}
59+
overlayChildren={overlay}
60+
showAllFeedback={showFeedback}
6461
/>
6562
).toJSON();
6663
expect(tree).toMatchSnapshot();

src/components/ExercisePreview.tsx

Lines changed: 22 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,40 @@
11
import React from "react";
22
import { ExerciseData, ExerciseQuestionData, StepBase } from "src/types";
3-
import { IncludeRemoveQuestion } from "./IncludeRemoveQuestion";
43
import { Exercise } from "./Exercise";
54

65
export interface ExercisePreviewProps {
76
exercise: ExerciseData;
8-
selected: boolean;
9-
onIncludeHandler: () => void;
10-
onRemoveHandler: () => void;
11-
onClickDetails: () => void;
12-
enableOverlay?: boolean;
7+
selected?: boolean;
8+
showAllFeedback?: boolean;
9+
overlayChildren?: React.ReactNode;
1310
}
1411

15-
/**
16-
* An Exercise version with less interaction with card and grants an Overlay with Include/Remove component
17-
*/
1812
export const ExercisePreview = (
1913
{
2014
exercise,
2115
selected,
22-
onIncludeHandler,
23-
onRemoveHandler,
24-
onClickDetails,
25-
enableOverlay = false,
16+
showAllFeedback = false,
17+
overlayChildren,
2618
}: ExercisePreviewProps) => {
2719

20+
const hideAnswerFeedback = (exercise: ExerciseData) => {
21+
exercise.questions.map(question => question.answers.map(a => a.correctness = undefined ));
22+
return exercise;
23+
};
24+
2825
const exercisePreviewProps = (exercise: ExerciseData) => {
2926
const formatAnswerData = (questions: ExerciseQuestionData[]) => questions.map((q) => (
3027
{ id: q.id, correct_answer_id: (q.answers.find((a) => a.correctness === '1.0')?.id || '') }));
3128

32-
const questionStateFields = {
33-
available_points: '1.0',
34-
is_completed: true,
35-
answer_id: '1',
36-
free_response: '',
37-
feedback_html: '',
38-
correct_answer_feedback_html: '',
39-
attempts_remaining: 0,
40-
attempt_number: 1,
41-
incorrectAnswerId: 0
42-
}
43-
44-
const questionStates = formatAnswerData(exercise.questions).reduce((acc, answer) => {
29+
const questionStateFields = formatAnswerData(exercise.questions).reduce((acc, answer) => {
4530
const { id, correct_answer_id } = answer;
46-
return { ...acc, [id]: { ...questionStateFields, correct_answer_id } };
31+
return {
32+
...acc,
33+
[id]: {
34+
correct_answer_id,
35+
is_completed: true,
36+
}
37+
};
4738
}, {});
4839

4940
const step: StepBase = {
@@ -64,32 +55,17 @@ export const ExercisePreview = (
6455
step: step,
6556
questionNumber: exercise.number as number,
6657
numberOfQuestions: exercise.questions.length,
67-
questionStates: questionStates,
68-
show_all_feedback: false, // Hide all feedback
58+
questionStates: questionStateFields,
59+
show_all_feedback: showAllFeedback,
6960
};
7061
};
7162

72-
const includeRemoveQuestionComponent = React.useMemo(() =>
73-
<IncludeRemoveQuestion
74-
buttonVariant={selected ? 'remove' : 'include'}
75-
onIncludeHandler={onIncludeHandler}
76-
onRemoveHandler={onRemoveHandler}
77-
onClickDetails={onClickDetails}
78-
/>
79-
, [selected, onIncludeHandler, onRemoveHandler, onClickDetails]);
80-
8163
return (
8264
<Exercise
83-
exercise={exercise}
65+
exercise={showAllFeedback ? exercise : hideAnswerFeedback(exercise)}
8466
className={selected ? 'preview-card is-selected' : 'preview-card'}
8567
previewMode
86-
{
87-
...(enableOverlay
88-
? {
89-
overlayChildren: includeRemoveQuestionComponent,
90-
}
91-
: {})
92-
}
68+
overlayChildren={overlayChildren}
9369
{...exercisePreviewProps(exercise)}
9470
/>
9571
);

src/components/IncludeRemoveQuestion/index.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,6 @@ export const IncludeRemoveQuestion = (
2525
case 'remove':
2626
onRemoveHandler();
2727
break;
28-
default:
29-
break;
3028
}
3129
};
3230

0 commit comments

Comments
 (0)