fix: display valid discussion topics in divide by cohorts section (#147)

* fix: display valid discussion topics in divide by cohorts section

* test: Add useContext test case in  discussion topic

* refactor: rename & document

Co-authored-by: =Awais Jibran <awaisdar001@gmail.com>
This commit is contained in:
Awais Ansari
2021-06-29 12:59:03 +05:00
committed by GitHub
parent abf7292375
commit bd76050e9b
7 changed files with 129 additions and 46 deletions

View File

@@ -1,4 +1,4 @@
import React from 'react';
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { Card, Form } from '@edx/paragon';
import { Formik } from 'formik';
@@ -9,6 +9,7 @@ import DivisionByGroupFields from '../shared/DivisionByGroupFields';
import AnonymousPostingFields from '../shared/AnonymousPostingFields';
import DiscussionTopics from '../shared/discussion-topics/DiscussionTopics';
import BlackoutDatesField, { blackoutDatesRegex } from '../shared/BlackoutDatesField';
import LegacyConfigFormProvider from './LegacyConfigFormProvider';
import messages from '../shared/messages';
import AppConfigFormDivider from '../shared/AppConfigFormDivider';
@@ -36,6 +37,7 @@ Yup.addMethod(Yup.object, 'uniqueProperty', function (propertyName, message) {
function LegacyConfigForm({
appConfig, onSubmit, formRef, intl, title,
}) {
const [validDiscussionTopics, setValidDiscussionTopics] = useState(appConfig.discussionTopics);
const legacyFormValidationSchema = Yup.object().shape({
blackoutDates: Yup.string().matches(
blackoutDatesRegex,
@@ -72,30 +74,37 @@ function LegacyConfigForm({
&& errors.discussionTopics
&& errors?.discussionTopics[index]?.name,
));
const contextValue = {
validDiscussionTopics,
setValidDiscussionTopics,
discussionTopicErrors,
};
return (
<Card className="mb-5 px-4 px-sm-5 pb-5" data-testid="legacyConfigForm">
<Form ref={formRef} onSubmit={handleSubmit}>
<h3 className="text-primary-500 my-3">{title}</h3>
<AppConfigFormDivider thick />
<AnonymousPostingFields
onBlur={handleBlur}
onChange={handleChange}
values={values}
/>
<AppConfigFormDivider thick />
<DiscussionTopics discussionTopicErrors={discussionTopicErrors} />
<AppConfigFormDivider thick />
<DivisionByGroupFields discussionTopicErrors={discussionTopicErrors} />
<AppConfigFormDivider thick />
<BlackoutDatesField
errors={errors}
onBlur={handleBlur}
onChange={handleChange}
values={values}
/>
</Form>
</Card>
<LegacyConfigFormProvider value={contextValue}>
<Card className="mb-5 px-4 px-sm-5 pb-5" data-testid="legacyConfigForm">
<Form ref={formRef} onSubmit={handleSubmit}>
<h3 className="text-primary-500 my-3">{title}</h3>
<AppConfigFormDivider thick />
<AnonymousPostingFields
onBlur={handleBlur}
onChange={handleChange}
values={values}
/>
<AppConfigFormDivider thick />
<DiscussionTopics />
<AppConfigFormDivider thick />
<DivisionByGroupFields />
<AppConfigFormDivider thick />
<BlackoutDatesField
errors={errors}
onBlur={handleBlur}
onChange={handleChange}
values={values}
/>
</Form>
</Card>
</LegacyConfigFormProvider>
);
}}
</Formik>

View File

@@ -0,0 +1,26 @@
import React, { createContext } from 'react';
import PropTypes from 'prop-types';
export const LegacyConfigFormContext = createContext({});
export default function LegacyConfigFormProvider({ children, value }) {
return (
<LegacyConfigFormContext.Provider value={value}>
{children}
</LegacyConfigFormContext.Provider>
);
}
LegacyConfigFormProvider.propTypes = {
children: PropTypes.node.isRequired,
value: PropTypes.shape({
discussionTopicErrors: PropTypes.arrayOf(PropTypes.bool),
validDiscussionTopics: PropTypes.arrayOf(PropTypes.shape({
validTopics: PropTypes.arrayOf(PropTypes.shape({
name: PropTypes.string,
id: PropTypes.string,
})),
setValidDiscussionTopics: PropTypes.func,
})),
}).isRequired,
};

View File

@@ -1,13 +1,14 @@
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import React, { useEffect, useContext } from 'react';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Form, TransitionReplace } from '@edx/paragon';
import { FieldArray, useFormikContext } from 'formik';
import FormSwitchGroup from '../../../../../generic/FormSwitchGroup';
import messages from './messages';
import AppConfigFormDivider from './AppConfigFormDivider';
import { LegacyConfigFormContext } from '../legacy/LegacyConfigFormProvider';
const DivisionByGroupFields = ({ intl, discussionTopicErrors }) => {
const DivisionByGroupFields = ({ intl }) => {
const { validDiscussionTopics } = useContext(LegacyConfigFormContext);
const {
handleChange,
handleBlur,
@@ -104,8 +105,8 @@ const DivisionByGroupFields = ({ intl, discussionTopicErrors }) => {
onBlur={handleBlur}
defaultValue={divideDiscussionIds}
>
{discussionTopics.map((topic, index) => (
topic.name && !discussionTopicErrors[index] ? (
{validDiscussionTopics.map((topic) => (
topic.name ? (
<Form.Checkbox
key={`checkbox-${topic.id}`}
id={`checkbox-${topic.id}`}
@@ -135,7 +136,6 @@ const DivisionByGroupFields = ({ intl, discussionTopicErrors }) => {
DivisionByGroupFields.propTypes = {
intl: intlShape.isRequired,
discussionTopicErrors: PropTypes.arrayOf(PropTypes.bool).isRequired,
};
export default injectIntl(DivisionByGroupFields);

View File

@@ -1,24 +1,28 @@
import React, { useEffect } from 'react';
import React, { useEffect, useContext } from 'react';
import { useDispatch } from 'react-redux';
import { Add } from '@edx/paragon/icons';
import { Button } from '@edx/paragon';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { FieldArray, useFormikContext } from 'formik';
import { v4 as uuid } from 'uuid';
import PropTypes from 'prop-types';
import messages from '../messages';
import TopicItem from './TopicItem';
import { updateValidationStatus } from '../../../../data/slice';
import { LegacyConfigFormContext } from '../../legacy/LegacyConfigFormProvider';
const DiscussionTopics = ({ intl, discussionTopicErrors }) => {
const DiscussionTopics = ({ intl }) => {
const {
values: appConfig,
setFieldValue,
validateForm,
} = useFormikContext();
const { discussionTopics, divideDiscussionIds } = appConfig;
const { discussionTopics } = appConfig;
const dispatch = useDispatch();
const {
discussionTopicErrors,
validDiscussionTopics,
setValidDiscussionTopics,
} = useContext(LegacyConfigFormContext);
const isFormInvalid = discussionTopicErrors.some((error) => error === true);
useEffect(() => {
@@ -28,16 +32,13 @@ const DiscussionTopics = ({ intl, discussionTopicErrors }) => {
const handleTopicDelete = async (topicIndex, topicId, remove) => {
await remove(topicIndex);
validateForm();
const updatedDividedDiscussionsIds = divideDiscussionIds.filter(
(id) => id !== topicId,
);
setFieldValue('divideDiscussionIds', updatedDividedDiscussionsIds);
const validTopics = validDiscussionTopics.filter(topic => topic.id !== topicId);
setValidDiscussionTopics(validTopics);
};
const addNewTopic = (push) => {
const payload = { name: '', id: uuid() };
push(payload);
setFieldValue('divideDiscussionIds', [...divideDiscussionIds, payload.id]);
};
return (
@@ -85,7 +86,6 @@ const DiscussionTopics = ({ intl, discussionTopicErrors }) => {
DiscussionTopics.propTypes = {
intl: intlShape.isRequired,
discussionTopicErrors: PropTypes.arrayOf(PropTypes.bool).isRequired,
};
export default injectIntl(DiscussionTopics);

View File

@@ -20,6 +20,7 @@ import { getAppsUrl } from '../../../../data/api';
import { fetchApps } from '../../../../data/thunks';
import executeThunk from '../../../../../../utils';
import { legacyApiResponse } from '../../../../factories/mockApiResponses';
import LegacyConfigFormProvider from '../../legacy/LegacyConfigFormProvider';
const appConfig = {
id: 'legacy',
@@ -36,7 +37,15 @@ const appConfig = {
blackoutDates: '[]',
};
const discussionTopicErrors = [false, false];
const contextValue = {
validDiscussionTopics: [
{ name: 'General', id: 'course' },
{ name: 'Edx', id: '13f106c6-6735-4e84-b097-0456cff55960' },
],
setValidDiscussionTopics: jest.fn(),
discussionTopicErrors: [false, false],
};
const courseId = 'course-v1:edX+TestX+Test_Course';
describe('DiscussionTopics', () => {
@@ -66,9 +75,11 @@ describe('DiscussionTopics', () => {
const wrapper = render(
<AppProvider store={store}>
<IntlProvider locale="en">
<Formik initialValues={data}>
<DiscussionTopics discussionTopicErrors={discussionTopicErrors} />
</Formik>
<LegacyConfigFormProvider value={contextValue}>
<Formik initialValues={data}>
<DiscussionTopics />
</Formik>
</LegacyConfigFormProvider>
</IntlProvider>
</AppProvider>,
);

View File

@@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React, { useState, useContext, useEffect } from 'react';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import {
Button,
@@ -13,15 +13,46 @@ import { Delete, ExpandLess, ExpandMore } from '@edx/paragon/icons';
import { useFormikContext } from 'formik';
import PropTypes from 'prop-types';
import messages from '../messages';
import { LegacyConfigFormContext } from '../../legacy/LegacyConfigFormProvider';
import uniqueItems from '../../../utils';
const TopicItem = ({
intl, index, name, onDelete, id, hasError,
}) => {
const { handleChange, handleBlur, errors } = useFormikContext();
const {
handleChange, handleBlur, errors, values: appConfig, setFieldValue,
} = useFormikContext();
const [inFocus, setInFocus] = useState(false);
const [showDeletePopup, setShowDeletePopup] = useState(false);
const [collapseIsOpen, setCollapseOpen] = useState();
const isGeneralTopic = id === 'course';
const {
validDiscussionTopics,
setValidDiscussionTopics,
} = useContext(LegacyConfigFormContext);
const { discussionTopics, divideDiscussionIds } = appConfig;
/**
* Update valid discussion topics & divided discussion topics.
* Removes a specific topic from valid discussion topics & divided discussion topics
* if it is invalid.
* Adds a specific topic to valid discussion topics & divided discussion topics
* if it is invalid.
*/
useEffect(() => {
if (hasError) {
const validTopicsIds = validDiscussionTopics.filter(topic => topic.id !== id);
setValidDiscussionTopics(validTopicsIds);
setFieldValue('divideDiscussionIds', divideDiscussionIds.filter(topic => topic.id !== id));
} else {
const validTopicsIds = uniqueItems(validDiscussionTopics.map(topic => topic.id), [id]);
const validTopics = discussionTopics.filter(
topic => validTopicsIds.includes(topic.id),
);
setValidDiscussionTopics(validTopics);
setFieldValue('divideDiscussionIds', uniqueItems(divideDiscussionIds, [id]));
}
}, [hasError, inFocus]);
const getHeading = (isOpen = false) => {
let heading;

View File

@@ -0,0 +1,6 @@
/** Append items and makes sure the items are unique. */
const uniqueItems = (sourceArray, items = []) => (
[...new Set([...sourceArray, ...items])]
);
export default uniqueItems;