diff --git a/src/containers/ResponseDisplay/PreviewDisplay.test.jsx b/src/containers/ResponseDisplay/PreviewDisplay.test.jsx index ffa85c9..ca0da40 100644 --- a/src/containers/ResponseDisplay/PreviewDisplay.test.jsx +++ b/src/containers/ResponseDisplay/PreviewDisplay.test.jsx @@ -1,56 +1,66 @@ -import React from 'react'; -import { shallow } from '@edx/react-unit-test-utils'; - +import { render } from '@testing-library/react'; +import { IntlProvider } from '@edx/frontend-platform/i18n'; import { FileTypes } from 'data/constants/files'; -import { FileRenderer } from 'components/FilePreview'; import { PreviewDisplay } from './PreviewDisplay'; -jest.mock('components/FilePreview', () => ({ - FileRenderer: () => 'FileRenderer', -})); +jest.unmock('@openedx/paragon'); +jest.unmock('react'); +jest.unmock('@edx/frontend-platform/i18n'); describe('PreviewDisplay', () => { - describe('component', () => { - const supportedTypes = Object.values(FileTypes); - const props = { - files: [ - ...supportedTypes.map((fileType, index) => ({ - name: `fake_file_${index}.${fileType}`, - description: `file description ${index}`, - downloadUrl: `/url-path/fake_file_${index}.${fileType}`, - })), - { - name: 'bad_ext_fake_file.other', - description: 'bad_ext file description', - downloadUrl: 'bad_ext.other', - }, - ], + const supportedTypes = Object.values(FileTypes); + const props = { + files: [ + ...supportedTypes.map((fileType, index) => ({ + name: `fake_file_${index}.${fileType}`, + description: `file description ${index}`, + downloadUrl: `/url-path/fake_file_${index}.${fileType}`, + })), + { + name: 'bad_ext_fake_file.other', + description: 'bad_ext file description', + downloadUrl: 'bad_ext.other', + }, + ], + }; + + const renderWithIntl = (component) => render( + + {component} + , + ); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('renders preview display container', () => { + const { container } = renderWithIntl(); + const previewDisplay = container.querySelector('.preview-display'); + expect(previewDisplay).toBeInTheDocument(); + }); + + it('renders empty container when no files provided', () => { + const { container } = renderWithIntl(); + const previewDisplay = container.querySelector('.preview-display'); + expect(previewDisplay).toBeInTheDocument(); + expect(previewDisplay.children.length).toBe(0); + }); + + it('only renders supported file types', () => { + const { container } = renderWithIntl(); + const previewDisplay = container.querySelector('.preview-display'); + expect(previewDisplay.children.length).toBe(supportedTypes.length); + }); + + it('filters out unsupported file types', () => { + const unsupportedFile = { + name: 'unsupported.xyz', + description: 'unsupported file', + downloadUrl: '/unsupported.xyz', }; - let el; - beforeEach(() => { - el = shallow(); - }); - - describe('snapshot', () => { - test('files render with props', () => { - expect(el.snapshot).toMatchSnapshot(); - }); - test('files does not exist', () => { - el = shallow(); - expect(el.snapshot).toMatchSnapshot(); - }); - }); - - describe('component', () => { - test('only renders compatible files', () => { - const cards = el.instance.findByType(FileRenderer); - expect(cards.length).toEqual(supportedTypes.length); - cards.forEach((_, index) => { - expect( - cards[index].props.file, - ).toEqual(props.files[index]); - }); - }); - }); + const { container } = renderWithIntl(); + const previewDisplay = container.querySelector('.preview-display'); + expect(previewDisplay.children.length).toBe(0); }); }); diff --git a/src/containers/ResponseDisplay/SubmissionFiles.test.jsx b/src/containers/ResponseDisplay/SubmissionFiles.test.jsx index 0025520..999f121 100644 --- a/src/containers/ResponseDisplay/SubmissionFiles.test.jsx +++ b/src/containers/ResponseDisplay/SubmissionFiles.test.jsx @@ -1,99 +1,104 @@ -import React from 'react'; -import { shallow } from '@edx/react-unit-test-utils'; - +import { render, screen } from '@testing-library/react'; +import { IntlProvider } from '@edx/frontend-platform/i18n'; import { downloadAllLimit, downloadSingleLimit } from 'data/constants/files'; - -import { formatMessage } from 'testUtils'; import { SubmissionFiles } from './SubmissionFiles'; -import messages from './messages'; -jest.mock('./components/FileNameCell', () => jest.fn().mockName('FileNameCell')); -jest.mock('./components/FileExtensionCell', () => jest.fn().mockName('FileExtensionCell')); -jest.mock('./components/FilePopoverCell', () => jest.fn().mockName('FilePopoverCell')); -jest.mock('./FileDownload', () => 'FileDownload'); +jest.unmock('@openedx/paragon'); +jest.unmock('react'); +jest.unmock('@edx/frontend-platform/i18n'); + +jest.mock('./components/FileNameCell', () => jest.fn(({ value }) =>
Name: {value}
)); +jest.mock('./components/FileExtensionCell', () => jest.fn(({ value }) =>
Extension: {value}
)); +jest.mock('./components/FilePopoverCell', () => jest.fn(() =>
Popover
)); +jest.mock('./FileDownload', () => jest.fn(({ files }) =>
Download {files.length} files
)); describe('SubmissionFiles', () => { - describe('component', () => { - const props = { - files: [ - { - name: 'some file name.jpg', - description: 'description for the file', - downloadURL: '/valid-url-wink-wink', - size: 0, - }, - { - name: 'file number 2.jpg', - description: 'description for this file', - downloadURL: '/url-2', - size: 0, - }, - ], - }; - let el; - beforeEach(() => { - el = shallow(); + const defaultProps = { + files: [ + { + name: 'some file name.jpg', + description: 'description for the file', + downloadURL: '/valid-url-wink-wink', + size: 100, + }, + { + name: 'file number 2.jpg', + description: 'description for this file', + downloadURL: '/url-2', + size: 200, + }, + ], + }; + + const renderWithIntl = (component) => render( + + {component} + , + ); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('behavior', () => { + it('displays submission files title with file count', () => { + renderWithIntl(); + const title = screen.getByTestId('submission-files-title'); + expect(title).toBeInTheDocument(); + expect(title).toHaveTextContent(`Submission Files (${defaultProps.files.length})`); }); - describe('snapshot', () => { - test('files existed for props', () => { - expect(el.snapshot).toMatchSnapshot(); - }); - - test('files does not exist', () => { - el = shallow(); - - expect(el.snapshot).toMatchSnapshot(); - }); - test('files size exceed', () => { - const files = props.files.map(file => ({ ...file, size: downloadSingleLimit + 1 })); - el = shallow(); - expect(el.snapshot).toMatchSnapshot(); - }); + it('renders file download component when files can be downloaded', () => { + renderWithIntl(); + const downloadComponent = screen.getByTestId('file-download'); + expect(downloadComponent).toBeInTheDocument(); + expect(downloadComponent).toHaveTextContent('Download 2 files'); }); - describe('behavior', () => { - test('title', () => { - const titleEl = el.instance.findByTestId('submission-files-title')[0].children[0]; - expect(titleEl.el).toEqual( - `${formatMessage(messages.submissionFiles)} (${props.files.length})`, - ); - }); + it('displays warning when individual file exceeds size limit', () => { + const largeFileProps = { + ...defaultProps, + files: [ + { ...defaultProps.files[0], size: downloadSingleLimit + 1 }, + defaultProps.files[1], + ], + }; + renderWithIntl(); - describe('canDownload', () => { - test('normal file size', () => { - expect(el.instance.findByTestId('file-download')).toHaveLength(1); - }); + expect(screen.queryByTestId('file-download')).not.toBeInTheDocument(); + const warningText = screen.getByTestId('exceed-download-text'); + expect(warningText).toBeInTheDocument(); + expect(warningText).toHaveTextContent('Exceeded the allow download size'); + }); - test('one of the file exceed the limit', () => { - const oneFileExceed = [{ ...props.files[0], size: downloadSingleLimit + 1 }, props.files[1]]; + it('displays warning when total file size exceeds limit', () => { + const largeFileSize = (downloadAllLimit + 1) / 20; + const largeFilesProps = { + ...defaultProps, + files: Array(20).fill({ + name: 'large file.jpg', + description: 'large file description', + downloadURL: '/large-file-url', + size: largeFileSize, + }), + }; + renderWithIntl(); - oneFileExceed.forEach(file => expect(file.size < downloadAllLimit).toEqual(true)); + expect(screen.queryByTestId('file-download')).not.toBeInTheDocument(); + }); - el = shallow(); - expect(el.instance.findByTestId('file-download')).toHaveLength(0); + it('displays title only when no files are provided', () => { + const { container } = renderWithIntl(); + const title = container.querySelector('.submission-files-title h3'); + expect(title).toBeInTheDocument(); + expect(title).toHaveTextContent('Submission Files (0)'); + expect(screen.queryByTestId('file-download')).not.toBeInTheDocument(); + }); - const warningEl = el.instance.findByTestId('exceed-download-text')[0]; - expect(warningEl.el.children[1]).toEqual(formatMessage(messages.exceedFileSize)); - }); - - test('total file size exceed the limit', () => { - const length = 20; - const totalFilesExceed = new Array(length).fill({ - name: 'some file name.jpg', - description: 'description for the file', - downloadURL: '/valid-url-wink-wink', - size: (downloadAllLimit + 1) / length, - }); - totalFilesExceed.forEach(file => { - expect(file.size < downloadAllLimit).toEqual(true); - expect(file.size < downloadSingleLimit).toEqual(true); - }); - - el = shallow(); - expect(el.instance.findByTestId('file-download')).toHaveLength(0); - }); - }); + it('renders data table with correct file information', () => { + const { container } = renderWithIntl(); + const dataTable = container.querySelector('.submission-files-table'); + expect(dataTable).toBeInTheDocument(); }); }); }); diff --git a/src/containers/ResponseDisplay/__snapshots__/PreviewDisplay.test.jsx.snap b/src/containers/ResponseDisplay/__snapshots__/PreviewDisplay.test.jsx.snap deleted file mode 100644 index cc82091..0000000 --- a/src/containers/ResponseDisplay/__snapshots__/PreviewDisplay.test.jsx.snap +++ /dev/null @@ -1,124 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`PreviewDisplay component snapshot files does not exist 1`] = ` -
-`; - -exports[`PreviewDisplay component snapshot files render with props 1`] = ` -
- - - - - - - - - - - -
-`; diff --git a/src/containers/ResponseDisplay/__snapshots__/SubmissionFiles.test.jsx.snap b/src/containers/ResponseDisplay/__snapshots__/SubmissionFiles.test.jsx.snap deleted file mode 100644 index ad819a5..0000000 --- a/src/containers/ResponseDisplay/__snapshots__/SubmissionFiles.test.jsx.snap +++ /dev/null @@ -1,230 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`SubmissionFiles component snapshot files does not exist 1`] = ` - -
-

- Submission Files (0) -

-
-
-`; - -exports[`SubmissionFiles component snapshot files existed for props 1`] = ` - - - - -

- Submission Files (2) -

- - - - - - -
- -
- - - -
-
-
- - - -
-
-`; - -exports[`SubmissionFiles component snapshot files size exceed 1`] = ` - - - - -

- Submission Files (2) -

- - - - - - -
- -
- - - -
-
-
- -
- - - - Exceeded the allow download size - - - -
-
-
-
-`; diff --git a/src/containers/ResponseDisplay/__snapshots__/index.test.jsx.snap b/src/containers/ResponseDisplay/__snapshots__/index.test.jsx.snap deleted file mode 100644 index e2fabe0..0000000 --- a/src/containers/ResponseDisplay/__snapshots__/index.test.jsx.snap +++ /dev/null @@ -1,99 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ResponseDisplay component snapshot file upload disable with valid response 1`] = ` -
- - - parsed html (sanitized (some text response here)) - - -
-`; - -exports[`ResponseDisplay component snapshot file upload disabled without response 1`] = ` -
- - -
-`; - -exports[`ResponseDisplay component snapshot file upload enable with valid response 1`] = ` -
- - - - - parsed html (sanitized (some text response here)) - - -
-`; - -exports[`ResponseDisplay component snapshot file upload enable without response 1`] = ` -
- - -
-`; diff --git a/src/containers/ResponseDisplay/components/FileExtensionCell.test.jsx b/src/containers/ResponseDisplay/components/FileExtensionCell.test.jsx index ca367b9..57f64d2 100644 --- a/src/containers/ResponseDisplay/components/FileExtensionCell.test.jsx +++ b/src/containers/ResponseDisplay/components/FileExtensionCell.test.jsx @@ -1,25 +1,42 @@ -import React from 'react'; -import { shallow } from '@edx/react-unit-test-utils'; - +import { render } from '@testing-library/react'; import FileExtensionCell from './FileExtensionCell'; -describe('FileExtensionCell', () => { - describe('component', () => { - const props = { - value: 'file_name.with_extension.pdf', - }; - let el; - beforeEach(() => { - el = shallow(); - }); - test('snapshot', () => { - expect(el.snapshot).toMatchSnapshot(); - }); +jest.unmock('@openedx/paragon'); +jest.unmock('react'); - describe('behavior', () => { - test('content', () => { - expect(el.instance.children[0].el).toEqual('PDF'); - }); - }); +describe('FileExtensionCell', () => { + const props = { + value: 'file_name.with_extension.pdf', + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('renders file extension in uppercase', () => { + const { getByText } = render(); + expect(getByText('PDF')).toBeInTheDocument(); + }); + + it('applies correct CSS class', () => { + const { container } = render(); + const element = container.firstChild; + expect(element).toHaveClass('text-truncate'); + }); + + it('extracts extension from file with multiple dots', () => { + const { getByText } = render(); + expect(getByText('DOCX')).toBeInTheDocument(); + }); + + it('handles file without extension', () => { + const { getByText } = render(); + expect(getByText('FILENAME')).toBeInTheDocument(); + }); + + it('handles empty file extension', () => { + const { container } = render(); + const element = container.firstChild; + expect(element).toHaveTextContent(''); }); }); diff --git a/src/containers/ResponseDisplay/components/FileNameCell.test.jsx b/src/containers/ResponseDisplay/components/FileNameCell.test.jsx index 7028fec..49ff689 100644 --- a/src/containers/ResponseDisplay/components/FileNameCell.test.jsx +++ b/src/containers/ResponseDisplay/components/FileNameCell.test.jsx @@ -1,25 +1,21 @@ import React from 'react'; -import { shallow } from '@edx/react-unit-test-utils'; +import { render, screen } from '@testing-library/react'; import FileNameCell from './FileNameCell'; describe('FileNameCell', () => { - describe('component', () => { - const props = { - value: 'some test text value', - }; - let el; - beforeEach(() => { - el = shallow(); - }); - test('snapshot', () => { - expect(el.snapshot).toMatchSnapshot(); - }); + const props = { + value: 'some test text value', + }; - describe('behavior', () => { - test('content', () => { - expect(el.instance.children[0].el).toEqual(props.value); - }); - }); + it('renders the value text', () => { + render(); + expect(screen.getByText('some test text value')).toBeInTheDocument(); + }); + + it('applies text truncation class', () => { + const { container } = render(); + const divElement = container.querySelector('div'); + expect(divElement).toHaveClass('text-truncate'); }); }); diff --git a/src/containers/ResponseDisplay/components/__snapshots__/FileExtensionCell.test.jsx.snap b/src/containers/ResponseDisplay/components/__snapshots__/FileExtensionCell.test.jsx.snap deleted file mode 100644 index 2a2d914..0000000 --- a/src/containers/ResponseDisplay/components/__snapshots__/FileExtensionCell.test.jsx.snap +++ /dev/null @@ -1,9 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`FileExtensionCell component snapshot 1`] = ` -
- PDF -
-`; diff --git a/src/containers/ResponseDisplay/components/__snapshots__/FileNameCell.test.jsx.snap b/src/containers/ResponseDisplay/components/__snapshots__/FileNameCell.test.jsx.snap deleted file mode 100644 index 143afb6..0000000 --- a/src/containers/ResponseDisplay/components/__snapshots__/FileNameCell.test.jsx.snap +++ /dev/null @@ -1,9 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`FileNameCell component snapshot 1`] = ` -
- some test text value -
-`; diff --git a/src/containers/ResponseDisplay/index.test.jsx b/src/containers/ResponseDisplay/index.test.jsx index 797124d..be56044 100644 --- a/src/containers/ResponseDisplay/index.test.jsx +++ b/src/containers/ResponseDisplay/index.test.jsx @@ -1,121 +1,127 @@ -import React from 'react'; -import { shallow } from '@edx/react-unit-test-utils'; - -import createDOMPurify from 'dompurify'; -import parse from 'html-react-parser'; - +import { render, screen } from '@testing-library/react'; import { fileUploadResponseOptions } from 'data/services/lms/constants'; import { selectors } from 'data/redux'; - import { ResponseDisplay, mapStateToProps } from '.'; +jest.unmock('@openedx/paragon'); +jest.unmock('react'); + jest.mock('data/redux', () => ({ selectors: { grading: { selected: { - response: (state) => ({ response: state }), + response: jest.fn((state) => state.response || { text: [], files: [] }), }, }, app: { ora: { - fileUploadResponseConfig: (state) => ({ config: state }), + fileUploadResponseConfig: jest.fn((state) => state.fileUploadResponseConfig || 'optional'), }, }, }, })); -jest.mock('./SubmissionFiles', () => 'SubmissionFiles'); + +jest.mock('./SubmissionFiles', () => jest.fn(({ files }) => ( +
Files: {files.length}
+))); + +jest.mock('./PreviewDisplay', () => jest.fn(({ files }) => ( +
Preview: {files.length}
+))); + jest.mock('dompurify', () => () => ({ - sanitize: (text) => `sanitized (${text})`, + sanitize: (text) => text, })); -jest.mock('html-react-parser', () => (text) => `parsed html (${text})`); + +jest.mock('html-react-parser', () => (text) => text); describe('ResponseDisplay', () => { - describe('component', () => { - const props = { - response: { - text: ['some text response here'], - files: [ - { - name: 'some file name.jpg', - description: 'description for the file', - downloadURL: '/valid-url-wink-wink', - }, - { - name: 'file number 2.jpg', - description: 'description for this file', - downloadURL: '/url-2', - }, - ], - }, - fileUploadResponseConfig: 'optional', - }; - let el; - beforeAll(() => { - global.window = {}; - }); - beforeEach(() => { - el = shallow(); - }); - describe('snapshot', () => { - test('file upload enable with valid response', () => { - expect(el.snapshot).toMatchSnapshot(); - }); + const defaultProps = { + response: { + text: ['some text response here', 'another text response'], + files: [ + { + name: 'some file name.jpg', + description: 'description for the file', + downloadURL: '/valid-url-wink-wink', + }, + { + name: 'file number 2.jpg', + description: 'description for this file', + downloadURL: '/url-2', + }, + ], + }, + fileUploadResponseConfig: 'optional', + }; - test('file upload enable without response', () => { - el = shallow(); - expect(el.snapshot).toMatchSnapshot(); - }); - test('file upload disable with valid response', () => { - el = shallow(); - expect(el.snapshot).toMatchSnapshot(); - }); + beforeAll(() => { + global.window = {}; + }); - test('file upload disabled without response', () => { - el = shallow(); - expect(el.snapshot).toMatchSnapshot(); - }); + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('behavior', () => { + it('renders response display container', () => { + const { container } = render(); + const responseDisplay = container.querySelector('.response-display'); + expect(responseDisplay).toBeInTheDocument(); }); - describe('behavior', () => { - test('get textContents', () => { - const textContents = el.instance.findByTestId('response-display-text-content'); - expect(textContents.length).toEqual( - props.response.text.length, - ); - textContents.forEach((text, index) => { - expect(text.el.children[0]).toEqual( - parse(createDOMPurify(window).sanitize(props.response.text[index])), - ); - }); - }); - test('get submittedFiles', () => { - expect(el.instance.findByTestId('submission-files')[0].props.files).toEqual(props.response.files); - }); - test('get allowFileUpload', () => { - expect(el.instance.findByTestId('allow-file-upload').length > 0).toEqual( - props.fileUploadResponseConfig !== fileUploadResponseOptions.none, - ); - }); + it('displays text content in cards', () => { + const { container } = render(); + const textContents = container.querySelectorAll('.response-display-text-content'); + expect(textContents).toHaveLength(defaultProps.response.text.length); + expect(textContents[0]).toHaveTextContent('some text response here'); + expect(textContents[1]).toHaveTextContent('another text response'); + }); + + it('displays submission files when file upload is allowed', () => { + render(); + const submissionFiles = screen.getByTestId('submission-files'); + expect(submissionFiles).toBeInTheDocument(); + expect(submissionFiles).toHaveTextContent('Files: 2'); + }); + + it('displays preview display when file upload is allowed', () => { + render(); + const previewDisplay = screen.getByTestId('preview-display'); + expect(previewDisplay).toBeInTheDocument(); + expect(previewDisplay).toHaveTextContent('Preview: 2'); + }); + + it('does not display file components when file upload is disabled', () => { + render(); + expect(screen.queryByTestId('submission-files')).not.toBeInTheDocument(); + expect(screen.queryByTestId('preview-display')).not.toBeInTheDocument(); + }); + + it('renders empty content when no text response provided', () => { + const { container } = render(); + const textContents = container.querySelectorAll('.response-display-text-content'); + expect(textContents).toHaveLength(0); }); }); + describe('mapStateToProps', () => { - let mapped; const testState = { - dummyText: ['text'], - dummyFiles: ['files', 'file-2'], + response: { + text: ['test text'], + files: ['file1', 'file2'], + }, + fileUploadResponseConfig: 'required', }; - beforeEach(() => { - mapped = mapStateToProps(testState); + + it('maps response from grading.selected.response selector', () => { + const mapped = mapStateToProps(testState); + expect(mapped.response).toEqual(selectors.grading.selected.response(testState)); }); - test('response loads from grading.selected.response', () => { - expect(mapped.response).toEqual( - selectors.grading.selected.response(testState), - ); - }); - test('response loads from grading.selected.response', () => { - expect(mapped.fileUploadResponseConfig).toEqual( - selectors.app.ora.fileUploadResponseConfig(testState), - ); + + it('maps fileUploadResponseConfig from app.ora.fileUploadResponseConfig selector', () => { + const mapped = mapStateToProps(testState); + expect(mapped.fileUploadResponseConfig).toEqual(selectors.app.ora.fileUploadResponseConfig(testState)); }); }); });