diff --git a/src/App.test.jsx b/src/App.test.jsx
index 78042244..dc313f58 100644
--- a/src/App.test.jsx
+++ b/src/App.test.jsx
@@ -1,117 +1,68 @@
import React from 'react';
-import { when } from 'jest-when';
-import { Route, Routes } from 'react-router-dom';
-
-import { ErrorPage } from '@edx/frontend-platform/react';
-import { useIntl } from '@edx/frontend-platform/i18n';
-import { formatMessage, shallow } from '@edx/react-unit-test-utils';
-
-import AssessmentView from 'views/AssessmentView';
-import SubmissionView from 'views/SubmissionView';
-import XBlockView from 'views/XBlockView';
-import XBlockStudioView from 'views/XBlockStudioView';
-import GradeView from 'views/GradeView';
-
-import PageRoute from 'components/PageRoute';
+import { render, screen } from '@testing-library/react';
+import '@testing-library/jest-dom';
+import { IntlProvider } from '@edx/frontend-platform/i18n';
+import { MemoryRouter } from 'react-router-dom';
import { useHandleModalCloseEvent } from 'hooks/modal';
-import messages from './messages';
-import routes from './routes';
-
import App from './App';
-jest.mock('react-router-dom', () => ({
- Routes: 'Routes',
- Route: 'Route',
-}));
+/* eslint-disable react/prop-types */
-jest.mock('@edx/frontend-platform/react', () => ({
- AuthenticatedPageRoute: 'AuthenticatedPageRoute',
- ErrorPage: 'ErrorPage',
-}));
-jest.mock('views/AssessmentView', () => 'AssessmentView');
-jest.mock('views/SubmissionView', () => 'SubmissionView');
-jest.mock('views/XBlockView', () => 'XBlockView');
-jest.mock('views/XBlockStudioView', () => 'XBlockStudioView');
-jest.mock('views/GradeView', () => 'GradeView');
-jest.mock('components/PageRoute', () => 'PageRoute');
+jest.unmock('@openedx/paragon');
+jest.unmock('react');
+jest.unmock('@edx/frontend-platform/i18n');
jest.mock('hooks/modal', () => ({
useHandleModalCloseEvent: jest.fn(),
}));
+jest.mock('@edx/frontend-platform/react', () => ({
+ ErrorPage: ({ message }) =>
{message || 'Error Page'}
,
+}));
+
const handleModalClose = jest.fn();
-when(useHandleModalCloseEvent).calledWith().mockReturnValue(handleModalClose);
const addEventListener = jest.fn();
const removeEventListener = jest.fn();
-let el;
describe('App component', () => {
+ const renderWithProviders = (component) => render(
+
+
+ {component}
+
+ ,
+ );
+
beforeEach(() => {
jest.clearAllMocks();
+ useHandleModalCloseEvent.mockReturnValue(handleModalClose);
jest.spyOn(window, 'addEventListener').mockImplementation(addEventListener);
- jest.spyOn(window, 'removeEventListener').mockImplementation(removeEventListener);
- el = shallow();
+ jest
+ .spyOn(window, 'removeEventListener')
+ .mockImplementation(removeEventListener);
+ });
+
+ it('renders app with accessible error page fallback', () => {
+ renderWithProviders();
+
+ expect(screen.getByRole('alert')).toHaveTextContent('Page not found');
});
- describe('behavior', () => {
- it('initializes i18n and refresh event from hooks', () => {
- expect(useIntl).toHaveBeenCalled();
- expect(useHandleModalCloseEvent).toHaveBeenCalled();
- });
- it('adds handler for modal close event that refreshes page data', () => {
- expect(React.useEffect.mock.calls.length).toEqual(1);
- const [[effect, prereqs]] = React.useEffect.mock.calls;
- expect(prereqs).toEqual([handleModalClose]);
- const out = effect();
- expect(addEventListener).toHaveBeenCalledWith('message', handleModalClose);
- out();
- expect(removeEventListener).toHaveBeenCalledWith('message', handleModalClose);
- });
+
+ it('calls useHandleModalCloseEvent hook and sets up event listeners', () => {
+ renderWithProviders();
+ expect(useHandleModalCloseEvent).toHaveBeenCalled();
+ expect(addEventListener).toHaveBeenCalledWith('message', handleModalClose);
});
- describe('render', () => {
- test('snapshot', () => {
- expect(el.snapshot).toMatchSnapshot();
- });
- const testComponent = (toTest, { route, Component, isModal }) => {
- expect(toTest.type).toEqual(Route);
- expect(toTest.props.path).toEqual(route);
- const { element } = toTest.props;
- expect(toTest.props.element.type).toEqual(PageRoute);
- if (isModal) {
- expect(toTest.props.element.props.isModal).toEqual(true);
- }
- const expectedElement = shallow();
- expect(shallow(element)).toMatchObject(expectedElement);
- };
- const testAssessmentRoute = (toTest, { route }) => {
- testComponent(toTest, { route, Component: AssessmentView, isModal: true });
- };
- test('route order', () => {
- const renderedRoutes = el.instance.findByType(Routes)[0].children;
- testComponent(renderedRoutes[0], { route: routes.xblock, Component: XBlockView });
- testComponent(renderedRoutes[1], { route: routes.xblockStudio, Component: XBlockStudioView });
- testComponent(renderedRoutes[2], { route: routes.xblockPreview, Component: XBlockView });
- testAssessmentRoute(renderedRoutes[3], { route: routes.peerAssessment });
- testAssessmentRoute(renderedRoutes[4], { route: routes.selfAssessment });
- testAssessmentRoute(renderedRoutes[5], { route: routes.studentTraining });
- testComponent(renderedRoutes[6], {
- route: routes.submission,
- Component: SubmissionView,
- isModal: true,
- });
- testComponent(renderedRoutes[7], {
- route: routes.graded,
- Component: GradeView,
- isModal: true,
- });
- expect(renderedRoutes[8].matches(shallow(
- }
- />,
- )));
- });
+
+ it('removes event listener on unmount', () => {
+ const { unmount } = renderWithProviders();
+
+ unmount();
+ expect(removeEventListener).toHaveBeenCalledWith(
+ 'message',
+ handleModalClose,
+ );
});
});
diff --git a/src/__snapshots__/App.test.jsx.snap b/src/__snapshots__/App.test.jsx.snap
deleted file mode 100644
index e688a807..00000000
--- a/src/__snapshots__/App.test.jsx.snap
+++ /dev/null
@@ -1,89 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`App component render snapshot 1`] = `
-
-
-
-
- }
- path="xblock/:courseId/:xblockId/:progressKey?"
- />
-
-
-
- }
- path="xblock_studio/:courseId/:xblockId/:progressKey?"
- />
-
-
-
- }
- path="xblock_preview/:courseId/:xblockId/:progressKey?"
- />
-
-
-
- }
- path="peer_assessment/:courseId/:xblockId/:progressKey?"
- />
-
-
-
- }
- path="self_assessment/:courseId/:xblockId/:progressKey?"
- />
-
-
-
- }
- path="student_training/:courseId/:xblockId/:progressKey?"
- />
-
-
-
- }
- path="submission/:courseId/:xblockId/:progressKey?"
- />
-
-
-
- }
- path="graded/:courseId/:xblockId/:progressKey?"
- />
-
- }
- key="error"
- path="/*"
- />
-
-`;
diff --git a/src/components/Assessment/EditableAssessment/OverallFeedback/__snapshots__/index.test.jsx.snap b/src/components/Assessment/EditableAssessment/OverallFeedback/__snapshots__/index.test.jsx.snap
deleted file mode 100644
index cf61a365..00000000
--- a/src/components/Assessment/EditableAssessment/OverallFeedback/__snapshots__/index.test.jsx.snap
+++ /dev/null
@@ -1,31 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[` render default 1`] = `
-
-
-
- Overall comments
-
-
-
- useOverallFeedbackPrompt
-
-
-
-
-
-`;
-
-exports[` render empty on studentTraining 1`] = `null`;
diff --git a/src/components/Assessment/EditableAssessment/OverallFeedback/index.test.jsx b/src/components/Assessment/EditableAssessment/OverallFeedback/index.test.jsx
index 8583a9dd..e2481c40 100644
--- a/src/components/Assessment/EditableAssessment/OverallFeedback/index.test.jsx
+++ b/src/components/Assessment/EditableAssessment/OverallFeedback/index.test.jsx
@@ -1,58 +1,83 @@
-import { shallow } from '@edx/react-unit-test-utils';
+import React from 'react';
+import { render, screen } from '@testing-library/react';
+import '@testing-library/jest-dom';
+import { IntlProvider } from '@edx/frontend-platform/i18n';
import { useOverallFeedbackPrompt, useOverallFeedbackFormFields } from 'hooks/assessment';
import { useViewStep } from 'hooks/routing';
import { stepNames } from 'constants/index';
import OverallFeedback from '.';
+jest.unmock('@openedx/paragon');
+jest.unmock('react');
+jest.unmock('@edx/frontend-platform/i18n');
+
jest.mock('hooks/assessment', () => ({
useOverallFeedbackPrompt: jest.fn(),
useOverallFeedbackFormFields: jest.fn(),
}));
-jest.mock('components/InfoPopover', () => 'InfoPopover');
+jest.mock('components/InfoPopover', () => ({
+ __esModule: true,
+ default: ({ children }) => (
+
+ {children}
+
+ ),
+}));
jest.mock('hooks/routing', () => ({
- useViewStep: jest.fn().mockReturnValue('step'),
+ useViewStep: jest.fn(),
}));
+const messages = {
+ 'frontend-app-ora.EditableAssessment.overallComments': 'Overall comments',
+ 'frontend-app-ora.EditableAssessment.addComments': 'Add comments (Optional)',
+};
+
describe('', () => {
- const mockOnChange = jest
- .fn()
- .mockName('useOverallFeedbackFormFields.onChange');
- const mockFeedbackValue = 'useOverallFeedbackFormFields.value';
- const mockPrompt = 'useOverallFeedbackPrompt';
+ const renderWithIntl = (component) => render(
+
+ {component}
+ ,
+ );
+
+ const mockOnChange = jest.fn();
+ const mockFeedbackValue = 'Test feedback content';
+ const mockPrompt = 'Please provide overall feedback';
- beforeAll(() => {
+ beforeEach(() => {
+ jest.clearAllMocks();
useOverallFeedbackPrompt.mockReturnValue(mockPrompt);
useOverallFeedbackFormFields.mockReturnValue({
value: mockFeedbackValue,
onChange: mockOnChange,
});
+ useViewStep.mockReturnValue('assessment');
});
- it('render default', () => {
- const wrapper = shallow();
- expect(wrapper.snapshot).toMatchSnapshot();
+ it('renders overall feedback form with prompt and textarea', () => {
+ renderWithIntl();
+
+ expect(screen.getByText('Overall comments')).toBeInTheDocument();
+ expect(screen.getByText('Please provide overall feedback')).toBeInTheDocument();
+ expect(screen.getByRole('textbox')).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: 'Help information' })).toBeInTheDocument();
});
- it('render empty on studentTraining', () => {
- useViewStep.mockReturnValueOnce(stepNames.studentTraining);
- const wrapper = shallow();
- expect(wrapper.snapshot).toMatchSnapshot();
+ it('renders nothing when step is studentTraining', () => {
+ useViewStep.mockReturnValue(stepNames.studentTraining);
- expect(wrapper.isEmptyRender()).toBe(true);
- });
+ const { container } = renderWithIntl();
- it('has correct mock value', () => {
- const wrapper = shallow();
+ expect(container.firstChild).toBeNull();
+ });
- expect(wrapper.instance.findByTestId('prompt-test-id')[0].children[0].el).toBe(
- mockPrompt,
- );
+ it('displays correct form field values from hooks', () => {
+ renderWithIntl();
- const { props } = wrapper.instance.findByType('Form.Control')[0];
- expect(props.value).toBe(mockFeedbackValue);
- expect(props.onChange).toBe(mockOnChange);
+ const textarea = screen.getByRole('textbox');
+ expect(textarea).toHaveValue(mockFeedbackValue);
+ expect(screen.getByText('Please provide overall feedback')).toBeInTheDocument();
});
});
diff --git a/src/components/Assessment/EditableAssessment/__snapshots__/index.test.jsx.snap b/src/components/Assessment/EditableAssessment/__snapshots__/index.test.jsx.snap
deleted file mode 100644
index a582ee80..00000000
--- a/src/components/Assessment/EditableAssessment/__snapshots__/index.test.jsx.snap
+++ /dev/null
@@ -1,125 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[` render empty criteria 1`] = `
-
-
-
- Rubric
-
-
-
-
-
-
-
-`;
-
-exports[` render with criteria 1`] = `
-
-
-
- Rubric
-
-
-
- }
- input={
-
- }
- key="criterion1"
- />
-
- }
- input={
-
- }
- key="criterion2"
- />
-
- }
- input={
-
- }
- key="criterion3"
- />
-
-
-
-
-
-`;
diff --git a/src/components/Assessment/EditableAssessment/index.test.jsx b/src/components/Assessment/EditableAssessment/index.test.jsx
index 592c8750..df25a43d 100644
--- a/src/components/Assessment/EditableAssessment/index.test.jsx
+++ b/src/components/Assessment/EditableAssessment/index.test.jsx
@@ -1,37 +1,66 @@
-import { shallow } from '@edx/react-unit-test-utils';
+import React from 'react';
+import { render, screen } from '@testing-library/react';
+import '@testing-library/jest-dom';
+import { IntlProvider } from '@edx/frontend-platform/i18n';
import { useCriteriaConfig } from 'hooks/assessment';
import EditableAssessment from '.';
+jest.unmock('@openedx/paragon');
+jest.unmock('react');
+jest.unmock('@edx/frontend-platform/i18n');
+
jest.mock('hooks/assessment', () => ({
useCriteriaConfig: jest.fn(),
}));
-jest.mock('components/CriterionContainer', () => 'CriterionContainer');
-jest.mock('components/CriterionContainer/RadioCriterion', () => 'RadioCriterion');
-jest.mock('components/CriterionContainer/CriterionFeedback', () => 'CriterionFeedback');
-jest.mock('./OverallFeedback', () => 'OverallFeedback');
-jest.mock('./AssessmentActions', () => 'AssessmentActions');
+jest.mock('components/CriterionContainer', () => () => (
+ Criterion Container
+));
+jest.mock('./OverallFeedback', () => () => (
+
+));
+jest.mock('./AssessmentActions', () => () => (
+ Assessment Actions
+));
+
+const messages = {
+ 'frontend-app-ora.EditableAssessment.rubric': 'Rubric',
+};
describe('', () => {
- it('render empty criteria', () => {
+ const renderWithIntl = (component) => render(
+
+ {component}
+ ,
+ );
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('renders assessment card structure with all core components', () => {
useCriteriaConfig.mockReturnValue([]);
- const wrapper = shallow();
- expect(wrapper.snapshot).toMatchSnapshot();
- expect(wrapper.instance.findByType('CriterionContainer')).toHaveLength(0);
+ renderWithIntl();
+
+ expect(screen.getByText('Rubric')).toBeInTheDocument();
+ expect(screen.getByRole('region', { name: 'Overall Feedback' })).toBeInTheDocument();
+ expect(screen.getByRole('group', { name: 'Assessment Actions' })).toBeInTheDocument();
});
- it('render with criteria', () => {
+ it('renders criterion containers based on criteria config', () => {
const mockCriteria = [
{ name: 'criterion1' },
{ name: 'criterion2' },
- { name: 'criterion3' },
];
useCriteriaConfig.mockReturnValue(mockCriteria);
- const wrapper = shallow();
- expect(wrapper.snapshot).toMatchSnapshot();
- expect(wrapper.instance.findByType('CriterionContainer')).toHaveLength(mockCriteria.length);
+ renderWithIntl();
+
+ expect(screen.getByText('Rubric')).toBeInTheDocument();
+ expect(screen.getAllByText('Criterion Container')).toHaveLength(2);
+ expect(screen.getByRole('region', { name: 'Overall Feedback' })).toBeInTheDocument();
+ expect(screen.getByRole('group', { name: 'Assessment Actions' })).toBeInTheDocument();
});
});
diff --git a/src/components/ModalActions/__snapshots__/index.test.jsx.snap b/src/components/ModalActions/__snapshots__/index.test.jsx.snap
deleted file mode 100644
index 614e5c99..00000000
--- a/src/components/ModalActions/__snapshots__/index.test.jsx.snap
+++ /dev/null
@@ -1,50 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[` can render primary and secondary with confirm 1`] = `
-
-`;
-
-exports[` can render primary and secondary without confirm 1`] = `
-
-`;
-
-exports[` render empty when no actions 1`] = `
-
-`;
-
-exports[` render skeleton when page data is loading 1`] = `
-
-`;
diff --git a/src/components/ModalActions/index.test.jsx b/src/components/ModalActions/index.test.jsx
index a191a383..c75ab366 100644
--- a/src/components/ModalActions/index.test.jsx
+++ b/src/components/ModalActions/index.test.jsx
@@ -1,19 +1,25 @@
-import { shallow } from '@edx/react-unit-test-utils';
+import React from 'react';
+import { render, screen } from '@testing-library/react';
+import '@testing-library/jest-dom';
+import { IntlProvider } from '@edx/frontend-platform/i18n';
import { useIsPageDataLoading } from 'hooks/app';
-
import useModalActionConfig from './hooks/useModalActionConfig';
import ModalActions from './index';
-jest.mock('components/ActionButton', () => 'ActionButton');
-jest.mock('components/ConfirmDialog', () => 'ConfirmDialog');
+jest.unmock('@openedx/paragon');
+jest.unmock('react');
+jest.unmock('@edx/frontend-platform/i18n');
+
jest.mock('hooks/app', () => ({
useIsPageDataLoading: jest.fn(),
}));
jest.mock('./hooks/useModalActionConfig', () => jest.fn());
describe('', () => {
+ const renderWithIntl = (component) => render({component});
+
const props = {
options: {},
};
@@ -21,50 +27,84 @@ describe('', () => {
useIsPageDataLoading.mockReturnValue(false);
});
- it('render skeleton when page data is loading', () => {
+ it('renders skeleton when page data is loading', () => {
useIsPageDataLoading.mockReturnValueOnce(true);
- const wrapper = shallow();
- expect(wrapper.snapshot).toMatchSnapshot();
- expect(wrapper.instance.findByType('Skeleton')).toHaveLength(1);
+ useModalActionConfig.mockReturnValue({});
+ const { container } = renderWithIntl();
+ expect(
+ container.querySelector('.react-loading-skeleton'),
+ ).toBeInTheDocument();
});
- it('render empty when no actions', () => {
+ it('renders empty actions container when no actions are configured', () => {
useModalActionConfig.mockReturnValue({});
- const wrapper = shallow();
- expect(wrapper.snapshot).toMatchSnapshot();
- expect(wrapper.instance.findByType('ActionButton')).toHaveLength(0);
- expect(wrapper.instance.findByType('ConfirmDialog')).toHaveLength(0);
+ const { container } = renderWithIntl();
+ const actionDiv = container.querySelector('.mt-2');
+ expect(actionDiv).toBeInTheDocument();
+ expect(actionDiv).toBeEmptyDOMElement();
});
- it('can render primary and secondary without confirm', () => {
+ it('renders primary and secondary buttons without confirm dialogs', () => {
useModalActionConfig.mockReturnValue({
primary: {
- action: {},
+ action: {
+ children: 'Primary Action',
+ onClick: jest.fn(),
+ },
},
secondary: {
- action: {},
+ action: {
+ children: 'Secondary Action',
+ onClick: jest.fn(),
+ },
},
});
- const wrapper = shallow();
- expect(wrapper.snapshot).toMatchSnapshot();
- expect(wrapper.instance.findByType('ActionButton')).toHaveLength(2);
- expect(wrapper.instance.findByType('ConfirmDialog')).toHaveLength(0);
+ renderWithIntl();
+
+ expect(
+ screen.getByRole('button', { name: 'Primary Action' }),
+ ).toBeInTheDocument();
+ expect(
+ screen.getByRole('button', { name: 'Secondary Action' }),
+ ).toBeInTheDocument();
});
- it('can render primary and secondary with confirm', () => {
+ it('renders primary and secondary buttons with confirm dialogs', () => {
useModalActionConfig.mockReturnValue({
primary: {
- action: {},
- confirmProps: {},
+ action: {
+ children: 'Primary Action',
+ onClick: jest.fn(),
+ },
+ confirmProps: {
+ title: 'Confirm Primary',
+ description: 'Are you sure?',
+ action: { onClick: jest.fn() },
+ isOpen: false,
+ close: jest.fn(),
+ },
},
secondary: {
- action: {},
- confirmProps: {},
+ action: {
+ children: 'Secondary Action',
+ onClick: jest.fn(),
+ },
+ confirmProps: {
+ title: 'Confirm Secondary',
+ description: 'Are you sure?',
+ action: { onClick: jest.fn() },
+ isOpen: false,
+ close: jest.fn(),
+ },
},
});
- const wrapper = shallow();
- expect(wrapper.snapshot).toMatchSnapshot();
- expect(wrapper.instance.findByType('ActionButton')).toHaveLength(2);
- expect(wrapper.instance.findByType('ConfirmDialog')).toHaveLength(2);
+ renderWithIntl();
+
+ expect(
+ screen.getByRole('button', { name: 'Primary Action' }),
+ ).toBeInTheDocument();
+ expect(
+ screen.getByRole('button', { name: 'Secondary Action' }),
+ ).toBeInTheDocument();
});
});
diff --git a/src/views/GradeView/__snapshots__/index.test.jsx.snap b/src/views/GradeView/__snapshots__/index.test.jsx.snap
deleted file mode 100644
index c52ca321..00000000
--- a/src/views/GradeView/__snapshots__/index.test.jsx.snap
+++ /dev/null
@@ -1,133 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[` renders correctly 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-`;
diff --git a/src/views/GradeView/index.test.jsx b/src/views/GradeView/index.test.jsx
index 9cf2486d..9b7ca43c 100644
--- a/src/views/GradeView/index.test.jsx
+++ b/src/views/GradeView/index.test.jsx
@@ -1,18 +1,37 @@
-import { shallow } from '@edx/react-unit-test-utils';
+import React from 'react';
+import { render, screen } from '@testing-library/react';
+import '@testing-library/jest-dom';
import GradeView from './index';
-jest.mock('components/ModalActions', () => 'ModalActions');
-jest.mock('./FinalGrade', () => 'FinalGrade');
-jest.mock('./Content', () => 'Content');
+jest.unmock('@openedx/paragon');
+jest.unmock('react');
+jest.unmock('@edx/frontend-platform/i18n');
+
+jest.mock('./FinalGrade', () => () => (
+ Final Grade Component
+));
+jest.mock('./Content', () => () => (
+ Grade Content Component
+));
+jest.mock('components/ModalActions', () => () => (
+ Modal Actions Component
+));
describe('', () => {
- it('renders correctly', () => {
- const wrapper = shallow();
- expect(wrapper.snapshot).toMatchSnapshot();
+ it('renders grade view with proper layout structure and responsive design', () => {
+ const { container } = render();
+
+ expect(screen.getByText('Final Grade Component')).toBeInTheDocument();
+ expect(screen.getByText('Grade Content Component')).toBeInTheDocument();
+ expect(screen.getByText('Modal Actions Component')).toBeInTheDocument();
+
+ const gradeViewBody = container.querySelector('.grade-view-body');
+ expect(gradeViewBody).toBeInTheDocument();
- expect(wrapper.instance.findByType('ModalActions')).toHaveLength(1);
- expect(wrapper.instance.findByType('FinalGrade')).toHaveLength(1);
- expect(wrapper.instance.findByType('Content')).toHaveLength(1);
+ const mainContainer = container.querySelector(
+ '.m-0.d-flex.justify-content-center',
+ );
+ expect(mainContainer).toBeInTheDocument();
});
});