Skip to content

Commit 9d12017

Browse files
authored
fix: player display invalid questions (#136)
* fix: update the context to have default current idx to -1
1 parent c4ddb3c commit 9d12017

32 files changed

+133
-84
lines changed

cypress/e2e/Admin/create/createView.cy.ts

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ import {
2727
QUESTION_APP_SETTINGS,
2828
} from '../../../fixtures/appSettings';
2929
import { QuizNavigator } from '../../../utils/navigation';
30-
import { WAITING_DELAY_MS } from '../../../utils/time';
3130
import { fillMultipleChoiceQuestion } from './multipleChoices.cy';
3231

3332
const newMultipleChoiceData = {
@@ -43,7 +42,7 @@ const newMultipleChoiceData = {
4342
},
4443
],
4544
explanation: 'my new explanation',
46-
hints: 'my new hints'
45+
hints: 'my new hints',
4746
};
4847

4948
describe('Create View', () => {
@@ -77,25 +76,23 @@ describe('Create View', () => {
7776
// Add three questions and make sure they are added to the QuestionTopBar
7877
cy.get(dataCyWrapper(ADD_NEW_QUESTION_TITLE_CY)).should('be.visible');
7978
fillMultipleChoiceQuestion(newMultipleChoiceData);
80-
// eslint-disable-next-line cypress/no-unnecessary-waiting
81-
cy.wait(WAITING_DELAY_MS); // Wait for the new question to appear
79+
// Wait for the new question to appear
80+
cy.get(`.${QUESTION_STEP_CLASSNAME}`).should('have.length', 1);
8281
cy.get(dataCyWrapper(NAVIGATION_ADD_QUESTION_BUTTON_CY)).click();
8382
cy.get(dataCyWrapper(CREATE_QUESTION_TITLE_CY))
8483
.should('be.visible')
8584
.should('have.value', '');
8685
fillMultipleChoiceQuestion(newMultipleChoiceData);
87-
// eslint-disable-next-line cypress/no-unnecessary-waiting
88-
cy.wait(WAITING_DELAY_MS);
86+
// Wait for the new question to appear
87+
cy.get(`.${QUESTION_STEP_CLASSNAME}`).should('have.length', 2);
8988
cy.get(dataCyWrapper(NAVIGATION_ADD_QUESTION_BUTTON_CY)).click();
9089
cy.get(dataCyWrapper(CREATE_QUESTION_TITLE_CY))
9190
.should('be.visible')
9291
.should('have.value', '');
9392
fillMultipleChoiceQuestion(newMultipleChoiceData);
9493
// Verify the questions are added to the order list by checking the number of
9594
// question nodes in the QuestionTopBar, as we cannot check the app settings directly
96-
cy.get('html')
97-
.find(`.${QUESTION_STEP_CLASSNAME}`)
98-
.should('have.length', 3);
95+
cy.get(`.${QUESTION_STEP_CLASSNAME}`).should('have.length', 3);
9996
});
10097
});
10198

@@ -192,9 +189,7 @@ describe('Create View', () => {
192189
);
193190
cy.get(dataCyWrapper(QUESTION_BAR_CY)).should('be.visible');
194191
fillMultipleChoiceQuestion(newMultipleChoiceData);
195-
cy.get('html')
196-
.find(`.${QUESTION_STEP_CLASSNAME}`)
197-
.should('have.length', 5);
192+
cy.get(`.${QUESTION_STEP_CLASSNAME}`).should('have.length', 5);
198193
});
199194

200195
it('Update Question type should not create a new question', () => {
@@ -210,9 +205,10 @@ describe('Create View', () => {
210205
cy.get(`${dataCyWrapper(CREATE_VIEW_SAVE_BUTTON_CY)}`).click();
211206

212207
// Check the current number of questions
213-
cy.get('html')
214-
.find(`.${QUESTION_STEP_CLASSNAME}`)
215-
.should('have.length', numberOfQuestions);
208+
cy.get(`.${QUESTION_STEP_CLASSNAME}`).should(
209+
'have.length',
210+
numberOfQuestions
211+
);
216212

217213
// update the question type and save
218214
const updatedQuestion = {
@@ -236,9 +232,10 @@ describe('Create View', () => {
236232
cy.get(`${dataCyWrapper(CREATE_VIEW_SAVE_BUTTON_CY)}`).click();
237233

238234
// Check that the current number of questions is still unchanged
239-
cy.get('html')
240-
.find(`.${QUESTION_STEP_CLASSNAME}`)
241-
.should('have.length', numberOfQuestions);
235+
cy.get(`.${QUESTION_STEP_CLASSNAME}`).should(
236+
'have.length',
237+
numberOfQuestions
238+
);
242239

243240
// Check the question title, question type, the answer and attempts.
244241
cy.get(`${dataCyWrapper(CREATE_QUESTION_TITLE_CY)} input`).should(

cypress/e2e/Admin/results/TableByUser.cy.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,9 @@ const testTableContent = (uId: string, ascending: boolean) => {
272272

273273
questionTitles.push(currentQuestionName);
274274
if (!isOrdered(questionTitles, ascending)) {
275-
throw new Error(`The questions are not ordered in ${ascending ? 'ASC' : 'DESC'}.`)
275+
throw new Error(
276+
`The questions are not ordered in ${ascending ? 'ASC' : 'DESC'}.`
277+
);
276278
}
277279

278280
const userResponse = USER_RESPONSES[uId].find(

cypress/e2e/legacy.cy.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,11 @@ import { AppSetting, Context } from '@graasp/sdk';
33
import { APP_SETTING_NAMES, QuestionType } from '../../src/config/constants';
44
import {
55
PLAY_VIEW_QUESTION_TITLE_CY,
6-
buildPlayViewTextInputCy,
76
QUESTION_BAR_NEXT_CY,
7+
buildPlayViewTextInputCy,
88
dataCyWrapper,
99
} from '../../src/config/selectors';
10-
import {
11-
datesFactory,
12-
mockAppDataFactory,
13-
} from '../../src/data/factories';
10+
import { datesFactory, mockAppDataFactory } from '../../src/data/factories';
1411
import { mockItem } from '../../src/data/items';
1512
import { mockCurrentMember } from '../../src/data/members';
1613

@@ -73,7 +70,7 @@ describe('Legacy', () => {
7370
};
7471

7572
// current user
76-
const textAppData = mockAppDataFactory({
73+
const textAppData = mockAppDataFactory({
7774
item: mockItem,
7875
creator: mockCurrentMember,
7976
data: {
@@ -107,7 +104,7 @@ describe('Legacy', () => {
107104
},
108105
appContext: {
109106
context: Context.Builder,
110-
}
107+
},
111108
});
112109
cy.visit('/');
113110

cypress/e2e/play/fillBlanks.cy.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Context } from '@graasp/sdk';
22

3+
import { QuestionStepStyleKeys } from '../../../src/components/navigation/questionNavigation/types';
34
import {
45
AppSettingData,
56
TextAppDataData,
@@ -29,7 +30,6 @@ import {
2930
QUESTION_APP_SETTINGS,
3031
setAttemptsOnAppSettings,
3132
} from '../../fixtures/appSettings';
32-
import { QuestionStepStyleKeys } from '../../../src/components/navigation/questionNavigation/types';
3333

3434
const { data } = QUESTION_APP_SETTINGS.find(
3535
({ name, data }) =>

cypress/fixtures/appSettings.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ export const CAPITAL_FRANCE_SETTING = {
156156
},
157157
],
158158
explanation: 'Paris is the capital of France.',
159-
hints: 'Think about the iconic Eiffel Tower.'
159+
hints: 'Think about the iconic Eiffel Tower.',
160160
},
161161
name: APP_SETTING_NAMES.QUESTION,
162162
};

cypress/utils/autoScrollableMenuSelected.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,8 @@ export const verifySelectedMenu = (
1515
labels: { id: string }[]
1616
) => {
1717
labels.forEach(({ id }) => {
18-
cy.get(
19-
dataCyWrapper(
20-
buildAutoScrollableMenuLinkCy(id)
21-
)
22-
).should('be.visible');
18+
cy.get(dataCyWrapper(buildAutoScrollableMenuLinkCy(id))).should(
19+
'be.visible'
20+
);
2321
});
2422
};

cypress/utils/time.ts

Lines changed: 0 additions & 1 deletion
This file was deleted.

src/components/App.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ import {
1919
queryClient,
2020
} from '../config/queryClient';
2121
import { mockContext as defaultMockContext } from '../data/config';
22+
import { mockMembers } from '../data/members';
2223
import graaspTheme from '../layout/theme';
2324
import View from './views/View';
24-
import { mockMembers } from '../data/members';
2525

2626
export const App = () => {
2727
const [mockContext, setMockContext] = useObjectState(defaultMockContext);

src/components/common/animations/ReorderAnimation.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export const ReorderAnimation = <T extends DataElementType>({
3131
}: Props<T>) => {
3232
const [totalHeight, setTotalHeight] = useState<number>(0);
3333
// Tracks the heights of each element to recompute totalHeight when heights changed.
34-
// That means, if a element's height changed, the map is updated and he totalHeight
34+
// That means, if a element's height changed, the map is updated and he totalHeight
3535
// recomputed with all the heights, including the updated one.
3636
const elementHeights = new Map<string, number>();
3737
// this variable is used to set the next element to the good y

src/components/context/QuizContext.tsx

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ type ContextType = {
2727
addQuestion: () => void;
2828
saveQuestion: (newData: QuestionData) => Promise<void>;
2929
isSettingsFetching: boolean;
30+
isLoaded: boolean;
3031
saveOrder: (order: string[], currQuestionId?: string) => void;
3132
};
3233

@@ -51,11 +52,16 @@ export const QuizProvider = ({ children }: Props) => {
5152
const { mutateAsync: patchAppSettingAsync } = mutations.usePatchAppSetting();
5253
// current question idx
5354
// -1 if we are adding a new question
54-
const [currentIdx, setCurrentIdx] = useState(0);
55+
const [currentIdx, setCurrentIdx] = useState(-1);
5556

5657
const [orderSetting, setOrderSetting] = useState<AppSetting>();
5758
const [order, setOrder] = useState<string[]>([]);
5859

60+
// This state indicates if the questions were received and the question order set correctly.
61+
// It allows QuizNavigation to display the Add question button when loading stops
62+
// and the current index updated correctly according if there is questions or not.
63+
const [isLoaded, setIsLoaded] = useState(false);
64+
5965
// Here use type of CurrentQuestion because only the id of appSetting is needed...
6066
const [currentQuestion, setCurrentQuestion] =
6167
useState<CurrentQuestion>(DEFAULT_QUESTION);
@@ -201,12 +207,37 @@ export const QuizProvider = ({ children }: Props) => {
201207
APP_SETTING_NAMES.QUESTION_LIST
202208
);
203209

210+
// Get all questions id. To support legacy code, if no question id, the id is used instead.
211+
const questionIds = getSettingsByName(
212+
settings,
213+
APP_SETTING_NAMES.QUESTION
214+
).map((appSetting) => appSetting?.data?.questionId ?? appSetting.id);
215+
216+
const filteredOrder: string[] = [];
204217
if (newOrderSetting && newOrderSetting.length > 0) {
205218
const value = newOrderSetting[0] as QuestionListType;
206219
setOrderSetting(value);
207-
setOrder(value?.data?.list ?? []);
220+
// Filter out questions that are not well formatted in AppSettings.
221+
filteredOrder.push(
222+
...(value?.data?.list.filter((id) => questionIds.includes(id)) ?? [])
223+
);
224+
setOrder(filteredOrder);
225+
}
226+
227+
// if it is first loading, set is loaded to true.
228+
if (!isLoaded) {
229+
// If there are questions, set current idx to the first one.
230+
// If it has already set current idx, don't do it again to not reset curr question on order changed.
231+
if (filteredOrder.length) {
232+
setCurrentIdx(0);
233+
}
234+
235+
setIsLoaded(true);
208236
}
209237
}
238+
// Disable exhaustive-deps for isLoaded, because we don't want
239+
// to reload this useEffect when isLoaded has changed.
240+
// eslint-disable-next-line react-hooks/exhaustive-deps
210241
}, [settings]);
211242

212243
// set current question
@@ -249,11 +280,22 @@ export const QuizProvider = ({ children }: Props) => {
249280
}, [patchAppSettingAsync, settings]);
250281

251282
const value: ContextType = useMemo(() => {
283+
const validIds =
284+
getSettingsByName(settings, APP_SETTING_NAMES.QUESTION_LIST)[0]?.data
285+
?.list ?? [];
286+
252287
const questions = settings
253-
? (getSettingsByName(
254-
settings,
255-
APP_SETTING_NAMES.QUESTION
256-
) as QuestionDataAppSetting[])
288+
? (
289+
getSettingsByName(
290+
settings,
291+
APP_SETTING_NAMES.QUESTION
292+
) as QuestionDataAppSetting[]
293+
)
294+
// Filter out questions that are not well formatted in AppSettings.
295+
.filter(
296+
(q) =>
297+
validIds.includes(q.data.questionId) || validIds.includes(q.id)
298+
)
257299
: [];
258300

259301
return {
@@ -268,6 +310,7 @@ export const QuizProvider = ({ children }: Props) => {
268310
addQuestion,
269311
saveQuestion,
270312
isSettingsFetching,
313+
isLoaded,
271314
saveOrder,
272315
};
273316
}, [
@@ -280,6 +323,7 @@ export const QuizProvider = ({ children }: Props) => {
280323
moveToPreviousQuestion,
281324
saveQuestion,
282325
isSettingsFetching,
326+
isLoaded,
283327
saveOrder,
284328
]);
285329

0 commit comments

Comments
 (0)