Skip to content

Commit

Permalink
test: added test cases for new sidebar (openedx#1267)
Browse files Browse the repository at this point in the history
* test: added test cases for new sidebar

* test: added factory for verified user

* refactor: updated description for notification widget
  • Loading branch information
sundasnoreen12 authored Jan 11, 2024
1 parent 59a68af commit 4928f50
Show file tree
Hide file tree
Showing 7 changed files with 148 additions and 40 deletions.
34 changes: 6 additions & 28 deletions src/courseware/course/Course.test.jsx
Original file line number Diff line number Diff line change
@@ -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', () => ({
Expand Down Expand Up @@ -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(<Course {...mockData} />, { store: testStore, wrapWithRouter: true });
};

it('loads learning sequence', async () => {
render(<Course {...mockData} />, { wrapWithRouter: true });
expect(screen.getByRole('navigation', { name: 'breadcrumb' })).toBeInTheDocument();
Expand Down Expand Up @@ -183,7 +161,7 @@ describe('Course', () => {
});

it('handles click to open/close notification tray', async () => {
render(<Course {...mockData} />, { 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);
Expand Down
3 changes: 2 additions & 1 deletion src/courseware/course/new-sidebar/common/SidebarBase.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -103,6 +103,7 @@ SidebarBase.propTypes = {
};

SidebarBase.defaultProps = {
title: '',
width: '50rem',
allowFullHeight: false,
showTitleBar: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -51,24 +52,29 @@ describe('DiscussionsWidget', () => {
await executeThunk(getCourseDiscussionTopics(courseId), store.dispatch);
});

function renderWithProvider(testData = {}) {
function renderWithProvider(Component, testData = {}) {
const { container } = render(
<SidebarContext.Provider value={{ ...mockData, ...testData }}>
<DiscussionsWidget />
<Component />
</SidebarContext.Provider>,
);
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();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ const NotificationsWidget = () => {
if (hideNotificationbar || !isNotificationbarAvailable) { return null; }

return (
<div className="border border-light-400 rounded-sm">
<div className="border border-light-400 rounded-sm" data-testid="notification-widget">
<UpgradeNotification
offer={offer}
verifiedMode={verifiedMode}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,19 @@ import React from 'react';
import MockAdapter from 'axios-mock-adapter';
import { Factory } from 'rosie';

import { getConfig } from '@edx/frontend-platform';
import { mergeConfig, getConfig } from '@edx/frontend-platform';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { breakpoints } from '@edx/paragon';

import { initializeMockApp, render, screen } from '../../../../../../setupTest';
import {
initializeMockApp, render, screen, within, act, fireEvent, waitFor,
} from '../../../../../../setupTest';
import initializeStore from '../../../../../../store';
import { appendBrowserTimezoneToUrl, executeThunk } from '../../../../../../utils';
import { fetchCourse } from '../../../../../data';
import SidebarContext from '../../../SidebarContext';
import NotificationsWidget from './NotificationsWidget';
import setupDiscussionSidebar from '../../../../test-utils';

initializeMockApp();
jest.mock('@edx/frontend-platform/analytics');
Expand All @@ -22,7 +25,6 @@ describe('NotificationsWidget', () => {
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}`;
Expand All @@ -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 () => {
Expand Down Expand Up @@ -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();
Expand Down
49 changes: 49 additions & 0 deletions src/courseware/course/test-utils.jsx
Original file line number Diff line number Diff line change
@@ -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(<Course {...mockData} />, { store: testStore, wrapWithRouter: true });
return wrapper;
};

export default setupDiscussionSidebar;
14 changes: 11 additions & 3 deletions src/courseware/data/__factories__/discussionTopics.factory.js
Original file line number Diff line number Diff line change
@@ -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')
Expand All @@ -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 }));
}

0 comments on commit 4928f50

Please sign in to comment.