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:
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>,
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
/** Append items and makes sure the items are unique. */
|
||||
const uniqueItems = (sourceArray, items = []) => (
|
||||
[...new Set([...sourceArray, ...items])]
|
||||
);
|
||||
|
||||
export default uniqueItems;
|
||||
Reference in New Issue
Block a user