diff --git a/src/course-home/outline-tab/OutlineTab.jsx b/src/course-home/outline-tab/OutlineTab.jsx
index 15ac5dd605..de900251be 100644
--- a/src/course-home/outline-tab/OutlineTab.jsx
+++ b/src/course-home/outline-tab/OutlineTab.jsx
@@ -5,6 +5,7 @@ import { sendTrackEvent } from '@edx/frontend-platform/analytics';
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Button } from '@openedx/paragon';
+import { PluginSlot } from '@openedx/frontend-plugin-framework';
import { AlertList } from '../../generic/user-messages';
import CourseDates from './widgets/CourseDates';
@@ -194,19 +195,24 @@ const OutlineTab = ({ intl }) => {
/>
)}
-
+
+
+
diff --git a/src/course-home/outline-tab/OutlineTab.test.jsx b/src/course-home/outline-tab/OutlineTab.test.jsx
index 47ceed621d..7bb8544d96 100644
--- a/src/course-home/outline-tab/OutlineTab.test.jsx
+++ b/src/course-home/outline-tab/OutlineTab.test.jsx
@@ -132,6 +132,16 @@ describe('Outline Tab', () => {
expect(expandedSectionNode).toHaveAttribute('aria-expanded', 'true');
});
+ it('includes outline_tab_notifications_plugin slot', async () => {
+ const { courseBlocks } = await buildMinimalCourseBlocks(courseId, 'Title', { resumeBlock: true });
+ setTabData({
+ course_blocks: { blocks: courseBlocks.blocks },
+ });
+ await fetchAndRender();
+
+ expect(screen.getByTestId('outline_tab_notifications_plugin')).toBeInTheDocument();
+ });
+
it('handles expand/collapse all button click', async () => {
await fetchAndRender();
// Button renders as "Expand All"
diff --git a/src/courseware/course/new-sidebar/sidebars/discussions-notifications/notifications/NotificationsWidget.jsx b/src/courseware/course/new-sidebar/sidebars/discussions-notifications/notifications/NotificationsWidget.jsx
index 3f8ab62bbc..7e7087ac5b 100644
--- a/src/courseware/course/new-sidebar/sidebars/discussions-notifications/notifications/NotificationsWidget.jsx
+++ b/src/courseware/course/new-sidebar/sidebars/discussions-notifications/notifications/NotificationsWidget.jsx
@@ -1,6 +1,7 @@
import React, { useContext, useEffect, useMemo } from 'react';
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
+import { PluginSlot } from '@openedx/frontend-plugin-framework';
import { useModel } from '../../../../../../generic/model-store';
import UpgradeNotification from '../../../../../../generic/upgrade-notification/UpgradeNotification';
import { WIDGETS } from '../../../../../../constants';
@@ -58,6 +59,10 @@ const NotificationsWidget = () => {
verification_status: verificationStatus,
};
+ const onToggleSidebar = () => {
+ toggleSidebar(currentSidebar, WIDGETS.NOTIFICATIONS);
+ };
+
// After three seconds, update notificationSeen (to hide red dot)
useEffect(() => {
setTimeout(onNotificationSeen, 3000);
@@ -68,22 +73,32 @@ const NotificationsWidget = () => {
return (
{verifiedMode
? (
-
+
+
+
) : (
{intl.formatMessage(messages.noNotificationsMessage)}
)}
diff --git a/src/courseware/course/sidebar/sidebars/notifications/NotificationTray.test.jsx b/src/courseware/course/sidebar/sidebars/notifications/NotificationTray.test.jsx
index 666874d75c..2e1815f7be 100644
--- a/src/courseware/course/sidebar/sidebars/notifications/NotificationTray.test.jsx
+++ b/src/courseware/course/sidebar/sidebars/notifications/NotificationTray.test.jsx
@@ -81,6 +81,19 @@ describe('NotificationTray', () => {
.toBeInTheDocument();
});
+ it('includes notification_tray_plugin slot', async () => {
+ await fetchAndRender(
+
+
+ ,
+ );
+ expect(screen.getByTestId('notification_tray_plugin')).toBeInTheDocument();
+ });
+
it('renders upgrade card', async () => {
await fetchAndRender(
{
,
);
- const UpgradeNotification = document.querySelector('.upgrade-notification');
- expect(UpgradeNotification)
- .toBeInTheDocument();
+ expect(document.querySelector('.upgrade-notification')).toBeInTheDocument();
+
expect(screen.getByRole('link', { name: 'Upgrade for $149' }))
.toBeInTheDocument();
expect(screen.queryByText('You have no new notifications at this time.'))
diff --git a/src/setupTest.js b/src/setupTest.js
index e1bfdc79b6..46752b6d25 100755
--- a/src/setupTest.js
+++ b/src/setupTest.js
@@ -29,11 +29,12 @@ import { getCourseOutlineStructure } from './courseware/data/thunks';
import { appendBrowserTimezoneToUrl, executeThunk } from './utils';
import buildSimpleCourseAndSequenceMetadata from './courseware/data/__factories__/sequenceMetadata.factory';
import { buildOutlineFromBlocks } from './courseware/data/__factories__/learningSequencesOutline.factory';
+import MockedPluginSlot from './tests/MockedPluginSlot';
jest.mock('@openedx/frontend-plugin-framework', () => ({
...jest.requireActual('@openedx/frontend-plugin-framework'),
Plugin: () => 'Plugin',
- PluginSlot: () => 'PluginSlot',
+ PluginSlot: MockedPluginSlot,
}));
jest.mock('@src/generic/plugin-store', () => ({
diff --git a/src/tests/MockedPluginSlot.jsx b/src/tests/MockedPluginSlot.jsx
new file mode 100644
index 0000000000..e86952ee0b
--- /dev/null
+++ b/src/tests/MockedPluginSlot.jsx
@@ -0,0 +1,26 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+const MockedPluginSlot = ({ children, id }) => (
+
+ PluginSlot_{id}
+ { children &&
{children}
}
+
+);
+
+MockedPluginSlot.displayName = 'PluginSlot';
+
+MockedPluginSlot.propTypes = {
+ children: PropTypes.oneOfType([
+ PropTypes.arrayOf(PropTypes.node),
+ PropTypes.node,
+ ]),
+ id: PropTypes.string,
+};
+
+MockedPluginSlot.defaultProps = {
+ children: undefined,
+ id: undefined,
+};
+
+export default MockedPluginSlot;
diff --git a/src/tests/MockedPluginSlot.test.jsx b/src/tests/MockedPluginSlot.test.jsx
new file mode 100644
index 0000000000..b830b68fbf
--- /dev/null
+++ b/src/tests/MockedPluginSlot.test.jsx
@@ -0,0 +1,43 @@
+import React from 'react';
+import { render, screen } from '@testing-library/react';
+import MockedPluginSlot from './MockedPluginSlot';
+
+describe('MockedPluginSlot', () => {
+ it('renders mock plugin with "PluginSlot" text', () => {
+ render(
);
+
+ const component = screen.getByText('PluginSlot_test_plugin');
+ expect(component).toBeInTheDocument();
+ });
+
+ it('renders as the slot children directly if there is content within', () => {
+ render(
+
+
+ How much wood could a woodchuck chuck if a woodchuck could chuck wood?
+
+
,
+ );
+
+ const component = screen.getByRole('article');
+ expect(component).toBeInTheDocument();
+
+ // Direct children
+ const quote = component.querySelector(':scope > q');
+ expect(quote.getAttribute('role')).toBe('note');
+ });
+
+ it('renders mock plugin with a data-testid ', () => {
+ render(
+
+ I am selling these fine leather jackets.
+ ,
+ );
+
+ const component = screen.getByTestId('guybrush');
+ expect(component).toBeInTheDocument();
+
+ const quote = component.querySelector('[role=note]');
+ expect(quote).toBeInTheDocument();
+ });
+});