From 4928f505bde012e4445065415c06a0672a7c4e09 Mon Sep 17 00:00:00 2001
From: sundasnoreen12 <72802712+sundasnoreen12@users.noreply.github.com>
Date: Thu, 11 Jan 2024 15:54:25 +0500
Subject: [PATCH] test: added test cases for new sidebar (#1267)
* test: added test cases for new sidebar
* test: added factory for verified user
* refactor: updated description for notification widget
---
src/courseware/course/Course.test.jsx | 34 ++-------
.../course/new-sidebar/common/SidebarBase.jsx | 3 +-
.../discussions/DiscussionsWidget.test.jsx | 14 ++--
.../notifications/NotificationsWidget.jsx | 2 +-
.../NotificationsWidget.test.jsx | 72 ++++++++++++++++++-
src/courseware/course/test-utils.jsx | 49 +++++++++++++
.../__factories__/discussionTopics.factory.js | 14 +++-
7 files changed, 148 insertions(+), 40 deletions(-)
create mode 100644 src/courseware/course/test-utils.jsx
diff --git a/src/courseware/course/Course.test.jsx b/src/courseware/course/Course.test.jsx
index 64de33b2..ab629700 100644
--- a/src/courseware/course/Course.test.jsx
+++ b/src/courseware/course/Course.test.jsx
@@ -1,18 +1,16 @@
import React from 'react';
+
import { Factory } from 'rosie';
-import { getConfig } from '@edx/frontend-platform';
-import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
-import MockAdapter from 'axios-mock-adapter';
+
import { breakpoints } from '@edx/paragon';
+
import {
act, fireEvent, getByRole, initializeTestStore, loadUnit, render, screen, waitFor,
} from '../../setupTest';
-import { buildTopicsFromUnits } from '../data/__factories__/discussionTopics.factory';
-import { handleNextSectionCelebration } from './celebration';
import * as celebrationUtils from './celebration/utils';
+import { handleNextSectionCelebration } from './celebration';
import Course from './Course';
-import { executeThunk } from '../../utils';
-import * as thunks from '../data/thunks';
+import setupDiscussionSidebar from './test-utils';
jest.mock('@edx/frontend-platform/analytics');
jest.mock('@edx/frontend-lib-special-exams/dist/data/thunks.js', () => ({
@@ -51,26 +49,6 @@ describe('Course', () => {
setItemSpy.mockRestore();
});
- const setupDiscussionSidebar = async () => {
- const courseHomeMetadata = Factory.build('courseHomeMetadata', { verified_mode: null });
- const testStore = await initializeTestStore({ provider: 'openedx', courseHomeMetadata });
- const state = testStore.getState();
- const { courseware: { courseId } } = state;
- const axiosMock = new MockAdapter(getAuthenticatedHttpClient());
- axiosMock.onGet(`${getConfig().LMS_BASE_URL}/api/discussion/v1/courses/${courseId}`).reply(200, { provider: 'openedx' });
- const topicsResponse = buildTopicsFromUnits(state.models.units);
- axiosMock.onGet(`${getConfig().LMS_BASE_URL}/api/discussion/v2/course_topics/${courseId}`)
- .reply(200, topicsResponse);
-
- await executeThunk(thunks.getCourseDiscussionTopics(courseId), testStore.dispatch);
- const [firstUnitId] = Object.keys(state.models.units);
- mockData.unitId = firstUnitId;
- const [firstSequenceId] = Object.keys(state.models.sequences);
- mockData.sequenceId = firstSequenceId;
-
- await render(, { store: testStore, wrapWithRouter: true });
- };
-
it('loads learning sequence', async () => {
render(, { wrapWithRouter: true });
expect(screen.getByRole('navigation', { name: 'breadcrumb' })).toBeInTheDocument();
@@ -183,7 +161,7 @@ describe('Course', () => {
});
it('handles click to open/close notification tray', async () => {
- render(, { wrapWithRouter: true });
+ await setupDiscussionSidebar();
const notificationShowButton = await screen.findByRole('button', { name: /Show notification tray/i });
expect(screen.queryByRole('region', { name: /notification tray/i })).toHaveClass('d-none');
fireEvent.click(notificationShowButton);
diff --git a/src/courseware/course/new-sidebar/common/SidebarBase.jsx b/src/courseware/course/new-sidebar/common/SidebarBase.jsx
index 63e4607e..540b8fce 100644
--- a/src/courseware/course/new-sidebar/common/SidebarBase.jsx
+++ b/src/courseware/course/new-sidebar/common/SidebarBase.jsx
@@ -91,7 +91,7 @@ const SidebarBase = ({
};
SidebarBase.propTypes = {
- title: PropTypes.string.isRequired,
+ title: PropTypes.string,
ariaLabel: PropTypes.string.isRequired,
sidebarId: PropTypes.string.isRequired,
className: PropTypes.string,
@@ -103,6 +103,7 @@ SidebarBase.propTypes = {
};
SidebarBase.defaultProps = {
+ title: '',
width: '50rem',
allowFullHeight: false,
showTitleBar: true,
diff --git a/src/courseware/course/new-sidebar/sidebars/discussions-notifications/discussions/DiscussionsWidget.test.jsx b/src/courseware/course/new-sidebar/sidebars/discussions-notifications/discussions/DiscussionsWidget.test.jsx
index 519aa09d..84685bf7 100644
--- a/src/courseware/course/new-sidebar/sidebars/discussions-notifications/discussions/DiscussionsWidget.test.jsx
+++ b/src/courseware/course/new-sidebar/sidebars/discussions-notifications/discussions/DiscussionsWidget.test.jsx
@@ -12,6 +12,7 @@ import { executeThunk } from '../../../../../../utils';
import { buildTopicsFromUnits } from '../../../../../data/__factories__/discussionTopics.factory';
import { getCourseDiscussionTopics } from '../../../../../data/thunks';
import SidebarContext from '../../../SidebarContext';
+import DiscussionsNotificationsSidebar from '../DiscussionsNotificationsSidebar';
import DiscussionsWidget from './DiscussionsWidget';
initializeMockApp();
@@ -51,24 +52,29 @@ describe('DiscussionsWidget', () => {
await executeThunk(getCourseDiscussionTopics(courseId), store.dispatch);
});
- function renderWithProvider(testData = {}) {
+ function renderWithProvider(Component, testData = {}) {
const { container } = render(
-
+
,
);
return container;
}
it('should show up if unit discussions associated with it', async () => {
- renderWithProvider();
+ renderWithProvider(DiscussionsWidget);
expect(screen.queryByTitle('Discussions')).toBeInTheDocument();
expect(screen.queryByTitle('Discussions'))
.toHaveAttribute('src', `http://localhost:2002/${courseId}/category/${unitId}?inContextSidebar`);
});
it('should show nothing if unit has no discussions associated with it', async () => {
- renderWithProvider({ isDiscussionbarAvailable: false });
+ renderWithProvider(DiscussionsWidget, { isDiscussionbarAvailable: false });
expect(screen.queryByTitle('Discussions')).not.toBeInTheDocument();
});
+
+ it('should display the Back to course button on small screens.', async () => {
+ renderWithProvider(DiscussionsNotificationsSidebar, { shouldDisplayFullScreen: true });
+ expect(screen.queryByText('Back to course')).toBeInTheDocument();
+ });
});
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 26ea9ac2..e146fb7a 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
@@ -38,7 +38,7 @@ const NotificationsWidget = () => {
if (hideNotificationbar || !isNotificationbarAvailable) { return null; }
return (
-
+
{
let axiosMock;
let store;
const ID = 'NEWSIDEBAR';
-
const defaultMetadata = Factory.build('courseMetadata');
const courseId = defaultMetadata.id;
let courseMetadataUrl = `${getConfig().LMS_BASE_URL}/api/courseware/course/${defaultMetadata.id}`;
@@ -47,6 +49,35 @@ describe('NotificationsWidget', () => {
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
axiosMock.onGet(courseMetadataUrl).reply(200, defaultMetadata);
axiosMock.onGet(courseHomeMetadataUrl).reply(200, courseHomeMetadata);
+ mergeConfig({ ENABLE_NEW_SIDEBAR: 'true' }, 'Custom app config');
+ });
+
+ it('successfully Open/Hide sidebar tray.', async () => {
+ const userVerifiedMode = Factory.build('verifiedMode');
+
+ await setupDiscussionSidebar(userVerifiedMode);
+
+ const sidebarButton = await screen.getByRole('button', { name: /Show sidebar tray/i });
+
+ await act(async () => {
+ fireEvent.click(sidebarButton);
+ });
+
+ await waitFor(async () => {
+ expect(screen.queryByTestId('sidebar-DISCUSSIONS_NOTIFICATIONS')).toBeInTheDocument();
+ expect(screen.queryByTestId('notification-widget')).toBeInTheDocument();
+ expect(screen.queryByTitle('Discussions')).toBeInTheDocument();
+ });
+
+ await act(async () => {
+ fireEvent.click(sidebarButton);
+ });
+
+ await waitFor(async () => {
+ expect(screen.queryByTestId('sidebar-DISCUSSIONS_NOTIFICATIONS')).not.toBeInTheDocument();
+ expect(screen.queryByTestId('notification-widget')).not.toBeInTheDocument();
+ expect(screen.queryByTitle('Discussions')).not.toBeInTheDocument();
+ });
});
it('renders upgrade card', async () => {
@@ -90,6 +121,41 @@ describe('NotificationsWidget', () => {
.toBeInTheDocument();
});
+ it.each([
+ {
+ description: 'close the notification widget.',
+ enabledInContext: true,
+ testId:
+ 'notification-widget',
+ },
+ {
+ description: 'close the sidebar when the notification widget is closed, and the discussion widget is unavailable.',
+ enabledInContext: false,
+ testId: 'sidebar-DISCUSSIONS_NOTIFICATIONS',
+ },
+ ])('successfully %s', async ({ enabledInContext, testId }) => {
+ const userVerifiedMode = Factory.build('verifiedMode');
+
+ await setupDiscussionSidebar(userVerifiedMode, enabledInContext);
+
+ const sidebarButton = screen.getByRole('button', { name: /Show sidebar tray/i });
+
+ await act(async () => {
+ fireEvent.click(sidebarButton);
+ });
+
+ const notificationWidget = await waitFor(() => screen.getByTestId('notification-widget'));
+ const closeNotificationButton = within(notificationWidget).getByRole('button', { name: /Close/i });
+
+ await act(async () => {
+ fireEvent.click(closeNotificationButton);
+ });
+
+ await waitFor(() => {
+ expect(screen.queryByTestId(testId)).not.toBeInTheDocument();
+ });
+ });
+
it('marks notification as seen 3 seconds later', async () => {
jest.useFakeTimers();
const onNotificationSeen = jest.fn();
diff --git a/src/courseware/course/test-utils.jsx b/src/courseware/course/test-utils.jsx
new file mode 100644
index 00000000..df7a7cbc
--- /dev/null
+++ b/src/courseware/course/test-utils.jsx
@@ -0,0 +1,49 @@
+import React from 'react';
+import { Factory } from 'rosie';
+import { getConfig } from '@edx/frontend-platform';
+import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
+import MockAdapter from 'axios-mock-adapter';
+import { breakpoints } from '@edx/paragon';
+import { initializeTestStore, render } from '../../setupTest';
+import { buildTopicsFromUnits } from '../data/__factories__/discussionTopics.factory';
+import { executeThunk } from '../../utils';
+import * as thunks from '../data/thunks';
+import Course from './Course';
+
+const mockData = {
+ nextSequenceHandler: () => {},
+ previousSequenceHandler: () => {},
+ unitNavigationHandler: () => {},
+};
+
+const setupDiscussionSidebar = async (verifiedMode = null, enabledInContext = true) => {
+ const store = await initializeTestStore();
+ const { courseware, models } = store.getState();
+ const { courseId, sequenceId } = courseware;
+ Object.assign(mockData, {
+ courseId,
+ sequenceId,
+ unitId: Object.values(models.units)[0].id,
+ });
+ global.innerWidth = breakpoints.extraLarge.minWidth;
+
+ const courseHomeMetadata = Factory.build('courseHomeMetadata', { verified_mode: verifiedMode });
+ const testStore = await initializeTestStore({ provider: 'openedx', courseHomeMetadata });
+ const state = testStore.getState();
+ const axiosMock = new MockAdapter(getAuthenticatedHttpClient());
+ axiosMock.onGet(`${getConfig().LMS_BASE_URL}/api/discussion/v1/courses/${courseId}`).reply(200, { provider: 'openedx' });
+ const topicsResponse = buildTopicsFromUnits(state.models.units, enabledInContext);
+ axiosMock.onGet(`${getConfig().LMS_BASE_URL}/api/discussion/v2/course_topics/${courseId}`)
+ .reply(200, topicsResponse);
+
+ await executeThunk(thunks.getCourseDiscussionTopics(courseId), testStore.dispatch);
+ const [firstUnitId] = Object.keys(state.models.units);
+ mockData.unitId = firstUnitId;
+ const [firstSequenceId] = Object.keys(state.models.sequences);
+ mockData.sequenceId = firstSequenceId;
+
+ const wrapper = await render(, { store: testStore, wrapWithRouter: true });
+ return wrapper;
+};
+
+export default setupDiscussionSidebar;
diff --git a/src/courseware/data/__factories__/discussionTopics.factory.js b/src/courseware/data/__factories__/discussionTopics.factory.js
index 1aeb2b19..47f863e9 100644
--- a/src/courseware/data/__factories__/discussionTopics.factory.js
+++ b/src/courseware/data/__factories__/discussionTopics.factory.js
@@ -1,6 +1,13 @@
/* eslint-disable import/prefer-default-export */
import { Factory } from 'rosie'; // eslint-disable-line import/no-extraneous-dependencies
+Factory.define('verifiedMode')
+ .attr('currency', 'USD')
+ .attr('currencySymbol', '$')
+ .attr('price', '$149')
+ .attr('sku', '8CF08E5')
+ .attr('upgradeUrl', 'http://localhost:18130/basket/add/?sku=8CF08E5');
+
Factory.define('discussionTopic')
.option('topicPrefix', null, '')
.option('courseId', null, 'course-v1:edX+DemoX+Demo_Course')
@@ -11,13 +18,14 @@ Factory.define('discussionTopic')
['id', 'courseId'],
(idx, id, courseId) => `block-v1:${courseId.replace('course-v1:', '')}+type@vertical+block@${id}`,
)
- .attr('enabled_in_context', null, true)
+ .attr('enabled_in_context', ['enabled_in_context'], (enabledInContext) => Boolean(enabledInContext))
+
.attr('thread_counts', [], {
discussion: 0,
question: 0,
});
// Given a pre-build units state, build topics from it.
-export function buildTopicsFromUnits(units) {
- return Object.values(units).map(unit => Factory.build('discussionTopic', { usage_key: unit.id }));
+export function buildTopicsFromUnits(units, enabledInContext = true) {
+ return Object.values(units).map(unit => Factory.build('discussionTopic', { usage_key: unit.id, enabled_in_context: enabledInContext }));
}