fix(deps): regenerate package-lock.json (#1855)
* fix(deps): regenerate package-lock.json Co-Authored-By: Claude Code <noreply@anthropic.com> * fix(deps): regenerate package-lock.json Moved @openedx/frontend-build from dependencies to devDependencies. Removed direct jest devDependency which was causing ts-jest hoisting issues. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(tests): use require() for MockedPluginSlot in jest.mock Jest hoists jest.mock() calls to the top of the file, which caused MockedPluginSlot to be undefined when the mock factory executed. Using require() inside the factory ensures it loads at runtime. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(types): handle nullable breakpoint types Paragon's breakpoint types now have optional minWidth/maxWidth properties. Added non-null assertions since these values are always defined in practice. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(tests): add IntlProvider to ContentIFrame tests Paragon's ModalDialog now uses useIntl() (openedx/paragon#3624), requiring an IntlProvider in the component ancestry. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(tests): await async operations in Course tests Fixed dangling waitFor blocks that weren't awaited, causing tests to not actually wait for async operations. Changed to properly use await with screen.findBy*() queries. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(tests): use dynamic imports in LearnerToolsSlot tests Jest hoists mock calls but ES imports run before the test body. Using dynamic imports in beforeEach ensures mocks are set up before modules are loaded. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -191,23 +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');
|
||||
});
|
||||
const sidebar = await screen.findByTestId('sidebar-DISCUSSIONS');
|
||||
expect(sidebar).toBeInTheDocument();
|
||||
expect(sidebar).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 });
|
||||
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');
|
||||
});
|
||||
const notificationShowButton = await screen.findByRole('button', { name: /Show notification tray/i });
|
||||
expect(screen.queryByRole('region', { name: /notification tray/i })).not.toBeInTheDocument();
|
||||
fireEvent.click(notificationShowButton);
|
||||
|
||||
const notificationTray = await screen.findByRole('region', { name: /notification tray/i });
|
||||
expect(notificationTray).toBeInTheDocument();
|
||||
expect(notificationTray).not.toHaveClass('d-none');
|
||||
});
|
||||
|
||||
it('doesn\'t renders course breadcrumbs by default', async () => {
|
||||
|
||||
@@ -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!;
|
||||
const shouldDisplaySidebarOpen = windowWidth > breakpoints.medium.minWidth!;
|
||||
const query = new URLSearchParams(window.location.search);
|
||||
const isInitiallySidebarOpen = shouldDisplaySidebarOpen || query.get('sidebar') === 'true';
|
||||
const sidebarKey = `sidebar.${courseId}`;
|
||||
|
||||
@@ -44,7 +44,7 @@ describe('NotificationsWidget', () => {
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
global.innerWidth = breakpoints.large.minWidth;
|
||||
global.innerWidth = breakpoints.large.minWidth!;
|
||||
store = initializeStore();
|
||||
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
|
||||
axiosMock.onGet(courseMetadataUrl).reply(200, defaultMetadata);
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import * as hooks from './hooks';
|
||||
import ContentIFrame, { IFRAME_FEATURE_POLICY } from './ContentIFrame';
|
||||
|
||||
// eslint-disable-next-line react/prop-types
|
||||
const IntlWrapper = ({ children }) => (
|
||||
<IntlProvider locale="en">{children}</IntlProvider>
|
||||
);
|
||||
|
||||
jest.mock('@edx/frontend-platform/react', () => ({ ErrorPage: () => <div>ErrorPage</div> }));
|
||||
|
||||
jest.mock('@src/generic/PageLoading', () => jest.fn(() => <div>PageLoading</div>));
|
||||
@@ -59,7 +65,7 @@ describe('ContentIFrame Component', () => {
|
||||
});
|
||||
describe('behavior', () => {
|
||||
beforeEach(() => {
|
||||
render(<ContentIFrame {...props} />);
|
||||
render(<ContentIFrame {...props} />, { wrapper: IntlWrapper });
|
||||
});
|
||||
it('initializes iframe behavior hook', () => {
|
||||
expect(hooks.useIFrameBehavior).toHaveBeenCalledWith({
|
||||
@@ -78,12 +84,12 @@ describe('ContentIFrame Component', () => {
|
||||
describe('if not hasLoaded', () => {
|
||||
it('displays errorPage if showError', () => {
|
||||
hooks.useIFrameBehavior.mockReturnValueOnce({ ...iframeBehavior, showError: true });
|
||||
render(<ContentIFrame {...props} />);
|
||||
render(<ContentIFrame {...props} />, { wrapper: IntlWrapper });
|
||||
const errorPage = screen.getByText('ErrorPage');
|
||||
expect(errorPage).toBeInTheDocument();
|
||||
});
|
||||
it('displays PageLoading component if not showError', () => {
|
||||
render(<ContentIFrame {...props} />);
|
||||
render(<ContentIFrame {...props} />, { wrapper: IntlWrapper });
|
||||
const pageLoading = screen.getByText('PageLoading');
|
||||
expect(pageLoading).toBeInTheDocument();
|
||||
});
|
||||
@@ -91,7 +97,7 @@ describe('ContentIFrame Component', () => {
|
||||
describe('hasLoaded', () => {
|
||||
it('does not display PageLoading or ErrorPage', () => {
|
||||
hooks.useIFrameBehavior.mockReturnValueOnce({ ...iframeBehavior, hasLoaded: true });
|
||||
render(<ContentIFrame {...props} />);
|
||||
render(<ContentIFrame {...props} />, { wrapper: IntlWrapper });
|
||||
const pageLoading = screen.queryByText('PageLoading');
|
||||
expect(pageLoading).toBeNull();
|
||||
const errorPage = screen.queryByText('ErrorPage');
|
||||
@@ -99,7 +105,7 @@ describe('ContentIFrame Component', () => {
|
||||
});
|
||||
});
|
||||
it('display iframe with props from hooks', () => {
|
||||
render(<ContentIFrame {...props} />);
|
||||
render(<ContentIFrame {...props} />, { wrapper: IntlWrapper });
|
||||
const iframe = screen.getByTitle(props.title);
|
||||
expect(iframe).toBeInTheDocument();
|
||||
expect(iframe).toHaveAttribute('id', props.elementId);
|
||||
@@ -112,14 +118,14 @@ describe('ContentIFrame Component', () => {
|
||||
});
|
||||
describe('if not shouldShowContent', () => {
|
||||
it('does not show PageLoading, ErrorPage, or unit-iframe-wrapper', () => {
|
||||
render(<ContentIFrame {...{ ...props, shouldShowContent: false }} />);
|
||||
render(<ContentIFrame {...{ ...props, shouldShowContent: false }} />, { wrapper: IntlWrapper });
|
||||
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', () => {
|
||||
render(<ContentIFrame {...props} />);
|
||||
render(<ContentIFrame {...props} />, { wrapper: IntlWrapper });
|
||||
const modal = screen.queryByRole('dialog');
|
||||
expect(modal).toBeNull();
|
||||
});
|
||||
@@ -138,7 +144,7 @@ describe('ContentIFrame Component', () => {
|
||||
...modalIFrameData,
|
||||
modalOptions: { ...modalOptions.withBody, isFullscreen: true },
|
||||
});
|
||||
render(<ContentIFrame {...props} />);
|
||||
render(<ContentIFrame {...props} />, { wrapper: IntlWrapper });
|
||||
});
|
||||
it('displays Modal with div wrapping provided body content if modal.body is provided', () => {
|
||||
const dialog = screen.getByRole('dialog');
|
||||
@@ -155,7 +161,7 @@ describe('ContentIFrame Component', () => {
|
||||
...modalIFrameData,
|
||||
modalOptions: { ...modalOptions.withUrl, isFullscreen: true },
|
||||
});
|
||||
render(<ContentIFrame {...props} />);
|
||||
render(<ContentIFrame {...props} />, { wrapper: IntlWrapper });
|
||||
});
|
||||
it('displays Modal with iframe to provided url if modal.body is not provided', () => {
|
||||
const iframe = screen.getByTitle(modalOptions.withUrl.title);
|
||||
@@ -169,7 +175,7 @@ describe('ContentIFrame Component', () => {
|
||||
describe('body modal', () => {
|
||||
beforeEach(() => {
|
||||
hooks.useModalIFrameData.mockReturnValueOnce({ ...modalIFrameData, modalOptions: modalOptions.withBody });
|
||||
render(<ContentIFrame {...props} />);
|
||||
render(<ContentIFrame {...props} />, { wrapper: IntlWrapper });
|
||||
});
|
||||
it('displays Modal with div wrapping provided body content if modal.body is provided', () => {
|
||||
const dialog = screen.getByRole('dialog');
|
||||
@@ -182,7 +188,7 @@ describe('ContentIFrame Component', () => {
|
||||
describe('url modal', () => {
|
||||
beforeEach(() => {
|
||||
hooks.useModalIFrameData.mockReturnValueOnce({ ...modalIFrameData, modalOptions: modalOptions.withUrl });
|
||||
render(<ContentIFrame {...props} />);
|
||||
render(<ContentIFrame {...props} />, { wrapper: IntlWrapper });
|
||||
});
|
||||
it('displays Modal with iframe to provided url if modal.body is not provided', () => {
|
||||
const iframe = screen.getByTitle(modalOptions.withUrl.title);
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { PluginSlot } from '@openedx/frontend-plugin-framework';
|
||||
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>),
|
||||
@@ -14,6 +10,10 @@ jest.mock('@edx/frontend-platform/auth', () => ({
|
||||
}));
|
||||
|
||||
describe('LearnerToolsSlot', () => {
|
||||
let auth;
|
||||
let PluginSlot;
|
||||
let LearnerToolsSlot;
|
||||
|
||||
const defaultProps = {
|
||||
courseId: 'course-v1:edX+DemoX+Demo_Course',
|
||||
unitId: 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@unit1',
|
||||
@@ -21,10 +21,15 @@ describe('LearnerToolsSlot', () => {
|
||||
enrollmentMode: 'verified',
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
jest.resetModules();
|
||||
jest.clearAllMocks();
|
||||
// Mock document.body for createPortal
|
||||
document.body.innerHTML = '<div id="root"></div>';
|
||||
|
||||
auth = await import('@edx/frontend-platform/auth');
|
||||
({ PluginSlot } = await import('@openedx/frontend-plugin-framework'));
|
||||
({ LearnerToolsSlot } = await import('./index'));
|
||||
});
|
||||
|
||||
it('renders PluginSlot with correct props when user is authenticated', () => {
|
||||
|
||||
@@ -26,13 +26,17 @@ 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', () => {
|
||||
// eslint-disable-next-line global-require
|
||||
const MockedPluginSlot = require('./tests/MockedPluginSlot').default;
|
||||
|
||||
return {
|
||||
...jest.requireActual('@openedx/frontend-plugin-framework'),
|
||||
Plugin: () => 'Plugin',
|
||||
PluginSlot: MockedPluginSlot,
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('@src/generic/plugin-store', () => ({
|
||||
...jest.requireActual('@src/generic/plugin-store'),
|
||||
|
||||
Reference in New Issue
Block a user