Skip to content

Commit

Permalink
fix: player display invalid questions (#136)
Browse files Browse the repository at this point in the history
* fix: update the context to have default current idx to -1
  • Loading branch information
ReidyT authored Feb 22, 2024
1 parent c4ddb3c commit 9d12017
Show file tree
Hide file tree
Showing 32 changed files with 133 additions and 84 deletions.
33 changes: 15 additions & 18 deletions cypress/e2e/Admin/create/createView.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import {
QUESTION_APP_SETTINGS,
} from '../../../fixtures/appSettings';
import { QuizNavigator } from '../../../utils/navigation';
import { WAITING_DELAY_MS } from '../../../utils/time';
import { fillMultipleChoiceQuestion } from './multipleChoices.cy';

const newMultipleChoiceData = {
Expand All @@ -43,7 +42,7 @@ const newMultipleChoiceData = {
},
],
explanation: 'my new explanation',
hints: 'my new hints'
hints: 'my new hints',
};

describe('Create View', () => {
Expand Down Expand Up @@ -77,25 +76,23 @@ describe('Create View', () => {
// Add three questions and make sure they are added to the QuestionTopBar
cy.get(dataCyWrapper(ADD_NEW_QUESTION_TITLE_CY)).should('be.visible');
fillMultipleChoiceQuestion(newMultipleChoiceData);
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(WAITING_DELAY_MS); // Wait for the new question to appear
// Wait for the new question to appear
cy.get(`.${QUESTION_STEP_CLASSNAME}`).should('have.length', 1);
cy.get(dataCyWrapper(NAVIGATION_ADD_QUESTION_BUTTON_CY)).click();
cy.get(dataCyWrapper(CREATE_QUESTION_TITLE_CY))
.should('be.visible')
.should('have.value', '');
fillMultipleChoiceQuestion(newMultipleChoiceData);
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(WAITING_DELAY_MS);
// Wait for the new question to appear
cy.get(`.${QUESTION_STEP_CLASSNAME}`).should('have.length', 2);
cy.get(dataCyWrapper(NAVIGATION_ADD_QUESTION_BUTTON_CY)).click();
cy.get(dataCyWrapper(CREATE_QUESTION_TITLE_CY))
.should('be.visible')
.should('have.value', '');
fillMultipleChoiceQuestion(newMultipleChoiceData);
// Verify the questions are added to the order list by checking the number of
// question nodes in the QuestionTopBar, as we cannot check the app settings directly
cy.get('html')
.find(`.${QUESTION_STEP_CLASSNAME}`)
.should('have.length', 3);
cy.get(`.${QUESTION_STEP_CLASSNAME}`).should('have.length', 3);
});
});

Expand Down Expand Up @@ -192,9 +189,7 @@ describe('Create View', () => {
);
cy.get(dataCyWrapper(QUESTION_BAR_CY)).should('be.visible');
fillMultipleChoiceQuestion(newMultipleChoiceData);
cy.get('html')
.find(`.${QUESTION_STEP_CLASSNAME}`)
.should('have.length', 5);
cy.get(`.${QUESTION_STEP_CLASSNAME}`).should('have.length', 5);
});

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

// Check the current number of questions
cy.get('html')
.find(`.${QUESTION_STEP_CLASSNAME}`)
.should('have.length', numberOfQuestions);
cy.get(`.${QUESTION_STEP_CLASSNAME}`).should(
'have.length',
numberOfQuestions
);

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

// Check that the current number of questions is still unchanged
cy.get('html')
.find(`.${QUESTION_STEP_CLASSNAME}`)
.should('have.length', numberOfQuestions);
cy.get(`.${QUESTION_STEP_CLASSNAME}`).should(
'have.length',
numberOfQuestions
);

// Check the question title, question type, the answer and attempts.
cy.get(`${dataCyWrapper(CREATE_QUESTION_TITLE_CY)} input`).should(
Expand Down
4 changes: 3 additions & 1 deletion cypress/e2e/Admin/results/TableByUser.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,9 @@ const testTableContent = (uId: string, ascending: boolean) => {

questionTitles.push(currentQuestionName);
if (!isOrdered(questionTitles, ascending)) {
throw new Error(`The questions are not ordered in ${ascending ? 'ASC' : 'DESC'}.`)
throw new Error(
`The questions are not ordered in ${ascending ? 'ASC' : 'DESC'}.`
);
}

const userResponse = USER_RESPONSES[uId].find(
Expand Down
11 changes: 4 additions & 7 deletions cypress/e2e/legacy.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,11 @@ import { AppSetting, Context } from '@graasp/sdk';
import { APP_SETTING_NAMES, QuestionType } from '../../src/config/constants';
import {
PLAY_VIEW_QUESTION_TITLE_CY,
buildPlayViewTextInputCy,
QUESTION_BAR_NEXT_CY,
buildPlayViewTextInputCy,
dataCyWrapper,
} from '../../src/config/selectors';
import {
datesFactory,
mockAppDataFactory,
} from '../../src/data/factories';
import { datesFactory, mockAppDataFactory } from '../../src/data/factories';
import { mockItem } from '../../src/data/items';
import { mockCurrentMember } from '../../src/data/members';

Expand Down Expand Up @@ -73,7 +70,7 @@ describe('Legacy', () => {
};

// current user
const textAppData = mockAppDataFactory({
const textAppData = mockAppDataFactory({
item: mockItem,
creator: mockCurrentMember,
data: {
Expand Down Expand Up @@ -107,7 +104,7 @@ describe('Legacy', () => {
},
appContext: {
context: Context.Builder,
}
},
});
cy.visit('/');

Expand Down
2 changes: 1 addition & 1 deletion cypress/e2e/play/fillBlanks.cy.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Context } from '@graasp/sdk';

import { QuestionStepStyleKeys } from '../../../src/components/navigation/questionNavigation/types';
import {
AppSettingData,
TextAppDataData,
Expand Down Expand Up @@ -29,7 +30,6 @@ import {
QUESTION_APP_SETTINGS,
setAttemptsOnAppSettings,
} from '../../fixtures/appSettings';
import { QuestionStepStyleKeys } from '../../../src/components/navigation/questionNavigation/types';

const { data } = QUESTION_APP_SETTINGS.find(
({ name, data }) =>
Expand Down
2 changes: 1 addition & 1 deletion cypress/fixtures/appSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ export const CAPITAL_FRANCE_SETTING = {
},
],
explanation: 'Paris is the capital of France.',
hints: 'Think about the iconic Eiffel Tower.'
hints: 'Think about the iconic Eiffel Tower.',
},
name: APP_SETTING_NAMES.QUESTION,
};
Expand Down
8 changes: 3 additions & 5 deletions cypress/utils/autoScrollableMenuSelected.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,8 @@ export const verifySelectedMenu = (
labels: { id: string }[]
) => {
labels.forEach(({ id }) => {
cy.get(
dataCyWrapper(
buildAutoScrollableMenuLinkCy(id)
)
).should('be.visible');
cy.get(dataCyWrapper(buildAutoScrollableMenuLinkCy(id))).should(
'be.visible'
);
});
};
1 change: 0 additions & 1 deletion cypress/utils/time.ts

This file was deleted.

2 changes: 1 addition & 1 deletion src/components/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ import {
queryClient,
} from '../config/queryClient';
import { mockContext as defaultMockContext } from '../data/config';
import { mockMembers } from '../data/members';
import graaspTheme from '../layout/theme';
import View from './views/View';
import { mockMembers } from '../data/members';

export const App = () => {
const [mockContext, setMockContext] = useObjectState(defaultMockContext);
Expand Down
2 changes: 1 addition & 1 deletion src/components/common/animations/ReorderAnimation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export const ReorderAnimation = <T extends DataElementType>({
}: Props<T>) => {
const [totalHeight, setTotalHeight] = useState<number>(0);
// Tracks the heights of each element to recompute totalHeight when heights changed.
// That means, if a element's height changed, the map is updated and he totalHeight
// That means, if a element's height changed, the map is updated and he totalHeight
// recomputed with all the heights, including the updated one.
const elementHeights = new Map<string, number>();
// this variable is used to set the next element to the good y
Expand Down
56 changes: 50 additions & 6 deletions src/components/context/QuizContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ type ContextType = {
addQuestion: () => void;
saveQuestion: (newData: QuestionData) => Promise<void>;
isSettingsFetching: boolean;
isLoaded: boolean;
saveOrder: (order: string[], currQuestionId?: string) => void;
};

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

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

// This state indicates if the questions were received and the question order set correctly.
// It allows QuizNavigation to display the Add question button when loading stops
// and the current index updated correctly according if there is questions or not.
const [isLoaded, setIsLoaded] = useState(false);

// Here use type of CurrentQuestion because only the id of appSetting is needed...
const [currentQuestion, setCurrentQuestion] =
useState<CurrentQuestion>(DEFAULT_QUESTION);
Expand Down Expand Up @@ -201,12 +207,37 @@ export const QuizProvider = ({ children }: Props) => {
APP_SETTING_NAMES.QUESTION_LIST
);

// Get all questions id. To support legacy code, if no question id, the id is used instead.
const questionIds = getSettingsByName(
settings,
APP_SETTING_NAMES.QUESTION
).map((appSetting) => appSetting?.data?.questionId ?? appSetting.id);

const filteredOrder: string[] = [];
if (newOrderSetting && newOrderSetting.length > 0) {
const value = newOrderSetting[0] as QuestionListType;
setOrderSetting(value);
setOrder(value?.data?.list ?? []);
// Filter out questions that are not well formatted in AppSettings.
filteredOrder.push(
...(value?.data?.list.filter((id) => questionIds.includes(id)) ?? [])
);
setOrder(filteredOrder);
}

// if it is first loading, set is loaded to true.
if (!isLoaded) {
// If there are questions, set current idx to the first one.
// If it has already set current idx, don't do it again to not reset curr question on order changed.
if (filteredOrder.length) {
setCurrentIdx(0);
}

setIsLoaded(true);
}
}
// Disable exhaustive-deps for isLoaded, because we don't want
// to reload this useEffect when isLoaded has changed.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [settings]);

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

const value: ContextType = useMemo(() => {
const validIds =
getSettingsByName(settings, APP_SETTING_NAMES.QUESTION_LIST)[0]?.data
?.list ?? [];

const questions = settings
? (getSettingsByName(
settings,
APP_SETTING_NAMES.QUESTION
) as QuestionDataAppSetting[])
? (
getSettingsByName(
settings,
APP_SETTING_NAMES.QUESTION
) as QuestionDataAppSetting[]
)
// Filter out questions that are not well formatted in AppSettings.
.filter(
(q) =>
validIds.includes(q.data.questionId) || validIds.includes(q.id)
)
: [];

return {
Expand All @@ -268,6 +310,7 @@ export const QuizProvider = ({ children }: Props) => {
addQuestion,
saveQuestion,
isSettingsFetching,
isLoaded,
saveOrder,
};
}, [
Expand All @@ -280,6 +323,7 @@ export const QuizProvider = ({ children }: Props) => {
moveToPreviousQuestion,
saveQuestion,
isSettingsFetching,
isLoaded,
saveOrder,
]);

Expand Down
4 changes: 3 additions & 1 deletion src/components/create/QuestionTypeSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ const QuestionTypeSelect = ({ value, onChange }: Props) => {

const onTypeChange: SelectProps['onChange'] = (e) => {
const value = e.target.value as QuestionType;
const defaultQuestionValues = DEFAULT_QUESTION_VALUES[value] as QuestionData;
const defaultQuestionValues = DEFAULT_QUESTION_VALUES[
value
] as QuestionData;
onChange(defaultQuestionValues);
};

Expand Down
4 changes: 2 additions & 2 deletions src/components/navigation/ResultTablesMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { MutableRefObject } from 'react';
import { useTranslation } from 'react-i18next';

import { Tab, Tabs } from '@mui/material';
Expand All @@ -8,13 +9,12 @@ import {
RESULT_TABLES_RESULT_BY_USER_BUTTON_CY,
RESULT_TABLES_TAB_CONTAINERS_CY,
} from '../../config/selectors';
import { MutableRefObject } from 'react';

type Props = {
tab: number;
tableMenuElem: MutableRefObject<HTMLElement | undefined>;
setTab: (tabIdx: number) => void;
}
};

/**
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ export const QuizNavigationBuilder = ({ sx }: Props) => {
order,
saveOrder,
addQuestion,
isLoaded,
} = useContext(QuizContext);

const { t } = useTranslation();
Expand Down Expand Up @@ -194,6 +195,10 @@ export const QuizNavigationBuilder = ({ sx }: Props) => {
return <Box sx={SX_CONTAINER}>{renderDraggableList()}</Box>;
};

if (!isLoaded) {
return null;
}

return (
<>
<Stack
Expand All @@ -206,7 +211,7 @@ export const QuizNavigationBuilder = ({ sx }: Props) => {
{renderMenu()}
<Button
data-cy={NAVIGATION_ADD_QUESTION_BUTTON_CY}
variant={currentIdx === -1 ? "contained" : "outlined"}
variant={currentIdx === -1 ? 'contained' : 'outlined'}
startIcon={<AddIcon />}
onClick={addQuestion}
>
Expand Down
2 changes: 1 addition & 1 deletion src/components/navigation/questionNavigation/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ export enum QuestionStepStyleKeys {
REMAIN_ATTEMPTS = 'remain_attempts',
}

export type QuestionStatus = `${QuestionStepStyleKeys}`;
export type QuestionStatus = `${QuestionStepStyleKeys}`;
4 changes: 2 additions & 2 deletions src/components/play/fillInTheBlanks/BlankedText.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ const BlankedText = ({
onDelete,
}: Props) => {
const renderWords = () => {
// This idx is used to compare the current blank with
// This idx is used to compare the current blank with
// the previous answer and the current filled word.
// Because "words" doesn't contains WORD types, this
// Because "words" doesn't contains WORD types, this
// index must be incremented each time a WORD is found.
let previousAnswerIdx = 0;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ export const PlayMultipleChoicesHints = ({
(showCorrection || response?.choices?.includes(c.value))
)
.map((c, idx) => (
<li key={c.explanation} data-cy={buildMultipleChoiceHintPlayCy(idx)}>
<li
key={c.explanation}
data-cy={buildMultipleChoiceHintPlayCy(idx)}
>
{c.explanation}
</li>
))}
Expand Down
Loading

0 comments on commit 9d12017

Please sign in to comment.