diff --git a/src/courseware/course/sequence/Unit/ContentIFrame.jsx b/src/courseware/course/sequence/Unit/ContentIFrame.jsx
index 8be355bf14..bc77182506 100644
--- a/src/courseware/course/sequence/Unit/ContentIFrame.jsx
+++ b/src/courseware/course/sequence/Unit/ContentIFrame.jsx
@@ -1,8 +1,6 @@
import PropTypes from 'prop-types';
-import React from 'react';
import { ErrorPage } from '@edx/frontend-platform/react';
-import { StrictDict } from '@edx/react-unit-test-utils';
import { ModalDialog } from '@openedx/paragon';
import { ContentIFrameLoaderSlot } from '../../../../plugin-slots/ContentIFrameLoaderSlot';
@@ -22,10 +20,10 @@ export const IFRAME_FEATURE_POLICY = (
'microphone *; camera *; midi *; geolocation *; encrypted-media *; clipboard-write *; autoplay *'
);
-export const testIDs = StrictDict({
+export const testIDs = {
contentIFrame: 'content-iframe-test-id',
modalIFrame: 'modal-iframe-test-id',
-});
+};
const ContentIFrame = ({
iframeUrl,
diff --git a/src/courseware/course/sequence/Unit/ContentIFrame.test.jsx b/src/courseware/course/sequence/Unit/ContentIFrame.test.jsx
index 6ef5f08a95..86f72d7df3 100644
--- a/src/courseware/course/sequence/Unit/ContentIFrame.test.jsx
+++ b/src/courseware/course/sequence/Unit/ContentIFrame.test.jsx
@@ -1,25 +1,11 @@
-import React from 'react';
+import { render, screen } from '@testing-library/react';
-import { ErrorPage } from '@edx/frontend-platform/react';
-import { ModalDialog } from '@openedx/paragon';
-import { shallow } from '@edx/react-unit-test-utils';
-
-import PageLoading from '@src/generic/PageLoading';
-
-import { ContentIFrameLoaderSlot } from '@src/plugin-slots/ContentIFrameLoaderSlot';
import * as hooks from './hooks';
-import ContentIFrame, { IFRAME_FEATURE_POLICY, testIDs } from './ContentIFrame';
-
-jest.mock('@edx/frontend-platform/react', () => ({ ErrorPage: 'ErrorPage' }));
+import ContentIFrame, { IFRAME_FEATURE_POLICY } from './ContentIFrame';
-jest.mock('@openedx/paragon', () => jest.requireActual('@edx/react-unit-test-utils')
- .mockComponents({
- ModalDialog: {
- Body: 'ModalDialog.Body',
- },
- }));
+jest.mock('@edx/frontend-platform/react', () => ({ ErrorPage: () =>
ErrorPage
}));
-jest.mock('@src/generic/PageLoading', () => 'PageLoading');
+jest.mock('@src/generic/PageLoading', () => jest.fn(() => PageLoading
));
jest.mock('./hooks', () => ({
useIFrameBehavior: jest.fn(),
@@ -67,14 +53,13 @@ const props = {
title: 'test-title',
};
-let el;
describe('ContentIFrame Component', () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe('behavior', () => {
beforeEach(() => {
- el = shallow();
+ render();
});
it('initializes iframe behavior hook', () => {
expect(hooks.useIFrameBehavior).toHaveBeenCalledWith({
@@ -89,63 +74,56 @@ describe('ContentIFrame Component', () => {
});
});
describe('output', () => {
- let component;
describe('if shouldShowContent', () => {
describe('if not hasLoaded', () => {
it('displays errorPage if showError', () => {
- hooks.useIFrameBehavior.mockReturnValueOnce({ ...iframeBehavior, showError: true });
- el = shallow();
- expect(el.instance.findByType(ErrorPage).length).toEqual(1);
+ hooks.useIFrameBehavior.mockReturnValueOnce({ ...iframeBehavior, showError: true, shouldShowContent: true });
+ render();
+ const errorPage = screen.getByText('ErrorPage');
+ expect(errorPage).toBeInTheDocument();
});
it('displays PageLoading component if not showError', () => {
- el = shallow();
- [component] = el.instance.findByType(ContentIFrameLoaderSlot);
- expect(component.props.loadingMessage).toEqual(props.loadingMessage);
+ render();
+ const pageLoading = screen.getByText('PageLoading');
+ expect(pageLoading).toBeInTheDocument();
});
});
describe('hasLoaded', () => {
it('does not display PageLoading or ErrorPage', () => {
hooks.useIFrameBehavior.mockReturnValueOnce({ ...iframeBehavior, hasLoaded: true });
- el = shallow();
- expect(el.instance.findByType(PageLoading).length).toEqual(0);
- expect(el.instance.findByType(ErrorPage).length).toEqual(0);
+ render();
+ const pageLoading = screen.queryByText('PageLoading');
+ expect(pageLoading).toBeNull();
+ const errorPage = screen.queryByText('ErrorPage');
+ expect(errorPage).toBeNull();
});
});
it('display iframe with props from hooks', () => {
- el = shallow();
- [component] = el.instance.findByTestId(testIDs.contentIFrame);
- expect(component.props).toEqual({
- allow: IFRAME_FEATURE_POLICY,
- allowFullScreen: true,
- scrolling: 'no',
- referrerPolicy: 'origin',
- title: props.title,
- id: props.elementId,
- src: props.iframeUrl,
- height: iframeBehavior.iframeHeight,
- onLoad: iframeBehavior.handleIFrameLoad,
- 'data-testid': testIDs.contentIFrame,
- });
+ render();
+ const iframe = screen.getByTitle(props.title);
+ expect(iframe).toBeInTheDocument();
+ expect(iframe).toHaveAttribute('id', props.elementId);
+ expect(iframe).toHaveAttribute('src', props.iframeUrl);
+ expect(iframe).toHaveAttribute('allow', IFRAME_FEATURE_POLICY);
+ expect(iframe).toHaveAttribute('allowfullscreen', '');
+ expect(iframe).toHaveAttribute('scrolling', 'no');
+ expect(iframe).toHaveAttribute('referrerpolicy', 'origin');
});
});
describe('if not shouldShowContent', () => {
it('does not show PageLoading, ErrorPage, or unit-iframe-wrapper', () => {
- el = shallow();
- expect(el.instance.findByType(PageLoading).length).toEqual(0);
- expect(el.instance.findByType(ErrorPage).length).toEqual(0);
- expect(el.instance.findByTestId(testIDs.contentIFrame).length).toEqual(0);
+ render();
+ expect(screen.queryByText('PageLoading')).toBeNull();
+ expect(screen.queryByText('ErrorPage')).toBeNull();
+ expect(screen.queryByTitle(props.title)).toBeNull();
});
});
it('does not display modal if modalOptions returns isOpen: false', () => {
- el = shallow();
- expect(el.instance.findByType(ModalDialog).length).toEqual(0);
+ render();
+ const modal = screen.queryByRole('dialog');
+ expect(modal).toBeNull();
});
describe('if modalOptions.isOpen', () => {
- const testModalOpenAndHandleClose = () => {
- test('Modal component isOpen, with handleModalClose from hook', () => {
- expect(component.props.onClose).toEqual(modalIFrameData.handleModalClose);
- });
- };
describe('fullscreen modal', () => {
describe('body modal', () => {
beforeEach(() => {
@@ -153,16 +131,14 @@ describe('ContentIFrame Component', () => {
...modalIFrameData,
modalOptions: { ...modalOptions.withBody, isFullscreen: true },
});
- el = shallow();
- [component] = el.instance.findByType(ModalDialog);
+ render();
});
it('displays Modal with div wrapping provided body content if modal.body is provided', () => {
- const content = component.findByType(ModalDialog.Body)[0].children[0];
- expect(content.matches(shallow(
- {modalOptions.withBody.body}
,
- ))).toEqual(true);
+ const dialog = screen.getByRole('dialog');
+ expect(dialog).toBeInTheDocument();
+ const modalBody = screen.getByText(modalOptions.withBody.body);
+ expect(modalBody).toBeInTheDocument();
});
- testModalOpenAndHandleClose();
});
describe('url modal', () => {
beforeEach(() => {
@@ -171,54 +147,38 @@ describe('ContentIFrame Component', () => {
...modalIFrameData,
modalOptions: { ...modalOptions.withUrl, isFullscreen: true },
});
- el = shallow();
- [component] = el.instance.findByType(ModalDialog);
+ render();
});
- testModalOpenAndHandleClose();
it('displays Modal with iframe to provided url if modal.body is not provided', () => {
- const content = component.findByType(ModalDialog.Body)[0].children[0];
- expect(content.matches(shallow(
- ,
- ))).toEqual(true);
+ const iframe = screen.getByTitle(modalOptions.withUrl.title);
+ expect(iframe).toBeInTheDocument();
+ expect(iframe).toHaveAttribute('allow', IFRAME_FEATURE_POLICY);
+ expect(iframe).toHaveAttribute('src', modalOptions.withUrl.url);
});
});
});
describe('body modal', () => {
beforeEach(() => {
hooks.useModalIFrameData.mockReturnValueOnce({ ...modalIFrameData, modalOptions: modalOptions.withBody });
- el = shallow();
- [component] = el.instance.findByType(ModalDialog);
+ render();
});
it('displays Modal with div wrapping provided body content if modal.body is provided', () => {
- const content = component.findByType(ModalDialog.Body)[0].children[0];
- expect(content.matches(shallow({modalOptions.withBody.body}
))).toEqual(true);
+ const dialog = screen.getByRole('dialog');
+ expect(dialog).toBeInTheDocument();
+ const modalBody = screen.getByText(modalOptions.withBody.body);
+ expect(modalBody).toBeInTheDocument();
});
- testModalOpenAndHandleClose();
});
describe('url modal', () => {
beforeEach(() => {
hooks.useModalIFrameData.mockReturnValueOnce({ ...modalIFrameData, modalOptions: modalOptions.withUrl });
- el = shallow();
- [component] = el.instance.findByType(ModalDialog);
+ render();
});
- testModalOpenAndHandleClose();
it('displays Modal with iframe to provided url if modal.body is not provided', () => {
- const content = component.findByType(ModalDialog.Body)[0].children[0];
- expect(content.matches(shallow(
- ,
- ))).toEqual(true);
+ const iframe = screen.getByTitle(modalOptions.withUrl.title);
+ expect(iframe).toBeInTheDocument();
+ expect(iframe).toHaveAttribute('allow', IFRAME_FEATURE_POLICY);
+ expect(iframe).toHaveAttribute('src', modalOptions.withUrl.url);
});
});
});
diff --git a/src/courseware/course/sequence/Unit/UnitSuspense.test.jsx b/src/courseware/course/sequence/Unit/UnitSuspense.test.jsx
index db829eaa46..4bf4bac146 100644
--- a/src/courseware/course/sequence/Unit/UnitSuspense.test.jsx
+++ b/src/courseware/course/sequence/Unit/UnitSuspense.test.jsx
@@ -1,22 +1,15 @@
-import React from 'react';
-
-import { formatMessage, shallow } from '@edx/react-unit-test-utils';
+import { render, screen } from '@testing-library/react';
+import { IntlProvider } from '@edx/frontend-platform/i18n';
import { useModel } from '@src/generic/model-store';
-import PageLoading from '@src/generic/PageLoading';
-
-import { GatedUnitContentMessageSlot } from '@src/plugin-slots/GatedUnitContentMessageSlot';
-import messages from '../messages';
-import HonorCode from '../honor-code';
-import LockPaywall from '../lock-paywall';
import hooks from './hooks';
import { modelKeys } from './constants';
import UnitSuspense from './UnitSuspense';
jest.mock('@edx/frontend-platform/i18n', () => ({
+ ...jest.requireActual('@edx/frontend-platform/i18n'),
defineMessages: m => m,
- useIntl: () => ({ formatMessage: jest.requireActual('@edx/react-unit-test-utils').formatMessage }),
}));
jest.mock('react', () => ({
@@ -24,10 +17,9 @@ jest.mock('react', () => ({
Suspense: 'Suspense',
}));
-jest.mock('../honor-code', () => 'HonorCode');
-jest.mock('../lock-paywall', () => 'LockPaywall');
+jest.mock('../honor-code', () => jest.fn(() => HonorCode
));
+jest.mock('../lock-paywall', () => jest.fn(() => LockPaywall
));
jest.mock('@src/generic/model-store', () => ({ useModel: jest.fn() }));
-jest.mock('@src/generic/PageLoading', () => 'PageLoading');
jest.mock('./hooks', () => ({
useShouldDisplayHonorCode: jest.fn(() => false),
@@ -46,7 +38,6 @@ const props = {
id: 'test-id',
};
-let el;
describe('UnitSuspense component', () => {
beforeEach(() => {
jest.clearAllMocks();
@@ -54,7 +45,7 @@ describe('UnitSuspense component', () => {
});
describe('behavior', () => {
it('initializes models', () => {
- el = shallow();
+ render();
const { calls } = useModel.mock;
const [unitCall] = calls.filter(call => call[0] === modelKeys.units);
const [metaCall] = calls.filter(call => call[0] === modelKeys.coursewareMeta);
@@ -66,8 +57,9 @@ describe('UnitSuspense component', () => {
describe('LockPaywall', () => {
const testNoPaywall = () => {
it('does not display LockPaywall', () => {
- el = shallow();
- expect(el.instance.findByType(LockPaywall).length).toEqual(0);
+ render();
+ const lockPaywall = screen.queryByText('LockPaywall');
+ expect(lockPaywall).toBeNull();
});
};
describe('gating not enabled', () => { testNoPaywall(); });
@@ -78,29 +70,29 @@ describe('UnitSuspense component', () => {
describe('gating enabled, gated content included', () => {
beforeEach(() => { mockModels(true, true); });
it('displays LockPaywall in Suspense wrapper with PageLoading fallback', () => {
- el = shallow();
- const [component] = el.instance.findByType(GatedUnitContentMessageSlot);
- expect(component.parent.type).toEqual('Suspense');
- expect(component.parent.props.fallback)
- .toEqual();
- expect(component.props.courseId).toEqual(props.courseId);
+ hooks.useShouldDisplayHonorCode.mockReturnValueOnce(false);
+ render();
+ const lockPaywall = screen.getByText('LockPaywall');
+ expect(lockPaywall).toBeInTheDocument();
+ const suspenseWrapper = lockPaywall.closest('suspense');
+ expect(suspenseWrapper).toBeInTheDocument();
});
});
});
describe('HonorCode', () => {
it('does not display HonorCode if useShouldDisplayHonorCode => false', () => {
hooks.useShouldDisplayHonorCode.mockReturnValueOnce(false);
- el = shallow();
- expect(el.instance.findByType(HonorCode).length).toEqual(0);
+ render();
+ const honorCode = screen.queryByText('HonorCode');
+ expect(honorCode).toBeNull();
});
it('displays HonorCode component in Suspense wrapper with PageLoading fallback if shouldDisplayHonorCode', () => {
hooks.useShouldDisplayHonorCode.mockReturnValueOnce(true);
- el = shallow();
- const [component] = el.instance.findByType(HonorCode);
- expect(component.parent.type).toEqual('Suspense');
- expect(component.parent.props.fallback)
- .toEqual();
- expect(component.props.courseId).toEqual(props.courseId);
+ render();
+ const honorCode = screen.getByText('HonorCode');
+ expect(honorCode).toBeInTheDocument();
+ const suspenseWrapper = honorCode.closest('suspense');
+ expect(suspenseWrapper).toBeInTheDocument();
});
});
});
diff --git a/src/courseware/course/sequence/Unit/constants.js b/src/courseware/course/sequence/Unit/constants.ts
similarity index 60%
rename from src/courseware/course/sequence/Unit/constants.js
rename to src/courseware/course/sequence/Unit/constants.ts
index f8eb23e1c2..af02894af0 100644
--- a/src/courseware/course/sequence/Unit/constants.js
+++ b/src/courseware/course/sequence/Unit/constants.ts
@@ -1,27 +1,25 @@
-import { StrictDict } from '@edx/react-unit-test-utils/dist';
-
-export const modelKeys = StrictDict({
+export const modelKeys = {
units: 'units',
coursewareMeta: 'coursewareMeta',
-});
+} as const;
-export const views = StrictDict({
+export const views = {
student: 'student_view',
public: 'public_view',
-});
+} as const;
export const loadingState = 'loading';
-export const messageTypes = StrictDict({
+export const messageTypes = {
modal: 'plugin.modal',
resize: 'plugin.resize',
videoFullScreen: 'plugin.videoFullScreen',
autoAdvance: 'plugin.autoAdvance',
-});
+} as const;
-export default StrictDict({
+export default {
modelKeys,
views,
loadingState,
messageTypes,
-});
+};
diff --git a/src/courseware/course/sequence/Unit/hooks/useExamAccess.js b/src/courseware/course/sequence/Unit/hooks/useExamAccess.js
index 9a50ea674b..099a55db3e 100644
--- a/src/courseware/course/sequence/Unit/hooks/useExamAccess.js
+++ b/src/courseware/course/sequence/Unit/hooks/useExamAccess.js
@@ -1,19 +1,13 @@
import React from 'react';
import { logError } from '@edx/frontend-platform/logging';
-import { StrictDict, useKeyedState } from '@edx/react-unit-test-utils';
import { useExamAccessToken, useFetchExamAccessToken, useIsExam } from '@edx/frontend-lib-special-exams';
-export const stateKeys = StrictDict({
- accessToken: 'accessToken',
- blockAccess: 'blockAccess',
-});
-
const useExamAccess = ({
id,
}) => {
const isExam = useIsExam();
- const [blockAccess, setBlockAccess] = useKeyedState(stateKeys.blockAccess, isExam);
+ const [blockAccess, setBlockAccess] = React.useState(isExam);
const fetchExamAccessToken = useFetchExamAccessToken();