diff --git a/src/components/FilePreview/__snapshots__/index.test.jsx.snap b/src/components/FilePreview/__snapshots__/index.test.jsx.snap
deleted file mode 100644
index cc93a6a7..00000000
--- a/src/components/FilePreview/__snapshots__/index.test.jsx.snap
+++ /dev/null
@@ -1,28 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[` render only supported files 1`] = `
-
-
-
-
-`;
-
-exports[` renders nothing when no files are uploaded 1`] = `null`;
-
-exports[` renders only div when no supported files are uploaded 1`] = `
`;
diff --git a/src/components/FilePreview/components/FileRenderer/__snapshots__/index.test.jsx.snap b/src/components/FilePreview/components/FileRenderer/__snapshots__/index.test.jsx.snap
deleted file mode 100644
index 6136f6d1..00000000
--- a/src/components/FilePreview/components/FileRenderer/__snapshots__/index.test.jsx.snap
+++ /dev/null
@@ -1,47 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`FileRenderer Component render default 1`] = `
-
-
-
-`;
-
-exports[`FileRenderer Component render error banner 1`] = `
-
-
-
-`;
-
-exports[`FileRenderer Component render loading banner 1`] = `
-
-
-
-`;
diff --git a/src/components/FilePreview/components/FileRenderer/hooks.test.js b/src/components/FilePreview/components/FileRenderer/hooks.test.js
index 5a7cbc9f..337b2e22 100644
--- a/src/components/FilePreview/components/FileRenderer/hooks.test.js
+++ b/src/components/FilePreview/components/FileRenderer/hooks.test.js
@@ -1,6 +1,7 @@
import { mockUseKeyedState } from '@edx/react-unit-test-utils';
import { useRenderData, stateKeys } from './hooks';
+import { errorStatuses, errorMessages } from '../constants';
const state = mockUseKeyedState(stateKeys);
@@ -43,4 +44,27 @@ describe('useRenderData', () => {
state.expectSetStateCalledWith(stateKeys.errorStatus, null);
state.expectSetStateCalledWith(stateKeys.isLoading, true);
});
+
+ it('returns correct error message for different error statuses', () => {
+ // Test notFound error message
+ state.mockVal(stateKeys.errorStatus, errorStatuses.notFound);
+ let out = useRenderData(props);
+ expect(out.error.headerMessage).toBe(errorMessages[errorStatuses.notFound]);
+
+ // Test fallback to serverError message for unknown error status
+ state.mockVal(stateKeys.errorStatus, errorStatuses.badRequest);
+ out = useRenderData(props);
+ expect(out.error.headerMessage).toBe(
+ errorMessages[errorStatuses.serverError],
+ );
+ });
+
+ it('handles unknown file types', () => {
+ const propsWithUnknownFile = {
+ ...props,
+ file: { fileName: 'file.unknown', fileUrl: 'http://example.com' },
+ };
+ const out = useRenderData(propsWithUnknownFile);
+ expect(out.Renderer).toBeUndefined();
+ });
});
diff --git a/src/components/FilePreview/components/FileRenderer/index.test.jsx b/src/components/FilePreview/components/FileRenderer/index.test.jsx
index 68ab7178..3fc209a4 100644
--- a/src/components/FilePreview/components/FileRenderer/index.test.jsx
+++ b/src/components/FilePreview/components/FileRenderer/index.test.jsx
@@ -1,18 +1,45 @@
-import { shallow } from '@edx/react-unit-test-utils';
+import { render, screen } from '@testing-library/react';
+import '@testing-library/jest-dom';
+import { IntlProvider } from '@edx/frontend-platform/i18n';
import { useRenderData } from './hooks';
import { FileRenderer } from './index';
-jest.mock('./Banners', () => ({
- ErrorBanner: () => 'ErrorBanner',
- LoadingBanner: () => 'LoadingBanner',
-}));
+jest.unmock('@openedx/paragon');
+jest.unmock('react');
+jest.unmock('@edx/frontend-platform/i18n');
+
+/* eslint-disable react/prop-types */
jest.mock('./hooks', () => ({
useRenderData: jest.fn(),
}));
+jest.mock('./Banners', () => ({
+ ErrorBanner: ({ message, headerMessage, actions }) => (
+
+
{headerMessage}
+
{message}
+ {actions?.map((action) => (
+
{action.message}
+ ))}
+
+ ),
+ LoadingBanner: () => Loading...
,
+}));
+
+jest.mock('./FileCard', () => ({ children, file, defaultOpen }) => (
+
+
+ {file.fileName} - {defaultOpen ? 'open' : 'closed'}
+
+ {children}
+
+));
+
describe('FileRenderer Component', () => {
+ const renderWithIntl = (component) => render({component} );
+
const props = {
file: {
fileName: 'some_file',
@@ -22,7 +49,7 @@ describe('FileRenderer Component', () => {
};
const defaultRenderData = {
- Renderer: () => 'Renderer',
+ Renderer: () => Renderer content
,
isLoading: false,
errorStatus: false,
error: null,
@@ -31,32 +58,97 @@ describe('FileRenderer Component', () => {
},
};
- it('render default', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('renders default renderer', () => {
useRenderData.mockReturnValue(defaultRenderData);
- const wrapper = shallow( );
- expect(wrapper.snapshot).toMatchSnapshot();
+ renderWithIntl( );
- expect(wrapper.instance.findByType('Renderer')).toHaveLength(1);
- expect(wrapper.instance.findByType('LoadingBanner')).toHaveLength(0);
- expect(wrapper.instance.findByType('ErrorBanner')).toHaveLength(0);
+ expect(screen.getByTestId('file-card')).toBeInTheDocument();
+ expect(screen.getByText('some_file - open')).toBeInTheDocument();
+ expect(screen.getByTestId('renderer')).toBeInTheDocument();
+ expect(screen.getByText('Renderer content')).toBeInTheDocument();
+ expect(screen.queryByTestId('loading-banner')).not.toBeInTheDocument();
+ expect(screen.queryByTestId('error-banner')).not.toBeInTheDocument();
});
- it('render loading banner', () => {
+ it('renders loading banner', () => {
useRenderData.mockReturnValue({ ...defaultRenderData, isLoading: true });
- const wrapper = shallow( );
- expect(wrapper.snapshot).toMatchSnapshot();
+ renderWithIntl( );
+
+ expect(screen.getByTestId('loading-banner')).toBeInTheDocument();
+ expect(screen.getByText('Loading...')).toBeInTheDocument();
+ expect(screen.queryByTestId('error-banner')).not.toBeInTheDocument();
+ expect(screen.queryByTestId('renderer')).not.toBeInTheDocument();
+ });
- expect(wrapper.instance.findByType('LoadingBanner')).toHaveLength(1);
- expect(wrapper.instance.findByType('ErrorBanner')).toHaveLength(0);
- expect(wrapper.instance.findByType('Renderer')).toHaveLength(0);
+ it('renders error banner', () => {
+ useRenderData.mockReturnValue({
+ errorStatus: true,
+ error: { message: 'some_error' },
+ });
+ renderWithIntl( );
+
+ expect(screen.getByTestId('error-banner')).toBeInTheDocument();
+ expect(screen.getByText('some_error')).toBeInTheDocument();
+ expect(screen.queryByTestId('loading-banner')).not.toBeInTheDocument();
+ expect(screen.queryByTestId('renderer')).not.toBeInTheDocument();
+ });
+
+ it('passes defaultOpen prop correctly', () => {
+ useRenderData.mockReturnValue(defaultRenderData);
+ renderWithIntl( );
+
+ expect(screen.getByText('some_file - closed')).toBeInTheDocument();
+ });
+
+ it('passes rendererProps to Renderer component', () => {
+ const CustomRenderer = jest.fn(() => Custom Renderer
);
+ const customRendererProps = {
+ prop1: 'value1',
+ prop2: 'value2',
+ };
+ useRenderData.mockReturnValue({
+ ...defaultRenderData,
+ Renderer: CustomRenderer,
+ rendererProps: customRendererProps,
+ });
+
+ renderWithIntl( );
+ expect(CustomRenderer).toHaveBeenCalledWith(customRendererProps, {});
+ });
+
+ it('passes all error properties to ErrorBanner', () => {
+ const errorData = {
+ headerMessage: 'Error Header',
+ message: 'Error Message',
+ actions: [{ id: 'retry', message: 'Retry', onClick: jest.fn() }],
+ };
+ useRenderData.mockReturnValue({
+ errorStatus: true,
+ error: errorData,
+ });
+
+ renderWithIntl( );
+ expect(screen.getByTestId('error-header')).toHaveTextContent(
+ 'Error Header',
+ );
+ expect(screen.getByTestId('error-message')).toHaveTextContent(
+ 'Error Message',
+ );
+ expect(screen.getByTestId('error-action-retry')).toHaveTextContent('Retry');
});
- it('render error banner', () => {
- useRenderData.mockReturnValue({ errorStatus: true, error: { message: 'some_error' } });
- const wrapper = shallow( );
- expect(wrapper.snapshot).toMatchSnapshot();
+ it('handles missing optional file properties', () => {
+ const minimalProps = {
+ file: { fileName: 'minimal_file' },
+ defaultOpen: true,
+ };
+ useRenderData.mockReturnValue(defaultRenderData);
- expect(wrapper.instance.findByType('ErrorBanner')).toHaveLength(1);
- expect(wrapper.instance.findByType('LoadingBanner')).toHaveLength(0);
+ renderWithIntl( );
+ expect(screen.getByText('minimal_file - open')).toBeInTheDocument();
});
});
diff --git a/src/components/FilePreview/index.test.jsx b/src/components/FilePreview/index.test.jsx
index 75b05d1c..09e06dcd 100644
--- a/src/components/FilePreview/index.test.jsx
+++ b/src/components/FilePreview/index.test.jsx
@@ -1,16 +1,27 @@
-import { shallow } from '@edx/react-unit-test-utils';
+import { render, screen } from '@testing-library/react';
+import '@testing-library/jest-dom';
import { useResponse } from 'hooks/app';
import { isSupported } from './components';
import FilePreview from './index';
+jest.unmock('@openedx/paragon');
+jest.unmock('react');
+jest.unmock('@edx/frontend-platform/i18n');
+
jest.mock('hooks/app', () => ({
useResponse: jest.fn(),
}));
jest.mock('./components', () => ({
- FileRenderer: () => 'FileRenderer',
+ // eslint-disable-next-line react/prop-types
+ FileRenderer: ({ file, defaultOpen }) => (
+
+ {/* eslint-disable-next-line react/prop-types */}
+ {file.fileName} - {defaultOpen ? 'open' : 'closed'}
+
+ ),
isSupported: jest.fn(),
}));
@@ -18,40 +29,58 @@ describe(' ', () => {
const props = {
defaultCollapsePreview: false,
};
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
it('renders nothing when no files are uploaded', () => {
- useResponse.mockReturnValueOnce({ uploadedFiles: null });
- const wrapper = shallow( );
- expect(wrapper.snapshot).toMatchSnapshot();
- expect(wrapper.isEmptyRender()).toBe(true);
+ useResponse.mockReturnValue({ uploadedFiles: null });
+ const { container } = render( );
+ expect(container.firstChild).toBeNull();
});
- it('renders only div when no supported files are uploaded', () => {
- useResponse.mockReturnValueOnce({
+ it('renders empty div when no supported files are uploaded', () => {
+ useResponse.mockReturnValue({
uploadedFiles: [{ fileName: 'file1.txt' }],
});
- isSupported.mockReturnValueOnce(false);
- const wrapper = shallow( );
- expect(wrapper.snapshot).toMatchSnapshot();
+ isSupported.mockReturnValue(false);
+ const { container } = render( );
- expect(wrapper.instance.findByType('FileRenderer')).toHaveLength(0);
+ expect(container.firstChild).toBeInTheDocument();
+ expect(container.firstChild.tagName).toBe('DIV');
+ expect(screen.queryByTestId('file-renderer')).not.toBeInTheDocument();
});
- it('render only supported files', () => {
+ it('renders only supported files', () => {
isSupported
.mockReturnValueOnce(false)
.mockReturnValueOnce(true)
.mockReturnValueOnce(true);
- useResponse.mockReturnValueOnce({
+ useResponse.mockReturnValue({
uploadedFiles: [
{ fileName: 'file1.txt' },
{ fileName: 'file2.pdf' },
{ fileName: 'file3.jpg' },
],
});
- const wrapper = shallow( );
- expect(wrapper.snapshot).toMatchSnapshot();
+ render( );
+
+ const fileRenderers = screen.getAllByTestId('file-renderer');
+ expect(fileRenderers).toHaveLength(2);
+ expect(screen.getByText('file2.pdf - open')).toBeInTheDocument();
+ expect(screen.getByText('file3.jpg - open')).toBeInTheDocument();
+ expect(screen.queryByText('file1.txt - open')).not.toBeInTheDocument();
+ });
+
+ it('passes defaultCollapsePreview prop correctly', () => {
+ isSupported.mockReturnValue(true);
+ useResponse.mockReturnValue({
+ uploadedFiles: [{ fileName: 'file1.pdf' }],
+ });
+ render( );
- expect(wrapper.instance.findByType('FileRenderer')).toHaveLength(2);
+ expect(screen.getByText('file1.pdf - closed')).toBeInTheDocument();
});
});
diff --git a/src/components/Rubric/RubricFeedback.test.jsx b/src/components/Rubric/RubricFeedback.test.jsx
index e1e491e1..b4804e22 100644
--- a/src/components/Rubric/RubricFeedback.test.jsx
+++ b/src/components/Rubric/RubricFeedback.test.jsx
@@ -1,10 +1,24 @@
import React from 'react';
-import { shallow } from '@edx/react-unit-test-utils';
+import { render, screen } from '@testing-library/react';
+import '@testing-library/jest-dom';
+import { IntlProvider } from '@edx/frontend-platform/i18n';
+import userEvent from '@testing-library/user-event';
import RubricFeedback from './RubricFeedback';
import messages from './messages';
+jest.unmock('@openedx/paragon');
+jest.unmock('react');
+jest.unmock('@edx/frontend-platform/i18n');
+
+// eslint-disable-next-line react/prop-types
+jest.mock('components/InfoPopover', () => ({ children }) => (
+ {children}
+));
+
describe(' ', () => {
+ const renderWithIntl = (component) => render({component} );
+
const props = {
overallFeedbackPrompt: 'overallFeedbackPrompt',
overallFeedback: 'overallFeedback',
@@ -13,30 +27,65 @@ describe(' ', () => {
onOverallFeedbackChange: jest.fn().mockName('onOverallFeedbackChange'),
};
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
describe('renders', () => {
- test('overall feedback is enabled', () => {
- const wrapper = shallow( );
- expect(wrapper.snapshot).toMatchSnapshot();
+ it('overall feedback is enabled', () => {
+ renderWithIntl( );
- expect(wrapper.instance.findByType('Form.Control.Feedback').length).toBe(0);
- expect(wrapper.instance.findByType('Form.Control')[0].props.disabled).toBe(false);
- expect(wrapper.instance.findByType('Form.Control')[0].props.floatingLabel).toBe(messages.addComments.defaultMessage);
+ expect(
+ screen.getByText(messages.overallComments.defaultMessage),
+ ).toBeInTheDocument();
+ expect(screen.getByDisplayValue('overallFeedback')).toBeInTheDocument();
+ expect(screen.getByDisplayValue('overallFeedback')).not.toBeDisabled();
+ expect(
+ screen.getByLabelText(messages.addComments.defaultMessage),
+ ).toBeInTheDocument();
+ expect(
+ screen.queryByText(messages.overallFeedbackError.defaultMessage),
+ ).not.toBeInTheDocument();
+ expect(screen.getByTestId('info-popover')).toBeInTheDocument();
+ expect(screen.getByText('overallFeedbackPrompt')).toBeInTheDocument();
});
- test('overall feedback is disabled', () => {
- const wrapper = shallow( );
- expect(wrapper.snapshot).toMatchSnapshot();
+ it('overall feedback is disabled', () => {
+ renderWithIntl( );
- expect(wrapper.instance.findByType('Form.Control.Feedback').length).toBe(0);
- expect(wrapper.instance.findByType('Form.Control')[0].props.disabled).toBe(true);
- expect(wrapper.instance.findByType('Form.Control')[0].props.floatingLabel).toBe(messages.comments.defaultMessage);
+ expect(
+ screen.getByText(messages.overallComments.defaultMessage),
+ ).toBeInTheDocument();
+ expect(screen.getByDisplayValue('overallFeedback')).toBeInTheDocument();
+ expect(screen.getByDisplayValue('overallFeedback')).toBeDisabled();
+ expect(
+ screen.getByLabelText(messages.comments.defaultMessage),
+ ).toBeInTheDocument();
+ expect(
+ screen.queryByText(messages.overallFeedbackError.defaultMessage),
+ ).not.toBeInTheDocument();
});
- test('overall feedback is invalid', () => {
- const wrapper = shallow( );
- expect(wrapper.snapshot).toMatchSnapshot();
+ it('overall feedback is invalid', () => {
+ renderWithIntl( );
+
+ expect(
+ screen.getByText(messages.overallComments.defaultMessage),
+ ).toBeInTheDocument();
+ expect(screen.getByDisplayValue('overallFeedback')).toBeInTheDocument();
+ expect(
+ screen.getByText(messages.overallFeedbackError.defaultMessage),
+ ).toBeInTheDocument();
+ });
+
+ it('handles feedback change', async () => {
+ const user = userEvent.setup();
+ renderWithIntl( );
+
+ const textarea = screen.getByRole('textbox');
+ await user.type(textarea, 'new feedback');
- expect(wrapper.instance.findByType('Form.Control.Feedback').length).toBe(1);
+ expect(props.onOverallFeedbackChange).toHaveBeenCalled();
});
});
});
diff --git a/src/components/Rubric/__snapshots__/RubricFeedback.test.jsx.snap b/src/components/Rubric/__snapshots__/RubricFeedback.test.jsx.snap
deleted file mode 100644
index a5a655c2..00000000
--- a/src/components/Rubric/__snapshots__/RubricFeedback.test.jsx.snap
+++ /dev/null
@@ -1,94 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[` renders overall feedback is disabled 1`] = `
-
-
-
- Overall comments
-
-
-
- overallFeedbackPrompt
-
-
-
-
-
-`;
-
-exports[` renders overall feedback is enabled 1`] = `
-
-
-
- Overall comments
-
-
-
- overallFeedbackPrompt
-
-
-
-
-
-`;
-
-exports[` renders overall feedback is invalid 1`] = `
-
-
-
- Overall comments
-
-
-
- overallFeedbackPrompt
-
-
-
-
-
- The overall feedback is required
-
-
-`;
diff --git a/src/views/XBlockView/Actions/__snapshots__/index.test.jsx.snap b/src/views/XBlockView/Actions/__snapshots__/index.test.jsx.snap
deleted file mode 100644
index 41f0d4d0..00000000
--- a/src/views/XBlockView/Actions/__snapshots__/index.test.jsx.snap
+++ /dev/null
@@ -1,101 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[` does not render button when step is staff 1`] = `
-
-`;
-
-exports[` render load next when step peer is not waiting nor waiting for submission 1`] = `
-
-
- default
- (optional)
-
-
-`;
-
-exports[` render load next when step studentTraining is not waiting nor waiting for submission 1`] = `
-
-
- default
-
-
-`;
-
-exports[` render message step is not staff and step state done 1`] = `
-
-
- default
-
-
-`;
-
-exports[` render message step is not staff and step state inProgress 1`] = `
-
-
- default
-
-
-`;
diff --git a/src/views/XBlockView/Actions/index.test.jsx b/src/views/XBlockView/Actions/index.test.jsx
index d8a0d4f0..7b049171 100644
--- a/src/views/XBlockView/Actions/index.test.jsx
+++ b/src/views/XBlockView/Actions/index.test.jsx
@@ -1,5 +1,7 @@
-import React from 'react';
-import { shallow } from '@edx/react-unit-test-utils';
+import { render, screen } from '@testing-library/react';
+import '@testing-library/jest-dom';
+import { IntlProvider } from '@edx/frontend-platform/i18n';
+import userEvent from '@testing-library/user-event';
import { stepNames, stepStates } from 'constants/index';
import {
@@ -11,97 +13,196 @@ import { useOpenModal } from 'hooks/modal';
import SubmissionActions from './index';
+jest.unmock('@openedx/paragon');
+jest.unmock('react');
+jest.unmock('@edx/frontend-platform/i18n');
+
jest.mock('hooks/actions', () => ({
useLoadNextAction: () => ({
action: {
labels: {
- default: 'default',
+ default: 'Load Next Assessment',
},
},
}),
}));
+
jest.mock('hooks/app', () => ({
useAssessmentStepConfig: jest.fn(),
useGlobalState: jest.fn(),
useStepInfo: jest.fn(),
}));
+
jest.mock('hooks/modal', () => ({
useOpenModal: jest.fn(),
}));
describe(' ', () => {
+ const renderWithIntl = (component) => render({component} );
+
const mockOpenModal = jest.fn();
+
beforeEach(() => {
useOpenModal.mockReturnValue(mockOpenModal);
- });
- afterEach(() => {
jest.clearAllMocks();
});
- [stepNames.studentTraining, stepNames.peer].forEach((stepName) => {
- it(`render load next when step ${stepName} is not waiting nor waiting for submission`, () => {
- useGlobalState.mockReturnValue({
- activeStepName: stepName,
- stepState: stepStates.notAvailable,
- });
- useStepInfo.mockReturnValue({
- [stepName]: {
- numberOfAssessmentsCompleted: 1,
- isWaitingForSubmissions: false,
- },
- });
- useAssessmentStepConfig.mockReturnValue({
- settings: {
- [stepName]: {
- minNumberToGrade: 1,
- },
+ it('renders load next button for student training step', async () => {
+ useGlobalState.mockReturnValue({
+ activeStepName: stepNames.studentTraining,
+ stepState: stepStates.notAvailable,
+ });
+ useStepInfo.mockReturnValue({
+ [stepNames.studentTraining]: {
+ numberOfAssessmentsCompleted: 1,
+ isWaitingForSubmissions: false,
+ },
+ });
+ useAssessmentStepConfig.mockReturnValue({
+ settings: {
+ [stepNames.studentTraining]: {
+ minNumberToGrade: 1,
},
- });
- const wrapper = shallow( );
- expect(wrapper.snapshot).toMatchSnapshot();
+ },
+ });
+
+ renderWithIntl( );
- expect(wrapper.instance.findByType('Button')).toHaveLength(1);
+ const button = screen.getByRole('button');
+ expect(button).toBeInTheDocument();
+ expect(button).toHaveTextContent('Load Next Assessment');
- React.useCallback.mock.calls[0][0]();
- expect(mockOpenModal).toHaveBeenCalledWith({ view: stepName, title: stepName });
+ const user = userEvent.setup();
+ await user.click(button);
+
+ expect(mockOpenModal).toHaveBeenCalledWith({
+ view: stepNames.studentTraining,
+ title: stepNames.studentTraining,
});
});
- [stepStates.inProgress, stepNames.done].forEach((stepState) => {
- it(`render message step is not staff and step state ${stepState}`, () => {
- useGlobalState.mockReturnValue({
- activeStepName: stepNames.studentTraining,
- stepState,
- });
- useStepInfo.mockReturnValue({
+ it('does not render button when step is staff', () => {
+ useGlobalState.mockReturnValue({
+ activeStepName: stepNames.staff,
+ stepState: stepStates.notAvailable,
+ });
+ useStepInfo.mockReturnValue({});
+ useAssessmentStepConfig.mockReturnValue({ settings: {} });
+
+ renderWithIntl( );
+
+ expect(screen.queryByRole('button')).not.toBeInTheDocument();
+ });
+
+ it('renders button for in-progress step state', () => {
+ useGlobalState.mockReturnValue({
+ activeStepName: stepNames.studentTraining,
+ stepState: stepStates.inProgress,
+ });
+ useStepInfo.mockReturnValue({
+ [stepNames.studentTraining]: {
+ numberOfAssessmentsCompleted: 1,
+ isWaitingForSubmissions: false,
+ },
+ });
+ useAssessmentStepConfig.mockReturnValue({
+ settings: {
[stepNames.studentTraining]: {
- numberOfAssessmentsCompleted: 1,
- isWaitingForSubmissions: false,
+ minNumberToGrade: 1,
},
- });
- useAssessmentStepConfig.mockReturnValue({
- settings: {
- [stepNames.studentTraining]: {
- minNumberToGrade: 1,
- },
+ },
+ });
+
+ renderWithIntl( );
+
+ const button = screen.getByRole('button');
+ expect(button).toBeInTheDocument();
+ });
+
+ it('renders container with correct CSS classes', () => {
+ useGlobalState.mockReturnValue({
+ activeStepName: stepNames.staff,
+ stepState: stepStates.notAvailable,
+ });
+ useStepInfo.mockReturnValue({});
+ useAssessmentStepConfig.mockReturnValue({ settings: {} });
+
+ const { container } = renderWithIntl( );
+
+ const containerDiv = container.querySelector('.text-center.py-2');
+ expect(containerDiv).toBeInTheDocument();
+ });
+
+ it('renders optional message for peer assessment when minimum assessments completed', () => {
+ useGlobalState.mockReturnValue({
+ activeStepName: stepNames.peer,
+ stepState: stepStates.notAvailable,
+ });
+ useStepInfo.mockReturnValue({
+ [stepNames.peer]: {
+ numberOfAssessmentsCompleted: 3,
+ isWaitingForSubmissions: false,
+ },
+ });
+ useAssessmentStepConfig.mockReturnValue({
+ settings: {
+ [stepNames.peer]: {
+ minNumberToGrade: 3,
},
- });
- const wrapper = shallow( );
- expect(wrapper.snapshot).toMatchSnapshot();
+ },
+ });
+
+ renderWithIntl( );
- expect(wrapper.instance.findByType('Button')).toHaveLength(1);
+ const button = screen.getByRole('button');
+ expect(button).toBeInTheDocument();
+ expect(button).toHaveTextContent(/Load Next Assessment.*optional/i);
+ });
+
+ it('does not render button when waiting for submissions', () => {
+ useGlobalState.mockReturnValue({
+ activeStepName: stepNames.peer,
+ stepState: stepStates.notAvailable,
+ });
+ useStepInfo.mockReturnValue({
+ [stepNames.peer]: {
+ numberOfAssessmentsCompleted: 1,
+ isWaitingForSubmissions: true,
+ },
+ });
+ useAssessmentStepConfig.mockReturnValue({
+ settings: {
+ [stepNames.peer]: {
+ minNumberToGrade: 3,
+ },
+ },
});
+
+ renderWithIntl( );
+
+ expect(screen.queryByRole('button')).not.toBeInTheDocument();
});
- it('does not render button when step is staff', () => {
+ it('renders button for done step state', () => {
useGlobalState.mockReturnValue({
- activeStepName: stepNames.staff,
+ activeStepName: stepNames.done,
stepState: stepStates.notAvailable,
});
+ useStepInfo.mockReturnValue({
+ [stepNames.done]: {
+ numberOfAssessmentsCompleted: 0,
+ isWaitingForSubmissions: false,
+ },
+ });
+ useAssessmentStepConfig.mockReturnValue({
+ settings: {
+ [stepNames.done]: {},
+ },
+ });
- const wrapper = shallow( );
- expect(wrapper.snapshot).toMatchSnapshot();
+ renderWithIntl( );
- expect(wrapper.instance.findByType('Button')).toHaveLength(0);
+ const button = screen.getByRole('button');
+ expect(button).toBeInTheDocument();
});
});
diff --git a/src/views/XBlockView/__snapshots__/index.test.jsx.snap b/src/views/XBlockView/__snapshots__/index.test.jsx.snap
deleted file mode 100644
index 8ffdd92f..00000000
--- a/src/views/XBlockView/__snapshots__/index.test.jsx.snap
+++ /dev/null
@@ -1,72 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[` does not render prompts and rubric when step is unavailable 1`] = `
-
-
- title
-
-
-
-
-
-
-
-
-`;
-
-exports[` render everything 1`] = `
-
-
- title
-
-
-
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[` render everything without rubric 1`] = `
-
-
- title
-
-
-
-
-
-
-
-
-
-
-
-
-`;
diff --git a/src/views/XBlockView/index.test.jsx b/src/views/XBlockView/index.test.jsx
index 9bc968f8..717d71e8 100644
--- a/src/views/XBlockView/index.test.jsx
+++ b/src/views/XBlockView/index.test.jsx
@@ -1,4 +1,6 @@
-import { shallow } from '@edx/react-unit-test-utils';
+import { render, screen } from '@testing-library/react';
+import '@testing-library/jest-dom';
+import { IntlProvider } from '@edx/frontend-platform/i18n';
import {
useORAConfigData,
usePrompts,
@@ -8,58 +10,120 @@ import {
import XBlockView from './index';
+jest.unmock('@openedx/paragon');
+jest.unmock('react');
+jest.unmock('@edx/frontend-platform/i18n');
+
+// Only mock the hooks that provide data
jest.mock('hooks/app', () => ({
useORAConfigData: jest.fn(),
usePrompts: jest.fn(),
useRubricConfig: jest.fn(),
useGlobalState: jest.fn(),
}));
-jest.mock('components/ProgressBar', () => 'ProgressBar');
-jest.mock('components/Prompt', () => 'Prompt');
-jest.mock('components/Rubric', () => 'Rubric');
-jest.mock('components/Instructions', () => 'Instructions');
-jest.mock('components/StatusAlert', () => 'StatusAlert');
-jest.mock('components/HotjarSurvey', () => 'HotjarSurvey');
-jest.mock('./StatusRow', () => 'StatusRow');
-jest.mock('./Actions', () => 'Actions');
+
+// Mock child components to avoid their complex dependencies
+jest.mock('components/ProgressBar', () => () => (
+ ProgressBar
+));
+// eslint-disable-next-line react/prop-types
+jest.mock('components/Prompt', () => ({ prompt }) => (
+ {prompt}
+));
+// eslint-disable-next-line react/prop-types
+jest.mock('components/Rubric', () => ({ isCollapsible }) => (
+
+ {isCollapsible ? 'Collapsible' : 'Not Collapsible'}
+
+));
+jest.mock('components/Instructions', () => () => (
+ Instructions
+));
+jest.mock('components/StatusAlert', () => () => (
+ StatusAlert
+));
+jest.mock('components/HotjarSurvey', () => () => (
+ HotjarSurvey
+));
+jest.mock('./StatusRow', () => () => (
+ StatusRow
+));
+jest.mock('./Actions', () => () => Actions
);
describe(' ', () => {
- it('render everything', () => {
- useORAConfigData.mockReturnValue({ title: 'title' });
+ const renderWithIntl = (component) => render({component} );
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('renders everything with title and prompts', () => {
+ useORAConfigData.mockReturnValue({ title: 'Test Title' });
usePrompts.mockReturnValue(['prompt1', 'prompt2']);
useRubricConfig.mockReturnValue({ showDuringResponse: true });
useGlobalState.mockReturnValue({ stepIsUnavailable: false });
- const wrapper = shallow( );
+ renderWithIntl( );
- expect(wrapper.snapshot).toMatchSnapshot();
- expect(wrapper.instance.findByType('Prompt')).toHaveLength(2);
- expect(wrapper.instance.findByType('Rubric')).toHaveLength(1);
+ expect(screen.getByText('Test Title')).toBeInTheDocument();
+ expect(screen.getByTestId('progress-bar')).toBeInTheDocument();
+ expect(screen.getByTestId('status-row')).toBeInTheDocument();
+ expect(screen.getByTestId('status-alert')).toBeInTheDocument();
+ expect(screen.getByTestId('hotjar-survey')).toBeInTheDocument();
+ expect(screen.getByTestId('instructions')).toBeInTheDocument();
+ expect(screen.getByTestId('actions')).toBeInTheDocument();
+ expect(screen.getAllByTestId('prompt')).toHaveLength(2);
+ expect(screen.getByText('prompt1')).toBeInTheDocument();
+ expect(screen.getByText('prompt2')).toBeInTheDocument();
+ expect(screen.getByTestId('rubric')).toBeInTheDocument();
+ expect(screen.getByText('Collapsible')).toBeInTheDocument();
});
- it('render everything without rubric', () => {
- useORAConfigData.mockReturnValue({ title: 'title' });
+ it('renders everything without rubric when showDuringResponse is false', () => {
+ useORAConfigData.mockReturnValue({ title: 'Test Title' });
usePrompts.mockReturnValue(['prompt1', 'prompt2']);
useRubricConfig.mockReturnValue({ showDuringResponse: false });
useGlobalState.mockReturnValue({ stepIsUnavailable: false });
- const wrapper = shallow( );
+ renderWithIntl( );
- expect(wrapper.snapshot).toMatchSnapshot();
- expect(wrapper.instance.findByType('Prompt')).toHaveLength(2);
- expect(wrapper.instance.findByType('Rubric')).toHaveLength(0);
+ expect(screen.getByText('Test Title')).toBeInTheDocument();
+ expect(screen.getByTestId('progress-bar')).toBeInTheDocument();
+ expect(screen.getAllByTestId('prompt')).toHaveLength(2);
+ expect(screen.getByText('prompt1')).toBeInTheDocument();
+ expect(screen.getByText('prompt2')).toBeInTheDocument();
+ expect(screen.queryByTestId('rubric')).not.toBeInTheDocument();
});
it('does not render prompts and rubric when step is unavailable', () => {
- useORAConfigData.mockReturnValue({ title: 'title' });
+ useORAConfigData.mockReturnValue({ title: 'Test Title' });
usePrompts.mockReturnValue(['prompt1', 'prompt2']);
useRubricConfig.mockReturnValue({ showDuringResponse: true });
useGlobalState.mockReturnValue({ stepIsUnavailable: true });
- const wrapper = shallow( );
+ renderWithIntl( );
+
+ expect(screen.getByText('Test Title')).toBeInTheDocument();
+ expect(screen.getByTestId('progress-bar')).toBeInTheDocument();
+ expect(screen.getByTestId('status-row')).toBeInTheDocument();
+ expect(screen.getByTestId('status-alert')).toBeInTheDocument();
+ expect(screen.getByTestId('hotjar-survey')).toBeInTheDocument();
+ expect(screen.getByTestId('instructions')).toBeInTheDocument();
+ expect(screen.getByTestId('actions')).toBeInTheDocument();
+ expect(screen.queryByTestId('prompt')).not.toBeInTheDocument();
+ expect(screen.queryByTestId('rubric')).not.toBeInTheDocument();
+ });
+
+ it('renders correct heading structure', () => {
+ useORAConfigData.mockReturnValue({ title: 'Test Title' });
+ usePrompts.mockReturnValue([]);
+ useRubricConfig.mockReturnValue({ showDuringResponse: false });
+ useGlobalState.mockReturnValue({ stepIsUnavailable: false });
+
+ renderWithIntl( );
- expect(wrapper.snapshot).toMatchSnapshot();
- expect(wrapper.instance.findByType('Prompt')).toHaveLength(0);
- expect(wrapper.instance.findByType('Rubric')).toHaveLength(0);
+ const heading = screen.getByRole('heading', { level: 3 });
+ expect(heading).toBeInTheDocument();
+ expect(heading).toHaveTextContent('Test Title');
});
});