From 76a27d8f7fa9243ac223131462e8695ec2f3dcfb Mon Sep 17 00:00:00 2001
From: Agrendalath
Date: Fri, 26 Jun 2020 04:23:08 +0200
Subject: [PATCH 01/12] [TNL-7268] WIP: Add high priority tests
---
jest.config.js | 2 +-
.../course/sequence/SequenceContent.test.jsx | 94 ++++
.../SequenceContent.test.jsx.snap | 271 +++++++++++
.../SequenceNavigation.test.jsx | 142 ++++++
.../SequenceNavigationDropdown.test.jsx | 79 ++++
.../SequenceNavigationTabs.test.jsx | 57 +++
.../sequence-navigation/UnitButton.test.jsx | 77 ++++
.../sequence-navigation/UnitIcon.test.jsx | 27 ++
.../UnitNavigation.test.jsx | 109 +++++
.../SequenceNavigation.test.jsx.snap | 244 ++++++++++
.../SequenceNavigationDropdown.test.jsx.snap | 26 ++
.../SequenceNavigationTabs.test.jsx.snap | 436 ++++++++++++++++++
.../__snapshots__/UnitButton.test.jsx.snap | 143 ++++++
.../__snapshots__/UnitIcon.test.jsx.snap | 61 +++
.../UnitNavigation.test.jsx.snap | 36 ++
src/setupTest.js | 1 +
.../@fortawesome/react-fontawesome.js | 32 ++
src/test/test-utils.js | 49 ++
18 files changed, 1885 insertions(+), 1 deletion(-)
create mode 100644 src/courseware/course/sequence/SequenceContent.test.jsx
create mode 100644 src/courseware/course/sequence/__snapshots__/SequenceContent.test.jsx.snap
create mode 100644 src/courseware/course/sequence/sequence-navigation/SequenceNavigation.test.jsx
create mode 100644 src/courseware/course/sequence/sequence-navigation/SequenceNavigationDropdown.test.jsx
create mode 100644 src/courseware/course/sequence/sequence-navigation/SequenceNavigationTabs.test.jsx
create mode 100644 src/courseware/course/sequence/sequence-navigation/UnitButton.test.jsx
create mode 100644 src/courseware/course/sequence/sequence-navigation/UnitIcon.test.jsx
create mode 100644 src/courseware/course/sequence/sequence-navigation/UnitNavigation.test.jsx
create mode 100644 src/courseware/course/sequence/sequence-navigation/__snapshots__/SequenceNavigation.test.jsx.snap
create mode 100644 src/courseware/course/sequence/sequence-navigation/__snapshots__/SequenceNavigationDropdown.test.jsx.snap
create mode 100644 src/courseware/course/sequence/sequence-navigation/__snapshots__/SequenceNavigationTabs.test.jsx.snap
create mode 100644 src/courseware/course/sequence/sequence-navigation/__snapshots__/UnitButton.test.jsx.snap
create mode 100644 src/courseware/course/sequence/sequence-navigation/__snapshots__/UnitIcon.test.jsx.snap
create mode 100644 src/courseware/course/sequence/sequence-navigation/__snapshots__/UnitNavigation.test.jsx.snap
create mode 100644 src/test/__mocks__/@fortawesome/react-fontawesome.js
create mode 100644 src/test/test-utils.js
diff --git a/jest.config.js b/jest.config.js
index 7c936b4c09..dc38c063ea 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -1,7 +1,7 @@
const { createConfig } = require('@edx/frontend-build');
module.exports = createConfig('jest', {
- setupFiles: [
+ setupFilesAfterEnv: [
'/src/setupTest.js',
],
coveragePathIgnorePatterns: [
diff --git a/src/courseware/course/sequence/SequenceContent.test.jsx b/src/courseware/course/sequence/SequenceContent.test.jsx
new file mode 100644
index 0000000000..f0bbf2e3b9
--- /dev/null
+++ b/src/courseware/course/sequence/SequenceContent.test.jsx
@@ -0,0 +1,94 @@
+import React from 'react';
+import { render, screen } from '../../../test/test-utils';
+import SequenceContent from './SequenceContent';
+
+describe('Sequence Content', () => {
+ window.scrollTo = jest.fn();
+ // HACK: Mock the MutationObserver as it's breaking async testing.
+ // According to StackOverflow it should be fixed in `jest-environment-jsdom` v16,
+ // but upgrading `jest` to v26 didn't fix this problem.
+ // ref: https://stackoverflow.com/questions/61036156/react-typescript-testing-typeerror-mutationobserver-is-not-a-constructor
+ global.MutationObserver = class {
+ // eslint-disable-next-line no-unused-vars,no-useless-constructor,no-empty-function
+ constructor(callback) {}
+
+ disconnect() {}
+
+ // eslint-disable-next-line no-unused-vars
+ observe(element, initObject) {}
+ };
+
+ const testUnits = [...Array(10).keys()].map(i => String(i + 1));
+ const initialState = {
+ courseware: {
+ sequenceStatus: 'loaded',
+ courseStatus: 'loaded',
+ courseId: '1',
+ },
+ models: {
+ courses: {
+ 1: {
+ sectionIds: ['1'],
+ },
+ },
+ sections: {
+ 1: {
+ sequenceIds: ['1', '2'],
+ },
+ },
+ sequences: {
+ 1: {
+ unitIds: testUnits,
+ showCompletion: true,
+ title: 'test-sequence',
+ gatedContent: {
+ prereqId: '1',
+ gatedSectionName: 'test-gated-section',
+ },
+ },
+ },
+ units: testUnits.reduce(
+ (acc, unitId) => Object.assign(acc, {
+ [unitId]: {
+ id: unitId,
+ contentType: 'other',
+ title: unitId,
+ },
+ }),
+ {},
+ ),
+ },
+ };
+
+ const mockData = {
+ gated: false,
+ courseId: '1',
+ sequenceId: '1',
+ unitId: '1',
+ unitLoadedHandler: () => {},
+ intl: {},
+ };
+
+ it('displays loading message', () => {
+ const { asFragment } = render( , { initialState });
+ expect(screen.getByText('Loading learning sequence...')).toBeInTheDocument();
+ expect(asFragment()).toMatchSnapshot();
+ });
+
+ it('displays messages for the locked content', async () => {
+ const { asFragment } = render( , { initialState });
+ expect(screen.getByText('Loading locked content messaging...')).toBeInTheDocument();
+ expect(asFragment()).toMatchSnapshot();
+
+ expect(await screen.findByText(/content locked/i)).toBeInTheDocument();
+ expect(screen.getByText('test-sequence')).toBeInTheDocument();
+ expect(screen.queryByText('Loading locked content messaging...')).not.toBeInTheDocument();
+ expect(asFragment()).toMatchSnapshot();
+ });
+
+ it('displays message for no content', () => {
+ const { asFragment } = render( , { initialState });
+ expect(screen.getByText('There is no content here.')).toBeInTheDocument();
+ expect(asFragment()).toMatchSnapshot();
+ });
+});
diff --git a/src/courseware/course/sequence/__snapshots__/SequenceContent.test.jsx.snap b/src/courseware/course/sequence/__snapshots__/SequenceContent.test.jsx.snap
new file mode 100644
index 0000000000..a3d7a4b66f
--- /dev/null
+++ b/src/courseware/course/sequence/__snapshots__/SequenceContent.test.jsx.snap
@@ -0,0 +1,271 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Sequence Content displays loading message 1`] = `
+
+
+
+ 1
+
+
+
+
+
+
+
+ Bookmark this page
+
+
+
+
+
+
+
+ Loading learning sequence...
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`Sequence Content displays message for no content 1`] = `
+
+
+ There is no content here.
+
+
+`;
+
+exports[`Sequence Content displays messages for the locked content 1`] = `
+
+
+
+
+
+ Loading locked content messaging...
+
+
+
+
+
+`;
+
+exports[`Sequence Content displays messages for the locked content 2`] = `
+
+
+
+ test-sequence
+
+
+ Content Locked
+
+
+ You must complete the prerequisite: 'test-gated-section' to access this content.
+
+
+
+ Go To Prerequisite Section
+
+
+
+`;
+
+exports[`Unit Navigation displays loading message 1`] = `
+
+
+
+ 1
+
+
+
+
+
+
+
+ Bookmark this page
+
+
+
+
+
+
+
+ Loading learning sequence...
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`Unit Navigation displays message for no content 1`] = `
+
+
+ There is no content here.
+
+
+`;
+
+exports[`Unit Navigation displays message for the locked content 1`] = `
+
+
+
+
+
+ Loading locked content messaging...
+
+
+
+
+
+`;
+
+exports[`Unit Navigation displays messages for the locked content 1`] = `
+
+
+
+
+
+ Loading locked content messaging...
+
+
+
+
+
+`;
+
+exports[`Unit Navigation displays messages for the locked content 2`] = `
+
+
+
+ test-sequence
+
+
+ Content Locked
+
+
+ You must complete the prerequisite: 'test-gated-section' to access this content.
+
+
+
+ Go To Prerequisite Section
+
+
+
+`;
diff --git a/src/courseware/course/sequence/sequence-navigation/SequenceNavigation.test.jsx b/src/courseware/course/sequence/sequence-navigation/SequenceNavigation.test.jsx
new file mode 100644
index 0000000000..d78b3241f9
--- /dev/null
+++ b/src/courseware/course/sequence/sequence-navigation/SequenceNavigation.test.jsx
@@ -0,0 +1,142 @@
+import React from 'react';
+// eslint-disable-next-line import/no-extraneous-dependencies
+import { cloneDeep } from 'lodash';
+import { fireEvent } from '@testing-library/dom';
+import { render, screen } from '../../../../test/test-utils';
+import SequenceNavigation from './SequenceNavigation';
+import useIndexOfLastVisibleChild from '../../../../tabs/useIndexOfLastVisibleChild';
+
+// Mock the hook to avoid relying on its implementation and mocking `getBoundingClientRect`.
+jest.mock('../../../../tabs/useIndexOfLastVisibleChild');
+useIndexOfLastVisibleChild.mockReturnValue([0, null, null]);
+
+describe('Sequence Navigation', () => {
+ const testUnits = [...Array(10).keys()].map(i => String(i + 1));
+ const initialState = {
+ courseware: {
+ sequenceStatus: 'loaded',
+ courseStatus: 'loaded',
+ courseId: '1',
+ },
+ models: {
+ courses: {
+ 1: {
+ sectionIds: ['1'],
+ },
+ },
+ sections: {
+ 1: {
+ sequenceIds: ['1', '2'],
+ },
+ },
+ sequences: {
+ 1: {
+ unitIds: testUnits,
+ showCompletion: true,
+ },
+ 2: {
+ unitIds: testUnits,
+ showCompletion: true,
+ },
+ },
+ units: testUnits.reduce(
+ (acc, unitId) => Object.assign(acc, {
+ [unitId]: {
+ contentType: 'other',
+ title: unitId,
+ },
+ }),
+ {},
+ ),
+ },
+ };
+
+ const mockData = {
+ previousSequenceHandler: () => {},
+ onNavigate: () => {},
+ nextSequenceHandler: () => {},
+ sequenceId: '1',
+ unitId: '3',
+ };
+
+ it('is empty while loading', () => {
+ // Clone initialState.
+ const testState = cloneDeep(initialState);
+ testState.courseware.sequenceStatus = 'loading';
+
+ const { asFragment } = render(
+ ,
+ { initialState: testState },
+ );
+ expect(asFragment()).toMatchSnapshot();
+ });
+
+ it('renders empty div without unitId', () => {
+ const { asFragment } = render( , { initialState });
+ expect(asFragment()).toMatchSnapshot();
+ });
+
+ it('renders locked button for gated content', () => {
+ // TODO: Not sure if this is working as expected, because the `contentType="lock"` will be overridden by the value
+ // from Redux. To make this provide a `fa-icon` lock we could introduce something like `overriddenContentType`.
+ const testState = cloneDeep(initialState);
+ testState.models.sequences['1'].gatedContent = { gated: true };
+
+ const { asFragment } = render(
+ ,
+ { initialState: testState },
+ );
+ expect(asFragment()).toMatchSnapshot();
+ });
+
+ it('renders correctly', () => {
+ const { asFragment } = render( , { initialState });
+ expect(asFragment()).toMatchSnapshot();
+ });
+
+ it('has both navigation buttons enabled for a non-corner unit of the sequence', () => {
+ render( , { initialState });
+
+ screen.getAllByRole('button', { name: /previous|next/i }).forEach(button => {
+ expect(button).toBeEnabled();
+ });
+ });
+
+ it('has the "Previous" button disabled for the first unit of the sequence', () => {
+ render( , { initialState });
+
+ expect(screen.getByRole('button', { name: /previous/i })).toBeDisabled();
+ expect(screen.getByRole('button', { name: /next/i })).toBeEnabled();
+ });
+
+ it('has the "Next" button disabled for the last unit of the sequence', () => {
+ render( , { initialState });
+
+ expect(screen.getByRole('button', { name: /previous/i })).toBeEnabled();
+ expect(screen.getByRole('button', { name: /next/i })).toBeDisabled();
+ });
+
+ it('handles "Previous" and "Next" click', () => {
+ const previousSequenceHandler = jest.fn();
+ const nextSequenceHandler = jest.fn();
+ render( , { initialState });
+
+ fireEvent.click(screen.getByRole('button', { name: /previous/i }));
+ expect(previousSequenceHandler).toHaveBeenCalledTimes(1);
+
+ fireEvent.click(screen.getByRole('button', { name: /next/i }));
+ expect(nextSequenceHandler).toHaveBeenCalledTimes(1);
+ });
+});
diff --git a/src/courseware/course/sequence/sequence-navigation/SequenceNavigationDropdown.test.jsx b/src/courseware/course/sequence/sequence-navigation/SequenceNavigationDropdown.test.jsx
new file mode 100644
index 0000000000..0b6a0a17b4
--- /dev/null
+++ b/src/courseware/course/sequence/sequence-navigation/SequenceNavigationDropdown.test.jsx
@@ -0,0 +1,79 @@
+import React from 'react';
+import { fireEvent } from '@testing-library/dom';
+import SequenceNavigationDropdown from './SequenceNavigationDropdown';
+import { render, screen } from '../../../../test/test-utils';
+
+describe('Sequence Navigation Dropdown', () => {
+ const testUnits = ['1', '2', '3'];
+
+ const initialState = {
+ models: {
+ units: testUnits.reduce(
+ (acc, unitId) => Object.assign(acc, {
+ [unitId]: {
+ contentType: 'other',
+ title: unitId,
+ },
+ }),
+ {},
+ ),
+ },
+ };
+
+ const mockData = {
+ unitId: '1',
+ onNavigate: () => {},
+ showCompletion: false,
+ unitIds: testUnits,
+ };
+
+ it('renders correctly without units', () => {
+ const { asFragment } = render( );
+
+ expect(asFragment()).toMatchSnapshot();
+ });
+
+ testUnits.forEach(unitId => {
+ it(`displays proper text for unit ${unitId} on mobile`, () => {
+ render( , { initialState });
+
+ expect(screen.getByRole('button')).toHaveTextContent(`${unitId} of ${testUnits.length}`);
+ });
+ });
+
+ testUnits.forEach(unitId => {
+ it(`marks unit ${unitId} as active`, () => {
+ render( , { initialState });
+
+ // Only the current unit should be marked as active.
+ screen.getAllByText(/^\d$/).forEach(element => {
+ if (element.textContent === unitId) {
+ expect(element.parentElement).toHaveClass('active');
+ } else {
+ expect(element.parentElement).not.toHaveClass('active');
+ }
+ });
+ });
+ });
+
+ it('handles the clicks', () => {
+ const onNavigate = jest.fn();
+
+ render( , { initialState });
+
+ screen.getAllByText(/^\d$/).forEach(element => fireEvent.click(element));
+ expect(onNavigate).toHaveBeenCalledTimes(testUnits.length);
+ });
+});
diff --git a/src/courseware/course/sequence/sequence-navigation/SequenceNavigationTabs.test.jsx b/src/courseware/course/sequence/sequence-navigation/SequenceNavigationTabs.test.jsx
new file mode 100644
index 0000000000..aea300359f
--- /dev/null
+++ b/src/courseware/course/sequence/sequence-navigation/SequenceNavigationTabs.test.jsx
@@ -0,0 +1,57 @@
+import React from 'react';
+import { render, screen } from '../../../../test/test-utils';
+import SequenceNavigationTabs from './SequenceNavigationTabs';
+import useIndexOfLastVisibleChild from '../../../../tabs/useIndexOfLastVisibleChild';
+
+// Mock the hook to avoid relying on its implementation and mocking `getBoundingClientRect`.
+jest.mock('../../../../tabs/useIndexOfLastVisibleChild');
+
+describe('Sequence Navigation Tabs', () => {
+ const testUnits = [...Array(10).keys()].map(i => String(i + 1));
+
+ const initialState = {
+ models: {
+ units: testUnits.reduce(
+ (acc, unitId) => Object.assign(acc, {
+ [unitId]: {
+ contentType: 'other',
+ title: unitId,
+ },
+ }),
+ {},
+ ),
+ },
+ };
+
+ const mockData = {
+ unitId: '1',
+ onNavigate: () => {
+ },
+ showCompletion: false,
+ unitIds: testUnits,
+ };
+
+ it('renders correctly without dropdown', () => {
+ useIndexOfLastVisibleChild.mockReturnValue([0, null, null]);
+ const { asFragment } = render( , { initialState });
+ expect(asFragment()).toMatchSnapshot();
+ });
+
+ it('renders correctly with dropdown', () => {
+ useIndexOfLastVisibleChild.mockReturnValue([-1, null, null]);
+ const { asFragment } = render( , { initialState });
+ expect(asFragment()).toMatchSnapshot();
+ });
+
+ it('renders unit buttons', () => {
+ useIndexOfLastVisibleChild.mockReturnValue([0, null, null]);
+ render( , { initialState });
+ expect(screen.getAllByRole('button').length).toEqual(testUnits.length);
+ });
+
+ it('renders unit buttons and dropdown button', () => {
+ useIndexOfLastVisibleChild.mockReturnValue([-1, null, null]);
+ render( , { initialState });
+ expect(screen.getAllByRole('button').length).toEqual(testUnits.length + 1);
+ });
+});
diff --git a/src/courseware/course/sequence/sequence-navigation/UnitButton.test.jsx b/src/courseware/course/sequence/sequence-navigation/UnitButton.test.jsx
new file mode 100644
index 0000000000..6851db54e6
--- /dev/null
+++ b/src/courseware/course/sequence/sequence-navigation/UnitButton.test.jsx
@@ -0,0 +1,77 @@
+import React from 'react';
+import { fireEvent } from '@testing-library/dom';
+import { render, screen } from '../../../../test/test-utils';
+import UnitButton from './UnitButton';
+
+describe('Unit Button', () => {
+ const initialState = {
+ models: {
+ units: {
+ other: {
+ contentType: 'other',
+ title: 'other-unit',
+ },
+ problem: {
+ contentType: 'problem',
+ title: 'problem-unit',
+ complete: true,
+ bookmarked: true,
+ },
+ },
+ },
+ };
+
+ const mockData = {
+ unitId: 'other',
+ onClick: () => {},
+ };
+
+ it('hides title by default', () => {
+ const { asFragment } = render( , { initialState });
+ expect(screen.getByTestId('icon')).toBeEmptyDOMElement();
+ expect(asFragment()).toMatchSnapshot();
+ });
+
+ it('shows title', () => {
+ const { asFragment } = render( , { initialState });
+ expect(screen.getByRole('button')).toHaveTextContent('other-unit');
+ expect(asFragment()).toMatchSnapshot();
+ });
+
+ it('does not show completion for non-completed unit', () => {
+ const { asFragment } = render( , { initialState });
+ expect(screen.queryByAltText('fa-check')).toBeNull();
+ expect(asFragment()).toMatchSnapshot();
+ });
+
+ it('shows completion for completed unit', () => {
+ const { asFragment } = render( , { initialState });
+ expect(screen.getByAltText('fa-check')).toBeInTheDocument();
+ expect(asFragment()).toMatchSnapshot();
+ });
+
+ it('hides completion', () => {
+ const { asFragment } = render( , { initialState });
+ expect(screen.queryByAltText('fa-check')).toBeNull();
+ expect(asFragment()).toMatchSnapshot();
+ });
+
+ it('does not show bookmark', () => {
+ const { asFragment } = render( , { initialState });
+ expect(screen.queryByAltText('fa-bookmark')).toBeNull();
+ expect(asFragment()).toMatchSnapshot();
+ });
+
+ it('shows bookmark', () => {
+ const { asFragment } = render( , { initialState });
+ expect(screen.getByAltText('fa-bookmark')).toBeInTheDocument();
+ expect(asFragment()).toMatchSnapshot();
+ });
+
+ it('handles the click', () => {
+ const onClick = jest.fn();
+ render( , { initialState });
+ fireEvent.click(screen.getByRole('button'));
+ expect(onClick).toHaveBeenCalledTimes(1);
+ });
+});
diff --git a/src/courseware/course/sequence/sequence-navigation/UnitIcon.test.jsx b/src/courseware/course/sequence/sequence-navigation/UnitIcon.test.jsx
new file mode 100644
index 0000000000..51f942c342
--- /dev/null
+++ b/src/courseware/course/sequence/sequence-navigation/UnitIcon.test.jsx
@@ -0,0 +1,27 @@
+import React from 'react';
+import { render, screen } from '../../../../test/test-utils';
+import UnitIcon from './UnitIcon';
+
+describe('Unit Icon', () => {
+ const types = {
+ video: 'fa-film',
+ other: 'fa-book',
+ vertical: 'fa-tasks',
+ problem: 'fa-edit',
+ lock: 'fa-lock',
+ undefined: 'fa-book',
+ };
+
+ Object.entries(types).forEach(([key, value]) => {
+ it(`renders correct icon for ${key} unit`, () => {
+ // Suppress warning for undefined prop type.
+ if (key === 'undefined') {
+ jest.spyOn(console, 'error').mockImplementation(() => {});
+ }
+
+ const { asFragment } = render( );
+ expect(screen.getByAltText(value)).toBeInTheDocument();
+ expect(asFragment()).toMatchSnapshot();
+ });
+ });
+});
diff --git a/src/courseware/course/sequence/sequence-navigation/UnitNavigation.test.jsx b/src/courseware/course/sequence/sequence-navigation/UnitNavigation.test.jsx
new file mode 100644
index 0000000000..7c8c038da5
--- /dev/null
+++ b/src/courseware/course/sequence/sequence-navigation/UnitNavigation.test.jsx
@@ -0,0 +1,109 @@
+import React from 'react';
+import { fireEvent } from '@testing-library/dom';
+import { render, screen } from '../../../../test/test-utils';
+import UnitNavigation from './UnitNavigation';
+
+describe('Unit Navigation', () => {
+ const testUnits = [...Array(10).keys()].map(i => String(i + 1));
+ const initialState = {
+ courseware: {
+ sequenceStatus: 'loaded',
+ courseStatus: 'loaded',
+ courseId: '1',
+ },
+ models: {
+ courses: {
+ 1: {
+ sectionIds: ['1'],
+ },
+ },
+ sections: {
+ 1: {
+ sequenceIds: ['1', '2'],
+ },
+ },
+ sequences: {
+ 1: {
+ unitIds: testUnits,
+ showCompletion: true,
+ },
+ 2: {
+ unitIds: testUnits,
+ showCompletion: true,
+ },
+ },
+ units: testUnits.reduce(
+ (acc, unitId) => Object.assign(acc, {
+ [unitId]: {
+ contentType: 'other',
+ title: unitId,
+ },
+ }),
+ {},
+ ),
+ },
+ };
+ const mockData = {
+ sequenceId: '1',
+ unitId: '2',
+ onClickPrevious: () => {},
+ onClickNext: () => {},
+ };
+
+ it('renders correctly without units', () => {
+ const { asFragment } = render( {}}
+ onClickNext={() => {}}
+ />);
+
+ expect(asFragment()).toMatchSnapshot();
+ });
+
+ it('handles the clicks', () => {
+ const onClickPrevious = jest.fn();
+ const onClickNext = jest.fn();
+
+ render( );
+
+ fireEvent.click(screen.getByRole('button', { name: /previous/i }));
+ expect(onClickPrevious).toHaveBeenCalledTimes(1);
+
+ fireEvent.click(screen.getByRole('button', { name: /next/i }));
+ expect(onClickNext).toHaveBeenCalledTimes(1);
+ });
+
+ it('should have the navigation buttons enabled for the non-corner unit in the sequence', () => {
+ render( , { initialState });
+ screen.getAllByRole('button').forEach(button => {
+ expect(button).toBeEnabled();
+ });
+ });
+
+ it('should have the "Previous" button disabled for the first unit in the sequence', () => {
+ render( , { initialState });
+ expect(screen.getByRole('button', { name: /previous/i })).toBeDisabled();
+ expect(screen.getByRole('button', { name: /next/i })).toBeEnabled();
+ });
+
+ it('should display "learn.end.of.course" message instead of the "Next" button for the last unit in the sequence', () => {
+ render(
+ , { initialState },
+ );
+ expect(screen.getByRole('button', { name: /previous/i })).toBeEnabled();
+ expect(screen.queryByRole('button', { name: /next/i })).not.toBeInTheDocument();
+ expect(screen.getByText("You've reached the end of this course!")).toBeInTheDocument();
+ });
+});
diff --git a/src/courseware/course/sequence/sequence-navigation/__snapshots__/SequenceNavigation.test.jsx.snap b/src/courseware/course/sequence/sequence-navigation/__snapshots__/SequenceNavigation.test.jsx.snap
new file mode 100644
index 0000000000..aa4cc9c5de
--- /dev/null
+++ b/src/courseware/course/sequence/sequence-navigation/__snapshots__/SequenceNavigation.test.jsx.snap
@@ -0,0 +1,244 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Sequence Navigation is empty while loading 1`] = ` `;
+
+exports[`Sequence Navigation renders correctly 1`] = `
+
+
+
+
+
+ Previous
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Next
+
+
+
+
+
+`;
+
+exports[`Sequence Navigation renders empty div without unitId 1`] = `
+
+
+
+
+
+ Previous
+
+
+
+
+
+ Next
+
+
+
+
+
+`;
+
+exports[`Sequence Navigation renders locked button for gated content 1`] = `
+
+
+
+
+
+ Previous
+
+
+
+
+
+
+
+ Next
+
+
+
+
+
+`;
diff --git a/src/courseware/course/sequence/sequence-navigation/__snapshots__/SequenceNavigationDropdown.test.jsx.snap b/src/courseware/course/sequence/sequence-navigation/__snapshots__/SequenceNavigationDropdown.test.jsx.snap
new file mode 100644
index 0000000000..1204f276ef
--- /dev/null
+++ b/src/courseware/course/sequence/sequence-navigation/__snapshots__/SequenceNavigationDropdown.test.jsx.snap
@@ -0,0 +1,26 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Sequence Navigation Dropdown renders correctly without units 1`] = `
+
+
+
+
+ 0 of 0
+
+
+
+
+
+`;
diff --git a/src/courseware/course/sequence/sequence-navigation/__snapshots__/SequenceNavigationTabs.test.jsx.snap b/src/courseware/course/sequence/sequence-navigation/__snapshots__/SequenceNavigationTabs.test.jsx.snap
new file mode 100644
index 0000000000..65de0dc31e
--- /dev/null
+++ b/src/courseware/course/sequence/sequence-navigation/__snapshots__/SequenceNavigationTabs.test.jsx.snap
@@ -0,0 +1,436 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Sequence Navigation Tabs renders correctly with dropdown 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1 of 10
+
+
+
+
+
+
+`;
+
+exports[`Sequence Navigation Tabs renders correctly without dropdown 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
diff --git a/src/courseware/course/sequence/sequence-navigation/__snapshots__/UnitButton.test.jsx.snap b/src/courseware/course/sequence/sequence-navigation/__snapshots__/UnitButton.test.jsx.snap
new file mode 100644
index 0000000000..eb1efa6f74
--- /dev/null
+++ b/src/courseware/course/sequence/sequence-navigation/__snapshots__/UnitButton.test.jsx.snap
@@ -0,0 +1,143 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Unit Button does not show bookmark 1`] = `
+
+
+
+
+
+`;
+
+exports[`Unit Button does not show completion for non-completed unit 1`] = `
+
+
+
+
+
+`;
+
+exports[`Unit Button hides completion 1`] = `
+
+
+
+
+
+
+`;
+
+exports[`Unit Button hides title by default 1`] = `
+
+
+
+
+
+`;
+
+exports[`Unit Button shows bookmark 1`] = `
+
+
+
+
+
+
+
+`;
+
+exports[`Unit Button shows completion for completed unit 1`] = `
+
+
+
+
+
+
+
+`;
+
+exports[`Unit Button shows title 1`] = `
+
+
+
+
+ other-unit
+
+
+
+`;
diff --git a/src/courseware/course/sequence/sequence-navigation/__snapshots__/UnitIcon.test.jsx.snap b/src/courseware/course/sequence/sequence-navigation/__snapshots__/UnitIcon.test.jsx.snap
new file mode 100644
index 0000000000..31ed98ba48
--- /dev/null
+++ b/src/courseware/course/sequence/sequence-navigation/__snapshots__/UnitIcon.test.jsx.snap
@@ -0,0 +1,61 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Unit Icon renders correct icon for lock unit 1`] = `
+
+
+
+`;
+
+exports[`Unit Icon renders correct icon for other unit 1`] = `
+
+
+
+`;
+
+exports[`Unit Icon renders correct icon for problem unit 1`] = `
+
+
+
+`;
+
+exports[`Unit Icon renders correct icon for undefined unit 1`] = `
+
+
+
+`;
+
+exports[`Unit Icon renders correct icon for vertical unit 1`] = `
+
+
+
+`;
+
+exports[`Unit Icon renders correct icon for video unit 1`] = `
+
+
+
+`;
diff --git a/src/courseware/course/sequence/sequence-navigation/__snapshots__/UnitNavigation.test.jsx.snap b/src/courseware/course/sequence/sequence-navigation/__snapshots__/UnitNavigation.test.jsx.snap
new file mode 100644
index 0000000000..c15047dafa
--- /dev/null
+++ b/src/courseware/course/sequence/sequence-navigation/__snapshots__/UnitNavigation.test.jsx.snap
@@ -0,0 +1,36 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Unit Navigation renders correctly without units 1`] = `
+
+
+
+
+
+ Previous
+
+
+
+
+ Next
+
+
+
+
+
+`;
diff --git a/src/setupTest.js b/src/setupTest.js
index c0dadbd66d..76f28b438b 100755
--- a/src/setupTest.js
+++ b/src/setupTest.js
@@ -1,5 +1,6 @@
import 'core-js/stable';
import 'regenerator-runtime/runtime';
+import '@testing-library/jest-dom';
import { getConfig, mergeConfig } from '@edx/frontend-platform';
import { configure as configureI18n } from '@edx/frontend-platform/i18n';
import { configure as configureLogging } from '@edx/frontend-platform/logging';
diff --git a/src/test/__mocks__/@fortawesome/react-fontawesome.js b/src/test/__mocks__/@fortawesome/react-fontawesome.js
new file mode 100644
index 0000000000..f574d7bf99
--- /dev/null
+++ b/src/test/__mocks__/@fortawesome/react-fontawesome.js
@@ -0,0 +1,32 @@
+/**
+ * Mocks `@fortawesome/react-fontawesome.js` to return a simple element containing `data-testid` attribute.
+ * This way we can check whether the icon matches without relying on its internal implementation
+ * and avoid storing its content in the snapshot tests.
+ */
+import React from 'react';
+import PropTypes from 'prop-types';
+
+// eslint-disable-next-line no-use-before-define
+FontAwesomeIcon.propTypes = {
+ icon: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.shape({
+ icon: PropTypes.arrayOf(PropTypes.any),
+ }),
+ ]).isRequired,
+};
+
+// eslint-disable-next-line import/prefer-default-export
+export function FontAwesomeIcon(props) {
+ const { icon } = props;
+
+ let iconName;
+ if (typeof icon === 'string') {
+ iconName = icon;
+ } else {
+ iconName = `fa-${icon.iconName}`;
+ }
+
+ // eslint-disable-next-line react/jsx-filename-extension
+ return ;
+}
diff --git a/src/test/test-utils.js b/src/test/test-utils.js
new file mode 100644
index 0000000000..d1567675d3
--- /dev/null
+++ b/src/test/test-utils.js
@@ -0,0 +1,49 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+// eslint-disable-next-line import/no-extraneous-dependencies
+import { render as rtlRender, screen } from '@testing-library/react';
+import { Provider } from 'react-redux';
+import { configureStore } from '@reduxjs/toolkit';
+import { IntlProvider } from '@edx/frontend-platform/node_modules/react-intl';
+import { reducer as modelsReducer } from '../model-store';
+import { reducer as coursewareReducer } from '../data';
+
+
+function render(
+ ui,
+ {
+ initialState = {},
+ store = configureStore({
+ reducer: {
+ models: modelsReducer,
+ courseware: coursewareReducer,
+ },
+ preloadedState: initialState,
+ }),
+ ...renderOptions
+ } = {},
+) {
+ function Wrapper({ children }) {
+ return (
+ // eslint-disable-next-line react/jsx-filename-extension
+
+
+ {children}
+
+
+ );
+ }
+
+ Wrapper.propTypes = {
+ children: PropTypes.node.isRequired,
+ };
+
+ return rtlRender(ui, { wrapper: Wrapper, ...renderOptions });
+}
+
+// re-export everything
+// eslint-disable-next-line import/no-extraneous-dependencies
+export * from '@testing-library/react';
+
+// override `render` method; export `screen` too to suppress errors
+export { render, screen };
From a1469c15431fc5cdd682c77a9697e6d8a59e1ce5 Mon Sep 17 00:00:00 2001
From: Agrendalath
Date: Mon, 29 Jun 2020 03:56:42 +0200
Subject: [PATCH 02/12] [TNL-7268] Add high priority tests
---
.../course/sequence/Sequence.test.jsx | 240 +++++++++
.../course/sequence/SequenceContent.test.jsx | 59 +--
src/courseware/course/sequence/Unit.test.jsx | 80 +++
.../__snapshots__/Sequence.test.jsx.snap | 475 ++++++++++++++++++
.../SequenceContent.test.jsx.snap | 146 ------
.../sequence/__snapshots__/Unit.test.jsx.snap | 203 ++++++++
.../SequenceNavigation.test.jsx | 44 +-
.../SequenceNavigationDropdown.test.jsx | 22 +-
.../SequenceNavigationTabs.test.jsx | 2 +-
.../sequence-navigation/UnitButton.test.jsx | 2 +-
.../sequence-navigation/UnitIcon.test.jsx | 2 +-
.../UnitNavigation.test.jsx | 43 +-
src/setupTest.js | 143 ++++++
src/test/test-utils.js | 49 --
14 files changed, 1155 insertions(+), 355 deletions(-)
create mode 100644 src/courseware/course/sequence/Sequence.test.jsx
create mode 100644 src/courseware/course/sequence/Unit.test.jsx
create mode 100644 src/courseware/course/sequence/__snapshots__/Sequence.test.jsx.snap
create mode 100644 src/courseware/course/sequence/__snapshots__/Unit.test.jsx.snap
delete mode 100644 src/test/test-utils.js
diff --git a/src/courseware/course/sequence/Sequence.test.jsx b/src/courseware/course/sequence/Sequence.test.jsx
new file mode 100644
index 0000000000..edc1f5281b
--- /dev/null
+++ b/src/courseware/course/sequence/Sequence.test.jsx
@@ -0,0 +1,240 @@
+import React from 'react';
+import { fireEvent, waitFor } from '@testing-library/dom';
+// eslint-disable-next-line import/no-extraneous-dependencies
+import { cloneDeep } from 'lodash';
+import { sendTrackEvent } from '@edx/frontend-platform/analytics';
+import {
+ initialState,
+ messageEvent, render, screen, testUnits,
+} from '../../../setupTest';
+import Sequence from './Sequence';
+
+jest.mock('@edx/frontend-platform/analytics');
+
+describe('Sequence', () => {
+ const mockData = {
+ unitId: '3',
+ sequenceId: '1',
+ courseId: '1',
+ unitNavigationHandler: () => {},
+ nextSequenceHandler: () => {},
+ previousSequenceHandler: () => {},
+ intl: {},
+ };
+
+ it('renders correctly without data', () => {
+ const { asFragment } = render(
+ , { initialState: {} },
+ );
+ expect(screen.getByText('Loading learning sequence...')).toBeInTheDocument();
+ expect(screen.queryByRole('button')).not.toBeInTheDocument();
+ expect(asFragment()).toMatchSnapshot();
+ });
+
+ it('renders correctly for gated content', async () => {
+ const { asFragment } = render( );
+ expect(screen.getByText('Loading locked content messaging...')).toBeInTheDocument();
+ // Only `Previous`, `Next` and `Bookmark` buttons.
+ expect(screen.getAllByRole('button').length).toEqual(3);
+
+ const beforeLoadingUnit = asFragment();
+ expect(beforeLoadingUnit).toMatchSnapshot();
+
+ window.postMessage(messageEvent, '*');
+ await waitFor(() => expect(screen.getByText(/You must complete the prerequisite/)).toBeInTheDocument());
+ expect(beforeLoadingUnit).toMatchDiffSnapshot(asFragment());
+ });
+
+ it('displays error message on sequence load failure', () => {
+ const testState = cloneDeep(initialState);
+ testState.courseware.sequenceStatus = 'failed';
+ const { asFragment } = render( , { initialState: testState });
+
+ expect(screen.getByText('There was an error loading this course.')).toBeInTheDocument();
+ expect(asFragment()).toMatchSnapshot();
+ });
+
+ it('handles loading unit', async () => {
+ const { asFragment } = render( );
+ expect(screen.getByText('Loading learning sequence...')).toBeInTheDocument();
+ // Renders navigation buttons plus one button for each unit.
+ expect(screen.getAllByRole('button').length).toEqual(3 + testUnits.length);
+
+ const beforeLoadingUnit = asFragment();
+ expect(beforeLoadingUnit).toMatchSnapshot();
+
+ window.postMessage(messageEvent, '*');
+ await waitFor(() => expect(screen.queryByText('Loading learning sequence...')).not.toBeInTheDocument());
+ // At this point there will be 2 `Previous` and 2 `Next` buttons.
+ expect(screen.getAllByRole('button', { name: /previous|next/i }).length).toEqual(4);
+ expect(beforeLoadingUnit).toMatchDiffSnapshot(asFragment());
+ });
+
+ it('navigates to the previous sequence if the unit is the first in the sequence', async () => {
+ sendTrackEvent.mockClear();
+ const unitId = '1';
+ const sequenceId = '2';
+ const previousSequenceHandler = jest.fn();
+ render( );
+
+ const sequencePreviousButton = screen.getByRole('button', { name: /previous/i });
+ fireEvent.click(sequencePreviousButton);
+ expect(previousSequenceHandler).toHaveBeenCalledTimes(1);
+ expect(sendTrackEvent).toHaveBeenCalledTimes(1);
+ expect(sendTrackEvent).toHaveBeenCalledWith('edx.ui.lms.sequence.previous_selected', {
+ current_tab: Number(unitId), id: unitId, tab_count: testUnits.length, widget_placement: 'top',
+ });
+
+ window.postMessage(messageEvent, '*');
+ await waitFor(() => expect(screen.queryByText('Loading learning sequence...')).not.toBeInTheDocument());
+ const unitPreviousButton = screen.getAllByRole('button', { name: /previous/i })
+ .filter(button => button !== sequencePreviousButton)[0];
+ fireEvent.click(unitPreviousButton);
+ expect(previousSequenceHandler).toHaveBeenCalledTimes(2);
+ expect(sendTrackEvent).toHaveBeenCalledTimes(2);
+ expect(sendTrackEvent).toHaveBeenNthCalledWith(2, 'edx.ui.lms.sequence.previous_selected', {
+ current_tab: Number(unitId), id: unitId, tab_count: testUnits.length, widget_placement: 'bottom',
+ });
+ });
+
+ it('navigates to the next sequence if the unit is the last in the sequence', async () => {
+ sendTrackEvent.mockClear();
+ const unitId = String(testUnits.length);
+ const sequenceId = '1';
+ const nextSequenceHandler = jest.fn();
+ render( );
+
+ const sequenceNextButton = screen.getByRole('button', { name: /next/i });
+ fireEvent.click(sequenceNextButton);
+ expect(nextSequenceHandler).toHaveBeenCalledTimes(1);
+ expect(sendTrackEvent).toHaveBeenCalledWith('edx.ui.lms.sequence.next_selected', {
+ current_tab: Number(unitId), id: unitId, tab_count: testUnits.length, widget_placement: 'top',
+ });
+
+ window.postMessage(messageEvent, '*');
+ await waitFor(() => expect(screen.queryByText('Loading learning sequence...')).not.toBeInTheDocument());
+ const unitNextButton = screen.getAllByRole('button', { name: /next/i })
+ .filter(button => button !== sequenceNextButton)[0];
+ fireEvent.click(unitNextButton);
+ expect(nextSequenceHandler).toHaveBeenCalledTimes(2);
+ expect(sendTrackEvent).toHaveBeenCalledTimes(2);
+ expect(sendTrackEvent).toHaveBeenNthCalledWith(2, 'edx.ui.lms.sequence.next_selected', {
+ current_tab: Number(unitId), id: unitId, tab_count: testUnits.length, widget_placement: 'bottom',
+ });
+ });
+
+ it('navigates to the previous/next unit if the unit is not in the corner of the sequence', () => {
+ sendTrackEvent.mockClear();
+ const unitNavigationHandler = jest.fn();
+ const previousSequenceHandler = jest.fn();
+ const nextSequenceHandler = jest.fn();
+ render( );
+
+ fireEvent.click(screen.getByRole('button', { name: /previous/i }));
+ expect(previousSequenceHandler).not.toHaveBeenCalled();
+ expect(unitNavigationHandler).toHaveBeenCalledWith(String(Number(mockData.unitId) - 1));
+
+ fireEvent.click(screen.getByRole('button', { name: /next/i }));
+ expect(nextSequenceHandler).not.toHaveBeenCalled();
+ expect(unitNavigationHandler).toHaveBeenNthCalledWith(2, String(Number(mockData.unitId) + 1));
+
+ expect(sendTrackEvent).toHaveBeenCalledTimes(2);
+ });
+
+ it('handles the `Previous` buttons for the first unit in the first sequence', async () => {
+ sendTrackEvent.mockClear();
+ const unitNavigationHandler = jest.fn();
+ const previousSequenceHandler = jest.fn();
+ const unitId = '1';
+ render( );
+ window.postMessage(messageEvent, '*');
+ await waitFor(() => expect(screen.queryByText('Loading learning sequence...')).not.toBeInTheDocument());
+
+ screen.getAllByRole('button', { name: /previous/i }).forEach(button => fireEvent.click(button));
+
+ expect(previousSequenceHandler).not.toHaveBeenCalled();
+ expect(unitNavigationHandler).not.toHaveBeenCalled();
+ expect(sendTrackEvent).not.toHaveBeenCalled();
+ });
+
+ it('handles the `Next` buttons for the last unit in the last sequence', async () => {
+ sendTrackEvent.mockClear();
+ const unitNavigationHandler = jest.fn();
+ const nextSequenceHandler = jest.fn();
+ const unitId = String(testUnits.length);
+ const sequenceId = String(Object.keys(initialState.models.sequences).length);
+ render( );
+ window.postMessage(messageEvent, '*');
+ await waitFor(() => expect(screen.queryByText('Loading learning sequence...')).not.toBeInTheDocument());
+
+ screen.getAllByRole('button', { name: /next/i }).forEach(button => fireEvent.click(button));
+
+ expect(nextSequenceHandler).toHaveBeenCalledTimes(1);
+ expect(unitNavigationHandler).not.toHaveBeenCalled();
+ expect(sendTrackEvent).toHaveBeenCalledWith('edx.ui.lms.sequence.next_selected', {
+ current_tab: Number(unitId), id: unitId, tab_count: testUnits.length, widget_placement: 'top',
+ });
+ });
+
+ it('handles the navigation buttons for empty sequence', async () => {
+ sendTrackEvent.mockClear();
+ const testState = cloneDeep(initialState);
+ testState.models.sequences['1'].unitIds = [];
+
+ const unitNavigationHandler = jest.fn();
+ const previousSequenceHandler = jest.fn();
+ const nextSequenceHandler = jest.fn();
+ render( , { initialState: testState });
+ window.postMessage(messageEvent, '*');
+ await waitFor(() => expect(screen.queryByText('Loading learning sequence...')).not.toBeInTheDocument());
+
+ screen.getAllByRole('button', { name: /previous/i }).forEach(button => fireEvent.click(button));
+ expect(previousSequenceHandler).toHaveBeenCalledTimes(2);
+ expect(unitNavigationHandler).not.toHaveBeenCalled();
+
+ screen.getAllByRole('button', { name: /next/i }).forEach(button => fireEvent.click(button));
+ expect(nextSequenceHandler).toHaveBeenCalledTimes(2);
+ expect(unitNavigationHandler).not.toHaveBeenCalled();
+
+ expect(sendTrackEvent).toHaveBeenNthCalledWith(1, 'edx.ui.lms.sequence.previous_selected', {
+ current_tab: 1, id: mockData.unitId, tab_count: 0, widget_placement: 'top',
+ });
+ expect(sendTrackEvent).toHaveBeenNthCalledWith(2, 'edx.ui.lms.sequence.previous_selected', {
+ current_tab: 1, id: mockData.unitId, tab_count: 0, widget_placement: 'bottom',
+ });
+ expect(sendTrackEvent).toHaveBeenNthCalledWith(3, 'edx.ui.lms.sequence.next_selected', {
+ current_tab: 1, id: mockData.unitId, tab_count: 0, widget_placement: 'top',
+ });
+ expect(sendTrackEvent).toHaveBeenNthCalledWith(4, 'edx.ui.lms.sequence.next_selected', {
+ current_tab: 1, id: mockData.unitId, tab_count: 0, widget_placement: 'bottom',
+ });
+ });
+
+ it('handles unit navigation button', () => {
+ sendTrackEvent.mockClear();
+ const unitNavigationHandler = jest.fn();
+ const targetUnit = '4';
+ render( );
+
+ fireEvent.click(screen.getByRole('button', { name: targetUnit }));
+ expect(unitNavigationHandler).toHaveBeenCalledWith(targetUnit);
+ expect(sendTrackEvent).toHaveBeenCalledWith('edx.ui.lms.sequence.tab_selected', {
+ current_tab: Number(mockData.unitId), id: mockData.unitId, target_tab: Number(targetUnit), tab_count: testUnits.length, widget_placement: 'top',
+ });
+ });
+});
diff --git a/src/courseware/course/sequence/SequenceContent.test.jsx b/src/courseware/course/sequence/SequenceContent.test.jsx
index f0bbf2e3b9..a0aa14c2e5 100644
--- a/src/courseware/course/sequence/SequenceContent.test.jsx
+++ b/src/courseware/course/sequence/SequenceContent.test.jsx
@@ -1,65 +1,8 @@
import React from 'react';
-import { render, screen } from '../../../test/test-utils';
+import { initialState, render, screen } from '../../../setupTest';
import SequenceContent from './SequenceContent';
describe('Sequence Content', () => {
- window.scrollTo = jest.fn();
- // HACK: Mock the MutationObserver as it's breaking async testing.
- // According to StackOverflow it should be fixed in `jest-environment-jsdom` v16,
- // but upgrading `jest` to v26 didn't fix this problem.
- // ref: https://stackoverflow.com/questions/61036156/react-typescript-testing-typeerror-mutationobserver-is-not-a-constructor
- global.MutationObserver = class {
- // eslint-disable-next-line no-unused-vars,no-useless-constructor,no-empty-function
- constructor(callback) {}
-
- disconnect() {}
-
- // eslint-disable-next-line no-unused-vars
- observe(element, initObject) {}
- };
-
- const testUnits = [...Array(10).keys()].map(i => String(i + 1));
- const initialState = {
- courseware: {
- sequenceStatus: 'loaded',
- courseStatus: 'loaded',
- courseId: '1',
- },
- models: {
- courses: {
- 1: {
- sectionIds: ['1'],
- },
- },
- sections: {
- 1: {
- sequenceIds: ['1', '2'],
- },
- },
- sequences: {
- 1: {
- unitIds: testUnits,
- showCompletion: true,
- title: 'test-sequence',
- gatedContent: {
- prereqId: '1',
- gatedSectionName: 'test-gated-section',
- },
- },
- },
- units: testUnits.reduce(
- (acc, unitId) => Object.assign(acc, {
- [unitId]: {
- id: unitId,
- contentType: 'other',
- title: unitId,
- },
- }),
- {},
- ),
- },
- };
-
const mockData = {
gated: false,
courseId: '1',
diff --git a/src/courseware/course/sequence/Unit.test.jsx b/src/courseware/course/sequence/Unit.test.jsx
new file mode 100644
index 0000000000..02ec4998ea
--- /dev/null
+++ b/src/courseware/course/sequence/Unit.test.jsx
@@ -0,0 +1,80 @@
+import React from 'react';
+// eslint-disable-next-line import/no-extraneous-dependencies
+import { cloneDeep } from 'lodash';
+import { waitFor } from '@testing-library/dom';
+import {
+ initialState, messageEvent, render, screen,
+} from '../../../setupTest';
+import Unit from './Unit';
+
+describe('Unit', () => {
+ const mockData = {
+ id: '3',
+ courseId: '1',
+ intl: {},
+ };
+
+ it('renders correctly', () => {
+ const { asFragment } = render( , { initialState });
+
+ expect(screen.getByText('Loading learning sequence...')).toBeInTheDocument();
+ expect(screen.getByTitle(mockData.id)).toHaveAttribute('height', String(0));
+ expect(asFragment()).toMatchSnapshot();
+ });
+
+ it('renders proper message for gated content', () => {
+ // Clone initialState.
+ const testState = cloneDeep(initialState);
+ testState.models.units[mockData.id].graded = true;
+ const { asFragment } = render( , { initialState: testState });
+
+ expect(screen.getByText('Loading locked content messaging...')).toBeInTheDocument();
+ expect(asFragment()).toMatchSnapshot();
+ });
+
+ it('handles receiving MessageEvent', async () => {
+ const { asFragment } = render( , { initialState });
+ const beforePostingMessage = asFragment();
+
+ window.postMessage(messageEvent, '*');
+ // Loading message is gone now.
+ await waitFor(() => expect(screen.queryByText('Loading learning sequence...')).not.toBeInTheDocument());
+ // Iframe's height is set via message.
+ expect(screen.getByTitle(mockData.id)).toHaveAttribute('height', String(messageEvent.payload.height));
+ expect(beforePostingMessage).toMatchDiffSnapshot(asFragment());
+ });
+
+ it('handles onLoaded after receiving MessageEvent', async () => {
+ const onLoaded = jest.fn();
+ render( , { initialState });
+
+ window.postMessage(messageEvent, '*');
+ await waitFor(() => expect(onLoaded).toHaveBeenCalledTimes(1));
+ });
+
+ it('resizes iframe on second MessageEvent, does not call onLoaded again', async () => {
+ const onLoaded = jest.fn();
+ // Clone message and set different height.
+ const testMessageWithOtherHeight = { ...messageEvent, payload: { height: 200 } };
+ render( , { initialState });
+
+ window.postMessage(messageEvent, '*');
+ await waitFor(() => expect(screen.getByTitle(mockData.id)).toHaveAttribute('height', String(messageEvent.payload.height)));
+ window.postMessage(testMessageWithOtherHeight, '*');
+ await waitFor(() => expect(screen.getByTitle(mockData.id)).toHaveAttribute('height', String(testMessageWithOtherHeight.payload.height)));
+ expect(onLoaded).toHaveBeenCalledTimes(1);
+ });
+
+ it('ignores MessageEvent with unhandled type', async () => {
+ // Clone message and set different type.
+ const testMessageWithUnhandledType = { ...messageEvent, type: 'wrong type' };
+ render( , { initialState });
+
+ window.postMessage(testMessageWithUnhandledType, '*');
+ // HACK: We don't have a function we could reliably await here, so this test relies on the timeout of `waitFor`.
+ await expect(waitFor(
+ () => expect(screen.getByTitle(mockData.id)).toHaveAttribute('height', String(testMessageWithUnhandledType.payload.height)),
+ { timeout: 100 },
+ )).rejects.toThrowErrorMatchingSnapshot();
+ });
+});
diff --git a/src/courseware/course/sequence/__snapshots__/Sequence.test.jsx.snap b/src/courseware/course/sequence/__snapshots__/Sequence.test.jsx.snap
new file mode 100644
index 0000000000..19ba71c7d9
--- /dev/null
+++ b/src/courseware/course/sequence/__snapshots__/Sequence.test.jsx.snap
@@ -0,0 +1,475 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Sequence displays error message on sequence load failure 1`] = `
+
+
+ There was an error loading this course.
+
+
+`;
+
+exports[`Sequence handles loading unit 1`] = `
+
+
+
+
+
+
+
+ Previous
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Next
+
+
+
+
+
+
+
+ 3
+
+
+
+
+
+
+
+ Bookmark this page
+
+
+
+
+
+
+
+ Loading learning sequence...
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`Sequence handles loading unit 2`] = `
+"Snapshot Diff:
+- First value
++ Second value
+
+@@ -190,40 +190,53 @@
+
+ Bookmark this page
+
+
+
+-
+-
+-
+-
+- Loading learning sequence...
+-
+-
+-
+-
+
+
+
++
++
++
++
++
++ Previous
++
++
++
++
++ Next
++
++
++
+
+
+
+
+ "
+`;
+
+exports[`Sequence renders correctly for gated content 1`] = `
+
+
+
+
+
+
+
+ Previous
+
+
+
+
+
+
+
+ Next
+
+
+
+
+
+
+
+
+
+ Loading locked content messaging...
+
+
+
+
+
+
+
+
+`;
+
+exports[`Sequence renders correctly for gated content 2`] = `
+"Snapshot Diff:
+- First value
++ Second value
+
+@@ -47,26 +47,31 @@
+
+
+
+-
+-
++
++ test-sequence-3
++
++
++ Content Locked
++
++
++ You must complete the prerequisite: 'test-gated-section' to access this content.
++
++
++
+-
+-
+- Loading locked content messaging...
+-
+-
+-
+-
++ Go To Prerequisite Section
++
++
+
+
+
+ "
+`;
+
+exports[`Sequence renders correctly without data 1`] = `
+
+
+
+
+
+ Loading learning sequence...
+
+
+
+
+
+`;
diff --git a/src/courseware/course/sequence/__snapshots__/SequenceContent.test.jsx.snap b/src/courseware/course/sequence/__snapshots__/SequenceContent.test.jsx.snap
index a3d7a4b66f..0018bd693e 100644
--- a/src/courseware/course/sequence/__snapshots__/SequenceContent.test.jsx.snap
+++ b/src/courseware/course/sequence/__snapshots__/SequenceContent.test.jsx.snap
@@ -123,149 +123,3 @@ exports[`Sequence Content displays messages for the locked content 2`] = `
`;
-
-exports[`Unit Navigation displays loading message 1`] = `
-
-
-
- 1
-
-
-
-
-
-
-
- Bookmark this page
-
-
-
-
-
-
-
- Loading learning sequence...
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Unit Navigation displays message for no content 1`] = `
-
-
- There is no content here.
-
-
-`;
-
-exports[`Unit Navigation displays message for the locked content 1`] = `
-
-
-
-
-
- Loading locked content messaging...
-
-
-
-
-
-`;
-
-exports[`Unit Navigation displays messages for the locked content 1`] = `
-
-
-
-
-
- Loading locked content messaging...
-
-
-
-
-
-`;
-
-exports[`Unit Navigation displays messages for the locked content 2`] = `
-
-
-
- test-sequence
-
-
- Content Locked
-
-
- You must complete the prerequisite: 'test-gated-section' to access this content.
-
-
-
- Go To Prerequisite Section
-
-
-
-`;
diff --git a/src/courseware/course/sequence/__snapshots__/Unit.test.jsx.snap b/src/courseware/course/sequence/__snapshots__/Unit.test.jsx.snap
new file mode 100644
index 0000000000..cf09688c66
--- /dev/null
+++ b/src/courseware/course/sequence/__snapshots__/Unit.test.jsx.snap
@@ -0,0 +1,203 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Unit handles receiving MessageEvent 1`] = `
+"Snapshot Diff:
+- First value
++ Second value
+
+@@ -28,33 +28,16 @@
+
+ Bookmark this page
+
+
+
+-
+
+-
+-
+- Loading learning sequence...
+-
+-
+-
+-
+-
-
-
- "
-`;
-
-exports[`Sequence renders correctly for gated content 1`] = `
-
-
-
-
-
-
-
- Previous
-
-
-
-
-
-
-
- Next
-
-
-
-
-
-
-
-
-
- Loading locked content messaging...
-
-
-
-
-
-
-
-
-`;
-
-exports[`Sequence renders correctly for gated content 2`] = `
-"Snapshot Diff:
-- First value
-+ Second value
-
-@@ -47,26 +47,31 @@
-
-
-
--
--
-+
-+ test-sequence-3
-+
-+
-+ Content Locked
-+
-+
-+ You must complete the prerequisite: 'test-gated-section' to access this content.
-+
-+
-+
--
--
-- Loading locked content messaging...
--
--
--
--
-+ Go To Prerequisite Section
-+
-+
-
-
-
- "
-`;
-
-exports[`Sequence renders correctly without data 1`] = `
-
-
-
-
-
- Loading learning sequence...
-
-
-
-
-
-`;
diff --git a/src/courseware/course/sequence/__snapshots__/SequenceContent.test.jsx.snap b/src/courseware/course/sequence/__snapshots__/SequenceContent.test.jsx.snap
deleted file mode 100644
index 0018bd693e..0000000000
--- a/src/courseware/course/sequence/__snapshots__/SequenceContent.test.jsx.snap
+++ /dev/null
@@ -1,125 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Sequence Content displays loading message 1`] = `
-
-
-
- 1
-
-
-
-
-
-
-
- Bookmark this page
-
-
-
-
-
-
-
- Loading learning sequence...
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Sequence Content displays message for no content 1`] = `
-
-
- There is no content here.
-
-
-`;
-
-exports[`Sequence Content displays messages for the locked content 1`] = `
-
-
-
-
-
- Loading locked content messaging...
-
-
-
-
-
-`;
-
-exports[`Sequence Content displays messages for the locked content 2`] = `
-
-
-
- test-sequence
-
-
- Content Locked
-
-
- You must complete the prerequisite: 'test-gated-section' to access this content.
-
-
-
- Go To Prerequisite Section
-
-
-
-`;
diff --git a/src/courseware/course/sequence/__snapshots__/Unit.test.jsx.snap b/src/courseware/course/sequence/__snapshots__/Unit.test.jsx.snap
deleted file mode 100644
index d166775048..0000000000
--- a/src/courseware/course/sequence/__snapshots__/Unit.test.jsx.snap
+++ /dev/null
@@ -1,194 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Unit handles receiving MessageEvent 1`] = `
-"Snapshot Diff:
-- First value
-+ Second value
-
-@@ -28,33 +28,16 @@
-
- Bookmark this page
-
-
-
--
-
--
--
-- Loading learning sequence...
--
--
--
--
--
-
-
-
- 3
-
-
-
-
-
-
-
- Bookmark this page
-
-
-
-
-
-
-
- Loading learning sequence...
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Unit renders proper message for gated content 1`] = `
-
-
-
- 3
-
-
-
-
-
-
-
- Bookmark this page
-
-
-
-
-
-
-
- Loading locked content messaging...
-
-
-
-
-
-
-
-
- Loading learning sequence...
-
-
-
-
-
-
-
-
-
-`;
diff --git a/src/courseware/course/sequence/sequence-navigation/SequenceNavigation.test.jsx b/src/courseware/course/sequence/sequence-navigation/SequenceNavigation.test.jsx
index 94c5726ce2..e52f48dc96 100644
--- a/src/courseware/course/sequence/sequence-navigation/SequenceNavigation.test.jsx
+++ b/src/courseware/course/sequence/sequence-navigation/SequenceNavigation.test.jsx
@@ -1,7 +1,7 @@
import React from 'react';
// eslint-disable-next-line import/no-extraneous-dependencies
import { cloneDeep } from 'lodash';
-import { fireEvent } from '@testing-library/dom';
+import { fireEvent, getByText } from '@testing-library/dom';
import {
initialState, render, screen, testUnits,
} from '../../../../setupTest';
@@ -26,40 +26,46 @@ describe('Sequence Navigation', () => {
const testState = cloneDeep(initialState);
testState.courseware.sequenceStatus = 'loading';
- const { asFragment } = render(
+ const { container } = render(
,
{ initialState: testState },
);
- expect(asFragment()).toMatchSnapshot();
+ expect(container).toBeEmptyDOMElement();
});
it('renders empty div without unitId', () => {
- const { asFragment } = render( , { initialState });
- expect(asFragment()).toMatchSnapshot();
+ const { container } = render( , { initialState });
+ expect(getByText(container, (content, element) => (
+ element.tagName.toLowerCase() === 'div' && element.getAttribute('style')))).toBeEmptyDOMElement();
});
it('renders locked button for gated content', () => {
- // TODO: Not sure if this is working as expected, because the `contentType="lock"` will be overridden by the value
- // from Redux. To make this provide a `fa-icon` lock we could introduce something like `overriddenContentType`.
const testState = cloneDeep(initialState);
testState.models.sequences['1'].gatedContent = { gated: true };
+ const onNavigate = jest.fn();
+ render( , { initialState: testState });
- const { asFragment } = render(
- ,
- { initialState: testState },
- );
- expect(asFragment()).toMatchSnapshot();
+ const unitButton = screen.getByTitle(mockData.unitId);
+ fireEvent.click(unitButton);
+ // The unit button should not work for gated content.
+ expect(onNavigate).not.toHaveBeenCalled();
+ // TODO: Not sure if this is working as expected, because the `contentType="lock"` will be overridden by the value
+ // from Redux. To make this provide a `fa-icon` lock we could introduce something like `overriddenContentType`.
+ expect(unitButton.firstChild).toHaveClass('fa-book');
});
- it('renders correctly', () => {
- const { asFragment } = render( , { initialState });
- expect(asFragment()).toMatchSnapshot();
+ it('renders correctly and handles unit button clicks', () => {
+ const onNavigate = jest.fn();
+ render( , { initialState });
+
+ const unitButtons = screen.getAllByRole('button', { name: /\d+/ });
+ expect(unitButtons).toHaveLength(testUnits.length);
+ unitButtons.forEach(button => fireEvent.click(button));
+ expect(onNavigate).toHaveBeenCalledTimes(unitButtons.length);
});
it('has both navigation buttons enabled for a non-corner unit of the sequence', () => {
- render( , { initialState });
+ render( , { initialState });
screen.getAllByRole('button', { name: /previous|next/i }).forEach(button => {
expect(button).toBeEnabled();
@@ -67,10 +73,7 @@ describe('Sequence Navigation', () => {
});
it('has the "Previous" button disabled for the first unit of the sequence', () => {
- render( , { initialState });
+ render( , { initialState });
expect(screen.getByRole('button', { name: /previous/i })).toBeDisabled();
expect(screen.getByRole('button', { name: /next/i })).toBeEnabled();
diff --git a/src/courseware/course/sequence/sequence-navigation/SequenceNavigationDropdown.test.jsx b/src/courseware/course/sequence/sequence-navigation/SequenceNavigationDropdown.test.jsx
index 68e33c457a..de7eb29d0f 100644
--- a/src/courseware/course/sequence/sequence-navigation/SequenceNavigationDropdown.test.jsx
+++ b/src/courseware/course/sequence/sequence-navigation/SequenceNavigationDropdown.test.jsx
@@ -14,31 +14,20 @@ describe('Sequence Navigation Dropdown', () => {
};
it('renders correctly without units', () => {
- const { asFragment } = render( );
-
- expect(asFragment()).toMatchSnapshot();
+ render( );
+ expect(screen.getByRole('button')).toHaveTextContent('0 of 0');
});
testUnits.forEach(unitId => {
it(`displays proper text for unit ${unitId} on mobile`, () => {
- render( , { initialState });
-
+ render( , { initialState });
expect(screen.getByRole('button')).toHaveTextContent(`${unitId} of ${testUnits.length}`);
});
});
testUnits.forEach(unitId => {
it(`marks unit ${unitId} as active`, () => {
- render( , { initialState });
+ render( , { initialState });
// Only the current unit should be marked as active.
screen.getAllByText(/^\d$/).forEach(element => {
@@ -53,11 +42,7 @@ describe('Sequence Navigation Dropdown', () => {
it('handles the clicks', () => {
const onNavigate = jest.fn();
-
- render( , { initialState });
+ render( , { initialState });
screen.getAllByText(/^\d+$/).forEach(element => fireEvent.click(element));
expect(onNavigate).toHaveBeenCalledTimes(testUnits.length);
diff --git a/src/courseware/course/sequence/sequence-navigation/SequenceNavigationTabs.test.jsx b/src/courseware/course/sequence/sequence-navigation/SequenceNavigationTabs.test.jsx
index 32cfb5c901..c047181b57 100644
--- a/src/courseware/course/sequence/sequence-navigation/SequenceNavigationTabs.test.jsx
+++ b/src/courseware/course/sequence/sequence-navigation/SequenceNavigationTabs.test.jsx
@@ -10,34 +10,26 @@ jest.mock('../../../../generic/tabs/useIndexOfLastVisibleChild');
describe('Sequence Navigation Tabs', () => {
const mockData = {
- unitId: '1',
+ unitId: '2',
onNavigate: () => {
},
showCompletion: false,
unitIds: testUnits,
};
- it('renders correctly without dropdown', () => {
- useIndexOfLastVisibleChild.mockReturnValue([0, null, null]);
- const { asFragment } = render( , { initialState });
- expect(asFragment()).toMatchSnapshot();
- });
-
- it('renders correctly with dropdown', () => {
- useIndexOfLastVisibleChild.mockReturnValue([-1, null, null]);
- const { asFragment } = render( , { initialState });
- expect(asFragment()).toMatchSnapshot();
- });
-
it('renders unit buttons', () => {
useIndexOfLastVisibleChild.mockReturnValue([0, null, null]);
render( , { initialState });
+
expect(screen.getAllByRole('button').length).toEqual(testUnits.length);
});
it('renders unit buttons and dropdown button', () => {
useIndexOfLastVisibleChild.mockReturnValue([-1, null, null]);
render( , { initialState });
+
+ expect(screen.getByRole('button', { name: `${mockData.unitId} of ${testUnits.length}` }))
+ .toHaveClass('dropdown-button');
expect(screen.getAllByRole('button').length).toEqual(testUnits.length + 1);
});
});
diff --git a/src/courseware/course/sequence/sequence-navigation/UnitButton.test.jsx b/src/courseware/course/sequence/sequence-navigation/UnitButton.test.jsx
index e75650b6dc..3f0b9d7c14 100644
--- a/src/courseware/course/sequence/sequence-navigation/UnitButton.test.jsx
+++ b/src/courseware/course/sequence/sequence-navigation/UnitButton.test.jsx
@@ -27,45 +27,38 @@ describe('Unit Button', () => {
};
it('hides title by default', () => {
- const { asFragment } = render( , { initialState });
- expect(screen.getByTestId('icon')).toBeEmptyDOMElement();
- expect(asFragment()).toMatchSnapshot();
+ render( , { initialState });
+ expect(screen.getByRole('button')).not.toHaveTextContent('other-unit');
});
it('shows title', () => {
- const { asFragment } = render( , { initialState });
+ render( , { initialState });
expect(screen.getByRole('button')).toHaveTextContent('other-unit');
- expect(asFragment()).toMatchSnapshot();
});
it('does not show completion for non-completed unit', () => {
- const { asFragment } = render( , { initialState });
+ render( , { initialState });
expect(screen.queryByAltText('fa-check')).toBeNull();
- expect(asFragment()).toMatchSnapshot();
});
it('shows completion for completed unit', () => {
- const { asFragment } = render( , { initialState });
+ render( , { initialState });
expect(screen.getByAltText('fa-check')).toBeInTheDocument();
- expect(asFragment()).toMatchSnapshot();
});
it('hides completion', () => {
- const { asFragment } = render( , { initialState });
+ render( , { initialState });
expect(screen.queryByAltText('fa-check')).toBeNull();
- expect(asFragment()).toMatchSnapshot();
});
it('does not show bookmark', () => {
- const { asFragment } = render( , { initialState });
+ render( , { initialState });
expect(screen.queryByAltText('fa-bookmark')).toBeNull();
- expect(asFragment()).toMatchSnapshot();
});
it('shows bookmark', () => {
- const { asFragment } = render( , { initialState });
+ render( , { initialState });
expect(screen.getByAltText('fa-bookmark')).toBeInTheDocument();
- expect(asFragment()).toMatchSnapshot();
});
it('handles the click', () => {
diff --git a/src/courseware/course/sequence/sequence-navigation/UnitIcon.test.jsx b/src/courseware/course/sequence/sequence-navigation/UnitIcon.test.jsx
index 9ffffa1265..509474ef2b 100644
--- a/src/courseware/course/sequence/sequence-navigation/UnitIcon.test.jsx
+++ b/src/courseware/course/sequence/sequence-navigation/UnitIcon.test.jsx
@@ -19,9 +19,8 @@ describe('Unit Icon', () => {
jest.spyOn(console, 'error').mockImplementation(() => {});
}
- const { asFragment } = render( );
+ render( );
expect(screen.getByTestId('icon')).toHaveClass(value);
- expect(asFragment()).toMatchSnapshot();
});
});
});
diff --git a/src/courseware/course/sequence/sequence-navigation/UnitNavigation.test.jsx b/src/courseware/course/sequence/sequence-navigation/UnitNavigation.test.jsx
index 09c373fd01..a55201ae49 100644
--- a/src/courseware/course/sequence/sequence-navigation/UnitNavigation.test.jsx
+++ b/src/courseware/course/sequence/sequence-navigation/UnitNavigation.test.jsx
@@ -14,7 +14,7 @@ describe('Unit Navigation', () => {
};
it('renders correctly without units', () => {
- const { asFragment } = render( {
onClickNext={() => {}}
/>);
- expect(asFragment()).toMatchSnapshot();
+ // Only "Previous" and "Next" buttons should be rendered.
+ expect(screen.getAllByRole('button')).toHaveLength(2);
});
it('handles the clicks', () => {
diff --git a/src/courseware/course/sequence/sequence-navigation/__snapshots__/SequenceNavigation.test.jsx.snap b/src/courseware/course/sequence/sequence-navigation/__snapshots__/SequenceNavigation.test.jsx.snap
deleted file mode 100644
index aa4cc9c5de..0000000000
--- a/src/courseware/course/sequence/sequence-navigation/__snapshots__/SequenceNavigation.test.jsx.snap
+++ /dev/null
@@ -1,244 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Sequence Navigation is empty while loading 1`] = ` `;
-
-exports[`Sequence Navigation renders correctly 1`] = `
-
-
-
-
-
- Previous
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Next
-
-
-
-
-
-`;
-
-exports[`Sequence Navigation renders empty div without unitId 1`] = `
-
-
-
-
-
- Previous
-
-
-
-
-
- Next
-
-
-
-
-
-`;
-
-exports[`Sequence Navigation renders locked button for gated content 1`] = `
-
-
-
-
-
- Previous
-
-
-
-
-
-
-
- Next
-
-
-
-
-
-`;
diff --git a/src/courseware/course/sequence/sequence-navigation/__snapshots__/SequenceNavigationDropdown.test.jsx.snap b/src/courseware/course/sequence/sequence-navigation/__snapshots__/SequenceNavigationDropdown.test.jsx.snap
deleted file mode 100644
index 1204f276ef..0000000000
--- a/src/courseware/course/sequence/sequence-navigation/__snapshots__/SequenceNavigationDropdown.test.jsx.snap
+++ /dev/null
@@ -1,26 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Sequence Navigation Dropdown renders correctly without units 1`] = `
-
-
-
-
- 0 of 0
-
-
-
-
-
-`;
diff --git a/src/courseware/course/sequence/sequence-navigation/__snapshots__/SequenceNavigationTabs.test.jsx.snap b/src/courseware/course/sequence/sequence-navigation/__snapshots__/SequenceNavigationTabs.test.jsx.snap
deleted file mode 100644
index 65de0dc31e..0000000000
--- a/src/courseware/course/sequence/sequence-navigation/__snapshots__/SequenceNavigationTabs.test.jsx.snap
+++ /dev/null
@@ -1,436 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Sequence Navigation Tabs renders correctly with dropdown 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 1 of 10
-
-
-
-
-
-
-`;
-
-exports[`Sequence Navigation Tabs renders correctly without dropdown 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-`;
diff --git a/src/courseware/course/sequence/sequence-navigation/__snapshots__/UnitButton.test.jsx.snap b/src/courseware/course/sequence/sequence-navigation/__snapshots__/UnitButton.test.jsx.snap
deleted file mode 100644
index eb1efa6f74..0000000000
--- a/src/courseware/course/sequence/sequence-navigation/__snapshots__/UnitButton.test.jsx.snap
+++ /dev/null
@@ -1,143 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Unit Button does not show bookmark 1`] = `
-
-
-
-
-
-`;
-
-exports[`Unit Button does not show completion for non-completed unit 1`] = `
-
-
-
-
-
-`;
-
-exports[`Unit Button hides completion 1`] = `
-
-
-
-
-
-
-`;
-
-exports[`Unit Button hides title by default 1`] = `
-
-
-
-
-
-`;
-
-exports[`Unit Button shows bookmark 1`] = `
-
-
-
-
-
-
-
-`;
-
-exports[`Unit Button shows completion for completed unit 1`] = `
-
-
-
-
-
-
-
-`;
-
-exports[`Unit Button shows title 1`] = `
-
-
-
-
- other-unit
-
-
-
-`;
diff --git a/src/courseware/course/sequence/sequence-navigation/__snapshots__/UnitIcon.test.jsx.snap b/src/courseware/course/sequence/sequence-navigation/__snapshots__/UnitIcon.test.jsx.snap
deleted file mode 100644
index 49aeec725d..0000000000
--- a/src/courseware/course/sequence/sequence-navigation/__snapshots__/UnitIcon.test.jsx.snap
+++ /dev/null
@@ -1,61 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Unit Icon renders correct icon for lock unit 1`] = `
-
-
-
-`;
-
-exports[`Unit Icon renders correct icon for other unit 1`] = `
-
-
-
-`;
-
-exports[`Unit Icon renders correct icon for problem unit 1`] = `
-
-
-
-`;
-
-exports[`Unit Icon renders correct icon for undefined unit 1`] = `
-
-
-
-`;
-
-exports[`Unit Icon renders correct icon for vertical unit 1`] = `
-
-
-
-`;
-
-exports[`Unit Icon renders correct icon for video unit 1`] = `
-
-
-
-`;
diff --git a/src/courseware/course/sequence/sequence-navigation/__snapshots__/UnitNavigation.test.jsx.snap b/src/courseware/course/sequence/sequence-navigation/__snapshots__/UnitNavigation.test.jsx.snap
deleted file mode 100644
index c15047dafa..0000000000
--- a/src/courseware/course/sequence/sequence-navigation/__snapshots__/UnitNavigation.test.jsx.snap
+++ /dev/null
@@ -1,36 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Unit Navigation renders correctly without units 1`] = `
-
-
-
-
-
- Previous
-
-
-
-
- Next
-
-
-
-
-
-`;
diff --git a/src/setupTest.js b/src/setupTest.js
index 0349bd2060..120652bcfc 100755
--- a/src/setupTest.js
+++ b/src/setupTest.js
@@ -146,6 +146,11 @@ const messageEvent = {
},
};
+// Send MessageEvent indicating that a unit has been loaded.
+function loadUnit(message = messageEvent) {
+ window.postMessage(message, '*');
+}
+
function render(
ui,
{
@@ -187,5 +192,5 @@ export * from '@testing-library/react';
// override `render` method; export `screen` too to suppress errors
export {
- render, screen, testUnits, baseInitialState as initialState, messageEvent,
+ render, screen, testUnits, baseInitialState as initialState, messageEvent, loadUnit,
};
From a4ce695adc33364c694802f4c711bd9fb9df3bdb Mon Sep 17 00:00:00 2001
From: Agrendalath
Date: Thu, 9 Jul 2020 04:02:25 +0200
Subject: [PATCH 07/12] [TNL-7268] Remove unused dependency
---
src/courseware/course/sequence/Unit.test.jsx | 2 --
src/setupTest.js | 25 ++++++++++----------
2 files changed, 12 insertions(+), 15 deletions(-)
diff --git a/src/courseware/course/sequence/Unit.test.jsx b/src/courseware/course/sequence/Unit.test.jsx
index 4d25189ae6..a730e0c37b 100644
--- a/src/courseware/course/sequence/Unit.test.jsx
+++ b/src/courseware/course/sequence/Unit.test.jsx
@@ -72,8 +72,6 @@ describe('Unit', () => {
window.postMessage(testMessageWithUnhandledType, '*');
// HACK: We don't have a function we could reliably await here, so this test relies on the timeout of `waitFor`.
- // FIXME: After the last updates `toThrowErrorMatchingSnapshot` (due to a bug) started returning DOM
- // after the error, so we had to fall back to `toThrowError` assertion for better readability.
await expect(waitFor(
() => expect(screen.getByTitle(mockData.id)).toHaveAttribute('height', String(testMessageWithUnhandledType.payload.height)),
{ timeout: 100 },
diff --git a/src/setupTest.js b/src/setupTest.js
index 120652bcfc..d35a4ad627 100755
--- a/src/setupTest.js
+++ b/src/setupTest.js
@@ -5,6 +5,18 @@ import { getConfig, mergeConfig } from '@edx/frontend-platform';
import { configure as configureI18n } from '@edx/frontend-platform/i18n';
import { configure as configureLogging } from '@edx/frontend-platform/logging';
import { configure as configureAuth, MockAuthService } from '@edx/frontend-platform/auth';
+import React from 'react';
+import PropTypes from 'prop-types';
+// eslint-disable-next-line import/no-extraneous-dependencies
+import { render as rtlRender, screen } from '@testing-library/react';
+import { Provider } from 'react-redux';
+import { configureStore } from '@reduxjs/toolkit';
+// eslint-disable-next-line import/no-extraneous-dependencies
+import { IntlProvider } from 'react-intl';
+import { reducer as courseHomeReducer } from './course-home/data';
+import { reducer as coursewareReducer } from './courseware/data/slice';
+import { reducer as modelsReducer } from './generic/model-store';
+import { UserMessagesProvider } from './generic/user-messages';
import appMessages from './i18n';
@@ -44,19 +56,6 @@ export default function initializeMockApp() {
return { loggingService, authService };
}
-import React from 'react';
-import PropTypes from 'prop-types';
-// eslint-disable-next-line import/no-extraneous-dependencies
-import { render as rtlRender, screen } from '@testing-library/react';
-import { Provider } from 'react-redux';
-import { configureStore } from '@reduxjs/toolkit';
-// eslint-disable-next-line import/no-extraneous-dependencies
-import { IntlProvider } from 'react-intl';
-import { reducer as courseHomeReducer } from './course-home/data';
-import { reducer as coursewareReducer } from './courseware/data/slice';
-import { reducer as modelsReducer } from './generic/model-store';
-import { UserMessagesProvider } from './generic/user-messages';
-
/**
* HACK: Mock the MutationObserver as it's breaking async testing.
* According to StackOverflow it should be fixed in `jest-environment-jsdom` v16,
From 616e9f91213fbd3908e3486ddfc471a66a66c36d Mon Sep 17 00:00:00 2001
From: Agrendalath
Date: Thu, 9 Jul 2020 16:28:12 +0200
Subject: [PATCH 08/12] [TNL-7268] Fix imports, ignore implicit exports for
tests
---
.eslintrc.js | 10 +++++++++-
src/courseware/course/sequence/Sequence.test.jsx | 4 +---
src/courseware/course/sequence/Unit.test.jsx | 4 +---
.../sequence-navigation/SequenceNavigation.test.jsx | 4 +---
.../SequenceNavigationDropdown.test.jsx | 3 +--
.../sequence/sequence-navigation/UnitButton.test.jsx | 3 +--
.../sequence-navigation/UnitNavigation.test.jsx | 3 +--
src/setupTest.js | 11 ++++-------
8 files changed, 19 insertions(+), 23 deletions(-)
diff --git a/.eslintrc.js b/.eslintrc.js
index 0e87381978..9ed6c9fe81 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -1,3 +1,11 @@
const { createConfig } = require('@edx/frontend-build');
-module.exports = createConfig('eslint');
\ No newline at end of file
+module.exports = createConfig('eslint', {
+ overrides: [{
+ files: ["**/__tests__/**/*.[jt]s?(x)", "**/?(*.)+(spec|test).[jt]s?(x)", "setupTest.js"],
+ rules: {
+ 'import/named': 'off',
+ 'import/no-extraneous-dependencies': 'off',
+ },
+ }],
+});
diff --git a/src/courseware/course/sequence/Sequence.test.jsx b/src/courseware/course/sequence/Sequence.test.jsx
index 14afec0666..28559d67aa 100644
--- a/src/courseware/course/sequence/Sequence.test.jsx
+++ b/src/courseware/course/sequence/Sequence.test.jsx
@@ -1,10 +1,8 @@
import React from 'react';
-import { fireEvent, waitFor } from '@testing-library/dom';
-// eslint-disable-next-line import/no-extraneous-dependencies
import { cloneDeep } from 'lodash';
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
import {
- initialState, loadUnit, render, screen, testUnits,
+ initialState, loadUnit, render, screen, testUnits, fireEvent, waitFor,
} from '../../../setupTest';
import Sequence from './Sequence';
diff --git a/src/courseware/course/sequence/Unit.test.jsx b/src/courseware/course/sequence/Unit.test.jsx
index a730e0c37b..16c23c1182 100644
--- a/src/courseware/course/sequence/Unit.test.jsx
+++ b/src/courseware/course/sequence/Unit.test.jsx
@@ -1,9 +1,7 @@
import React from 'react';
-// eslint-disable-next-line import/no-extraneous-dependencies
import { cloneDeep } from 'lodash';
-import { waitFor } from '@testing-library/dom';
import {
- initialState, loadUnit, messageEvent, render, screen,
+ initialState, loadUnit, messageEvent, render, screen, waitFor,
} from '../../../setupTest';
import Unit from './Unit';
diff --git a/src/courseware/course/sequence/sequence-navigation/SequenceNavigation.test.jsx b/src/courseware/course/sequence/sequence-navigation/SequenceNavigation.test.jsx
index e52f48dc96..185b73acc9 100644
--- a/src/courseware/course/sequence/sequence-navigation/SequenceNavigation.test.jsx
+++ b/src/courseware/course/sequence/sequence-navigation/SequenceNavigation.test.jsx
@@ -1,9 +1,7 @@
import React from 'react';
-// eslint-disable-next-line import/no-extraneous-dependencies
import { cloneDeep } from 'lodash';
-import { fireEvent, getByText } from '@testing-library/dom';
import {
- initialState, render, screen, testUnits,
+ initialState, render, screen, testUnits, fireEvent, getByText,
} from '../../../../setupTest';
import SequenceNavigation from './SequenceNavigation';
import useIndexOfLastVisibleChild from '../../../../generic/tabs/useIndexOfLastVisibleChild';
diff --git a/src/courseware/course/sequence/sequence-navigation/SequenceNavigationDropdown.test.jsx b/src/courseware/course/sequence/sequence-navigation/SequenceNavigationDropdown.test.jsx
index de7eb29d0f..81eb3b7141 100644
--- a/src/courseware/course/sequence/sequence-navigation/SequenceNavigationDropdown.test.jsx
+++ b/src/courseware/course/sequence/sequence-navigation/SequenceNavigationDropdown.test.jsx
@@ -1,8 +1,7 @@
import React from 'react';
-import { fireEvent } from '@testing-library/dom';
import SequenceNavigationDropdown from './SequenceNavigationDropdown';
import {
- initialState, render, screen, testUnits,
+ initialState, render, screen, testUnits, fireEvent,
} from '../../../../setupTest';
describe('Sequence Navigation Dropdown', () => {
diff --git a/src/courseware/course/sequence/sequence-navigation/UnitButton.test.jsx b/src/courseware/course/sequence/sequence-navigation/UnitButton.test.jsx
index 3f0b9d7c14..7f3f77d266 100644
--- a/src/courseware/course/sequence/sequence-navigation/UnitButton.test.jsx
+++ b/src/courseware/course/sequence/sequence-navigation/UnitButton.test.jsx
@@ -1,6 +1,5 @@
import React from 'react';
-import { fireEvent } from '@testing-library/dom';
-import { render, screen } from '../../../../setupTest';
+import { render, screen, fireEvent } from '../../../../setupTest';
import UnitButton from './UnitButton';
describe('Unit Button', () => {
diff --git a/src/courseware/course/sequence/sequence-navigation/UnitNavigation.test.jsx b/src/courseware/course/sequence/sequence-navigation/UnitNavigation.test.jsx
index a55201ae49..4ab8d29651 100644
--- a/src/courseware/course/sequence/sequence-navigation/UnitNavigation.test.jsx
+++ b/src/courseware/course/sequence/sequence-navigation/UnitNavigation.test.jsx
@@ -1,7 +1,6 @@
import React from 'react';
-import { fireEvent } from '@testing-library/dom';
import {
- initialState, render, screen, testUnits,
+ initialState, render, screen, testUnits, fireEvent,
} from '../../../../setupTest';
import UnitNavigation from './UnitNavigation';
diff --git a/src/setupTest.js b/src/setupTest.js
index d35a4ad627..327617b468 100755
--- a/src/setupTest.js
+++ b/src/setupTest.js
@@ -7,11 +7,9 @@ import { configure as configureLogging } from '@edx/frontend-platform/logging';
import { configure as configureAuth, MockAuthService } from '@edx/frontend-platform/auth';
import React from 'react';
import PropTypes from 'prop-types';
-// eslint-disable-next-line import/no-extraneous-dependencies
-import { render as rtlRender, screen } from '@testing-library/react';
+import { render as rtlRender } from '@testing-library/react';
import { Provider } from 'react-redux';
import { configureStore } from '@reduxjs/toolkit';
-// eslint-disable-next-line import/no-extraneous-dependencies
import { IntlProvider } from 'react-intl';
import { reducer as courseHomeReducer } from './course-home/data';
import { reducer as coursewareReducer } from './courseware/data/slice';
@@ -185,11 +183,10 @@ function render(
return rtlRender(ui, { wrapper: Wrapper, ...renderOptions });
}
-// re-export everything
-// eslint-disable-next-line import/no-extraneous-dependencies
+// Re-export everything.
export * from '@testing-library/react';
-// override `render` method; export `screen` too to suppress errors
+// Override `render` method; export `screen` too to suppress errors.
export {
- render, screen, testUnits, baseInitialState as initialState, messageEvent, loadUnit,
+ render, testUnits, baseInitialState as initialState, messageEvent, loadUnit,
};
From d84f2a8ae7c97956e94d6b9556453efd8f4844cf Mon Sep 17 00:00:00 2001
From: Agrendalath
Date: Wed, 15 Jul 2020 22:50:48 +0200
Subject: [PATCH 09/12] [TNL-7268] Fix tests after rebase
---
package-lock.json | 9 ++++++
package.json | 1 +
.../course/sequence/Sequence.test.jsx | 7 +++--
.../course/sequence/SequenceContent.test.jsx | 4 +--
.../sequence-navigation/UnitButton.test.jsx | 30 ++++++++++++-------
src/setupTest.js | 16 ----------
.../@fortawesome/react-fontawesome.js | 2 +-
7 files changed, 37 insertions(+), 32 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 9097043aa5..af6043082b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2833,6 +2833,15 @@
"@testing-library/dom": "^7.14.2"
}
},
+ "@testing-library/user-event": {
+ "version": "12.0.11",
+ "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-12.0.11.tgz",
+ "integrity": "sha512-r7QNfktLE2n8IODEl32orup/HNOMueJpoXRDeTMlvWR4nZIHJwx59+8SkLf6nqV4Ot5Xo6qNeaWrvC1KO4eOng==",
+ "dev": true,
+ "requires": {
+ "@babel/runtime": "^7.10.2"
+ }
+ },
"@tootallnate/once": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz",
diff --git a/package.json b/package.json
index c6387e5392..b9423c4471 100644
--- a/package.json
+++ b/package.json
@@ -64,6 +64,7 @@
"@testing-library/dom": "^7.16.2",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.3.0",
+ "@testing-library/user-event": "^12.0.2",
"axios-mock-adapter": "^1.18.1",
"codecov": "^3.6.1",
"es-check": "^5.1.0",
diff --git a/src/courseware/course/sequence/Sequence.test.jsx b/src/courseware/course/sequence/Sequence.test.jsx
index 28559d67aa..a452f91c09 100644
--- a/src/courseware/course/sequence/Sequence.test.jsx
+++ b/src/courseware/course/sequence/Sequence.test.jsx
@@ -21,18 +21,19 @@ describe('Sequence', () => {
it('renders correctly without data', () => {
render( , { initialState: {} });
- expect(screen.getByText('Loading learning sequence...')).toBeInTheDocument();
+ expect(screen.getByText('There is no content here.')).toBeInTheDocument();
expect(screen.queryByRole('button')).not.toBeInTheDocument();
});
it('renders correctly for gated content', async () => {
- render( );
+ const { container } = render( );
expect(screen.getByText('Loading locked content messaging...')).toBeInTheDocument();
// Only `Previous`, `Next` and `Bookmark` buttons.
expect(screen.getAllByRole('button').length).toEqual(3);
expect(await screen.findByText('Content Locked')).toBeInTheDocument();
- expect(screen.getByAltText('fa-lock')).toBeInTheDocument();
+ const unitContainer = container.querySelector('.unit-container');
+ expect(unitContainer.querySelector('svg')).toHaveClass('fa-lock');
expect(screen.getByText(/You must complete the prerequisite/)).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Go To Prerequisite Section' })).toBeInTheDocument();
expect(screen.queryByText('Loading locked content messaging...')).not.toBeInTheDocument();
diff --git a/src/courseware/course/sequence/SequenceContent.test.jsx b/src/courseware/course/sequence/SequenceContent.test.jsx
index bbed341e1b..a26521b10a 100644
--- a/src/courseware/course/sequence/SequenceContent.test.jsx
+++ b/src/courseware/course/sequence/SequenceContent.test.jsx
@@ -18,13 +18,13 @@ describe('Sequence Content', () => {
});
it('displays messages for the locked content', async () => {
- render( , { initialState });
+ const { container } = render( , { initialState });
expect(screen.getByText('Loading locked content messaging...')).toBeInTheDocument();
expect(await screen.findByText('Content Locked')).toBeInTheDocument();
expect(screen.getByText('test-sequence')).toBeInTheDocument();
expect(screen.queryByText('Loading locked content messaging...')).not.toBeInTheDocument();
- expect(screen.getByAltText('fa-lock')).toBeInTheDocument();
+ expect(container.querySelector('svg')).toHaveClass('fa-lock');
expect(screen.getByText(/You must complete the prerequisite/)).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Go To Prerequisite Section' })).toBeInTheDocument();
});
diff --git a/src/courseware/course/sequence/sequence-navigation/UnitButton.test.jsx b/src/courseware/course/sequence/sequence-navigation/UnitButton.test.jsx
index 7f3f77d266..08fb90f6ba 100644
--- a/src/courseware/course/sequence/sequence-navigation/UnitButton.test.jsx
+++ b/src/courseware/course/sequence/sequence-navigation/UnitButton.test.jsx
@@ -36,28 +36,38 @@ describe('Unit Button', () => {
});
it('does not show completion for non-completed unit', () => {
- render( , { initialState });
- expect(screen.queryByAltText('fa-check')).toBeNull();
+ const { container } = render( , { initialState });
+ container.querySelectorAll('svg').forEach(icon => {
+ expect(icon).not.toHaveClass('fa-check');
+ });
});
it('shows completion for completed unit', () => {
- render( , { initialState });
- expect(screen.getByAltText('fa-check')).toBeInTheDocument();
+ const { container } = render( , { initialState });
+ const buttonIcons = container.querySelectorAll('svg');
+ expect(buttonIcons).toHaveLength(3);
+ expect(buttonIcons[1]).toHaveClass('fa-check');
});
it('hides completion', () => {
- render( , { initialState });
- expect(screen.queryByAltText('fa-check')).toBeNull();
+ const { container } = render( , { initialState });
+ container.querySelectorAll('svg').forEach(icon => {
+ expect(icon).not.toHaveClass('fa-check');
+ });
});
it('does not show bookmark', () => {
- render( , { initialState });
- expect(screen.queryByAltText('fa-bookmark')).toBeNull();
+ const { container } = render( , { initialState });
+ container.querySelectorAll('svg').forEach(icon => {
+ expect(icon).not.toHaveClass('fa-bookmark');
+ });
});
it('shows bookmark', () => {
- render( , { initialState });
- expect(screen.getByAltText('fa-bookmark')).toBeInTheDocument();
+ const { container } = render( , { initialState });
+ const buttonIcons = container.querySelectorAll('svg');
+ expect(buttonIcons).toHaveLength(3);
+ expect(buttonIcons[2]).toHaveClass('fa-bookmark');
});
it('handles the click', () => {
diff --git a/src/setupTest.js b/src/setupTest.js
index 327617b468..82932e5887 100755
--- a/src/setupTest.js
+++ b/src/setupTest.js
@@ -54,22 +54,6 @@ export default function initializeMockApp() {
return { loggingService, authService };
}
-/**
- * HACK: Mock the MutationObserver as it's breaking async testing.
- * According to StackOverflow it should be fixed in `jest-environment-jsdom` v16,
- * but upgrading `jest` to v26 didn't fix this problem.
- * ref: https://stackoverflow.com/questions/61036156/react-typescript-testing-typeerror-mutationobserver-is-not-a-constructor
- */
-global.MutationObserver = class {
- // eslint-disable-next-line no-unused-vars,no-useless-constructor,no-empty-function
- constructor(callback) {}
-
- disconnect() {}
-
- // eslint-disable-next-line no-unused-vars
- observe(element, initObject) {}
-};
-
window.scrollTo = jest.fn();
// Generated units for convenience.
diff --git a/src/test/__mocks__/@fortawesome/react-fontawesome.js b/src/test/__mocks__/@fortawesome/react-fontawesome.js
index f574d7bf99..2d1ef1ccc7 100644
--- a/src/test/__mocks__/@fortawesome/react-fontawesome.js
+++ b/src/test/__mocks__/@fortawesome/react-fontawesome.js
@@ -28,5 +28,5 @@ export function FontAwesomeIcon(props) {
}
// eslint-disable-next-line react/jsx-filename-extension
- return ;
+ return ;
}
From 56a8c9dfd8a595e000d528f51406a85bdab76d77 Mon Sep 17 00:00:00 2001
From: Agrendalath
Date: Wed, 15 Jul 2020 22:53:04 +0200
Subject: [PATCH 10/12] [TNL-7268] Remove icon mock
As we're not using snapshots, we will not need this anymore.
---
.../sequence-navigation/UnitIcon.test.jsx | 6 ++--
.../@fortawesome/react-fontawesome.js | 32 -------------------
2 files changed, 3 insertions(+), 35 deletions(-)
delete mode 100644 src/test/__mocks__/@fortawesome/react-fontawesome.js
diff --git a/src/courseware/course/sequence/sequence-navigation/UnitIcon.test.jsx b/src/courseware/course/sequence/sequence-navigation/UnitIcon.test.jsx
index 509474ef2b..8bfae84c87 100644
--- a/src/courseware/course/sequence/sequence-navigation/UnitIcon.test.jsx
+++ b/src/courseware/course/sequence/sequence-navigation/UnitIcon.test.jsx
@@ -1,5 +1,5 @@
import React from 'react';
-import { render, screen } from '../../../../setupTest';
+import { render } from '../../../../setupTest';
import UnitIcon from './UnitIcon';
describe('Unit Icon', () => {
@@ -19,8 +19,8 @@ describe('Unit Icon', () => {
jest.spyOn(console, 'error').mockImplementation(() => {});
}
- render( );
- expect(screen.getByTestId('icon')).toHaveClass(value);
+ const { container } = render( );
+ expect(container.querySelector('svg')).toHaveClass(value);
});
});
});
diff --git a/src/test/__mocks__/@fortawesome/react-fontawesome.js b/src/test/__mocks__/@fortawesome/react-fontawesome.js
deleted file mode 100644
index 2d1ef1ccc7..0000000000
--- a/src/test/__mocks__/@fortawesome/react-fontawesome.js
+++ /dev/null
@@ -1,32 +0,0 @@
-/**
- * Mocks `@fortawesome/react-fontawesome.js` to return a simple element containing `data-testid` attribute.
- * This way we can check whether the icon matches without relying on its internal implementation
- * and avoid storing its content in the snapshot tests.
- */
-import React from 'react';
-import PropTypes from 'prop-types';
-
-// eslint-disable-next-line no-use-before-define
-FontAwesomeIcon.propTypes = {
- icon: PropTypes.oneOfType([
- PropTypes.string,
- PropTypes.shape({
- icon: PropTypes.arrayOf(PropTypes.any),
- }),
- ]).isRequired,
-};
-
-// eslint-disable-next-line import/prefer-default-export
-export function FontAwesomeIcon(props) {
- const { icon } = props;
-
- let iconName;
- if (typeof icon === 'string') {
- iconName = icon;
- } else {
- iconName = `fa-${icon.iconName}`;
- }
-
- // eslint-disable-next-line react/jsx-filename-extension
- return ;
-}
From cae6b38ab7c8c05808e153df67d520b0bea0618d Mon Sep 17 00:00:00 2001
From: Agrendalath
Date: Thu, 23 Jul 2020 16:41:39 +0200
Subject: [PATCH 11/12] [TNL-7268] refactor tests to use factories
---
src/course-home/data/redux.test.js | 2 -
src/courseware/CoursewareContainer.test.jsx | 20 +-
.../course/sequence/Sequence.test.jsx | 305 +++++++++++-------
.../course/sequence/SequenceContent.test.jsx | 38 ++-
src/courseware/course/sequence/Unit.test.jsx | 62 ++--
.../SequenceNavigation.jsx | 7 +-
.../SequenceNavigation.test.jsx | 96 +++---
.../SequenceNavigationDropdown.test.jsx | 61 ++--
.../SequenceNavigationTabs.test.jsx | 50 ++-
.../sequence-navigation/UnitButton.test.jsx | 70 ++--
.../sequence-navigation/UnitIcon.test.jsx | 24 +-
.../UnitNavigation.test.jsx | 48 +--
.../__factories__/courseBlocks.factory.js | 68 ++--
.../__factories__/sequenceMetadata.factory.js | 39 ++-
src/courseware/data/redux.test.js | 19 +-
src/setupTest.js | 143 ++++----
16 files changed, 631 insertions(+), 421 deletions(-)
diff --git a/src/course-home/data/redux.test.js b/src/course-home/data/redux.test.js
index db87abf66e..adf9b36bcf 100644
--- a/src/course-home/data/redux.test.js
+++ b/src/course-home/data/redux.test.js
@@ -8,8 +8,6 @@ import * as thunks from './thunks';
import executeThunk from '../../utils';
-import './__factories__';
-import '../../courseware/data/__factories__/courseMetadata.factory';
import initializeMockApp from '../../setupTest';
import initializeStore from '../../store';
diff --git a/src/courseware/CoursewareContainer.test.jsx b/src/courseware/CoursewareContainer.test.jsx
index 26b9a5773a..ed2a088bb3 100644
--- a/src/courseware/CoursewareContainer.test.jsx
+++ b/src/courseware/CoursewareContainer.test.jsx
@@ -14,7 +14,6 @@ import tabMessages from '../tab-page/messages';
import initializeMockApp from '../setupTest';
import CoursewareContainer from './CoursewareContainer';
-import './data/__factories__';
import buildSimpleCourseBlocks from './data/__factories__/courseBlocks.factory';
import initializeStore from '../store';
@@ -79,28 +78,29 @@ describe('CoursewareContainer', () => {
it('should successfully render sequence navigation and unit', async () => {
const courseMetadata = Factory.build('courseMetadata');
const courseId = courseMetadata.id;
- const { courseBlocks, unitBlock, sequenceBlock } = buildSimpleCourseBlocks(courseId, courseMetadata.name);
+ const { courseBlocks, unitBlocks, sequenceBlock } = buildSimpleCourseBlocks(courseId, courseMetadata.name);
const sequenceMetadata = Factory.build(
'sequenceMetadata',
{},
- { courseId, unitBlocks: [unitBlock], sequenceBlock },
+ { courseId, unitBlocks, sequenceBlock: sequenceBlock[0] },
);
const courseMetadataUrl = `${getConfig().LMS_BASE_URL}/api/courseware/course/${courseId}`;
const courseBlocksUrlRegExp = new RegExp(`${getConfig().LMS_BASE_URL}/api/courses/v2/blocks/*`);
- const sequenceMetadataUrl = `${getConfig().LMS_BASE_URL}/api/courseware/sequence/${sequenceBlock.id}`;
- const unitId = unitBlock.id;
+ const sequenceMetadataUrl = `${getConfig().LMS_BASE_URL}/api/courseware/sequence/${sequenceBlock[0].id}`;
+ const unitId = unitBlocks[0].id;
axiosMock.onGet(courseMetadataUrl).reply(200, courseMetadata);
axiosMock.onGet(courseBlocksUrlRegExp).reply(200, courseBlocks);
axiosMock.onGet(`${getConfig().LMS_BASE_URL}/api/courseware/resume/${courseId}`).reply(200, {
- sectionId: sequenceBlock.id,
- unitId: unitBlock.id,
+ sectionId: sequenceBlock[0].id,
+ unitId: unitBlocks[0].id,
});
axiosMock.onGet(sequenceMetadataUrl).reply(200, sequenceMetadata);
// Print out any URLs that we didn't handle above - useful for debugging the test.
axiosMock.onAny().reply((config) => {
+ // eslint-disable-next-line no-console
console.log(config.url);
return [200, {}];
});
@@ -126,7 +126,7 @@ describe('CoursewareContainer', () => {
expect(sequenceNavButtons[0]).toHaveTextContent('Previous');
// Prove this button is rendering an SVG book icon, meaning it's a unit.
- expect(sequenceNavButtons[1].querySelector('svg')).toHaveClass('fa-book');
+ expect(sequenceNavButtons[1].querySelector('svg')).toHaveClass('fa-tasks');
expect(sequenceNavButtons[2]).toHaveTextContent('Next');
expect(container.querySelector('.fake-unit')).toHaveTextContent('Unit Contents');
@@ -146,11 +146,11 @@ describe('CoursewareContainer', () => {
},
});
const courseId = courseMetadata.id;
- const { courseBlocks, unitBlock, sequenceBlock } = buildSimpleCourseBlocks(courseId, courseMetadata.name);
+ const { courseBlocks, unitBlocks, sequenceBlock } = buildSimpleCourseBlocks(courseId, courseMetadata.name);
const sequenceMetadata = Factory.build(
'sequenceMetadata',
{},
- { courseId, unitBlocks: [unitBlock], sequenceBlock },
+ { courseId, unitBlocks, sequenceBlock: sequenceBlock[0] },
);
const forbiddenCourseUrl = `${getConfig().LMS_BASE_URL}/api/courseware/course/${courseId}`;
diff --git a/src/courseware/course/sequence/Sequence.test.jsx b/src/courseware/course/sequence/Sequence.test.jsx
index a452f91c09..5c6108d3f9 100644
--- a/src/courseware/course/sequence/Sequence.test.jsx
+++ b/src/courseware/course/sequence/Sequence.test.jsx
@@ -1,32 +1,67 @@
import React from 'react';
-import { cloneDeep } from 'lodash';
+import { Factory } from 'rosie';
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
import {
- initialState, loadUnit, render, screen, testUnits, fireEvent, waitFor,
+ loadUnit, render, screen, fireEvent, waitFor, initializeTestStore,
} from '../../../setupTest';
import Sequence from './Sequence';
+import { fetchSequenceFailure } from '../../data/slice';
jest.mock('@edx/frontend-platform/analytics');
describe('Sequence', () => {
- const mockData = {
- unitId: '3',
- sequenceId: '1',
- courseId: '1',
- unitNavigationHandler: () => {},
- nextSequenceHandler: () => {},
- previousSequenceHandler: () => {},
- intl: {},
- };
-
- it('renders correctly without data', () => {
- render( , { initialState: {} });
+ let mockData;
+ const courseMetadata = Factory.build('courseMetadata');
+ const unitBlocks = Array.from({ length: 3 }).map(() => Factory.build(
+ 'block',
+ { type: 'vertical' },
+ { courseId: courseMetadata.id },
+ ));
+
+ beforeAll(async () => {
+ const store = await initializeTestStore({ courseMetadata, unitBlocks });
+ const { courseware } = store.getState();
+ mockData = {
+ unitId: unitBlocks[0].id,
+ sequenceId: courseware.sequenceId,
+ courseId: courseware.courseId,
+ unitNavigationHandler: () => {},
+ nextSequenceHandler: () => {},
+ previousSequenceHandler: () => {},
+ };
+ });
+
+ it('renders correctly without data', async () => {
+ const testStore = await initializeTestStore({ excludeFetchCourse: true, excludeFetchSequence: true }, false);
+ render( , { store: testStore });
+
expect(screen.getByText('There is no content here.')).toBeInTheDocument();
expect(screen.queryByRole('button')).not.toBeInTheDocument();
});
it('renders correctly for gated content', async () => {
- const { container } = render( );
+ const sequenceBlock = [Factory.build(
+ 'block',
+ { type: 'sequential', children: [unitBlocks.map(block => block.id)] },
+ { courseId: courseMetadata.id },
+ )];
+ const gatedContent = {
+ gated: true,
+ prereq_id: `${sequenceBlock[0].id}-prereq`,
+ prereq_section_name: `${sequenceBlock[0].display_name}-prereq`,
+ gated_section_name: sequenceBlock[0].display_name,
+ };
+ const sequenceMetadata = [Factory.build(
+ 'sequenceMetadata',
+ { courseId: courseMetadata.id, gated_content: gatedContent },
+ { unitBlocks, sequenceBlock: sequenceBlock[0] },
+ )];
+ const testStore = await initializeTestStore({ unitBlocks, sequenceBlock, sequenceMetadata }, false);
+ const { container } = render(
+ ,
+ { store: testStore },
+ );
+
expect(screen.getByText('Loading locked content messaging...')).toBeInTheDocument();
// Only `Previous`, `Next` and `Bookmark` buttons.
expect(screen.getAllByRole('button').length).toEqual(3);
@@ -39,10 +74,10 @@ describe('Sequence', () => {
expect(screen.queryByText('Loading locked content messaging...')).not.toBeInTheDocument();
});
- it('displays error message on sequence load failure', () => {
- const testState = cloneDeep(initialState);
- testState.courseware.sequenceStatus = 'failed';
- render( , { initialState: testState });
+ it('displays error message on sequence load failure', async () => {
+ const testStore = await initializeTestStore({ excludeFetchCourse: true, excludeFetchSequence: true }, false);
+ testStore.dispatch(fetchSequenceFailure({ sequenceId: mockData.sequenceId }));
+ render( , { store: testStore });
expect(screen.getByText('There was an error loading this course.')).toBeInTheDocument();
});
@@ -51,7 +86,7 @@ describe('Sequence', () => {
render( );
expect(screen.getByText('Loading learning sequence...')).toBeInTheDocument();
// Renders navigation buttons plus one button for each unit.
- expect(screen.getAllByRole('button').length).toEqual(3 + testUnits.length);
+ expect(screen.getAllByRole('button')).toHaveLength(3 + unitBlocks.length);
loadUnit();
await waitFor(() => expect(screen.queryByText('Loading learning sequence...')).not.toBeInTheDocument());
@@ -60,21 +95,41 @@ describe('Sequence', () => {
});
describe('sequence and unit navigation buttons', () => {
- it('navigates to the previous sequence if the unit is the first in the sequence', async () => {
+ let testStore;
+ const sequenceBlock = [Factory.build(
+ 'block',
+ { type: 'sequential', children: [unitBlocks.map(block => block.id)] },
+ { courseId: courseMetadata.id },
+ ), Factory.build(
+ 'block',
+ { type: 'sequential', children: [unitBlocks.map(block => block.id)] },
+ { courseId: courseMetadata.id },
+ )];
+
+ beforeAll(async () => {
+ testStore = await initializeTestStore({ courseMetadata, unitBlocks, sequenceBlock }, false);
+ });
+
+ beforeEach(() => {
sendTrackEvent.mockClear();
- const unitId = '1';
- const sequenceId = '2';
- const previousSequenceHandler = jest.fn();
- render( );
+ });
+
+ it('navigates to the previous sequence if the unit is the first in the sequence', async () => {
+ const testData = {
+ ...mockData,
+ sequenceId: sequenceBlock[1].id,
+ previousSequenceHandler: jest.fn(),
+ };
+ render( , { store: testStore });
const sequencePreviousButton = screen.getByRole('button', { name: /previous/i });
fireEvent.click(sequencePreviousButton);
- expect(previousSequenceHandler).toHaveBeenCalledTimes(1);
+ expect(testData.previousSequenceHandler).toHaveBeenCalledTimes(1);
expect(sendTrackEvent).toHaveBeenCalledTimes(1);
expect(sendTrackEvent).toHaveBeenCalledWith('edx.ui.lms.sequence.previous_selected', {
- current_tab: Number(unitId),
- id: unitId,
- tab_count: testUnits.length,
+ current_tab: 1,
+ id: testData.unitId,
+ tab_count: unitBlocks.length,
widget_placement: 'top',
});
@@ -83,30 +138,32 @@ describe('Sequence', () => {
const unitPreviousButton = screen.getAllByRole('button', { name: /previous/i })
.filter(button => button !== sequencePreviousButton)[0];
fireEvent.click(unitPreviousButton);
- expect(previousSequenceHandler).toHaveBeenCalledTimes(2);
+ expect(testData.previousSequenceHandler).toHaveBeenCalledTimes(2);
expect(sendTrackEvent).toHaveBeenCalledTimes(2);
expect(sendTrackEvent).toHaveBeenNthCalledWith(2, 'edx.ui.lms.sequence.previous_selected', {
- current_tab: Number(unitId),
- id: unitId,
- tab_count: testUnits.length,
+ current_tab: 1,
+ id: testData.unitId,
+ tab_count: unitBlocks.length,
widget_placement: 'bottom',
});
});
it('navigates to the next sequence if the unit is the last in the sequence', async () => {
- sendTrackEvent.mockClear();
- const unitId = String(testUnits.length);
- const sequenceId = '1';
- const nextSequenceHandler = jest.fn();
- render( );
+ const testData = {
+ ...mockData,
+ unitId: unitBlocks[unitBlocks.length - 1].id,
+ sequenceId: sequenceBlock[0].id,
+ nextSequenceHandler: jest.fn(),
+ };
+ render( , { store: testStore });
const sequenceNextButton = screen.getByRole('button', { name: /next/i });
fireEvent.click(sequenceNextButton);
- expect(nextSequenceHandler).toHaveBeenCalledTimes(1);
+ expect(testData.nextSequenceHandler).toHaveBeenCalledTimes(1);
expect(sendTrackEvent).toHaveBeenCalledWith('edx.ui.lms.sequence.next_selected', {
- current_tab: Number(unitId),
- id: unitId,
- tab_count: testUnits.length,
+ current_tab: unitBlocks.length,
+ id: testData.unitId,
+ tab_count: unitBlocks.length,
widget_placement: 'top',
});
@@ -115,141 +172,167 @@ describe('Sequence', () => {
const unitNextButton = screen.getAllByRole('button', { name: /next/i })
.filter(button => button !== sequenceNextButton)[0];
fireEvent.click(unitNextButton);
- expect(nextSequenceHandler).toHaveBeenCalledTimes(2);
+ expect(testData.nextSequenceHandler).toHaveBeenCalledTimes(2);
expect(sendTrackEvent).toHaveBeenCalledTimes(2);
expect(sendTrackEvent).toHaveBeenNthCalledWith(2, 'edx.ui.lms.sequence.next_selected', {
- current_tab: Number(unitId),
- id: unitId,
- tab_count: testUnits.length,
+ current_tab: unitBlocks.length,
+ id: testData.unitId,
+ tab_count: unitBlocks.length,
widget_placement: 'bottom',
});
});
it('navigates to the previous/next unit if the unit is not in the corner of the sequence', () => {
- sendTrackEvent.mockClear();
- const unitNavigationHandler = jest.fn();
- const previousSequenceHandler = jest.fn();
- const nextSequenceHandler = jest.fn();
- render( );
+ const unitNumber = 1;
+ const testData = {
+ ...mockData,
+ unitId: unitBlocks[unitNumber].id,
+ sequenceId: sequenceBlock[0].id,
+ unitNavigationHandler: jest.fn(),
+ previousSequenceHandler: jest.fn(),
+ nextSequenceHandler: jest.fn(),
+ };
+ render( , { store: testStore });
fireEvent.click(screen.getByRole('button', { name: /previous/i }));
- expect(previousSequenceHandler).not.toHaveBeenCalled();
- expect(unitNavigationHandler).toHaveBeenCalledWith(String(Number(mockData.unitId) - 1));
+ expect(testData.previousSequenceHandler).not.toHaveBeenCalled();
+ expect(testData.unitNavigationHandler).toHaveBeenCalledWith(unitBlocks[unitNumber - 1].id);
fireEvent.click(screen.getByRole('button', { name: /next/i }));
- expect(nextSequenceHandler).not.toHaveBeenCalled();
+ expect(testData.nextSequenceHandler).not.toHaveBeenCalled();
// As `previousSequenceHandler` and `nextSequenceHandler` are mocked, we aren't really changing the position here.
// Therefore the next unit will still be `the initial one + 1`.
- expect(unitNavigationHandler).toHaveBeenNthCalledWith(2, String(Number(mockData.unitId) + 1));
+ expect(testData.unitNavigationHandler).toHaveBeenNthCalledWith(2, unitBlocks[unitNumber + 1].id);
expect(sendTrackEvent).toHaveBeenCalledTimes(2);
});
it('handles the `Previous` buttons for the first unit in the first sequence', async () => {
- sendTrackEvent.mockClear();
- const unitNavigationHandler = jest.fn();
- const previousSequenceHandler = jest.fn();
- const unitId = '1';
- render( );
+ const testData = {
+ ...mockData,
+ unitId: unitBlocks[0].id,
+ sequenceId: sequenceBlock[0].id,
+ unitNavigationHandler: jest.fn(),
+ previousSequenceHandler: jest.fn(),
+ };
+ render( , { store: testStore });
loadUnit();
await waitFor(() => expect(screen.queryByText('Loading learning sequence...')).not.toBeInTheDocument());
screen.getAllByRole('button', { name: /previous/i }).forEach(button => fireEvent.click(button));
- expect(previousSequenceHandler).not.toHaveBeenCalled();
- expect(unitNavigationHandler).not.toHaveBeenCalled();
+ expect(testData.previousSequenceHandler).not.toHaveBeenCalled();
+ expect(testData.unitNavigationHandler).not.toHaveBeenCalled();
expect(sendTrackEvent).not.toHaveBeenCalled();
});
it('handles the `Next` buttons for the last unit in the last sequence', async () => {
- sendTrackEvent.mockClear();
- const unitNavigationHandler = jest.fn();
- const nextSequenceHandler = jest.fn();
- const unitId = String(testUnits.length);
- const sequenceId = String(Object.keys(initialState.models.sequences).length);
- render( );
+ const testData = {
+ ...mockData,
+ unitId: unitBlocks[unitBlocks.length - 1].id,
+ sequenceId: sequenceBlock[sequenceBlock.length - 1].id,
+ unitNavigationHandler: jest.fn(),
+ nextSequenceHandler: jest.fn(),
+ };
+ render( , { store: testStore });
loadUnit();
await waitFor(() => expect(screen.queryByText('Loading learning sequence...')).not.toBeInTheDocument());
screen.getAllByRole('button', { name: /next/i }).forEach(button => fireEvent.click(button));
- expect(nextSequenceHandler).toHaveBeenCalledTimes(1);
- expect(unitNavigationHandler).not.toHaveBeenCalled();
- expect(sendTrackEvent).toHaveBeenCalledWith('edx.ui.lms.sequence.next_selected', {
- current_tab: Number(unitId),
- id: unitId,
- tab_count: testUnits.length,
- widget_placement: 'top',
- });
+ expect(testData.nextSequenceHandler).not.toHaveBeenCalled();
+ expect(testData.unitNavigationHandler).not.toHaveBeenCalled();
+ expect(sendTrackEvent).not.toHaveBeenCalled();
});
it('handles the navigation buttons for empty sequence', async () => {
- sendTrackEvent.mockClear();
- const testState = cloneDeep(initialState);
- testState.models.sequences['1'].unitIds = [];
-
- const unitNavigationHandler = jest.fn();
- const previousSequenceHandler = jest.fn();
- const nextSequenceHandler = jest.fn();
- render( , { initialState: testState });
+ const testSequenceBlock = [Factory.build(
+ 'block',
+ { type: 'sequential', children: [unitBlocks.map(block => block.id)] },
+ { courseId: courseMetadata.id },
+ ), Factory.build(
+ 'block',
+ { type: 'sequential', children: [] },
+ { courseId: courseMetadata.id },
+ ), Factory.build(
+ 'block',
+ { type: 'sequential', children: [unitBlocks.map(block => block.id)] },
+ { courseId: courseMetadata.id },
+ )];
+ const testSequenceMetadata = testSequenceBlock.map(block => Factory.build(
+ 'sequenceMetadata',
+ { courseId: courseMetadata.id },
+ { unitBlocks: block.children.length ? unitBlocks : [], sequenceBlock: block },
+ ));
+ const innerTestStore = await initializeTestStore({
+ courseMetadata, unitBlocks, sequenceBlock: testSequenceBlock, sequenceMetadata: testSequenceMetadata,
+ }, false);
+ const testData = {
+ ...mockData,
+ unitId: unitBlocks[0].id,
+ sequenceId: testSequenceBlock[1].id,
+ unitNavigationHandler: jest.fn(),
+ previousSequenceHandler: jest.fn(),
+ nextSequenceHandler: jest.fn(),
+ };
+
+ render( , { store: innerTestStore });
loadUnit();
await waitFor(() => expect(screen.queryByText('Loading learning sequence...')).not.toBeInTheDocument());
screen.getAllByRole('button', { name: /previous/i }).forEach(button => fireEvent.click(button));
- expect(previousSequenceHandler).toHaveBeenCalledTimes(2);
- expect(unitNavigationHandler).not.toHaveBeenCalled();
+ expect(testData.previousSequenceHandler).toHaveBeenCalledTimes(2);
+ expect(testData.unitNavigationHandler).not.toHaveBeenCalled();
screen.getAllByRole('button', { name: /next/i }).forEach(button => fireEvent.click(button));
- expect(nextSequenceHandler).toHaveBeenCalledTimes(2);
- expect(unitNavigationHandler).not.toHaveBeenCalled();
+ expect(testData.nextSequenceHandler).toHaveBeenCalledTimes(2);
+ expect(testData.unitNavigationHandler).not.toHaveBeenCalled();
expect(sendTrackEvent).toHaveBeenNthCalledWith(1, 'edx.ui.lms.sequence.previous_selected', {
current_tab: 1,
- id: mockData.unitId,
+ id: testData.unitId,
tab_count: 0,
widget_placement: 'top',
});
expect(sendTrackEvent).toHaveBeenNthCalledWith(2, 'edx.ui.lms.sequence.previous_selected', {
current_tab: 1,
- id: mockData.unitId,
+ id: testData.unitId,
tab_count: 0,
widget_placement: 'bottom',
});
expect(sendTrackEvent).toHaveBeenNthCalledWith(3, 'edx.ui.lms.sequence.next_selected', {
current_tab: 1,
- id: mockData.unitId,
+ id: testData.unitId,
tab_count: 0,
widget_placement: 'top',
});
expect(sendTrackEvent).toHaveBeenNthCalledWith(4, 'edx.ui.lms.sequence.next_selected', {
current_tab: 1,
- id: mockData.unitId,
+ id: testData.unitId,
tab_count: 0,
widget_placement: 'bottom',
});
});
it('handles unit navigation button', () => {
- sendTrackEvent.mockClear();
- const unitNavigationHandler = jest.fn();
- const targetUnit = '4';
- render( );
-
- fireEvent.click(screen.getByRole('button', { name: targetUnit }));
- expect(unitNavigationHandler).toHaveBeenCalledWith(targetUnit);
+ const currentTabNumber = 1;
+ const targetUnitNumber = 2;
+ const targetUnit = unitBlocks[targetUnitNumber - 1];
+ const testData = {
+ ...mockData,
+ unitId: unitBlocks[currentTabNumber - 1].id,
+ sequenceId: sequenceBlock[0].id,
+ unitNavigationHandler: jest.fn(),
+ };
+ render( , { store: testStore });
+
+ fireEvent.click(screen.getByRole('button', { name: targetUnit.display_name }));
+ expect(testData.unitNavigationHandler).toHaveBeenCalledWith(targetUnit.id);
expect(sendTrackEvent).toHaveBeenCalledWith('edx.ui.lms.sequence.tab_selected', {
- current_tab: Number(mockData.unitId),
- id: mockData.unitId,
- target_tab: Number(targetUnit),
- tab_count: testUnits.length,
+ current_tab: currentTabNumber,
+ id: testData.unitId,
+ target_tab: targetUnitNumber,
+ tab_count: unitBlocks.length,
widget_placement: 'top',
});
});
diff --git a/src/courseware/course/sequence/SequenceContent.test.jsx b/src/courseware/course/sequence/SequenceContent.test.jsx
index a26521b10a..23c60a272f 100644
--- a/src/courseware/course/sequence/SequenceContent.test.jsx
+++ b/src/courseware/course/sequence/SequenceContent.test.jsx
@@ -1,36 +1,44 @@
import React from 'react';
-import { initialState, render, screen } from '../../../setupTest';
+import { initializeTestStore, render, screen } from '../../../setupTest';
import SequenceContent from './SequenceContent';
describe('Sequence Content', () => {
- const mockData = {
- gated: false,
- courseId: '1',
- sequenceId: '1',
- unitId: '1',
- unitLoadedHandler: () => {},
- intl: {},
- };
+ let mockData;
+ let store;
+
+ beforeAll(async () => {
+ store = await initializeTestStore();
+ const { models, courseware } = store.getState();
+ mockData = {
+ gated: false,
+ courseId: courseware.courseId,
+ sequenceId: courseware.sequenceId,
+ unitId: models.sequences[courseware.sequenceId].unitIds[0],
+ unitLoadedHandler: () => {},
+ };
+ });
it('displays loading message', () => {
- render( , { initialState });
+ render( );
expect(screen.getByText('Loading learning sequence...')).toBeInTheDocument();
});
it('displays messages for the locked content', async () => {
- const { container } = render( , { initialState });
- expect(screen.getByText('Loading locked content messaging...')).toBeInTheDocument();
+ const { gatedContent } = store.getState().models.sequences[mockData.sequenceId];
+ const { container } = render( );
+ expect(screen.getByText('Loading locked content messaging...')).toBeInTheDocument();
expect(await screen.findByText('Content Locked')).toBeInTheDocument();
- expect(screen.getByText('test-sequence')).toBeInTheDocument();
expect(screen.queryByText('Loading locked content messaging...')).not.toBeInTheDocument();
expect(container.querySelector('svg')).toHaveClass('fa-lock');
- expect(screen.getByText(/You must complete the prerequisite/)).toBeInTheDocument();
+ expect(screen.getByText(
+ `You must complete the prerequisite: '${gatedContent.gatedSectionName}' to access this content.`,
+ )).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Go To Prerequisite Section' })).toBeInTheDocument();
});
it('displays message for no content', () => {
- render( , { initialState });
+ render( );
expect(screen.getByText('There is no content here.')).toBeInTheDocument();
});
});
diff --git a/src/courseware/course/sequence/Unit.test.jsx b/src/courseware/course/sequence/Unit.test.jsx
index 16c23c1182..6f65251a91 100644
--- a/src/courseware/course/sequence/Unit.test.jsx
+++ b/src/courseware/course/sequence/Unit.test.jsx
@@ -1,50 +1,66 @@
import React from 'react';
-import { cloneDeep } from 'lodash';
+import { Factory } from 'rosie';
import {
- initialState, loadUnit, messageEvent, render, screen, waitFor,
+ initializeTestStore, loadUnit, messageEvent, render, screen, waitFor,
} from '../../../setupTest';
import Unit from './Unit';
describe('Unit', () => {
- const mockData = {
- id: '3',
- courseId: '1',
- intl: {},
- };
+ let mockData;
+ const courseMetadata = Factory.build(
+ 'courseMetadata',
+ { content_type_gating_enabled: true },
+ );
+ const unitBlocks = [Factory.build(
+ 'block',
+ { type: 'problem' },
+ { courseId: courseMetadata.id },
+ ), Factory.build(
+ 'block',
+ { type: 'vertical', graded: true, bookmarked: true },
+ { courseId: courseMetadata.id },
+ )];
+ const [unit, gradedUnit] = unitBlocks;
+
+ beforeAll(async () => {
+ await initializeTestStore({ courseMetadata, unitBlocks });
+ mockData = {
+ id: unit.id,
+ courseId: courseMetadata.id,
+ };
+ });
it('renders correctly', () => {
- render( , { initialState });
+ render( );
expect(screen.getByText('Loading learning sequence...')).toBeInTheDocument();
- expect(screen.getByTitle(mockData.id)).toHaveAttribute('height', String(0));
- expect(screen.getByTitle(mockData.id)).toHaveAttribute(
+ const renderedUnit = screen.getByTitle(unit.display_name);
+ expect(renderedUnit).toHaveAttribute('height', String(0));
+ expect(renderedUnit).toHaveAttribute(
'src', `http://localhost:18000/xblock/${mockData.id}?show_title=0&show_bookmark_button=0`,
);
});
it('renders proper message for gated content', () => {
- // Clone initialState.
- const testState = cloneDeep(initialState);
- testState.models.units[mockData.id].graded = true;
- render( , { initialState: testState });
+ render( );
- expect(screen.getByText('Loading locked content messaging...')).toBeInTheDocument();
expect(screen.getByText('Loading learning sequence...')).toBeInTheDocument();
+ expect(screen.getByText('Loading locked content messaging...')).toBeInTheDocument();
});
it('handles receiving MessageEvent', async () => {
- render( , { initialState });
+ render( );
loadUnit();
// Loading message is gone now.
await waitFor(() => expect(screen.queryByText('Loading learning sequence...')).not.toBeInTheDocument());
// Iframe's height is set via message.
- expect(screen.getByTitle(mockData.id)).toHaveAttribute('height', String(messageEvent.payload.height));
+ expect(screen.getByTitle(unit.display_name)).toHaveAttribute('height', String(messageEvent.payload.height));
});
it('calls onLoaded after receiving MessageEvent', async () => {
const onLoaded = jest.fn();
- render( , { initialState });
+ render( );
loadUnit();
await waitFor(() => expect(onLoaded).toHaveBeenCalledTimes(1));
@@ -54,24 +70,24 @@ describe('Unit', () => {
const onLoaded = jest.fn();
// Clone message and set different height.
const testMessageWithOtherHeight = { ...messageEvent, payload: { height: 200 } };
- render( , { initialState });
+ render( );
loadUnit();
- await waitFor(() => expect(screen.getByTitle(mockData.id)).toHaveAttribute('height', String(messageEvent.payload.height)));
+ await waitFor(() => expect(screen.getByTitle(unit.display_name)).toHaveAttribute('height', String(messageEvent.payload.height)));
window.postMessage(testMessageWithOtherHeight, '*');
- await waitFor(() => expect(screen.getByTitle(mockData.id)).toHaveAttribute('height', String(testMessageWithOtherHeight.payload.height)));
+ await waitFor(() => expect(screen.getByTitle(unit.display_name)).toHaveAttribute('height', String(testMessageWithOtherHeight.payload.height)));
expect(onLoaded).toHaveBeenCalledTimes(1);
});
it('ignores MessageEvent with unhandled type', async () => {
// Clone message and set different type.
const testMessageWithUnhandledType = { ...messageEvent, type: 'wrong type' };
- render( , { initialState });
+ render( );
window.postMessage(testMessageWithUnhandledType, '*');
// HACK: We don't have a function we could reliably await here, so this test relies on the timeout of `waitFor`.
await expect(waitFor(
- () => expect(screen.getByTitle(mockData.id)).toHaveAttribute('height', String(testMessageWithUnhandledType.payload.height)),
+ () => expect(screen.getByTitle(unit.display_name)).toHaveAttribute('height', String(testMessageWithUnhandledType.payload.height)),
{ timeout: 100 },
)).rejects.toThrowError(/Expected the element to have attribute/);
});
diff --git a/src/courseware/course/sequence/sequence-navigation/SequenceNavigation.jsx b/src/courseware/course/sequence/sequence-navigation/SequenceNavigation.jsx
index 44ab4f7077..c5d38cb8ab 100644
--- a/src/courseware/course/sequence/sequence-navigation/SequenceNavigation.jsx
+++ b/src/courseware/course/sequence/sequence-navigation/SequenceNavigation.jsx
@@ -11,6 +11,7 @@ import UnitButton from './UnitButton';
import SequenceNavigationTabs from './SequenceNavigationTabs';
import { useSequenceNavigationMetadata } from './hooks';
import { useModel } from '../../../../generic/model-store';
+import { LOADED } from '../../../data/slice';
export default function SequenceNavigation({
unitId,
@@ -22,8 +23,10 @@ export default function SequenceNavigation({
}) {
const sequence = useModel('sequences', sequenceId);
const { isFirstUnit, isLastUnit } = useSequenceNavigationMetadata(sequenceId, unitId);
- const isLocked = sequence.gatedContent !== undefined && sequence.gatedContent.gated;
const sequenceStatus = useSelector(state => state.courseware.sequenceStatus);
+ const isLocked = sequenceStatus === LOADED ? (
+ sequence.gatedContent !== undefined && sequence.gatedContent.gated
+ ) : undefined;
const renderUnitButtons = () => {
if (isLocked) {
@@ -46,7 +49,7 @@ export default function SequenceNavigation({
);
};
- return sequenceStatus === 'loaded' && (
+ return sequenceStatus === LOADED && (
diff --git a/src/courseware/course/sequence/sequence-navigation/SequenceNavigation.test.jsx b/src/courseware/course/sequence/sequence-navigation/SequenceNavigation.test.jsx
index 185b73acc9..4ca50dabc4 100644
--- a/src/courseware/course/sequence/sequence-navigation/SequenceNavigation.test.jsx
+++ b/src/courseware/course/sequence/sequence-navigation/SequenceNavigation.test.jsx
@@ -1,7 +1,7 @@
import React from 'react';
-import { cloneDeep } from 'lodash';
+import { Factory } from 'rosie';
import {
- initialState, render, screen, testUnits, fireEvent, getByText,
+ render, screen, fireEvent, getByText, initializeTestStore,
} from '../../../../setupTest';
import SequenceNavigation from './SequenceNavigation';
import useIndexOfLastVisibleChild from '../../../../generic/tabs/useIndexOfLastVisibleChild';
@@ -11,59 +11,79 @@ jest.mock('../../../../generic/tabs/useIndexOfLastVisibleChild');
useIndexOfLastVisibleChild.mockReturnValue([0, null, null]);
describe('Sequence Navigation', () => {
- const mockData = {
- previousSequenceHandler: () => {},
- onNavigate: () => {},
- nextSequenceHandler: () => {},
- sequenceId: '1',
- unitId: '3',
- };
-
- it('is empty while loading', () => {
- // Clone initialState.
- const testState = cloneDeep(initialState);
- testState.courseware.sequenceStatus = 'loading';
-
- const { container } = render(
- ,
- { initialState: testState },
- );
+ let mockData;
+ const courseMetadata = Factory.build('courseMetadata');
+ const unitBlocks = Array.from({ length: 3 }).map(() => Factory.build(
+ 'block',
+ { type: 'problem' },
+ { courseId: courseMetadata.id },
+ ));
+
+ beforeAll(async () => {
+ const store = await initializeTestStore({ courseMetadata, unitBlocks });
+ const { courseware } = store.getState();
+ mockData = {
+ unitId: unitBlocks[1].id,
+ sequenceId: courseware.sequenceId,
+ previousSequenceHandler: () => {},
+ onNavigate: () => {},
+ nextSequenceHandler: () => {},
+ };
+ });
+
+ it('is empty while loading', async () => {
+ const testStore = await initializeTestStore({ excludeFetchSequence: true }, false);
+ const { container } = render( , { store: testStore });
+
expect(container).toBeEmptyDOMElement();
});
it('renders empty div without unitId', () => {
- const { container } = render( , { initialState });
+ const { container } = render( );
expect(getByText(container, (content, element) => (
element.tagName.toLowerCase() === 'div' && element.getAttribute('style')))).toBeEmptyDOMElement();
});
- it('renders locked button for gated content', () => {
- const testState = cloneDeep(initialState);
- testState.models.sequences['1'].gatedContent = { gated: true };
- const onNavigate = jest.fn();
- render( , { initialState: testState });
-
- const unitButton = screen.getByTitle(mockData.unitId);
+ it('renders locked button for gated content', async () => {
+ const sequenceBlock = [Factory.build(
+ 'block',
+ { type: 'sequential', children: [unitBlocks.map(block => block.id)] },
+ { courseId: courseMetadata.id },
+ )];
+ const sequenceMetadata = [Factory.build(
+ 'sequenceMetadata',
+ { courseId: courseMetadata.id, gated_content: { gated: true } },
+ { unitBlocks, sequenceBlock: sequenceBlock[0] },
+ )];
+ const testStore = await initializeTestStore({ unitBlocks, sequenceBlock, sequenceMetadata }, false);
+ const testData = {
+ ...mockData,
+ sequenceId: sequenceBlock[0].id,
+ onNavigate: jest.fn(),
+ };
+ render( , { store: testStore });
+
+ const unitButton = screen.getByTitle(unitBlocks[1].display_name);
fireEvent.click(unitButton);
// The unit button should not work for gated content.
- expect(onNavigate).not.toHaveBeenCalled();
+ expect(testData.onNavigate).not.toHaveBeenCalled();
// TODO: Not sure if this is working as expected, because the `contentType="lock"` will be overridden by the value
// from Redux. To make this provide a `fa-icon` lock we could introduce something like `overriddenContentType`.
- expect(unitButton.firstChild).toHaveClass('fa-book');
+ expect(unitButton.firstChild).toHaveClass('fa-edit');
});
it('renders correctly and handles unit button clicks', () => {
const onNavigate = jest.fn();
- render( , { initialState });
+ render( );
const unitButtons = screen.getAllByRole('button', { name: /\d+/ });
- expect(unitButtons).toHaveLength(testUnits.length);
+ expect(unitButtons).toHaveLength(unitButtons.length);
unitButtons.forEach(button => fireEvent.click(button));
expect(onNavigate).toHaveBeenCalledTimes(unitButtons.length);
});
it('has both navigation buttons enabled for a non-corner unit of the sequence', () => {
- render( , { initialState });
+ render( );
screen.getAllByRole('button', { name: /previous|next/i }).forEach(button => {
expect(button).toBeEnabled();
@@ -71,7 +91,7 @@ describe('Sequence Navigation', () => {
});
it('has the "Previous" button disabled for the first unit of the sequence', () => {
- render( , { initialState });
+ render( );
expect(screen.getByRole('button', { name: /previous/i })).toBeDisabled();
expect(screen.getByRole('button', { name: /next/i })).toBeEnabled();
@@ -80,9 +100,8 @@ describe('Sequence Navigation', () => {
it('has the "Next" button disabled for the last unit of the sequence', () => {
render( , { initialState });
+ unitId={unitBlocks[unitBlocks.length - 1].id}
+ />);
expect(screen.getByRole('button', { name: /previous/i })).toBeEnabled();
expect(screen.getByRole('button', { name: /next/i })).toBeDisabled();
@@ -91,10 +110,7 @@ describe('Sequence Navigation', () => {
it('handles "Previous" and "Next" click', () => {
const previousSequenceHandler = jest.fn();
const nextSequenceHandler = jest.fn();
- render( , { initialState });
+ render( );
fireEvent.click(screen.getByRole('button', { name: /previous/i }));
expect(previousSequenceHandler).toHaveBeenCalledTimes(1);
diff --git a/src/courseware/course/sequence/sequence-navigation/SequenceNavigationDropdown.test.jsx b/src/courseware/course/sequence/sequence-navigation/SequenceNavigationDropdown.test.jsx
index 81eb3b7141..8a9e380ca3 100644
--- a/src/courseware/course/sequence/sequence-navigation/SequenceNavigationDropdown.test.jsx
+++ b/src/courseware/course/sequence/sequence-navigation/SequenceNavigationDropdown.test.jsx
@@ -1,39 +1,53 @@
import React from 'react';
+import { Factory } from 'rosie';
+import { getAllByRole } from '@testing-library/dom';
import SequenceNavigationDropdown from './SequenceNavigationDropdown';
import {
- initialState, render, screen, testUnits, fireEvent,
+ render, screen, fireEvent, initializeTestStore,
} from '../../../../setupTest';
describe('Sequence Navigation Dropdown', () => {
- const mockData = {
- unitId: '1',
- onNavigate: () => {},
- showCompletion: false,
- unitIds: testUnits,
- };
+ let mockData;
+ const courseMetadata = Factory.build('courseMetadata');
+ const unitBlocks = Array.from({ length: 3 }).map(() => Factory.build(
+ 'block',
+ { type: 'vertical' },
+ { courseId: courseMetadata.id },
+ ));
+
+ beforeAll(async () => {
+ await initializeTestStore({ courseMetadata, unitBlocks });
+ mockData = {
+ unitId: unitBlocks[1].id,
+ unitIds: unitBlocks.map(block => block.id),
+ showCompletion: false,
+ onNavigate: () => {},
+ };
+ });
it('renders correctly without units', () => {
render( );
expect(screen.getByRole('button')).toHaveTextContent('0 of 0');
});
- testUnits.forEach(unitId => {
- it(`displays proper text for unit ${unitId} on mobile`, () => {
- render( , { initialState });
- expect(screen.getByRole('button')).toHaveTextContent(`${unitId} of ${testUnits.length}`);
+ unitBlocks.forEach((unit, index) => {
+ it(`displays proper text for unit ${index + 1} on mobile`, () => {
+ render( );
+ expect(screen.getByRole('button')).toHaveTextContent(`${index + 1} of ${unitBlocks.length}`);
});
});
- testUnits.forEach(unitId => {
- it(`marks unit ${unitId} as active`, () => {
- render( , { initialState });
+ unitBlocks.forEach((unit, indedx) => {
+ it(`marks unit ${indedx + 1} as active`, () => {
+ const { container } = render( );
+ const dropdownMenu = container.querySelector('.dropdown-menu');
// Only the current unit should be marked as active.
- screen.getAllByText(/^\d$/).forEach(element => {
- if (element.textContent === unitId) {
- expect(element.parentElement).toHaveClass('active');
+ getAllByRole(dropdownMenu, 'button', { hidden: true }).forEach(button => {
+ if (button.textContent === unit.display_name) {
+ expect(button).toHaveClass('active');
} else {
- expect(element.parentElement).not.toHaveClass('active');
+ expect(button).not.toHaveClass('active');
}
});
});
@@ -41,12 +55,13 @@ describe('Sequence Navigation Dropdown', () => {
it('handles the clicks', () => {
const onNavigate = jest.fn();
- render( , { initialState });
+ const { container } = render( );
- screen.getAllByText(/^\d+$/).forEach(element => fireEvent.click(element));
- expect(onNavigate).toHaveBeenCalledTimes(testUnits.length);
- testUnits.forEach(unit => {
- expect(onNavigate).toHaveBeenNthCalledWith(Number(unit), unit);
+ const dropdownMenu = container.querySelector('.dropdown-menu');
+ getAllByRole(dropdownMenu, 'button', { hidden: true }).forEach(button => fireEvent.click(button));
+ expect(onNavigate).toHaveBeenCalledTimes(unitBlocks.length);
+ unitBlocks.forEach((unit, index) => {
+ expect(onNavigate).toHaveBeenNthCalledWith(index + 1, unit.id);
});
});
});
diff --git a/src/courseware/course/sequence/sequence-navigation/SequenceNavigationTabs.test.jsx b/src/courseware/course/sequence/sequence-navigation/SequenceNavigationTabs.test.jsx
index c047181b57..bb2321412d 100644
--- a/src/courseware/course/sequence/sequence-navigation/SequenceNavigationTabs.test.jsx
+++ b/src/courseware/course/sequence/sequence-navigation/SequenceNavigationTabs.test.jsx
@@ -1,7 +1,6 @@
import React from 'react';
-import {
- initialState, render, screen, testUnits,
-} from '../../../../setupTest';
+import { Factory } from 'rosie';
+import { initializeTestStore, render, screen } from '../../../../setupTest';
import SequenceNavigationTabs from './SequenceNavigationTabs';
import useIndexOfLastVisibleChild from '../../../../generic/tabs/useIndexOfLastVisibleChild';
@@ -9,27 +8,48 @@ import useIndexOfLastVisibleChild from '../../../../generic/tabs/useIndexOfLastV
jest.mock('../../../../generic/tabs/useIndexOfLastVisibleChild');
describe('Sequence Navigation Tabs', () => {
- const mockData = {
- unitId: '2',
- onNavigate: () => {
- },
- showCompletion: false,
- unitIds: testUnits,
- };
+ let mockData;
+
+ const courseMetadata = Factory.build('courseMetadata');
+ const unitBlocks = [Factory.build(
+ 'block',
+ { type: 'problem' },
+ { courseId: courseMetadata.id },
+ ), Factory.build(
+ 'block',
+ { type: 'video', complete: true },
+ { courseId: courseMetadata.id },
+ ), Factory.build(
+ 'block',
+ { type: 'other', complete: true, bookmarked: true },
+ { courseId: courseMetadata.id },
+ )];
+ const activeBlockNumber = 2;
+
+ beforeAll(async () => {
+ await initializeTestStore({ courseMetadata, unitBlocks });
+ mockData = {
+ // Blocks are numbered from 1 in the UI, so we're decreasing this by 1 to have correct block's ID in the array.
+ unitId: unitBlocks[activeBlockNumber - 1].id,
+ onNavigate: () => {},
+ showCompletion: false,
+ unitIds: unitBlocks.map(unit => unit.id),
+ };
+ });
it('renders unit buttons', () => {
useIndexOfLastVisibleChild.mockReturnValue([0, null, null]);
- render( , { initialState });
+ render( );
- expect(screen.getAllByRole('button').length).toEqual(testUnits.length);
+ expect(screen.getAllByRole('button')).toHaveLength(unitBlocks.length);
});
it('renders unit buttons and dropdown button', () => {
useIndexOfLastVisibleChild.mockReturnValue([-1, null, null]);
- render( , { initialState });
+ render( );
- expect(screen.getByRole('button', { name: `${mockData.unitId} of ${testUnits.length}` }))
+ expect(screen.getAllByRole('button')).toHaveLength(unitBlocks.length + 1);
+ expect(screen.getByRole('button', { name: `${activeBlockNumber} of ${unitBlocks.length}` }))
.toHaveClass('dropdown-button');
- expect(screen.getAllByRole('button').length).toEqual(testUnits.length + 1);
});
});
diff --git a/src/courseware/course/sequence/sequence-navigation/UnitButton.test.jsx b/src/courseware/course/sequence/sequence-navigation/UnitButton.test.jsx
index 08fb90f6ba..5547432b62 100644
--- a/src/courseware/course/sequence/sequence-navigation/UnitButton.test.jsx
+++ b/src/courseware/course/sequence/sequence-navigation/UnitButton.test.jsx
@@ -1,70 +1,76 @@
import React from 'react';
-import { render, screen, fireEvent } from '../../../../setupTest';
+import { Factory } from 'rosie';
+import {
+ fireEvent, initializeTestStore, render, screen,
+} from '../../../../setupTest';
import UnitButton from './UnitButton';
describe('Unit Button', () => {
- const initialState = {
- models: {
- units: {
- other: {
- contentType: 'other',
- title: 'other-unit',
- },
- problem: {
- contentType: 'problem',
- title: 'problem-unit',
- complete: true,
- bookmarked: true,
- },
- },
- },
- };
+ let mockData;
+ const courseMetadata = Factory.build('courseMetadata');
+ const unitBlocks = [Factory.build(
+ 'block',
+ { type: 'problem' },
+ { courseId: courseMetadata.id },
+ ), Factory.build(
+ 'block',
+ { type: 'video', complete: true },
+ { courseId: courseMetadata.id },
+ ), Factory.build(
+ 'block',
+ { type: 'other', complete: true, bookmarked: true },
+ { courseId: courseMetadata.id },
+ )];
+ const [unit, completedUnit, bookmarkedUnit] = unitBlocks;
- const mockData = {
- unitId: 'other',
- onClick: () => {},
- };
+ beforeAll(async () => {
+ await initializeTestStore({ courseMetadata, unitBlocks });
+ mockData = {
+ unitId: unit.id,
+ onClick: () => {},
+ };
+ });
it('hides title by default', () => {
- render( , { initialState });
- expect(screen.getByRole('button')).not.toHaveTextContent('other-unit');
+ render( );
+ expect(screen.getByRole('button')).not.toHaveTextContent(unit.display_name);
});
it('shows title', () => {
- render( , { initialState });
- expect(screen.getByRole('button')).toHaveTextContent('other-unit');
+ render( );
+ expect(screen.getByRole('button')).toHaveTextContent(unit.display_name);
});
it('does not show completion for non-completed unit', () => {
- const { container } = render( , { initialState });
+ const { container } = render( );
container.querySelectorAll('svg').forEach(icon => {
expect(icon).not.toHaveClass('fa-check');
});
});
it('shows completion for completed unit', () => {
- const { container } = render( , { initialState });
+ const { container } = render( );
const buttonIcons = container.querySelectorAll('svg');
- expect(buttonIcons).toHaveLength(3);
+ expect(buttonIcons).toHaveLength(2);
expect(buttonIcons[1]).toHaveClass('fa-check');
});
it('hides completion', () => {
- const { container } = render( , { initialState });
+ const { container } = render( );
container.querySelectorAll('svg').forEach(icon => {
expect(icon).not.toHaveClass('fa-check');
});
});
it('does not show bookmark', () => {
- const { container } = render( , { initialState });
+ const { container } = render( );
container.querySelectorAll('svg').forEach(icon => {
expect(icon).not.toHaveClass('fa-bookmark');
});
});
it('shows bookmark', () => {
- const { container } = render( , { initialState });
+ const { container } = render( );
const buttonIcons = container.querySelectorAll('svg');
expect(buttonIcons).toHaveLength(3);
expect(buttonIcons[2]).toHaveClass('fa-bookmark');
@@ -72,7 +78,7 @@ describe('Unit Button', () => {
it('handles the click', () => {
const onClick = jest.fn();
- render( , { initialState });
+ render( );
fireEvent.click(screen.getByRole('button'));
expect(onClick).toHaveBeenCalledTimes(1);
});
diff --git a/src/courseware/course/sequence/sequence-navigation/UnitIcon.test.jsx b/src/courseware/course/sequence/sequence-navigation/UnitIcon.test.jsx
index 8bfae84c87..3e94c8be41 100644
--- a/src/courseware/course/sequence/sequence-navigation/UnitIcon.test.jsx
+++ b/src/courseware/course/sequence/sequence-navigation/UnitIcon.test.jsx
@@ -1,5 +1,6 @@
import React from 'react';
-import { render } from '../../../../setupTest';
+import { Factory } from 'rosie';
+import { initializeTestStore, render } from '../../../../setupTest';
import UnitIcon from './UnitIcon';
describe('Unit Icon', () => {
@@ -12,15 +13,26 @@ describe('Unit Icon', () => {
undefined: 'fa-book',
};
- Object.entries(types).forEach(([key, value]) => {
- it(`renders correct icon for ${key} unit`, () => {
+ const courseMetadata = Factory.build('courseMetadata');
+ const unitBlocks = Object.keys(types).map(contentType => Factory.build(
+ 'block',
+ { id: contentType, type: contentType },
+ { courseId: courseMetadata.id },
+ ));
+
+ beforeAll(async () => {
+ await initializeTestStore({ courseMetadata, unitBlocks });
+ });
+
+ unitBlocks.forEach(block => {
+ it(`renders correct icon for ${block.type} unit`, () => {
// Suppress warning for undefined prop type.
- if (key === 'undefined') {
+ if (block.type === 'undefined') {
jest.spyOn(console, 'error').mockImplementation(() => {});
}
- const { container } = render( );
- expect(container.querySelector('svg')).toHaveClass(value);
+ const { container } = render( );
+ expect(container.querySelector('svg')).toHaveClass(types[block.type]);
});
});
});
diff --git a/src/courseware/course/sequence/sequence-navigation/UnitNavigation.test.jsx b/src/courseware/course/sequence/sequence-navigation/UnitNavigation.test.jsx
index 4ab8d29651..dcfc06fa9f 100644
--- a/src/courseware/course/sequence/sequence-navigation/UnitNavigation.test.jsx
+++ b/src/courseware/course/sequence/sequence-navigation/UnitNavigation.test.jsx
@@ -1,16 +1,29 @@
import React from 'react';
+import { Factory } from 'rosie';
import {
- initialState, render, screen, testUnits, fireEvent,
+ render, screen, fireEvent, initializeTestStore,
} from '../../../../setupTest';
import UnitNavigation from './UnitNavigation';
describe('Unit Navigation', () => {
- const mockData = {
- sequenceId: '1',
- unitId: '2',
- onClickPrevious: () => {},
- onClickNext: () => {},
- };
+ let mockData;
+ const courseMetadata = Factory.build('courseMetadata');
+ const unitBlocks = Array.from({ length: 3 }).map(() => Factory.build(
+ 'block',
+ { type: 'vertical' },
+ { courseId: courseMetadata.id },
+ ));
+
+ beforeAll(async () => {
+ const store = await initializeTestStore({ courseMetadata, unitBlocks });
+ const { courseware } = store.getState();
+ mockData = {
+ unitId: unitBlocks[1].id,
+ sequenceId: courseware.sequenceId,
+ onClickPrevious: () => {},
+ onClickNext: () => {},
+ };
+ });
it('renders correctly without units', () => {
render( {
expect(onClickNext).toHaveBeenCalledTimes(1);
});
- it('should have the navigation buttons enabled for the non-corner unit in the sequence', () => {
- render( , { initialState });
+ it('has the navigation buttons enabled for the non-corner unit in the sequence', () => {
+ render( );
+
screen.getAllByRole('button').forEach(button => {
expect(button).toBeEnabled();
});
});
- it('should have the "Previous" button disabled for the first unit in the sequence', () => {
- render( , { initialState });
+ it('has the "Previous" button disabled for the first unit in the sequence', () => {
+ render( );
+
expect(screen.getByRole('button', { name: /previous/i })).toBeDisabled();
expect(screen.getByRole('button', { name: /next/i })).toBeEnabled();
});
- it('should display "learn.end.of.course" message instead of the "Next" button for the last unit in the sequence', () => {
- render(
- , { initialState },
- );
+ it('displays "learn.end.of.course" message instead of the "Next" button for the last unit in the sequence', () => {
+ render( );
+
expect(screen.getByRole('button', { name: /previous/i })).toBeEnabled();
expect(screen.queryByRole('button', { name: /next/i })).not.toBeInTheDocument();
expect(screen.getByText("You've reached the end of this course!")).toBeInTheDocument();
diff --git a/src/courseware/data/__factories__/courseBlocks.factory.js b/src/courseware/data/__factories__/courseBlocks.factory.js
index 699d46f2ef..a04d7f3a21 100644
--- a/src/courseware/data/__factories__/courseBlocks.factory.js
+++ b/src/courseware/data/__factories__/courseBlocks.factory.js
@@ -1,37 +1,51 @@
import { Factory } from 'rosie'; // eslint-disable-line import/no-extraneous-dependencies
-
import './block.factory';
+// Generates an Array of block IDs, either from a single block or an array of blocks.
+const getIds = (attr) => {
+ const blocks = Array.isArray(attr) ? attr : [attr];
+ return blocks.map(block => block.id);
+};
+
+// Generates an Object in { [block.id]: block } format, either from a single block or an array of blocks.
+const getBlocks = (attr) => {
+ const blocks = Array.isArray(attr) ? attr : [attr];
+ // eslint-disable-next-line no-return-assign,no-sequences
+ return blocks.reduce((acc, block) => (acc[block.id] = block, acc), {});
+};
+
Factory.define('courseBlocks')
.option('courseId', 'course-v1:edX+DemoX+Demo_Course')
- .option('unit', ['courseId'], courseId => Factory.build(
- 'block',
- { type: 'vertical' },
- { courseId },
- ))
- .option('sequence', ['courseId', 'unit'], (courseId, child) => Factory.build(
+ .option('units', ['courseId'], courseId => ([
+ Factory.build(
+ 'block',
+ { type: 'vertical' },
+ { courseId },
+ ),
+ ]))
+ .option('sequence', ['courseId', 'units'], (courseId, child) => Factory.build(
'block',
- { type: 'sequential', children: [child.id] },
+ { type: 'sequential', children: getIds(child) },
{ courseId },
))
.option('section', ['courseId', 'sequence'], (courseId, child) => Factory.build(
'block',
- { type: 'chapter', children: [child.id] },
+ { type: 'chapter', children: getIds(child) },
{ courseId },
))
.option('course', ['courseId', 'section'], (courseId, child) => Factory.build(
'block',
- { type: 'course', children: [child.id] },
+ { type: 'course', children: getIds(child) },
{ courseId },
))
.attr(
'blocks',
- ['course', 'section', 'sequence', 'unit'],
- (course, section, sequence, unit) => ({
+ ['course', 'section', 'sequence', 'units'],
+ (course, section, sequence, units) => ({
[course.id]: course,
- [section.id]: section,
- [sequence.id]: sequence,
- [unit.id]: unit,
+ ...getBlocks(section),
+ ...getBlocks(sequence),
+ ...getBlocks(units),
}),
)
.attr('root', ['course'], course => course.id);
@@ -39,39 +53,39 @@ Factory.define('courseBlocks')
/**
* Builds a course with a single chapter, sequence, and unit.
*/
-export default function buildSimpleCourseBlocks(courseId, title) {
- const unitBlock = Factory.build(
+export default function buildSimpleCourseBlocks(courseId, title, options = {}) {
+ const unitBlocks = options.unitBlocks || [Factory.build(
'block',
{ type: 'vertical' },
{ courseId },
- );
- const sequenceBlock = Factory.build(
+ )];
+ const sequenceBlock = options.sequenceBlock || [Factory.build(
'block',
- { type: 'sequential', children: [unitBlock.id] },
+ { type: 'sequential', children: unitBlocks.map(block => block.id) },
{ courseId },
- );
- const sectionBlock = Factory.build(
+ )];
+ const sectionBlock = options.sectionBlock || Factory.build(
'block',
- { type: 'chapter', children: [sequenceBlock.id] },
+ { type: 'chapter', children: sequenceBlock.map(block => block.id) },
{ courseId },
);
- const courseBlock = Factory.build(
+ const courseBlock = options.courseBlocks || Factory.build(
'block',
{ type: 'course', display_name: title, children: [sectionBlock.id] },
{ courseId },
);
return {
- courseBlocks: Factory.build(
+ courseBlocks: options.courseBlocks || Factory.build(
'courseBlocks',
{ courseId },
{
- unit: unitBlock,
+ units: unitBlocks,
sequence: sequenceBlock,
section: sectionBlock,
course: courseBlock,
},
),
- unitBlock,
+ unitBlocks,
sequenceBlock,
sectionBlock,
courseBlock,
diff --git a/src/courseware/data/__factories__/sequenceMetadata.factory.js b/src/courseware/data/__factories__/sequenceMetadata.factory.js
index 073567245c..0ed157119c 100644
--- a/src/courseware/data/__factories__/sequenceMetadata.factory.js
+++ b/src/courseware/data/__factories__/sequenceMetadata.factory.js
@@ -1,6 +1,6 @@
import { Factory } from 'rosie'; // eslint-disable-line import/no-extraneous-dependencies
-
import './block.factory';
+import buildSimpleCourseBlocks from './courseBlocks.factory';
Factory.define('sequenceMetadata')
.option('courseId', (courseId) => {
@@ -33,8 +33,8 @@ Factory.define('sequenceMetadata')
.attr('gated_content', ['sequenceBlock'], sequenceBlock => ({
gated: false,
prereq_url: null,
- prereq_id: null,
- prereq_section_name: null,
+ prereq_id: `${sequenceBlock.id}-prereq`,
+ prereq_section_name: `${sequenceBlock.display_name}-prereq`,
gated_section_name: sequenceBlock.display_name,
}))
.attr('items', ['unitBlocks', 'sequenceBlock'], (unitBlocks, sequenceBlock) => unitBlocks.map(
@@ -42,10 +42,10 @@ Factory.define('sequenceMetadata')
href: '',
graded: unitBlock.graded,
id: unitBlock.id,
- bookmarked: false,
+ bookmarked: unitBlock.bookmarked || false,
path: `Chapter Display Name > ${sequenceBlock.display_name} > ${unitBlock.display_name}`,
- type: 'other',
- complete: null,
+ type: unitBlock.type,
+ complete: unitBlock.complete || null,
content: '',
page_title: unitBlock.display_name,
}),
@@ -61,3 +61,30 @@ Factory.define('sequenceMetadata')
show_completion: true,
banner_text: null,
});
+
+/**
+ * Build a simple course and simple metadata for its sequence.
+ */
+export default function buildSimpleCourseAndSequenceMetadata(options = {}) {
+ const courseMetadata = options.courseMetadata || Factory.build('courseMetadata', {
+ can_load_courseware: {
+ has_access: false,
+ },
+ });
+ const courseId = courseMetadata.id;
+ const simpleCourseBlocks = buildSimpleCourseBlocks(courseId, courseMetadata.name, options);
+ const { unitBlocks, sequenceBlock } = simpleCourseBlocks;
+ const sequenceMetadata = options.sequenceMetadata || sequenceBlock.map(block => Factory.build(
+ 'sequenceMetadata',
+ { courseId },
+ {
+ unitBlocks,
+ sequenceBlock: block,
+ },
+ ));
+ return {
+ ...simpleCourseBlocks,
+ courseMetadata,
+ sequenceMetadata,
+ };
+}
diff --git a/src/courseware/data/redux.test.js b/src/courseware/data/redux.test.js
index 79d6094850..10ec8e552a 100644
--- a/src/courseware/data/redux.test.js
+++ b/src/courseware/data/redux.test.js
@@ -9,7 +9,6 @@ import * as thunks from './thunks';
import executeThunk from '../../utils';
import buildSimpleCourseBlocks from './__factories__/courseBlocks.factory';
-import './__factories__';
import initializeMockApp from '../../setupTest';
import initializeStore from '../../store';
@@ -25,17 +24,17 @@ describe('Data layer integration tests', () => {
// building minimum set of api responses to test all thunks
const courseMetadata = Factory.build('courseMetadata');
const courseId = courseMetadata.id;
- const { courseBlocks, unitBlock, sequenceBlock } = buildSimpleCourseBlocks(courseId);
+ const { courseBlocks, unitBlocks, sequenceBlock } = buildSimpleCourseBlocks(courseId);
const sequenceMetadata = Factory.build(
'sequenceMetadata',
{},
- { courseId, unitBlocks: [unitBlock], sequenceBlock },
+ { courseId, unitBlocks, sequenceBlock: sequenceBlock[0] },
);
const courseUrl = `${courseBaseUrl}/${courseId}`;
const sequenceUrl = `${sequenceBaseUrl}/${sequenceMetadata.item_id}`;
- const sequenceId = sequenceBlock.id;
- const unitId = unitBlock.id;
+ const sequenceId = sequenceBlock[0].id;
+ const unitId = unitBlocks[0].id;
let store;
@@ -125,13 +124,13 @@ describe('Data layer integration tests', () => {
// ensure that initial state has no additional sequence info
let state = store.getState();
expect(state.models.sequences).toEqual({
- [sequenceBlock.id]: expect.not.objectContaining({
+ [sequenceId]: expect.not.objectContaining({
gatedContent: expect.any(Object),
activeUnitIndex: expect.any(Number),
}),
});
expect(state.models.units).toEqual({
- [unitBlock.id]: expect.not.objectContaining({
+ [unitId]: expect.not.objectContaining({
complete: null,
bookmarked: expect.any(Boolean),
}),
@@ -145,20 +144,20 @@ describe('Data layer integration tests', () => {
expect(state.courseware.sequenceStatus).toEqual('loading');
expect(state.courseware.sequenceId).toEqual(null);
- await executeThunk(thunks.fetchSequence(sequenceBlock.id), store.dispatch);
+ await executeThunk(thunks.fetchSequence(sequenceId), store.dispatch);
// Update our state variable again.
state = store.getState();
// ensure that additional information appeared in store
expect(state.models.sequences).toEqual({
- [sequenceBlock.id]: expect.objectContaining({
+ [sequenceId]: expect.objectContaining({
gatedContent: expect.any(Object),
activeUnitIndex: expect.any(Number),
}),
});
expect(state.models.units).toEqual({
- [unitBlock.id]: expect.objectContaining({
+ [unitId]: expect.objectContaining({
complete: null,
bookmarked: expect.any(Boolean),
}),
diff --git a/src/setupTest.js b/src/setupTest.js
index 82932e5887..17a988a4ac 100755
--- a/src/setupTest.js
+++ b/src/setupTest.js
@@ -1,22 +1,28 @@
import 'core-js/stable';
import 'regenerator-runtime/runtime';
import '@testing-library/jest-dom';
+import './courseware/data/__factories__';
+import './course-home/data/__factories__';
import { getConfig, mergeConfig } from '@edx/frontend-platform';
import { configure as configureI18n } from '@edx/frontend-platform/i18n';
import { configure as configureLogging } from '@edx/frontend-platform/logging';
-import { configure as configureAuth, MockAuthService } from '@edx/frontend-platform/auth';
+import { configure as configureAuth, getAuthenticatedHttpClient, MockAuthService } from '@edx/frontend-platform/auth';
import React from 'react';
import PropTypes from 'prop-types';
import { render as rtlRender } from '@testing-library/react';
-import { Provider } from 'react-redux';
import { configureStore } from '@reduxjs/toolkit';
import { IntlProvider } from 'react-intl';
+import MockAdapter from 'axios-mock-adapter';
+import AppProvider from '@edx/frontend-platform/react/AppProvider';
import { reducer as courseHomeReducer } from './course-home/data';
import { reducer as coursewareReducer } from './courseware/data/slice';
import { reducer as modelsReducer } from './generic/model-store';
import { UserMessagesProvider } from './generic/user-messages';
import appMessages from './i18n';
+import { fetchCourse, fetchSequence } from './courseware/data';
+import executeThunk from './utils';
+import buildSimpleCourseAndSequenceMetadata from './courseware/data/__factories__/sequenceMetadata.factory';
class MockLoggingService {
logInfo = jest.fn();
@@ -56,71 +62,8 @@ export default function initializeMockApp() {
window.scrollTo = jest.fn();
-// Generated units for convenience.
-const testUnits = [...Array(10).keys()].map(i => String(i + 1));
-
-// Base state containing various use-cases.
-const baseInitialState = {
- courseware: {
- sequenceStatus: 'loaded',
- courseStatus: 'loaded',
- courseId: '1',
- },
- models: {
- courses: {
- 1: {
- sectionIds: ['1'],
- contentTypeGatingEnabled: true,
- },
- },
- sections: {
- 1: {
- sequenceIds: ['1', '2'],
- },
- },
- sequences: {
- 1: {
- unitIds: testUnits,
- showCompletion: true,
- title: 'test-sequence',
- gatedContent: {
- gated: false,
- prereqId: '1',
- gatedSectionName: 'test-gated-section',
- },
- },
- 2: {
- unitIds: testUnits,
- showCompletion: true,
- title: 'test-sequence-2',
- },
- 3: {
- unitIds: testUnits,
- showCompletion: true,
- title: 'test-sequence-3',
- bannerText: 'test-banner-3',
- gatedContent: {
- gated: true,
- prereqId: '1',
- gatedSectionName: 'test-gated-section',
- },
- },
- },
- units: testUnits.reduce(
- (acc, unitId) => Object.assign(acc, {
- [unitId]: {
- id: unitId,
- contentType: 'other',
- title: unitId,
- },
- }),
- {},
- ),
- },
-};
-
// MessageEvent used for indicating that a unit has been loaded.
-const messageEvent = {
+export const messageEvent = {
type: 'plugin.resize',
payload: {
height: 300,
@@ -128,22 +71,62 @@ const messageEvent = {
};
// Send MessageEvent indicating that a unit has been loaded.
-function loadUnit(message = messageEvent) {
+export function loadUnit(message = messageEvent) {
window.postMessage(message, '*');
}
+let globalStore;
+
+export async function initializeTestStore(options = {}, overrideStore = true) {
+ const store = configureStore({
+ reducer: {
+ models: modelsReducer,
+ courseware: coursewareReducer,
+ courseHome: courseHomeReducer,
+ },
+ });
+ if (overrideStore) {
+ globalStore = store;
+ }
+ initializeMockApp();
+ const axiosMock = new MockAdapter(getAuthenticatedHttpClient());
+ axiosMock.reset();
+
+ const {
+ courseBlocks, sequenceBlock, courseMetadata, sequenceMetadata,
+ } = buildSimpleCourseAndSequenceMetadata(options);
+
+ const forbiddenCourseUrl = `${getConfig().LMS_BASE_URL}/api/courseware/course/${courseMetadata.id}`;
+ const courseBlocksUrlRegExp = new RegExp(`${getConfig().LMS_BASE_URL}/api/courses/v2/blocks/*`);
+
+ axiosMock.onGet(forbiddenCourseUrl).reply(200, courseMetadata);
+ axiosMock.onGet(courseBlocksUrlRegExp).reply(200, courseBlocks);
+ sequenceMetadata.forEach(metadata => {
+ const sequenceMetadataUrl = `${getConfig().LMS_BASE_URL}/api/courseware/sequence/${metadata.item_id}`;
+ axiosMock.onGet(sequenceMetadataUrl).reply(200, metadata);
+ });
+
+ axiosMock.onAny().reply((config) => {
+ // eslint-disable-next-line no-console
+ console.log(config.url);
+ return [200, {}];
+ });
+
+ // eslint-disable-next-line no-unused-expressions
+ !options.excludeFetchCourse && await executeThunk(fetchCourse(courseMetadata.id), store.dispatch);
+
+ if (!options.excludeFetchSequence) {
+ await Promise.all(sequenceBlock
+ .map(block => executeThunk(fetchSequence(block.id), store.dispatch)));
+ }
+
+ return store;
+}
+
function render(
ui,
{
- initialState = baseInitialState,
- store = configureStore({
- reducer: {
- models: modelsReducer,
- courseware: coursewareReducer,
- courseHome: courseHomeReducer,
- },
- preloadedState: initialState,
- }),
+ store = null,
...renderOptions
} = {},
) {
@@ -151,11 +134,11 @@ function render(
return (
// eslint-disable-next-line react/jsx-filename-extension
-
+
{children}
-
+
);
}
@@ -170,7 +153,7 @@ function render(
// Re-export everything.
export * from '@testing-library/react';
-// Override `render` method; export `screen` too to suppress errors.
+// Override `render` method.
export {
- render, testUnits, baseInitialState as initialState, messageEvent, loadUnit,
+ render,
};
From 7e07bb0f903a9564d381f9caf07548d897c68ee6 Mon Sep 17 00:00:00 2001
From: Agrendalath
Date: Thu, 23 Jul 2020 16:52:44 +0200
Subject: [PATCH 12/12] [TNL-7268] Fix tests after rebase
---
src/courseware/course/sequence/Sequence.test.jsx | 14 +++++++++-----
.../SequenceNavigation.test.jsx | 4 ++--
.../data/__factories__/sequenceMetadata.factory.js | 5 ++---
3 files changed, 13 insertions(+), 10 deletions(-)
diff --git a/src/courseware/course/sequence/Sequence.test.jsx b/src/courseware/course/sequence/Sequence.test.jsx
index 5c6108d3f9..966525b24d 100644
--- a/src/courseware/course/sequence/Sequence.test.jsx
+++ b/src/courseware/course/sequence/Sequence.test.jsx
@@ -53,10 +53,14 @@ describe('Sequence', () => {
};
const sequenceMetadata = [Factory.build(
'sequenceMetadata',
- { courseId: courseMetadata.id, gated_content: gatedContent },
- { unitBlocks, sequenceBlock: sequenceBlock[0] },
+ { gated_content: gatedContent },
+ { courseId: courseMetadata.id, unitBlocks, sequenceBlock: sequenceBlock[0] },
)];
- const testStore = await initializeTestStore({ unitBlocks, sequenceBlock, sequenceMetadata }, false);
+ const testStore = await initializeTestStore(
+ {
+ courseMetadata, unitBlocks, sequenceBlock, sequenceMetadata,
+ }, false,
+ );
const { container } = render(
,
{ store: testStore },
@@ -261,8 +265,8 @@ describe('Sequence', () => {
)];
const testSequenceMetadata = testSequenceBlock.map(block => Factory.build(
'sequenceMetadata',
- { courseId: courseMetadata.id },
- { unitBlocks: block.children.length ? unitBlocks : [], sequenceBlock: block },
+ {},
+ { courseId: courseMetadata.id, unitBlocks: block.children.length ? unitBlocks : [], sequenceBlock: block },
));
const innerTestStore = await initializeTestStore({
courseMetadata, unitBlocks, sequenceBlock: testSequenceBlock, sequenceMetadata: testSequenceMetadata,
diff --git a/src/courseware/course/sequence/sequence-navigation/SequenceNavigation.test.jsx b/src/courseware/course/sequence/sequence-navigation/SequenceNavigation.test.jsx
index 4ca50dabc4..885f9e1612 100644
--- a/src/courseware/course/sequence/sequence-navigation/SequenceNavigation.test.jsx
+++ b/src/courseware/course/sequence/sequence-navigation/SequenceNavigation.test.jsx
@@ -52,8 +52,8 @@ describe('Sequence Navigation', () => {
)];
const sequenceMetadata = [Factory.build(
'sequenceMetadata',
- { courseId: courseMetadata.id, gated_content: { gated: true } },
- { unitBlocks, sequenceBlock: sequenceBlock[0] },
+ { gated_content: { gated: true } },
+ { courseId: courseMetadata.id, unitBlocks, sequenceBlock: sequenceBlock[0] },
)];
const testStore = await initializeTestStore({ unitBlocks, sequenceBlock, sequenceMetadata }, false);
const testData = {
diff --git a/src/courseware/data/__factories__/sequenceMetadata.factory.js b/src/courseware/data/__factories__/sequenceMetadata.factory.js
index 0ed157119c..c53962da43 100644
--- a/src/courseware/data/__factories__/sequenceMetadata.factory.js
+++ b/src/courseware/data/__factories__/sequenceMetadata.factory.js
@@ -76,10 +76,9 @@ export default function buildSimpleCourseAndSequenceMetadata(options = {}) {
const { unitBlocks, sequenceBlock } = simpleCourseBlocks;
const sequenceMetadata = options.sequenceMetadata || sequenceBlock.map(block => Factory.build(
'sequenceMetadata',
- { courseId },
+ {},
{
- unitBlocks,
- sequenceBlock: block,
+ courseId, unitBlocks, sequenceBlock: block,
},
));
return {