Compare commits

..

5 Commits

Author SHA1 Message Date
Awais Ansari
16901009cb fix: profile picture is not showing in forum responses (#850) 2026-01-02 14:57:44 +05:00
Awais Ansari
52a4e5c94c fix: cohort name difference in filter and post view (#844) 2025-12-18 18:34:51 +05:00
Awais Ansari
29da3f5939 fix: fetch topics on MFE load (#843) 2025-12-18 18:34:27 +05:00
Tobias Macey
1268442415 fix: prevent generatePath error when author is invalid in AuthorLabel
Wrap learnerPostsLink creation in useMemo with guard to prevent
'Missing :learnerUsername param' error. The generatePath function
was being called unconditionally during render even when the link
wouldn't be displayed, causing errors when author was null, undefined,
or the 'anonymous' string.

The fix ensures generatePath is only called when showUserNameAsLink
is true, which validates that author is a valid username.
2025-11-14 12:26:00 -05:00
renovate[bot]
60996abdc5 chore(deps): update dependency @edx/frontend-platform to v8.5.2 (#775)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-14 12:26:00 -05:00
10 changed files with 6443 additions and 1965 deletions

View File

@@ -1,7 +0,0 @@
version: 2
updates:
# Adding new check for github-actions
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"

View File

@@ -12,7 +12,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Nodejs

8210
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -31,17 +31,17 @@
"dependencies": {
"@edx/brand": "npm:@openedx/brand-openedx@^1.2.2",
"@edx/frontend-component-footer": "^14.6.0",
"@edx/frontend-component-header": "^8.1.0",
"@edx/frontend-component-header": "^6.2.0",
"@edx/frontend-platform": "^8.3.3",
"@edx/openedx-atlas": "^0.7.0",
"@edx/openedx-atlas": "^0.6.0",
"@openedx/paragon": "^23.4.5",
"@reduxjs/toolkit": "1.9.7",
"@tinymce/tinymce-react": "5.1.1",
"babel-polyfill": "6.26.0",
"classnames": "2.5.1",
"core-js": "3.47.0",
"core-js": "3.21.1",
"dompurify": "^2.4.3",
"formik": "2.4.9",
"formik": "2.4.5",
"lodash.snakecase": "4.1.1",
"prop-types": "15.8.1",
"raw-loader": "4.0.2",
@@ -51,25 +51,25 @@
"react-google-recaptcha-v3": "^1.11.0",
"react-helmet": "6.1.0",
"react-redux": "7.2.9",
"react-router": "6.30.3",
"react-router-dom": "6.30.3",
"react-router": "6.18.0",
"react-router-dom": "6.18.0",
"redux": "4.2.1",
"regenerator-runtime": "0.14.1",
"timeago.js": "4.0.2",
"tinymce": "5.10.9",
"tinymce": "5.10.7",
"yup": "0.32.11"
},
"devDependencies": {
"@edx/browserslist-config": "1.5.0",
"@edx/browserslist-config": "1.2.0",
"@openedx/frontend-build": "^14.6.2",
"@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": "^0.28.0",
"axios-mock-adapter": "1.22.0",
"babel-plugin-react-intl": "8.2.25",
"eslint-plugin-simple-import-sort": "7.0.0",
"glob": "7.2.3",
"glob": "7.2.0",
"jest": "29.7.0",
"rosie": "2.1.1"
}

View File

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

View File

@@ -226,7 +226,7 @@ const Comment = ({
/>
) : (
<HTMLLoader
cssClassName="comment-body html-loader text-break mt-14px font-style text-gray-700"
cssClassName="comment-body html-loader text-break mt-14px font-style text-primary-500"
componentId="comment"
htmlNode={renderedBody}
testId={id}

View File

@@ -173,7 +173,7 @@ const Reply = ({ responseId }) => {
<HTMLLoader
componentId="reply"
htmlNode={renderedBody}
cssClassName="html-loader text-break font-style text-gray-700"
cssClassName="html-loader text-break font-style text-primary-500"
testId={id}
/>
)}

View File

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

View File

@@ -1,157 +0,0 @@
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"
className="mb-0 font-style text-primary-500"
style={{ lineHeight: '21px' }}
aria-level="1"
tabIndex="-1"