523 lines
22 KiB
JavaScript
523 lines
22 KiB
JavaScript
import { getConfig, history } from '@edx/frontend-platform';
|
|
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
|
import { AppProvider } from '@edx/frontend-platform/react';
|
|
import { waitForElementToBeRemoved, fireEvent } from '@testing-library/dom';
|
|
import '@testing-library/jest-dom/extend-expect';
|
|
import { render, screen } from '@testing-library/react';
|
|
import React from 'react';
|
|
import { Route, Switch } from 'react-router';
|
|
import { Factory } from 'rosie';
|
|
import MockAdapter from 'axios-mock-adapter';
|
|
|
|
import { UserMessagesProvider } from '../generic/user-messages';
|
|
import tabMessages from '../tab-page/messages';
|
|
import { initializeMockApp } from '../setupTest';
|
|
|
|
import CoursewareContainer from './CoursewareContainer';
|
|
import { buildSimpleCourseBlocks, buildBinaryCourseBlocks } from '../shared/data/__factories__/courseBlocks.factory';
|
|
import initializeStore from '../store';
|
|
import { appendBrowserTimezoneToUrl } from '../utils';
|
|
import { buildOutlineFromBlocks } from './data/__factories__/learningSequencesOutline.factory';
|
|
|
|
// NOTE: Because the unit creates an iframe, we choose to mock it out as its rendering isn't
|
|
// pertinent to this test. Instead, we render a simple div that displays the properties we expect
|
|
// to have been passed into the component. Separate tests can handle unit rendering, but this
|
|
// proves that the component is rendered and receives the correct props. We probably COULD render
|
|
// Unit.jsx and its iframe in this test, but it's already complex enough.
|
|
function MockUnit({ courseId, id }) { // eslint-disable-line react/prop-types
|
|
return (
|
|
<div className="fake-unit">Unit Contents {courseId} {id}</div>
|
|
);
|
|
}
|
|
|
|
jest.mock(
|
|
'./course/sequence/Unit',
|
|
() => MockUnit,
|
|
);
|
|
|
|
jest.mock('@edx/frontend-platform/analytics');
|
|
|
|
initializeMockApp();
|
|
|
|
describe('CoursewareContainer', () => {
|
|
let store;
|
|
let component;
|
|
let axiosMock;
|
|
|
|
// This is a standard set of data that can be used in CoursewareContainer tests.
|
|
// By default, `setUpMockRequests()` will configure the mock LMS API to return use this data.
|
|
// Certain test cases override these in order to test with special blocks/metadata.
|
|
const defaultCourseMetadata = Factory.build('courseMetadata');
|
|
const defaultCourseHomeMetadata = Factory.build('courseHomeMetadata');
|
|
const defaultCourseId = defaultCourseMetadata.id;
|
|
const defaultUnitBlocks = [
|
|
Factory.build(
|
|
'block',
|
|
{ type: 'vertical' },
|
|
{ courseId: defaultCourseId },
|
|
),
|
|
Factory.build(
|
|
'block',
|
|
{ type: 'vertical' },
|
|
{ courseId: defaultCourseId },
|
|
),
|
|
Factory.build(
|
|
'block',
|
|
{ type: 'vertical' },
|
|
{ courseId: defaultCourseId },
|
|
),
|
|
];
|
|
const {
|
|
courseBlocks: defaultCourseBlocks,
|
|
sequenceBlocks: [defaultSequenceBlock],
|
|
} = buildSimpleCourseBlocks(
|
|
defaultCourseId,
|
|
defaultCourseHomeMetadata.title,
|
|
{ unitBlocks: defaultUnitBlocks },
|
|
);
|
|
|
|
beforeEach(() => {
|
|
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
|
|
|
|
store = initializeStore();
|
|
|
|
component = (
|
|
<AppProvider store={store}>
|
|
<UserMessagesProvider>
|
|
<Switch>
|
|
<Route
|
|
path={[
|
|
'/course/:courseId/:sequenceId/:unitId',
|
|
'/course/:courseId/:sequenceId',
|
|
'/course/:courseId',
|
|
]}
|
|
component={CoursewareContainer}
|
|
/>
|
|
</Switch>
|
|
</UserMessagesProvider>
|
|
</AppProvider>
|
|
);
|
|
});
|
|
|
|
function setUpMockRequests(options = {}) {
|
|
// If we weren't given course blocks or metadata, use the defaults.
|
|
const courseBlocks = options.courseBlocks || defaultCourseBlocks;
|
|
const courseOutline = buildOutlineFromBlocks(courseBlocks);
|
|
const courseMetadata = options.courseMetadata || defaultCourseMetadata;
|
|
const courseHomeMetadata = options.courseHomeMetadata || defaultCourseHomeMetadata;
|
|
const courseId = courseMetadata.id;
|
|
// If we weren't given a list of sequence metadatas for URL mocking,
|
|
// then construct it ourselves by looking at courseBlocks.
|
|
const sequenceMetadatas = options.sequenceMetadatas || (
|
|
Object.values(courseBlocks.blocks)
|
|
.filter(block => block.type === 'sequential')
|
|
.map(sequenceBlock => Factory.build(
|
|
'sequenceMetadata',
|
|
{},
|
|
{
|
|
courseId,
|
|
sequenceBlock,
|
|
unitBlocks: sequenceBlock.children.map(unitId => courseBlocks.blocks[unitId]),
|
|
},
|
|
))
|
|
);
|
|
|
|
const learningSequencesUrlRegExp = new RegExp(`${getConfig().LMS_BASE_URL}/api/learning_sequences/v1/course_outline/*`);
|
|
axiosMock.onGet(learningSequencesUrlRegExp).reply(200, courseOutline);
|
|
|
|
const courseMetadataUrl = appendBrowserTimezoneToUrl(`${getConfig().LMS_BASE_URL}/api/courseware/course/${courseId}`);
|
|
axiosMock.onGet(courseMetadataUrl).reply(200, courseMetadata);
|
|
|
|
const courseHomeMetadataUrl = appendBrowserTimezoneToUrl(`${getConfig().LMS_BASE_URL}/api/course_home/course_metadata/${courseId}`);
|
|
axiosMock.onGet(courseHomeMetadataUrl).reply(200, courseHomeMetadata);
|
|
|
|
sequenceMetadatas.forEach(sequenceMetadata => {
|
|
const sequenceMetadataUrl = `${getConfig().LMS_BASE_URL}/api/courseware/sequence/${sequenceMetadata.item_id}`;
|
|
axiosMock.onGet(sequenceMetadataUrl).reply(200, sequenceMetadata);
|
|
const proctoredExamApiUrl = `${getConfig().LMS_BASE_URL}/api/edx_proctoring/v1/proctored_exam/attempt/course_id/${courseId}/content_id/${sequenceMetadata.item_id}?is_learning_mfe=true`;
|
|
axiosMock.onGet(proctoredExamApiUrl).reply(200, { exam: {}, active_attempt: {} });
|
|
});
|
|
|
|
// Set up handlers for noticing when units are in the sequence spot
|
|
const courseBlocksUrlRegExp = new RegExp(`${getConfig().LMS_BASE_URL}/api/courses/v2/blocks/*`);
|
|
axiosMock.onGet(courseBlocksUrlRegExp).reply(200, courseBlocks);
|
|
Object.values(courseBlocks.blocks)
|
|
.filter(block => block.type === 'vertical')
|
|
.forEach(unitBlock => {
|
|
const sequenceMetadataUrl = `${getConfig().LMS_BASE_URL}/api/courseware/sequence/${unitBlock.id}`;
|
|
axiosMock.onGet(sequenceMetadataUrl).reply(422, {});
|
|
});
|
|
|
|
const discussionConfigUrl = new RegExp(`${getConfig().LMS_BASE_URL}/api/discussion/v1/courses/*`);
|
|
axiosMock.onGet(discussionConfigUrl).reply(200, { provider: 'legacy' });
|
|
}
|
|
|
|
async function loadContainer() {
|
|
const { container } = render(component);
|
|
// Wait for the page spinner to be removed, such that we can wait for our main
|
|
// content to load before making any assertions.
|
|
await waitForElementToBeRemoved(screen.getByRole('status'));
|
|
return container;
|
|
}
|
|
|
|
it('should initialize to show a spinner', () => {
|
|
history.push('/course/abc123');
|
|
render(component);
|
|
|
|
const spinner = screen.getByRole('status');
|
|
|
|
expect(spinner.firstChild).toContainHTML(
|
|
`<span class="sr-only">${tabMessages.loading.defaultMessage}</span>`,
|
|
);
|
|
});
|
|
|
|
describe('when receiving successful course data', () => {
|
|
const courseMetadata = defaultCourseMetadata;
|
|
const courseHomeMetadata = defaultCourseHomeMetadata;
|
|
const courseId = defaultCourseId;
|
|
|
|
function assertLoadedHeader(container) {
|
|
const courseHeader = container.querySelector('.learning-header');
|
|
// Ensure the course number and org appear - this proves we loaded course metadata properly.
|
|
expect(courseHeader).toHaveTextContent(courseHomeMetadata.number);
|
|
expect(courseHeader).toHaveTextContent(courseHomeMetadata.org);
|
|
// Ensure the course title is showing up in the header. This means we loaded course blocks properly.
|
|
expect(courseHeader.querySelector('.course-title')).toHaveTextContent(courseHomeMetadata.title);
|
|
}
|
|
|
|
function assertSequenceNavigation(container, expectedUnitCount = 3) {
|
|
// Ensure we had appropriate sequence navigation buttons. We should only have one unit.
|
|
const sequenceNavButtons = container.querySelectorAll('nav.sequence-navigation button');
|
|
expect(sequenceNavButtons).toHaveLength(expectedUnitCount + 2);
|
|
|
|
expect(sequenceNavButtons[0]).toHaveTextContent('Previous');
|
|
// Prove this button is rendering an SVG tasks icon, meaning it's a unit/vertical.
|
|
expect(sequenceNavButtons[1].querySelector('svg')).toHaveClass('fa-tasks');
|
|
expect(sequenceNavButtons[sequenceNavButtons.length - 1]).toHaveTextContent('Next');
|
|
}
|
|
|
|
beforeEach(async () => {
|
|
// On page load, SequenceContext attempts to scroll to the top of the page.
|
|
global.scrollTo = jest.fn();
|
|
setUpMockRequests();
|
|
});
|
|
|
|
describe('when the URL only contains a course ID', () => {
|
|
const sequenceBlock = defaultSequenceBlock;
|
|
const unitBlocks = defaultUnitBlocks;
|
|
|
|
it('should use the resume block response to pick a unit if it contains one', async () => {
|
|
axiosMock.onGet(`${getConfig().LMS_BASE_URL}/api/courseware/resume/${courseId}`).reply(200, {
|
|
sectionId: sequenceBlock.id,
|
|
unitId: unitBlocks[1].id,
|
|
});
|
|
|
|
history.push(`/course/${courseId}`);
|
|
const container = await loadContainer();
|
|
|
|
assertLoadedHeader(container);
|
|
assertSequenceNavigation(container);
|
|
|
|
expect(container.querySelector('.fake-unit')).toHaveTextContent('Unit Contents');
|
|
expect(container.querySelector('.fake-unit')).toHaveTextContent(courseId);
|
|
expect(container.querySelector('.fake-unit')).toHaveTextContent(unitBlocks[1].id);
|
|
});
|
|
|
|
it('should use the first sequence ID and activeUnitIndex if the resume block response is empty', async () => {
|
|
// set the position to the third unit so we can prove activeUnitIndex is working
|
|
const sequenceMetadata = Factory.build(
|
|
'sequenceMetadata',
|
|
{ position: 3 }, // position index is 1-based and is converted to 0-based for activeUnitIndex
|
|
{ courseId, unitBlocks, sequenceBlock },
|
|
);
|
|
setUpMockRequests({ sequenceMetadatas: [sequenceMetadata] });
|
|
|
|
// Note how there is no sectionId/unitId returned in this mock response!
|
|
axiosMock.onGet(`${getConfig().LMS_BASE_URL}/api/courseware/resume/${courseId}`).reply(200, {});
|
|
|
|
history.push(`/course/${courseId}`);
|
|
const container = await loadContainer();
|
|
|
|
assertLoadedHeader(container);
|
|
assertSequenceNavigation(container);
|
|
|
|
expect(container.querySelector('.fake-unit')).toHaveTextContent('Unit Contents');
|
|
expect(container.querySelector('.fake-unit')).toHaveTextContent(courseId);
|
|
expect(container.querySelector('.fake-unit')).toHaveTextContent(unitBlocks[2].id);
|
|
});
|
|
});
|
|
|
|
describe('when the URL contains a section ID instead of a sequence ID', () => {
|
|
const {
|
|
courseBlocks, unitTree, sequenceTree, sectionTree,
|
|
} = buildBinaryCourseBlocks(
|
|
courseId, courseHomeMetadata.title,
|
|
);
|
|
|
|
function setUrl(urlSequenceId, urlUnitId = null) {
|
|
history.push(`/course/${courseId}/${urlSequenceId}/${urlUnitId || ''}`);
|
|
}
|
|
|
|
function assertLocation(container, sequenceId, unitId) {
|
|
const expectedUrl = `http://localhost/course/${courseId}/${sequenceId}/${unitId}`;
|
|
expect(global.location.href).toEqual(expectedUrl);
|
|
expect(container.querySelector('.fake-unit')).toHaveTextContent(unitId);
|
|
}
|
|
|
|
beforeEach(async () => {
|
|
setUpMockRequests({ courseBlocks });
|
|
});
|
|
|
|
describe('when the URL contains a unit ID', () => {
|
|
it('should ignore the section ID and redirect based on the unit ID', async () => {
|
|
const urlUnit = unitTree[1][1][1];
|
|
setUrl(sectionTree[1].id, urlUnit.id);
|
|
const container = await loadContainer();
|
|
assertLoadedHeader(container);
|
|
assertSequenceNavigation(container, 2);
|
|
assertLocation(container, sequenceTree[1][1].id, urlUnit.id);
|
|
});
|
|
|
|
it('should ignore invalid unit IDs and redirect to the course root', async () => {
|
|
setUrl(sectionTree[1].id, 'foobar');
|
|
await loadContainer();
|
|
expect(global.location.href).toEqual(`http://localhost/course/${courseId}`);
|
|
});
|
|
});
|
|
|
|
describe('when the URL does not contain a unit ID', () => {
|
|
it('should choose a unit within the section\'s first sequence', async () => {
|
|
setUrl(sectionTree[1].id);
|
|
const container = await loadContainer();
|
|
assertLoadedHeader(container);
|
|
assertSequenceNavigation(container, 2);
|
|
assertLocation(container, sequenceTree[1][0].id, unitTree[1][0][0].id);
|
|
});
|
|
});
|
|
|
|
describe('when the section is empty', () => {
|
|
// Make a (shallow-)copy of the course blocks.
|
|
// Remove all descendents of the second section.
|
|
const blocksWithEmptySection = { ...courseBlocks.blocks };
|
|
blocksWithEmptySection[sectionTree[1].id] = {
|
|
...sectionTree[1],
|
|
children: [],
|
|
};
|
|
sequenceTree[1].forEach(sequence => { delete blocksWithEmptySection[sequence.id]; });
|
|
unitTree[1].flat().forEach(unit => { delete blocksWithEmptySection[unit.id]; });
|
|
const courseBlocksWithEmptySection = {
|
|
...courseBlocks,
|
|
blocks: blocksWithEmptySection,
|
|
};
|
|
|
|
beforeEach(async () => {
|
|
setUpMockRequests({ courseBlocks: courseBlocksWithEmptySection });
|
|
});
|
|
|
|
it('should ignore the section ID and instead redirect to the course root', async () => {
|
|
setUrl(sectionTree[1].id);
|
|
await loadContainer();
|
|
expect(global.location.href).toEqual(`http://localhost/course/${courseId}`);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('when the URL contains a unit marker', () => {
|
|
it('should redirect /first to the first unit', async () => {
|
|
history.push(`/course/${courseId}/${defaultSequenceBlock.id}/first`);
|
|
await loadContainer();
|
|
expect(global.location.href).toEqual(`http://localhost/course/${courseId}/${defaultSequenceBlock.id}/${defaultUnitBlocks[0].id}`);
|
|
});
|
|
|
|
it('should redirect /last to the last unit', async () => {
|
|
history.push(`/course/${courseId}/${defaultSequenceBlock.id}/last`);
|
|
await loadContainer();
|
|
expect(global.location.href).toEqual(`http://localhost/course/${courseId}/${defaultSequenceBlock.id}/${defaultUnitBlocks[2].id}`);
|
|
});
|
|
});
|
|
|
|
describe('when the URL only contains a unit ID', () => {
|
|
const { courseBlocks, unitTree, sequenceTree } = buildBinaryCourseBlocks(courseId, courseMetadata.name);
|
|
|
|
beforeEach(async () => {
|
|
setUpMockRequests({ courseBlocks });
|
|
});
|
|
|
|
it('should insert the sequence ID into the URL', async () => {
|
|
const unit = unitTree[1][0][1];
|
|
history.push(`/course/${courseId}/${unit.id}`);
|
|
const container = await loadContainer();
|
|
|
|
assertLoadedHeader(container);
|
|
assertSequenceNavigation(container, 2);
|
|
const expectedSequenceId = sequenceTree[1][0].id;
|
|
const expectedUrl = `http://localhost/course/${courseId}/${expectedSequenceId}/${unit.id}`;
|
|
expect(global.location.href).toEqual(expectedUrl);
|
|
expect(container.querySelector('.fake-unit')).toHaveTextContent(unit.id);
|
|
});
|
|
});
|
|
|
|
describe('when the URL contains a course ID and sequence ID', () => {
|
|
const sequenceBlock = defaultSequenceBlock;
|
|
const unitBlocks = defaultUnitBlocks;
|
|
|
|
it('should pick the first unit if position was not defined (activeUnitIndex becomes 0)', async () => {
|
|
history.push(`/course/${courseId}/${sequenceBlock.id}`);
|
|
const container = await loadContainer();
|
|
|
|
assertLoadedHeader(container);
|
|
assertSequenceNavigation(container);
|
|
|
|
expect(container.querySelector('.fake-unit')).toHaveTextContent('Unit Contents');
|
|
expect(container.querySelector('.fake-unit')).toHaveTextContent(courseId);
|
|
expect(container.querySelector('.fake-unit')).toHaveTextContent(unitBlocks[0].id);
|
|
});
|
|
|
|
it('should use activeUnitIndex to pick a unit from the sequence', async () => {
|
|
const sequenceMetadata = Factory.build(
|
|
'sequenceMetadata',
|
|
{ position: 3 }, // position index is 1-based and is converted to 0-based for activeUnitIndex
|
|
{ courseId, unitBlocks, sequenceBlock },
|
|
);
|
|
setUpMockRequests({ sequenceMetadatas: [sequenceMetadata] });
|
|
|
|
history.push(`/course/${courseId}/${sequenceBlock.id}`);
|
|
const container = await loadContainer();
|
|
|
|
assertLoadedHeader(container);
|
|
assertSequenceNavigation(container);
|
|
|
|
expect(container.querySelector('.fake-unit')).toHaveTextContent('Unit Contents');
|
|
expect(container.querySelector('.fake-unit')).toHaveTextContent(courseId);
|
|
expect(container.querySelector('.fake-unit')).toHaveTextContent(unitBlocks[2].id);
|
|
});
|
|
});
|
|
|
|
describe('when the URL contains a course, sequence, and unit ID', () => {
|
|
const sequenceBlock = defaultSequenceBlock;
|
|
const unitBlocks = defaultUnitBlocks;
|
|
|
|
it('should load the specified unit', async () => {
|
|
history.push(`/course/${courseId}/${sequenceBlock.id}/${unitBlocks[2].id}`);
|
|
const container = await loadContainer();
|
|
|
|
assertLoadedHeader(container);
|
|
assertSequenceNavigation(container);
|
|
|
|
expect(container.querySelector('.fake-unit')).toHaveTextContent('Unit Contents');
|
|
expect(container.querySelector('.fake-unit')).toHaveTextContent(courseId);
|
|
expect(container.querySelector('.fake-unit')).toHaveTextContent(unitBlocks[2].id);
|
|
});
|
|
|
|
it('should navigate between units and check block completion', async () => {
|
|
axiosMock.onPost(`${courseId}/xblock/${sequenceBlock.id}/handler/get_completion`).reply(200, {
|
|
complete: true,
|
|
});
|
|
|
|
history.push(`/course/${courseId}/${sequenceBlock.id}/${unitBlocks[0].id}`);
|
|
const container = await loadContainer();
|
|
|
|
const sequenceNavButtons = container.querySelectorAll('nav.sequence-navigation button');
|
|
const sequenceNextButton = sequenceNavButtons[4];
|
|
expect(sequenceNextButton).toHaveTextContent('Next');
|
|
fireEvent.click(sequenceNavButtons[4]);
|
|
|
|
expect(global.location.href).toEqual(`http://localhost/course/${courseId}/${sequenceBlock.id}/${unitBlocks[1].id}`);
|
|
});
|
|
});
|
|
|
|
describe('when the current sequence is an exam', () => {
|
|
const { location } = window;
|
|
|
|
beforeEach(() => {
|
|
delete window.location;
|
|
window.location = {
|
|
assign: jest.fn(),
|
|
};
|
|
});
|
|
|
|
afterEach(() => {
|
|
window.location = location;
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('when receiving a course_access error_code', () => {
|
|
function setUpWithDeniedStatus(errorCode) {
|
|
const courseMetadata = Factory.build('courseMetadata');
|
|
const courseHomeMetadata = Factory.build('courseHomeMetadata', {
|
|
course_access: {
|
|
has_access: false,
|
|
error_code: errorCode,
|
|
additional_context_user_message: 'uhoh oh no', // only used by audit_expired
|
|
developer_message: 'data_sharing_consent_url', // only used by data_sharing_access_required
|
|
},
|
|
});
|
|
|
|
const courseId = courseMetadata.id;
|
|
|
|
const { courseBlocks, sequenceBlocks, unitBlocks } = buildSimpleCourseBlocks(courseId, courseMetadata.name);
|
|
setUpMockRequests({ courseBlocks, courseMetadata, courseHomeMetadata });
|
|
history.push(`/course/${courseId}/${sequenceBlocks[0].id}/${unitBlocks[0].id}`);
|
|
return { courseMetadata, unitBlocks };
|
|
}
|
|
|
|
it('should go to course home for an enrollment_required error code', async () => {
|
|
const { courseMetadata } = setUpWithDeniedStatus('enrollment_required');
|
|
await loadContainer();
|
|
|
|
expect(global.location.href).toEqual(`http://localhost/course/${courseMetadata.id}/home`);
|
|
});
|
|
|
|
it('should go to course survey for a survey_required error code', async () => {
|
|
const { courseMetadata } = setUpWithDeniedStatus('survey_required');
|
|
await loadContainer();
|
|
|
|
expect(global.location.href).toEqual(`http://localhost/redirect/survey/${courseMetadata.id}`);
|
|
});
|
|
|
|
it('should go to consent page for a data_sharing_access_required error code', async () => {
|
|
setUpWithDeniedStatus('data_sharing_access_required');
|
|
await loadContainer();
|
|
|
|
expect(global.location.href).toEqual('http://localhost/redirect/consent?consentPath=data_sharing_consent_url');
|
|
});
|
|
|
|
it('should go to access denied page for a incorrect_active_enterprise error code', async () => {
|
|
const { courseMetadata } = setUpWithDeniedStatus('incorrect_active_enterprise');
|
|
await loadContainer();
|
|
|
|
expect(global.location.href).toEqual(`http://localhost/course/${courseMetadata.id}/access-denied`);
|
|
});
|
|
|
|
it('should go to course home for an authentication_required error code', async () => {
|
|
const { courseMetadata } = setUpWithDeniedStatus('authentication_required');
|
|
await loadContainer();
|
|
|
|
expect(global.location.href).toEqual(`http://localhost/course/${courseMetadata.id}/home`);
|
|
});
|
|
|
|
it('should go to dashboard for an unfulfilled_milestones error code', async () => {
|
|
setUpWithDeniedStatus('unfulfilled_milestones');
|
|
await loadContainer();
|
|
|
|
expect(global.location.href).toEqual('http://localhost/redirect/dashboard');
|
|
});
|
|
|
|
it('should go to the dashboard with an attached access_response_error for an audit_expired error code', async () => {
|
|
setUpWithDeniedStatus('audit_expired');
|
|
await loadContainer();
|
|
|
|
expect(global.location.href).toEqual('http://localhost/redirect/dashboard?access_response_error=uhoh%20oh%20no');
|
|
});
|
|
|
|
it('should go to the dashboard with a notlive start date for a course_not_started error code', async () => {
|
|
setUpWithDeniedStatus('course_not_started');
|
|
await loadContainer();
|
|
|
|
const startDate = '2/5/2013'; // This date is based on our courseMetadata factory's sample data.
|
|
expect(global.location.href).toEqual(`http://localhost/redirect/dashboard?notlive=${startDate}`);
|
|
});
|
|
});
|
|
});
|