Compare commits

...

10 Commits

Author SHA1 Message Date
renovate[bot]
7a4948f709 chore(deps): update dependency axios to v1 2026-02-27 18:17:54 +00:00
Brian Smith
e1383d1e65 fix(deps): regenerate package-lock.json
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-02-13 17:14:44 -05:00
bydawen
f7b5615660 fix: matching styles for report/delete post buttons (#794)
* fix: matching styles for report/delete post buttons

* feat: add tests for the Post.jsx
2026-02-10 09:17:20 -06:00
bydawen
0d5e3b8a1c fix: add space gap between modals buttons + fixed reply/post/comment sections text colors (#805) 2026-01-28 12:43:40 -06:00
Feanil Patel
4e90bbc756 Merge pull request #793 from raccoongang/buhaienko/fix/remove-primary-color-from-discussion-post-and-messages-text
fix: remove primary color from discussion post and messages text
2026-01-16 13:51:16 -05:00
oleksandr.buhaienko
b4dcaca660 fix: remove primary color from discussion post and messages text 2026-01-16 19:22:25 +02:00
Awais Ansari
5defe5cbd4 fix: profile picture is not showing in forum responses (#849) 2026-01-02 14:57:49 +05:00
Awais Ansari
911b8b3fc5 fix: cohort name difference in filter and post view (#841)
* fix: cohort name difference in filter and post view

* test: fix cohort test cases

---------

Co-authored-by: Awais  Ansari <awais.ansari@A006-01824.local>
2025-12-16 20:07:40 +05:00
Awais Ansari
4917da3245 fix: fetch topics on MFE load (#840)
* fix: added fetch topic API call in learners tab

* refactor: fetch topics on MFE load

* test: added legacy param in legacy topic test cases

---------

Co-authored-by: Awais  Ansari <awais.ansari@A006-01824.local>
2025-12-16 19:39:36 +05:00
renovate[bot]
e5388690b2 fix(deps): update dependency core-js to v3.47.0 2025-12-10 12:05:24 +05:00
25 changed files with 3536 additions and 6254 deletions

9453
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -39,7 +39,7 @@
"@tinymce/tinymce-react": "5.1.1",
"babel-polyfill": "6.26.0",
"classnames": "2.5.1",
"core-js": "3.21.1",
"core-js": "3.47.0",
"dompurify": "^2.4.3",
"formik": "2.4.9",
"lodash.snakecase": "4.1.1",
@@ -65,7 +65,7 @@
"@testing-library/jest-dom": "5.17.0",
"@testing-library/react": "14.3.1",
"@testing-library/user-event": "13.5.0",
"axios": "^0.30.0",
"axios": "^1.0.0",
"axios-mock-adapter": "1.22.0",
"babel-plugin-react-intl": "8.2.25",
"eslint-plugin-simple-import-sort": "7.0.0",

View File

@@ -175,7 +175,7 @@ const FilterBar = ({
<ActionItem
key={toString(cohort.id)}
id={toString(cohort.id)}
label={capitalize(cohort.name)}
label={cohort.name}
value={toString(cohort.id)}
selected={selectedFilters.cohort}
/>

View File

@@ -75,7 +75,7 @@ const HoverCard = ({
const actionFunction = actionHandlers[endorseIcons.action];
actionFunction();
}}
className={['endorse', 'unendorse'].includes(endorseIcons.id) ? 'text-dark-500' : 'text-success-500'}
className="text-primary"
size="sm"
alt="Endorse"
/>

View File

@@ -4,6 +4,7 @@ import {
} from 'react';
import { breakpoints, useWindowSize } from '@openedx/paragon';
import isEmpty from 'lodash/isEmpty';
import { useDispatch, useSelector } from 'react-redux';
import {
matchPath, useLocation, useMatch, useNavigate,
@@ -20,10 +21,13 @@ import { ContentActions, RequestStatus, Routes } from '../../data/constants';
import { selectTopicsUnderCategory } from '../../data/selectors';
import fetchCourseBlocks from '../../data/thunks';
import DiscussionContext from '../common/context';
import { selectTopics as selectInContextTopics } from '../in-context-topics/data/selectors';
import fetchCourseTopicsV3 from '../in-context-topics/data/thunks';
import PostCommentsContext from '../post-comments/postCommentsContext';
import { clearRedirect } from '../posts/data';
import { threadsLoadingStatus } from '../posts/data/selectors';
import { selectTopics } from '../topics/data/selectors';
import fetchCourseTopics from '../topics/data/thunks';
import tourCheckpoints from '../tours/constants';
import selectTours from '../tours/data/selectors';
import { updateTourShowStatus } from '../tours/data/thunks';
@@ -32,6 +36,7 @@ import { checkPermissions, discussionsPath } from '../utils';
import { ContentSelectors } from './constants';
import {
selectAreThreadsFiltered,
selectDiscussionProvider,
selectEnableInContext,
selectIsPostingEnabled,
selectIsUserLearner,
@@ -104,6 +109,21 @@ export function useCourseBlockData(courseId) {
}, [courseId, isEnrolled, courseStatus, isUserLearner]);
}
export function useTopicsData(courseId, enableInContextSidebar) {
const dispatch = useDispatch();
const enableInContext = useSelector(selectEnableInContext);
const provider = useSelector(selectDiscussionProvider);
const topics = useSelector(enableInContext ? selectInContextTopics : selectTopics);
useEffect(() => {
if (isEmpty(topics) && provider) {
dispatch((enableInContext || enableInContextSidebar)
? fetchCourseTopicsV3(courseId)
: fetchCourseTopics(courseId));
}
}, [topics, provider, enableInContext, enableInContextSidebar]);
}
export function useRedirectToThread(courseId, enableInContextSidebar) {
const dispatch = useDispatch();
const navigate = useNavigate();

View File

@@ -16,7 +16,7 @@ import { ALL_ROUTES, DiscussionProvider, Routes as ROUTES } from '../../data/con
import DiscussionContext from '../common/context';
import ContentUnavailable from '../content-unavailable/ContentUnavailable';
import {
useCourseBlockData, useCourseDiscussionData, useIsOnTablet, useRedirectToThread, useSidebarVisible,
useCourseBlockData, useCourseDiscussionData, useIsOnTablet, useRedirectToThread, useSidebarVisible, useTopicsData,
} from '../data/hooks';
import { selectDiscussionProvider, selectEnableInContext, selectIsUserLearner } from '../data/selectors';
import { EmptyLearners, EmptyTopics } from '../empty-posts';
@@ -62,6 +62,7 @@ const DiscussionsHome = () => {
useCourseDiscussionData(courseId);
useRedirectToThread(courseId, enableInContextSidebar);
useCourseBlockData(courseId);
useTopicsData(courseId, enableInContextSidebar);
useFeedbackWrapper();
/* Display the content area if we are currently viewing/editing a post or creating one.
If the window is larger than a particular size, show the sidebar for navigating between posts/topics.

View File

@@ -27,10 +27,10 @@ import { fetchThreads } from '../posts/data/thunks';
import fetchCourseTopics from '../topics/data/thunks';
import DiscussionsHome from './DiscussionsHome';
import '../posts/data/__factories__/threads.factory';
import '../in-context-topics/data/__factories__/inContextTopics.factory';
import '../topics/data/__factories__/topics.factory';
import '../../components/NavigationBar/data/__factories__/navigationBar.factory';
import '../in-context-topics/data/__factories__/inContextTopics.factory';
import '../posts/data/__factories__/threads.factory';
import '../topics/data/__factories__/topics.factory';
const courseConfigApiUrl = getCourseConfigApiUrl();
let axiosMock;
@@ -224,7 +224,7 @@ describe('DiscussionsHome', () => {
it('should display post editor form when click on add a post button in legacy topics view', async () => {
axiosMock.onGet(getDiscussionsConfigUrl(courseId)).reply(200, {
enable_in_context: false, hasModerationPrivileges: true, isEmailVerified: true,
enable_in_context: false, hasModerationPrivileges: true, isEmailVerified: true, provider: 'legacy',
});
await executeThunk(fetchCourseConfig(courseId), store.dispatch, store.getState);
await renderComponent(`/${courseId}/topics`);

View File

@@ -1,6 +1,4 @@
import React, {
useCallback, useContext, useEffect, useMemo,
} from 'react';
import { useCallback, useEffect, useMemo } from 'react';
import { Spinner } from '@openedx/paragon';
import classNames from 'classnames';
@@ -9,8 +7,7 @@ import { useDispatch, useSelector } from 'react-redux';
import SearchInfo from '../../components/SearchInfo';
import { RequestStatus } from '../../data/constants';
import DiscussionContext from '../common/context';
import { selectAreThreadsFiltered, selectDiscussionProvider } from '../data/selectors';
import { selectAreThreadsFiltered } from '../data/selectors';
import { clearFilter, clearSort } from '../posts/data/slices';
import NoResults from '../posts/NoResults';
import { handleKeyDown } from '../utils';
@@ -19,7 +16,6 @@ import {
selectNonCoursewareTopics, selectTopicFilter, selectTopics,
} from './data/selectors';
import { setFilter } from './data/slices';
import fetchCourseTopicsV3 from './data/thunks';
import { ArchivedBaseGroup, SectionBaseGroup, Topic } from './topic';
const TopicsList = () => {
@@ -71,20 +67,12 @@ const TopicsList = () => {
const TopicsView = () => {
const dispatch = useDispatch();
const { courseId } = useContext(DiscussionContext);
const provider = useSelector(selectDiscussionProvider);
const topicFilter = useSelector(selectTopicFilter);
const filteredTopics = useSelector(selectFilteredTopics);
const loadingStatus = useSelector(selectLoadingStatus);
const isPostsFiltered = useSelector(selectAreThreadsFiltered);
const topics = useSelector(selectTopics);
useEffect(() => {
if (provider) {
dispatch(fetchCourseTopicsV3(courseId));
}
}, [provider]);
useEffect(() => {
if (isPostsFiltered) {
dispatch(clearFilter());

View File

@@ -1,4 +1,4 @@
import React, {
import {
useCallback, useContext, useEffect, useMemo,
} from 'react';

View File

@@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useMemo } from 'react';
import { useCallback, useEffect, useMemo } from 'react';
import { Button, Spinner } from '@openedx/paragon';
import { useDispatch, useSelector } from 'react-redux';

View File

@@ -14,5 +14,5 @@ export const selectLearnerSorting = () => state => state.learners.sortedBy;
export const selectLearnerNextPage = () => state => state.learners.nextPage;
export const selectLearnerAvatar = author => state => (
state.learners.learnerProfiles[author]?.profileImage?.imageUrlLarge
state.learners.learnerProfiles[author]?.profileImage?.imageUrlSmall
);

View File

@@ -9,7 +9,9 @@ import {
} from 'react-router-dom';
import { Factory } from 'rosie';
import { camelCaseObject, initializeMockApp } from '@edx/frontend-platform';
import {
camelCaseObject, getConfig, initializeMockApp, setConfig,
} from '@edx/frontend-platform';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { AppProvider } from '@edx/frontend-platform/react';
@@ -738,6 +740,20 @@ describe('ThreadView', () => {
expect(screen.queryByTestId('comment-comment-4'))
.toBeInTheDocument();
});
it('it show avatar for reply author when ENABLE_PROFILE_IMAGE is true', async () => {
setConfig({
...getConfig(),
ENABLE_PROFILE_IMAGE: 'true',
});
await waitFor(() => renderComponent(discussionPostId));
const comment = await waitFor(() => screen.findByTestId('comment-comment-1'));
expect(comment).toBeInTheDocument();
const replyAuthorAvatar = within(comment).getAllByAltText('edx');
expect(replyAuthorAvatar.length).toBeGreaterThan(0);
});
});
describe('for question thread', () => {

View File

@@ -46,7 +46,7 @@ const Comment = ({
const {
id, parentId, childCount, abuseFlagged, endorsed, threadId, endorsedAt, endorsedBy, endorsedByLabel, renderedBody,
voted, following, voteCount, authorLabel, author, createdAt, lastEdit, rawBody, closed, closedBy, closeReason,
editByLabel, closedByLabel, users: postUsers,
editByLabel, closedByLabel, users: commentUsers,
} = comment;
const intl = useIntl();
const hasChildren = childCount > 0;
@@ -209,7 +209,7 @@ const Comment = ({
closed={closed}
createdAt={createdAt}
lastEdit={lastEdit}
postUsers={postUsers}
commentUsers={commentUsers}
/>
{isEditing ? (
<CommentEditor
@@ -226,7 +226,7 @@ const Comment = ({
/>
) : (
<HTMLLoader
cssClassName="comment-body html-loader text-break mt-14px font-style text-primary-500"
cssClassName="comment-body html-loader text-break mt-14px font-style text-gray-700"
componentId="comment"
htmlNode={renderedBody}
testId={id}

View File

@@ -19,7 +19,7 @@ const CommentHeader = ({
closed,
createdAt,
lastEdit,
postUsers,
commentUsers,
}) => {
const colorClass = AvatarOutlineAndLabelColors[authorLabel];
const hasAnyAlert = useAlertBannerVisible({
@@ -31,7 +31,7 @@ const CommentHeader = ({
const authorAvatar = useSelector(selectAuthorAvatar(author));
const profileImage = getConfig()?.ENABLE_PROFILE_IMAGE === 'true'
? Object.values(postUsers ?? {})[0]?.profile?.image
? Object.values(commentUsers ?? {})[0]?.profile?.image
: null;
return (
@@ -43,7 +43,7 @@ const CommentHeader = ({
<Avatar
className={`border-0 ml-0.5 mr-2.5 ${colorClass ? `outline-${colorClass}` : 'outline-anonymous'}`}
alt={author}
src={profileImage?.hasImage ? profileImage?.imageUrlSmall : authorAvatar}
src={profileImage?.hasImage ? profileImage?.imageUrlSmall : authorAvatar?.imageUrlSmall}
style={{
width: '32px',
height: '32px',
@@ -72,7 +72,7 @@ CommentHeader.propTypes = {
editorUsername: PropTypes.string,
reason: PropTypes.string,
}),
postUsers: PropTypes.shape({}).isRequired,
commentUsers: PropTypes.shape({}).isRequired,
};
CommentHeader.defaultProps = {

View File

@@ -22,7 +22,7 @@ const defaultProps = {
closed: false,
createdAt: '2025-09-23T10:00:00Z',
lastEdit: null,
postUsers: {
commentUsers: {
'test-user': {
profile: { image: { hasImage: true, imageUrlSmall: 'http://avatar.test/img.png' } },
},

View File

@@ -5,6 +5,7 @@ import { Avatar, useToggle } from '@openedx/paragon';
import { useDispatch, useSelector } from 'react-redux';
import * as timeago from 'timeago.js';
import { getConfig } from '@edx/frontend-platform';
import { useIntl } from '@edx/frontend-platform/i18n';
import HTMLLoader from '../../../../components/HTMLLoader';
@@ -24,7 +25,7 @@ import CommentEditor from './CommentEditor';
const Reply = ({ responseId }) => {
timeago.register('time-locale', timeLocale);
const {
id, abuseFlagged, author, authorLabel, endorsed, lastEdit, closed, closedBy,
id, abuseFlagged, author, authorLabel, endorsed, lastEdit, closed, closedBy, users: replyUsers,
closeReason, createdAt, threadId, parentId, rawBody, renderedBody, editByLabel, closedByLabel,
} = useSelector(selectCommentOrResponseById(responseId));
const intl = useIntl();
@@ -78,6 +79,10 @@ const Reply = ({ responseId }) => {
[ContentActions.REPORT]: handleAbusedFlag,
}), [handleEditContent, handleReplyEndorse, showDeleteConfirmation, handleAbusedFlag]);
const profileImage = getConfig()?.ENABLE_PROFILE_IMAGE === 'true'
? Object.values(replyUsers ?? {})[0]?.profile?.image
: null;
return (
<div className="d-flex flex-column mt-2.5 " data-testid={`reply-${id}`} role="listitem">
<Confirmation
@@ -123,7 +128,7 @@ const Reply = ({ responseId }) => {
<Avatar
className={`ml-0.5 mt-0.5 border-0 ${colorClass ? `outline-${colorClass}` : 'outline-anonymous'}`}
alt={author}
src={authorAvatar?.imageUrlSmall}
src={profileImage?.hasImage ? profileImage?.imageUrlSmall : authorAvatar?.imageUrlSmall}
style={{
width: '32px',
height: '32px',
@@ -168,7 +173,7 @@ const Reply = ({ responseId }) => {
<HTMLLoader
componentId="reply"
htmlNode={renderedBody}
cssClassName="html-loader text-break font-style text-primary-500"
cssClassName="html-loader text-break font-style text-gray-700"
testId={id}
/>
)}

View File

@@ -30,6 +30,16 @@ Factory.define('comment')
parent_id: null,
children: [],
abuse_flagged_any_user: false,
users: {
edx: {
profile: {
image: {
hasImage: true,
imageUrlSmall: 'http://test.site/default-avatar-small.png',
},
},
},
},
});
Factory.define('commentsResult')

View File

@@ -1,19 +1,11 @@
import React, {
useCallback, useContext, useEffect, useMemo,
} from 'react';
import React, { useCallback, useContext, useMemo } from 'react';
import PropTypes from 'prop-types';
import isEmpty from 'lodash/isEmpty';
import { useDispatch, useSelector } from 'react-redux';
import SearchInfo from '../../components/SearchInfo';
import { selectCurrentCategoryGrouping, selectTopicsUnderCategory } from '../../data/selectors';
import DiscussionContext from '../common/context';
import { selectEnableInContext } from '../data/selectors';
import { selectTopics as selectInContextTopics } from '../in-context-topics/data/selectors';
import fetchCourseTopicsV3 from '../in-context-topics/data/thunks';
import { selectTopics } from '../topics/data/selectors';
import fetchCourseTopics from '../topics/data/thunks';
import { handleKeyDown } from '../utils';
import { selectAllThreadsIds, selectTopicThreadsIds } from './data/selectors';
import { setSearchQuery } from './data/slices';
@@ -51,27 +43,12 @@ CategoryPostsList.propTypes = {
};
const PostsView = () => {
const {
topicId,
category,
courseId,
enableInContextSidebar,
} = useContext(DiscussionContext);
const { topicId, category } = useContext(DiscussionContext);
const dispatch = useDispatch();
const enableInContext = useSelector(selectEnableInContext);
const searchString = useSelector(({ threads }) => threads.filters.search);
const resultsFound = useSelector(({ threads }) => threads.totalThreads);
const textSearchRewrite = useSelector(({ threads }) => threads.textSearchRewrite);
const loadingStatus = useSelector(({ threads }) => threads.status);
const topics = useSelector(enableInContext ? selectInContextTopics : selectTopics);
useEffect(() => {
if (isEmpty(topics)) {
dispatch((enableInContext || enableInContextSidebar)
? fetchCourseTopicsV3(courseId)
: fetchCourseTopics(courseId));
}
}, [topics]);
const handleOnClear = useCallback(() => {
dispatch(setSearchQuery(''));

View File

@@ -1,5 +1,3 @@
import React from 'react';
import { fireEvent, render, screen } from '@testing-library/react';
import MockAdapter from 'axios-mock-adapter';
import { act } from 'react-dom/test-utils';
@@ -232,7 +230,7 @@ describe('PostsView', () => {
test('test that the cohorts filter works', async () => {
await act(async () => {
fireEvent.click(screen.getByLabelText('Cohort 1'));
fireEvent.click(screen.getByLabelText('cohort 1'));
});
dropDownButton = screen.getByRole('button', {
@@ -280,7 +278,7 @@ describe('PostsView', () => {
queryParam: { group_id: undefined },
},
{
label: 'Cohort 1',
label: 'cohort 1',
queryParam: { group_id: 'cohort-1' },
},
])(

View File

@@ -165,7 +165,7 @@ const PostFilterBar = () => {
<ActionItem
key={cohort.id}
id={toString(cohort.id)}
label={capitalize(cohort.name)}
label={cohort.name}
value={toString(cohort.id)}
selected={currentFilters.cohort}
/>

View File

@@ -143,6 +143,7 @@ const Post = ({ handleAddResponseButton, openRestrictionDialogue }) => {
onClose={hideDeleteConfirmation}
confirmAction={handleDeleteConfirmation}
closeButtonVariant="tertiary"
confirmButtonVariant="danger"
confirmButtonText={intl.formatMessage(messages.deleteConfirmationDelete)}
/>
{!abuseFlagged && (
@@ -152,7 +153,6 @@ const Post = ({ handleAddResponseButton, openRestrictionDialogue }) => {
description={intl.formatMessage(messages.reportPostDescription)}
onClose={hideReportConfirmation}
confirmAction={handleReportConfirmation}
confirmButtonVariant="danger"
/>
)}
<HoverCard
@@ -189,7 +189,7 @@ const Post = ({ handleAddResponseButton, openRestrictionDialogue }) => {
title={title}
postUsers={postUsers}
/>
<div className="d-flex mt-14px text-break font-style text-primary-500">
<div className="d-flex mt-14px text-break font-style text-gray-700">
<HTMLLoader htmlNode={renderedBody} componentId="post" cssClassName="html-loader w-100" testId={postId} />
</div>
{(topicContext || topic) && (

View File

@@ -0,0 +1,157 @@
import React from 'react';
import { fireEvent, render, screen } from '@testing-library/react';
import { useSelector } from 'react-redux';
import Post from './Post';
jest.mock('react-redux', () => ({
...jest.requireActual('react-redux'),
useDispatch: () => jest.fn(),
useSelector: jest.fn(),
}));
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useLocation: () => ({ pathname: '/test' }),
useNavigate: () => jest.fn(),
}));
jest.mock('@edx/frontend-platform/i18n', () => ({
useIntl: () => ({
formatMessage: (msg) => ((msg && msg.defaultMessage) ? msg.defaultMessage : 'test-message'),
}),
defineMessages: (msgs) => msgs,
}));
jest.mock('@openedx/paragon', () => {
const actual = jest.requireActual('@openedx/paragon');
// eslint-disable-next-line global-require
const PropTypes = require('prop-types');
const MockHyperlink = ({ children }) => <div>{children}</div>;
MockHyperlink.propTypes = { children: PropTypes.node.isRequired };
return {
...actual,
Hyperlink: MockHyperlink,
useToggle: actual.useToggle,
};
});
jest.mock('../../common', () => {
// eslint-disable-next-line global-require
const PropTypes = require('prop-types');
const MockConfirmation = ({ confirmButtonVariant, isOpen }) => {
if (!isOpen) { return null; }
return (
<div data-testid="mock-confirmation" data-variant={confirmButtonVariant}>
Mock Confirmation
</div>
);
};
MockConfirmation.propTypes = {
confirmButtonVariant: PropTypes.string,
isOpen: PropTypes.bool.isRequired,
};
// eslint-disable-next-line react/prop-types
const MockAlertBanner = () => <div />;
return {
Confirmation: MockConfirmation,
AlertBanner: MockAlertBanner,
};
});
jest.mock('./PostHeader', () => function MockPostHeader() { return <div>PostHeader</div>; });
jest.mock('./PostFooter', () => function MockPostFooter() { return <div>PostFooter</div>; });
jest.mock('./ClosePostReasonModal', () => function MockCloseModal() { return <div />; });
jest.mock('../../../components/HTMLLoader', () => function MockLoader() { return <div>Body Content</div>; });
jest.mock('../../common/HoverCard', () => {
// eslint-disable-next-line global-require
const { ContentActions } = require('../../../data/constants');
// eslint-disable-next-line global-require
const PropTypes = require('prop-types');
const MockHoverCard = ({ actionHandlers }) => (
<div>
<button
type="button"
data-testid="trigger-delete"
onClick={() => actionHandlers[ContentActions.DELETE] && actionHandlers[ContentActions.DELETE]()}
>
Delete Post
</button>
<button
type="button"
data-testid="trigger-report"
onClick={() => actionHandlers[ContentActions.REPORT] && actionHandlers[ContentActions.REPORT]()}
>
Report Post
</button>
</div>
);
MockHoverCard.propTypes = {
actionHandlers: PropTypes.shape({}).isRequired,
};
return MockHoverCard;
});
describe('Post Component - Delete/Report Confirmation', () => {
const mockPostId = '123';
beforeEach(() => {
useSelector.mockReturnValue({
topicId: 'topic-1',
abuseFlagged: false,
closed: false,
pinned: false,
voted: false,
following: false,
author: {},
title: 'Test Post',
renderedBody: '<div>Hello</div>',
users: {},
});
});
const renderPost = () => {
// eslint-disable-next-line global-require
const DiscussionContext = require('../../common/context').default;
return render(
<DiscussionContext.Provider value={{ postId: mockPostId, enableInContextSidebar: false, courseId: 'course-1' }}>
<Post
handleAddResponseButton={jest.fn()}
openRestrictionDialogue={jest.fn()}
/>
</DiscussionContext.Provider>,
);
};
it('passes "danger" variant to Confirmation modal when deleting a post', () => {
renderPost();
const deleteBtn = screen.getByTestId('trigger-delete');
fireEvent.click(deleteBtn);
const confirmation = screen.getByTestId('mock-confirmation');
expect(confirmation).toHaveAttribute('data-variant', 'danger');
});
it('does NOT pass "danger" variant to Confirmation modal when reporting a post', () => {
renderPost();
const reportBtn = screen.getByTestId('trigger-report');
fireEvent.click(reportBtn);
const confirmation = screen.getByTestId('mock-confirmation');
expect(confirmation).not.toHaveAttribute('data-variant', 'danger');
});
});

View File

@@ -126,7 +126,7 @@ const PostHeader = ({
</div>
) : (
<h5
className="mb-0 font-style text-primary-500"
className="mb-0 font-style"
style={{ lineHeight: '21px' }}
aria-level="1"
tabIndex="-1"

View File

@@ -1,19 +1,15 @@
import React, {
useCallback, useContext, useEffect, useMemo,
} from 'react';
import { useCallback, useEffect, useMemo } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useParams } from 'react-router-dom';
import SearchInfo from '../../components/SearchInfo';
import { RequestStatus } from '../../data/constants';
import DiscussionContext from '../common/context';
import { selectDiscussionProvider } from '../data/selectors';
import NoResults from '../posts/NoResults';
import { handleKeyDown } from '../utils';
import { selectCategories, selectNonCoursewareTopics, selectTopicFilter } from './data/selectors';
import { setFilter, setTopicsCount } from './data/slices';
import fetchCourseTopics from './data/thunks';
import LegacyTopicGroup from './topic-group/LegacyTopicGroup';
import Topic from './topic-group/topic/Topic';
import countFilteredTopics from './utils';
@@ -64,19 +60,11 @@ const TopicsView = () => {
const topicsSelector = useSelector(({ topics }) => topics);
const filteredTopicsCount = useSelector(({ topics }) => topics.results.count);
const loadingStatus = useSelector(({ topics }) => topics.status);
const { courseId } = useContext(DiscussionContext);
const handleOnClear = useCallback(() => {
dispatch(setFilter(''));
}, []);
useEffect(() => {
// Don't load till the provider information is available
if (provider) {
dispatch(fetchCourseTopics(courseId));
}
}, [provider]);
useEffect(() => {
const count = countFilteredTopics(topicsSelector, provider);
dispatch(setTopicsCount(count));

View File

@@ -1,5 +1,7 @@
import PropTypes from 'prop-types';
import { mergeConfig } from '@edx/frontend-platform';
import '@testing-library/jest-dom/extend-expect';
import 'babel-polyfill';
@@ -66,3 +68,10 @@ global.ResizeObserver = jest.fn().mockImplementation(() => ({
}));
jest.setTimeout(1000000);
mergeConfig({
LEARNING_BASE_URL: process.env.LEARNING_BASE_URL,
LEARNER_FEEDBACK_URL: process.env.LEARNER_FEEDBACK_URL,
STAFF_FEEDBACK_URL: process.env.STAFF_FEEDBACK_URL,
ENABLE_PROFILE_IMAGE: process.env.ENABLE_PROFILE_IMAGE || 'false',
}, 'DiscussionsConfig');