Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 0 additions & 16 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,4 @@ const config = createConfig('jest', {

// delete config.testURL;

config.reporters = [...(config.reporters || []), ["jest-console-group-reporter", {
// change this setting if need to see less details for each test
// reportType: "summary" | "details",
// enable: true | false,
afterEachTest: {
enable: true,
filePaths: false,
reportType: "details",
},
afterAllTests: {
reportType: "summary",
enable: true,
filePaths: true,
},
}]];

module.exports = config;
21,842 changes: 12,644 additions & 9,198 deletions package-lock.json

Large diffs are not rendered by default.

25 changes: 12 additions & 13 deletions src/courseware/course/Course.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ describe('Course', () => {
it('handles click to open/close discussions sidebar', async () => {
await setupDiscussionSidebar();

waitFor(() => {
await waitFor(() => {
expect(screen.getByTestId('sidebar-DISCUSSIONS')).toBeInTheDocument();
expect(screen.getByTestId('sidebar-DISCUSSIONS')).not.toHaveClass('d-none');

Expand Down Expand Up @@ -191,22 +191,22 @@ describe('Course', () => {
const { rerender } = render(<Course {...testData} />, { store: testStore });
loadUnit();

waitFor(() => {
expect(screen.findByTestId('sidebar-DISCUSSIONS')).toBeInTheDocument();
expect(screen.findByTestId('sidebar-DISCUSSIONS')).not.toHaveClass('d-none');
await waitFor(() => {
expect(screen.getByTestId('sidebar-DISCUSSIONS')).toBeInTheDocument();
expect(screen.getByTestId('sidebar-DISCUSSIONS')).not.toHaveClass('d-none');
});

rerender(null);
});

it('handles click to open/close notification tray', async () => {
await setupDiscussionSidebar();
waitFor(() => {
const notificationShowButton = screen.findByRole('button', { name: /Show notification tray/i });
await waitFor(() => {
const notificationShowButton = screen.getByRole('button', { name: /Show notification tray/i });
expect(screen.queryByRole('region', { name: /notification tray/i })).not.toBeInTheDocument();
fireEvent.click(notificationShowButton);
expect(screen.queryByRole('region', { name: /notification tray/i })).toBeInTheDocument();
expect(screen.queryByRole('region', { name: /notification tray/i })).not.toHaveClass('d-none');
expect(screen.getByRole('region', { name: /notification tray/i })).toBeInTheDocument();
expect(screen.getByRole('region', { name: /notification tray/i })).not.toHaveClass('d-none');
});
});

Expand Down Expand Up @@ -296,7 +296,7 @@ describe('Course', () => {
sequenceId: sequenceBlocks[0].id,
};
render(<Course {...testData} />, { store: testStore, wrapWithRouter: true });
waitFor(() => expect(screen.findByText('Some random banner text to display.')).toBeInTheDocument());
await waitFor(() => expect(screen.getByText('Some random banner text to display.')).toBeInTheDocument());
});

it('renders Entrance Exam alert with passing score', async () => {
Expand Down Expand Up @@ -330,7 +330,7 @@ describe('Course', () => {
sequenceId: sequenceBlocks[0].id,
};
render(<Course {...testData} />, { store: testStore, wrapWithRouter: true });
waitFor(() => expect(screen.findByText('Your score is 100%. You have passed the entrance exam.')).toBeInTheDocument());
await waitFor(() => expect(screen.getByText('Your score is 100%. You have passed the entrance exam.')).toBeInTheDocument());
});

it('renders Entrance Exam alert with non-passing score', async () => {
Expand Down Expand Up @@ -364,7 +364,7 @@ describe('Course', () => {
sequenceId: sequenceBlocks[0].id,
};
render(<Course {...testData} />, { store: testStore, wrapWithRouter: true });
waitFor(() => expect(screen.findByText('To access course materials, you must score 70% or higher on this exam. Your current score is 30%.')).toBeInTheDocument());
await waitFor(() => expect(screen.getByText('To access course materials, you must score 70% or higher on this exam. Your current score is 30%.')).toBeInTheDocument());
});
});

Expand All @@ -382,8 +382,7 @@ describe('Course', () => {
unitId: Object.values(models.units)[0].id,
};
render(<Course {...testData} />, { store: testStore, wrapWithRouter: true });
const learnerTools = screen.queryByTestId(mockLearnerToolsTestId);
await waitFor(() => expect(learnerTools).toBeInTheDocument());
await waitFor(() => expect(screen.queryByTestId(mockLearnerToolsTestId)).toBeInTheDocument());
});

it('does not display learner tools when screen is too narrow (mobile)', async () => {
Expand Down
4 changes: 2 additions & 2 deletions src/courseware/course/new-sidebar/SidebarContextProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ const SidebarProvider: React.FC<Props> = ({
const { verifiedMode } = useModel('courseHomeMeta', courseId);
const topic = useModel('discussionTopics', unitId);
const windowWidth = useWindowSize().width ?? window.innerWidth;
const shouldDisplayFullScreen = windowWidth < breakpoints.large.minWidth;
const shouldDisplaySidebarOpen = windowWidth > breakpoints.medium.minWidth;
const shouldDisplayFullScreen = windowWidth < (breakpoints.large.minWidth ?? 992);
const shouldDisplaySidebarOpen = windowWidth > (breakpoints.medium.minWidth ?? 768);
const query = new URLSearchParams(window.location.search);
const isInitiallySidebarOpen = shouldDisplaySidebarOpen || query.get('sidebar') === 'true';
const sidebarKey = `sidebar.${courseId}`;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ describe('NotificationsWidget', () => {
}

beforeEach(async () => {
global.innerWidth = breakpoints.large.minWidth;
global.innerWidth = breakpoints.large.minWidth ?? 992;
store = initializeStore();
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
axiosMock.onGet(courseMetadataUrl).reply(200, defaultMetadata);
Expand Down
51 changes: 51 additions & 0 deletions src/courseware/course/sequence/Unit/ContentIFrame.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,57 @@ jest.mock('@edx/frontend-platform/react', () => ({ ErrorPage: () => <div>ErrorPa

jest.mock('@src/generic/PageLoading', () => jest.fn(() => <div>PageLoading</div>));

jest.mock('@openedx/paragon', () => {
const actual = jest.requireActual('@openedx/paragon');
const PropTypes = jest.requireActual('prop-types');

const MockModalDialog = ({ children, isOpen, onClose }) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[important]: Is it possible to abandon the mock of this Paragon component?

if (!isOpen) { return null; }

return (
<div role="dialog" aria-modal="true" className="mock-modal">
<button
type="button"
data-testid="modal-backdrop"
onClick={onClose}
aria-label="Close"
>
</button>
<div className="mock-modal-content">
{children}
</div>
</div>
);
};

MockModalDialog.propTypes = {
children: PropTypes.node,
isOpen: PropTypes.bool,
onClose: PropTypes.func,
};

const createSubComponent = (baseClass) => {
const Component = ({ children, className }) => (
<div className={`${baseClass} ${className || ''}`}>{children}</div>
);
Component.propTypes = {
children: PropTypes.node,
className: PropTypes.string,
};
return Component;
};

MockModalDialog.Body = createSubComponent('mock-modal-body');
MockModalDialog.Header = createSubComponent('mock-modal-header');
MockModalDialog.Footer = createSubComponent('mock-modal-footer');

return {
...actual,
ModalDialog: MockModalDialog,
};
});

jest.mock('./hooks', () => ({
useIFrameBehavior: jest.fn(),
useModalIFrameData: jest.fn(),
Expand Down
8 changes: 2 additions & 6 deletions src/plugin-slots/LearnerToolsSlot/index.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,6 @@ import * as auth from '@edx/frontend-platform/auth';

import { LearnerToolsSlot } from './index';

jest.mock('@openedx/frontend-plugin-framework', () => ({
PluginSlot: jest.fn(() => <div data-testid="plugin-slot">Plugin Slot</div>),
}));

jest.mock('@edx/frontend-platform/auth', () => ({
getAuthenticatedUser: jest.fn(),
}));
Expand Down Expand Up @@ -98,7 +94,7 @@ describe('LearnerToolsSlot', () => {

render(<LearnerToolsSlot {...defaultProps} />);

// The portal should render to document.body
expect(document.body.querySelector('[data-testid="plugin-slot"]')).toBeInTheDocument();
// The portal should render to document.body with the id as testid
expect(document.body.querySelector('[data-testid="org.openedx.frontend.learning.learner_tools.v1"]')).toBeInTheDocument();
});
});
17 changes: 17 additions & 0 deletions src/product-tours/ProductTours.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ import { buildOutlineFromBlocks } from '../courseware/data/__factories__/learnin

import { UserMessagesProvider } from '../generic/user-messages';
import { DECODE_ROUTES } from '../constants';
import {
DismissButtonFormattedMessage,
NextButtonFormattedMessage,
OkayButtonFormattedMessage,
} from './GenericTourFormattedMessages';

initializeMockApp();
jest.mock('@edx/frontend-platform/analytics');
Expand Down Expand Up @@ -158,6 +163,18 @@ describe('Course Home Tours', () => {
expect(await screen.queryByRole('dialog')).not.toBeInTheDocument();
},
);

describe('GenericTourFormattedMessages', () => {
it('renders all formatted message components to satisfy coverage', () => {
render(<DismissButtonFormattedMessage />);
render(<NextButtonFormattedMessage />);
render(<OkayButtonFormattedMessage />);

expect(screen.getByText('Dismiss')).toBeInTheDocument();
expect(screen.getByText('Next')).toBeInTheDocument();
expect(screen.getByText('Okay')).toBeInTheDocument();
});
});
});

jest.mock(
Expand Down
14 changes: 8 additions & 6 deletions src/setupTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,15 @@ import { getCourseOutlineStructure } from './courseware/data/thunks';
import { appendBrowserTimezoneToUrl, executeThunk } from './utils';
import buildSimpleCourseAndSequenceMetadata from './courseware/data/__factories__/sequenceMetadata.factory';
import { buildOutlineFromBlocks } from './courseware/data/__factories__/learningSequencesOutline.factory';
import MockedPluginSlot from './tests/MockedPluginSlot';

jest.mock('@openedx/frontend-plugin-framework', () => ({
...jest.requireActual('@openedx/frontend-plugin-framework'),
Plugin: () => 'Plugin',
PluginSlot: MockedPluginSlot,
}));
jest.mock('@openedx/frontend-plugin-framework', () => {
const MockedPluginSlot = jest.requireActual('./tests/MockedPluginSlot').default;

return {
Plugin: () => 'Plugin',
PluginSlot: jest.fn(MockedPluginSlot),
};
});

jest.mock('@src/generic/plugin-store', () => ({
...jest.requireActual('@src/generic/plugin-store'),
Expand Down
4 changes: 2 additions & 2 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ export const executeThunk = async (thunk, dispatch, getState = undefined) => {
*/
export const appendBrowserTimezoneToUrl = (url: string) => {
const browserTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
const urlObject = new URL(url);
const urlObject = new URL(url, url.startsWith('http') ? undefined : 'http://localhost');
if (browserTimezone) {
urlObject.searchParams.append('browser_timezone', browserTimezone);
}
return urlObject.href;
return url.startsWith('http') ? urlObject.href : `${urlObject.pathname}${urlObject.search}`;
};
40 changes: 40 additions & 0 deletions webpack.dev.config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const path = require('path');
const { createConfig } = require('@openedx/frontend-build');
const sass = require('sass');

const config = createConfig('webpack-dev');

Expand All @@ -8,4 +9,43 @@ config.resolve.alias = {
'@src': path.resolve(__dirname, 'src'),
};

// Fix for react-focus-on webpack 5 compatibility issue
// The package has ES modules without file extensions in imports
config.module.rules.push({
test: /\.m?js$/,
resolve: {
fullySpecified: false,
},
});

// Fix for sass-loader deprecation warnings
config.module.rules.forEach((rule) => {
if (rule.oneOf) {
rule.oneOf.forEach((oneOfRule) => {
if (oneOfRule.use) {
oneOfRule.use.forEach((loaderConfig) => {
if (loaderConfig.loader && loaderConfig.loader.includes('sass-loader')) {
// eslint-disable-next-line no-param-reassign
loaderConfig.options = {
...loaderConfig.options,
api: 'modern',
implementation: sass,
sassOptions: {
...loaderConfig.options?.sassOptions,
silenceDeprecations: [
'import',
'abs-percent',
'color-functions',
'global-builtin',
'legacy-js-api',
],
},
};
}
});
}
});
}
});

module.exports = config;
40 changes: 40 additions & 0 deletions webpack.prod.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const path = require('path');
const { createConfig } = require('@openedx/frontend-build');
const CopyPlugin = require('copy-webpack-plugin');
const sass = require('sass');

const config = createConfig('webpack-prod');

Expand All @@ -20,4 +21,43 @@ config.resolve.alias = {
'@src': path.resolve(__dirname, 'src'),
};

// Fix for react-focus-on webpack 5 compatibility issue
// The package has ES modules without file extensions in imports
config.module.rules.push({
test: /\.m?js$/,
resolve: {
fullySpecified: false,
},
});

// Fix for sass-loader deprecation warnings
config.module.rules.forEach((rule) => {
if (rule.oneOf) {
rule.oneOf.forEach((oneOfRule) => {
if (oneOfRule.use) {
oneOfRule.use.forEach((loaderConfig) => {
if (loaderConfig.loader && loaderConfig.loader.includes('sass-loader')) {
// eslint-disable-next-line no-param-reassign
loaderConfig.options = {
...loaderConfig.options,
api: 'modern',
implementation: sass,
sassOptions: {
...loaderConfig.options?.sassOptions,
silenceDeprecations: [
'import',
'abs-percent',
'color-functions',
'global-builtin',
'legacy-js-api',
],
},
};
}
});
}
});
}
});

module.exports = config;