Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
421cf61
feat(assessmentform): categorise settings in sticky sections
pchunky Aug 17, 2022
9cdabbe
style(assessmentform): FormattedMessage -> intl.formatMessage
pchunky Aug 19, 2022
7882c70
style(assessmentform): remove unused proptypes
pchunky Aug 19, 2022
1c85795
style(assessment): containers -> components
pchunky Aug 19, 2022
c45e362
feat(section): add margin between children
pchunky Aug 23, 2022
e75f07f
feat(selectfield): support for mui variants
pchunky Aug 23, 2022
e274ddb
feat(datetimepicker): support for variants and marginless
pchunky Aug 23, 2022
a62502c
feat(checkboxfield): add description, disabledhint, fix styles
pchunky Aug 23, 2022
1484f3b
feat(assessmentform): style title, dates
pchunky Aug 23, 2022
d49d8ae
feat(assessmentform): add iconradio
pchunky Aug 23, 2022
a6e0144
feat(assessmentform): replace published toggle with radio
pchunky Aug 23, 2022
7c64ac5
feat(assessmentform): add infolabel
pchunky Aug 23, 2022
87aec2a
feat: MaterialUploader -> FileManager
pchunky Aug 23, 2022
aad076e
feat(assessmentform): style exp fields
pchunky Aug 23, 2022
1efb5ab
feat(assessmentform): replace autograded toggle with radio
pchunky Aug 23, 2022
0c546de
feat(assessmentform): replace grading options with checkboxes
pchunky Aug 23, 2022
6950cec
feat(assessmentform): replace answers options with checkboxes
pchunky Aug 23, 2022
d366e24
feat(assessmentform): style tabs, pers. timeline, mcq elements
pchunky Aug 23, 2022
d28dc08
fix(section): header stuck too low on new assessment page
pchunky Aug 24, 2022
8b0725f
style(assessmentform): abstract yup validation out
pchunky Aug 28, 2022
e15e9a8
style(assessmentform): move to a TypeScript component
pchunky Aug 28, 2022
0842354
fix(selectfield): unoverridable margin
pchunky Aug 28, 2022
8cae6a4
feat(textfield): support for password visibility
pchunky Aug 28, 2022
fd593c5
fix(assessmentform): clean up margins, shrinking labels
pchunky Aug 28, 2022
e0a3f89
feat(conditionlist): style table, fix margins
pchunky Aug 28, 2022
b05f308
feat(assessmentform): add session password checkbox
pchunky Aug 28, 2022
b9a98bc
feat(new assessment): only prompt discard when form isDirty
pchunky Aug 29, 2022
1dbcca1
feat(assessmentform): add warning for disabled materials
pchunky Aug 29, 2022
32ac878
style(assessment): fix lint errors
pchunky Aug 29, 2022
08283b6
test(assessmentform): for disabled/disappearing fields
pchunky Aug 29, 2022
220e620
test(assessmentedit): rewrite tests with react-testing-library
pchunky Aug 29, 2022
f7efd67
style(conditionlist): FormattedMessage -> intl.formatMessage
pchunky Sep 2, 2022
44a7648
style(conditionlist): abstract title and description
pchunky Sep 2, 2022
53a3390
feat(filemanager): add link to view files in new tab
pchunky Sep 2, 2022
65715a7
feat(assessmentform): add description for answer options
pchunky Sep 5, 2022
9b5758c
fix(assessment form): remove unused message
ekowidianto Sep 6, 2022
4198e59
fix(assessment form): fix autograded checkbox disabled field logic
ekowidianto Sep 6, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { FC, useEffect } from 'react';
import { defineMessages, FormattedMessage } from 'react-intl';
import { defineMessages, injectIntl, WrappedComponentProps } from 'react-intl';
import { Controller, useForm } from 'react-hook-form';
import * as yup from 'yup';
import { yupResolver } from '@hookform/resolvers/yup';
Expand All @@ -19,7 +19,7 @@ import {
} from 'types/course/achievements';
import { ConditionData, Conditions } from 'types/course/conditions';

interface Props {
interface Props extends WrappedComponentProps {
editing: boolean; // If the Form is in editing mode, `Add Conditions` button will be displayed.
handleClose: (isDirty: boolean) => void;
onSubmit: (
Expand Down Expand Up @@ -62,6 +62,15 @@ const translations = defineMessages({
id: 'course.achievement.form.update',
defaultMessage: 'Update',
},
unlockConditions: {
id: 'course.achievement.form.unlockConditions',
defaultMessage: 'Unlock conditions',
},
unlockConditionsHint: {
id: 'course.achievement.form.unlockConditionsHint',
defaultMessage:
'This achievement will be unlocked if a student meets the following conditions.',
},
});

const validationSchema = yup.object({
Expand All @@ -78,6 +87,7 @@ const AchievementForm: FC<Props> = (props) => {
initialValues,
onSubmit,
setIsDirty,
intl,
} = props;
const {
control,
Expand Down Expand Up @@ -112,7 +122,7 @@ const AchievementForm: FC<Props> = (props) => {
key="achievement-form-update-button"
type="submit"
>
<FormattedMessage {...translations.update} />
{intl.formatMessage(translations.update)}
</Button>
</div>
) : (
Expand All @@ -126,7 +136,7 @@ const AchievementForm: FC<Props> = (props) => {
key="achievement-form-cancel-button"
onClick={(): void => handleClose(isDirty)}
>
<FormattedMessage {...formTranslations.cancel} />
{intl.formatMessage(formTranslations.cancel)}
</Button>
<Button
color="primary"
Expand All @@ -136,7 +146,7 @@ const AchievementForm: FC<Props> = (props) => {
key="achievement-form-submit-button"
type="submit"
>
<FormattedMessage {...formTranslations.submit} />
{intl.formatMessage(formTranslations.submit)}
</Button>
</div>
);
Expand All @@ -157,7 +167,7 @@ const AchievementForm: FC<Props> = (props) => {
field={field}
fieldState={fieldState}
disabled={disabled}
label={<FormattedMessage {...translations.title} />}
label={intl.formatMessage(translations.title)}
// @ts-ignore: component is still written in JS
fullWidth
InputLabelProps={{
Expand All @@ -176,7 +186,7 @@ const AchievementForm: FC<Props> = (props) => {
field={field}
fieldState={fieldState}
disabled={disabled}
label={<FormattedMessage {...translations.description} />}
label={intl.formatMessage(translations.description)}
// @ts-ignore: component is still written in JS
fullWidth
InputLabelProps={{
Expand Down Expand Up @@ -207,22 +217,22 @@ const AchievementForm: FC<Props> = (props) => {
field={field}
fieldState={fieldState}
disabled={disabled}
label={<FormattedMessage {...translations.published} />}
label={intl.formatMessage(translations.published)}
/>
)}
/>
{editing && conditionAttributes && (
<div>
<ConditionList
newConditionUrls={conditionAttributes.enabledConditions}
conditions={conditionAttributes.conditions}
/>
</div>
<ConditionList
title={intl.formatMessage(translations.unlockConditions)}
description={intl.formatMessage(translations.unlockConditionsHint)}
newConditionUrls={conditionAttributes.enabledConditions}
conditions={conditionAttributes.conditions}
/>
)}
{actionButtons}
</form>
</>
);
};

export default AchievementForm;
export default injectIntl(AchievementForm);
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
import { ComponentProps } from 'react';

import { fireEvent, render, RenderResult } from 'utilities/test-utils';
import ProviderWrapper from 'lib/components/ProviderWrapper';
import storeCreator from '../../../store';
import AssessmentForm from '..';

const INITIAL_VALUES = {
id: 1,
title: 'Test Assessment',
description: 'Awesome description 4',
autograded: false,
start_at: new Date(),
base_exp: 0,
time_bonus_exp: 0,
use_public: true,
use_private: true,
use_evaluation: false,
tabbed_view: false,
published: false,
};

let store;
let initialValues;
let props: ComponentProps<typeof AssessmentForm>;
let form: RenderResult;

const getComponent = (): JSX.Element => (
<ProviderWrapper store={store}>
<AssessmentForm {...props} />
</ProviderWrapper>
);

beforeEach(() => {
store = storeCreator({ assessments: {} });
initialValues = INITIAL_VALUES;

props = {
initialValues,
gamified: false,
modeSwitching: true,
containsCodaveri: false,
showPersonalizedTimelineFeatures: false,
randomizationAllowed: false,
conditionAttributes: {
conditions: [],
new_condition_urls: [],
},
folderAttributes: {
folder_id: 1,
materials: [],
enable_materials_action: true,
},
onSubmit: (): void => {},
};

form = render(getComponent());
});

describe('<AssessmentForm />', () => {
it('renders assessment details sections options', () => {
expect(form.getByText('Assessment details')).toBeVisible();
expect(form.getByLabelText('Starts at *')).toBeVisible();
expect(form.getByLabelText('Ends at')).toBeVisible();
expect(form.getByLabelText('Title *')).toHaveValue(INITIAL_VALUES.title);
expect(form.getByText('Description')).toBeVisible();
expect(form.getByDisplayValue(INITIAL_VALUES.description)).toBeVisible();
});

it('renders grading section options', () => {
expect(form.getByText('Grading')).toBeVisible();
expect(form.getByText('Grading mode')).toBeVisible();

expect(form.getByText('Autograded')).toBeVisible();
expect(form.getByDisplayValue('autograded')).not.toBeChecked();

expect(form.getByText('Manual')).toBeVisible();
expect(form.getByDisplayValue('manual')).toBeChecked();

expect(form.getByLabelText('Public test cases')).toBeChecked();
expect(form.getByLabelText('Private test cases')).toBeChecked();
expect(form.getByLabelText('Evaluation test cases')).not.toBeChecked();

expect(
form.getByLabelText('Enable delayed grade publication'),
).not.toBeChecked();
});

it('renders answers and test cases section options', () => {
expect(form.getByText('Answers and test cases')).toBeVisible();
expect(form.getByLabelText('Allow to skip steps')).not.toBeChecked();
expect(
form.getByLabelText('Allow submission with incorrect answers'),
).not.toBeChecked();
expect(form.getByLabelText('Show private test cases')).not.toBeChecked();
expect(form.getByLabelText('Show evaluation test cases')).not.toBeChecked();
expect(form.getByLabelText('Show MCQ/MRQ solution(s)')).not.toBeChecked();
});

it('renders organisation section options', () => {
expect(form.getByText('Organisation')).toBeVisible();
expect(form.getByText('Single Page')).toBeVisible();
});

it('renders exams and access control section options', () => {
expect(form.getByText('Exams and access control')).toBeVisible();
expect(
form.getByLabelText('Block students from viewing finalized submissions'),
).not.toBeChecked();
expect(form.getByLabelText('Show MCQ submit result')).not.toBeChecked();
expect(form.getByLabelText('Enable password protection')).not.toBeChecked();
});

it('renders gamified options when course is gamified', () => {
expect(form.queryByText('Gamification')).not.toBeInTheDocument();
expect(form.queryByLabelText('Bonus ends at')).not.toBeInTheDocument();
expect(form.queryByLabelText('Base EXP')).not.toBeInTheDocument();
expect(form.queryByLabelText('Time Bonus EXP')).not.toBeInTheDocument();

props.gamified = true;
form.rerender(getComponent());

expect(form.getByText('Gamification')).toBeVisible();
expect(form.getByLabelText('Bonus ends at')).toBeVisible();
expect(form.getByLabelText('Base EXP')).toHaveValue(
INITIAL_VALUES.base_exp,
);
expect(form.getByLabelText('Time Bonus EXP')).toHaveValue(
INITIAL_VALUES.time_bonus_exp,
);
});

it('renders editing options when rendered in edit assessment page', () => {
expect(form.queryByText('Visibility')).not.toBeInTheDocument();
expect(form.queryByText('Published')).not.toBeInTheDocument();
expect(form.queryByText('Draft')).not.toBeInTheDocument();
expect(form.queryByText('Files')).not.toBeInTheDocument();
expect(form.queryByText('Add Files')).not.toBeInTheDocument();
expect(form.queryByText('Unlock conditions')).not.toBeInTheDocument();
expect(form.queryByText('Add a condition')).not.toBeInTheDocument();

props.editing = true;
form.rerender(getComponent());

expect(form.getByText('Visibility')).toBeVisible();
expect(form.getByText('Published')).toBeVisible();
expect(form.getByDisplayValue('published')).not.toBeChecked();
expect(form.getByText('Draft')).toBeVisible();
expect(form.getByDisplayValue('draft')).toBeChecked();
expect(form.getByText('Files')).toBeVisible();
expect(form.getByText('Add Files')).toBeVisible();
expect(form.queryByText('Unlock conditions')).not.toBeInTheDocument();
expect(form.queryByText('Add a condition')).not.toBeInTheDocument();

props.gamified = true;
form.rerender(getComponent());
expect(form.getByText('Unlock conditions')).toBeVisible();
expect(form.getByText('Add a condition')).toBeVisible();
});

it('prevents grading mode switching when there are submissions', () => {
expect(form.getByDisplayValue('autograded')).toBeEnabled();
expect(form.getByDisplayValue('manual')).toBeEnabled();

props.modeSwitching = false;
form.rerender(getComponent());

expect(form.getByDisplayValue('autograded')).toBeDisabled();
expect(form.getByDisplayValue('manual')).toBeDisabled();
});

it('prevents grading mode switching when there are codaveri questions', () => {
expect(form.getByDisplayValue('autograded')).toBeEnabled();
expect(form.getByDisplayValue('manual')).toBeEnabled();

props.containsCodaveri = true;
form.rerender(getComponent());

expect(form.getByDisplayValue('autograded')).toBeDisabled();
expect(form.getByDisplayValue('manual')).toBeDisabled();
});

it('disables unavailable options in autograded mode', () => {
expect(form.getByLabelText('Allow to skip steps')).toBeDisabled();
expect(
form.getByLabelText('Allow submission with incorrect answers'),
).toBeDisabled();
expect(
form.getByLabelText('Enable delayed grade publication'),
).toBeEnabled();
expect(form.getByLabelText('Show MCQ submit result')).toBeDisabled();
expect(form.getByLabelText('Enable password protection')).toBeEnabled();

const autogradedRadio = form.getByDisplayValue('autograded');
fireEvent.click(autogradedRadio);

expect(form.getByLabelText('Allow to skip steps')).toBeEnabled();
expect(
form.getByLabelText('Allow submission with incorrect answers'),
).toBeEnabled();
expect(
form.getByLabelText('Enable delayed grade publication'),
).toBeDisabled();
expect(form.getByLabelText('Show MCQ submit result')).toBeEnabled();
expect(form.getByLabelText('Enable password protection')).toBeDisabled();
});

it('handles password protection options', () => {
expect(
form.queryByLabelText('Assessment password *'),
).not.toBeInTheDocument();
expect(
form.queryByLabelText('Enable session protection'),
).not.toBeInTheDocument();
expect(
form.queryByLabelText('Session unlock password *'),
).not.toBeInTheDocument();

const passwordCheckbox = form.getByLabelText('Enable password protection');
expect(passwordCheckbox).toBeEnabled();
expect(passwordCheckbox).not.toBeChecked();

fireEvent.click(passwordCheckbox);

expect(form.getByLabelText('Assessment password *')).toBeVisible();

const sessionProtectionCheckbox = form.getByLabelText(
'Enable session protection',
);
expect(sessionProtectionCheckbox).toBeEnabled();
expect(sessionProtectionCheckbox).not.toBeChecked();
expect(
form.queryByLabelText('Session unlock password *'),
).not.toBeInTheDocument();

fireEvent.click(sessionProtectionCheckbox);

expect(form.getByLabelText('Session unlock password *')).toBeVisible();
});

it('renders personalised timelines options when enabled', () => {
expect(form.queryByLabelText('Has personal times')).not.toBeInTheDocument();
expect(
form.queryByLabelText('Affects personal times'),
).not.toBeInTheDocument();

props.showPersonalizedTimelineFeatures = true;
form.rerender(getComponent());

expect(form.getByLabelText('Has personal times')).toBeEnabled();
expect(form.getByLabelText('Affects personal times')).toBeEnabled();
});

it('renders randomization options when enabled', () => {
expect(
form.queryByLabelText('Enable Randomization'),
).not.toBeInTheDocument();

props.randomizationAllowed = true;
form.rerender(getComponent());

expect(form.getByLabelText('Enable Randomization')).toBeEnabled();
});
});
Loading