feat: File Preview panel (#39)

* feat: update display preview panel

* chore: update package

* test: update unit testing

* test: update test

* test: update unit testing

* feat: update preview display to use custom renderer

* chore: use worker for react-pdf

* test: update unit testing

* chore: update requested change

* chore: update gitignore for sample files

* fix: hard-code filetype mappings

* fix: make integration test work again

* chore: update tests

* feat: add FileInfo to preview cards

Co-authored-by: Leangseu Kim <lkim@edx.org>
This commit is contained in:
Ben Warzeski
2021-12-10 14:57:56 -05:00
committed by GitHub
parent 7e49670a95
commit e584529cc5
39 changed files with 1263 additions and 81 deletions

1
.gitignore vendored
View File

@@ -5,6 +5,7 @@ npm-debug.log
coverage
dist/
public/samples/
### pyenv ###
.python-version

61
package-lock.json generated
View File

@@ -4138,8 +4138,7 @@
"@types/json-schema": {
"version": "7.0.9",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz",
"integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==",
"dev": true
"integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ=="
},
"@types/json5": {
"version": "0.0.29",
@@ -4681,8 +4680,7 @@
"ajv-keywords": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
"dev": true
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ=="
},
"alphanum-sort": {
"version": "1.0.2",
@@ -5541,8 +5539,7 @@
"big.js": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
"integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==",
"dev": true
"integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ=="
},
"bin-build": {
"version": "3.0.0",
@@ -8171,8 +8168,7 @@
"emojis-list": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz",
"integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==",
"dev": true
"integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q=="
},
"encodeurl": {
"version": "1.0.2",
@@ -9891,7 +9887,6 @@
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz",
"integrity": "sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==",
"dev": true,
"requires": {
"loader-utils": "^2.0.0",
"schema-utils": "^3.0.0"
@@ -15837,7 +15832,6 @@
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz",
"integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==",
"dev": true,
"requires": {
"minimist": "^1.2.5"
}
@@ -15994,7 +15988,6 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz",
"integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==",
"dev": true,
"requires": {
"big.js": "^5.2.2",
"emojis-list": "^3.0.0",
@@ -16472,6 +16465,11 @@
}
}
},
"make-cancellable-promise": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/make-cancellable-promise/-/make-cancellable-promise-1.1.0.tgz",
"integrity": "sha512-X5Opjm2xcZsOLuJ+Bnhb4t5yfu4ehlA3OKEYLtqUchgVzL/QaqW373ZUVxVHKwvJ38cmYuR4rAHD2yUvAIkTPA=="
},
"make-dir": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
@@ -16490,6 +16488,11 @@
}
}
},
"make-event-props": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/make-event-props/-/make-event-props-1.3.0.tgz",
"integrity": "sha512-oWiDZMcVB1/A487251hEWza1xzgCzl6MXxe9aF24l5Bt9N9UEbqTqKumEfuuLhmlhRZYnc+suVvW4vUs8bwO7Q=="
},
"makeerror": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz",
@@ -16648,12 +16651,22 @@
"yargs-parser": "^20.2.3"
}
},
"merge-class-names": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/merge-class-names/-/merge-class-names-1.4.2.tgz",
"integrity": "sha512-bOl98VzwCGi25Gcn3xKxnR5p/WrhWFQB59MS/aGENcmUc6iSm96yrFDF0XSNurX9qN4LbJm0R9kfvsQ17i8zCw=="
},
"merge-descriptors": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
"integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=",
"dev": true
},
"merge-refs": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/merge-refs/-/merge-refs-1.0.0.tgz",
"integrity": "sha512-WZ4S5wqD9FCR9hxkLgvcHJCBxzXzy3VVE6p8W2OzxRzB+hLRlcadGE2bW9xp2KSzk10rvp4y+pwwKO6JQVguMg=="
},
"merge-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
@@ -16782,8 +16795,7 @@
"minimist": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
"dev": true
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
},
"minimist-options": {
"version": "4.1.0",
@@ -21091,6 +21103,28 @@
"warning": "^4.0.3"
}
},
"react-pdf": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/react-pdf/-/react-pdf-5.5.0.tgz",
"integrity": "sha512-0Sk0lFd95py7H7/G0+fPzUrLsGHQ6SzC+3eJChgSnLtEmrRV36uNAjuMgf5mz2ds55MK4vYCJHGuP9CyvaSEOA==",
"requires": {
"@babel/runtime": "^7.0.0",
"file-loader": "^6.0.0",
"make-cancellable-promise": "^1.0.0",
"make-event-props": "^1.1.0",
"merge-class-names": "^1.1.1",
"merge-refs": "^1.0.0",
"pdfjs-dist": "2.9.359",
"prop-types": "^15.6.2"
},
"dependencies": {
"pdfjs-dist": {
"version": "2.9.359",
"resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-2.9.359.tgz",
"integrity": "sha512-P2nYtkacdlZaNNwrBLw1ZyMm0oE2yY/5S/GDCAmMJ7U4+ciL/D0mrlEC/o4HZZc/LNE3w8lEVzBEyVgEQlPVKQ=="
}
}
},
"react-popper": {
"version": "2.2.5",
"resolved": "https://registry.npmjs.org/react-popper/-/react-popper-2.2.5.tgz",
@@ -22060,7 +22094,6 @@
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz",
"integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==",
"dev": true,
"requires": {
"@types/json-schema": "^7.0.8",
"ajv": "^6.12.5",

View File

@@ -53,6 +53,7 @@
"react": "17.0.2",
"react-dom": "17.0.2",
"react-intl": "^5.20.9",
"react-pdf": "^5.5.0",
"react-redux": "^7.2.4",
"react-router": "5.2.0",
"react-router-dom": "5.2.0",

22
public/pdf.worker.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,32 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`FilePopoverContent component snapshot 1`] = `
<Fragment>
<div
className="help-popover-option"
>
<strong>
<FormattedMessage
defaultMessage="File Name"
description="Popover title for file name"
id="ora-grading.FilePopoverContent.filePopoverNameTitle"
/>
</strong>
<br />
some file name
</div>
<div
className="help-popover-option"
>
<strong>
<FormattedMessage
defaultMessage="File Description"
description="Popover title for file description"
id="ora-grading.FilePopoverCellContent.filePopoverDescriptionTitle"
/>
</strong>
<br />
long descriptive text...
</div>
</Fragment>
`;

View File

@@ -0,0 +1,34 @@
import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import messages from './messages';
export const FilePopoverContent = ({ file }) => (
<>
<div className="help-popover-option">
<strong><FormattedMessage {...messages.filePopoverNameTitle} /></strong>
<br />
{file.name}
</div>
<div className="help-popover-option">
<strong><FormattedMessage {...messages.filePopoverDescriptionTitle} /></strong>
<br />
{file.description}
</div>
</>
);
FilePopoverContent.defaultProps = {
};
FilePopoverContent.propTypes = {
file: PropTypes.shape({
name: PropTypes.string.isRequired,
description: PropTypes.string,
downloadURL: PropTypes.string,
}).isRequired,
};
export default FilePopoverContent;

View File

@@ -0,0 +1,30 @@
import React from 'react';
import { shallow } from 'enzyme';
import FilePopoverContent from '.';
describe('FilePopoverContent', () => {
describe('component', () => {
const props = {
file: {
name: 'some file name',
description: 'long descriptive text...',
downloadURL: 'this-url-is.working',
},
};
let el;
beforeEach(() => {
el = shallow(<FilePopoverContent {...props} />);
});
test('snapshot', () => {
expect(el).toMatchSnapshot();
});
describe('behavior', () => {
test('content', () => {
expect(el.text()).toContain(props.file.name);
expect(el.text()).toContain(props.file.description);
});
});
});
});

View File

@@ -0,0 +1,16 @@
import { defineMessages } from '@edx/frontend-platform/i18n';
const messages = defineMessages({
filePopoverNameTitle: {
id: 'ora-grading.FilePopoverContent.filePopoverNameTitle',
defaultMessage: 'File Name',
description: 'Popover title for file name',
},
filePopoverDescriptionTitle: {
id: 'ora-grading.FilePopoverCellContent.filePopoverDescriptionTitle',
defaultMessage: 'File Description',
description: 'Popover title for file description',
},
});
export default messages;

View File

@@ -0,0 +1,34 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Card, Collapsible } from '@edx/paragon';
import FilePopoverContent from 'components/FilePopoverContent';
import FileInfo from './FileInfo';
import './FileCard.scss';
/**
* <FileCard />
*/
export const FileCard = ({ file, children }) => (
<Card className="file-card" key={file.name}>
<Collapsible className="file-collapsible" defaultOpen title={<h3>{file.name}</h3>}>
<div className="preview-panel">
<FileInfo><FilePopoverContent file={file} /></FileInfo>
{children}
</div>
</Collapsible>
</Card>
);
FileCard.defaultProps = {
};
FileCard.propTypes = {
file: PropTypes.shape({
name: PropTypes.string.isRequired,
downloadUrl: PropTypes.string.isRequired,
description: PropTypes.string.isRequired,
}).isRequired,
children: PropTypes.node.isRequired,
};
export default FileCard;

View File

@@ -0,0 +1,13 @@
.preview-panel {
.image-renderer {
width: 100%;
height: auto;
}
.pdf-renderer {
.react-pdf__Page__canvas {
width: 100% !important;
height: auto !important;
}
}
}

View File

@@ -0,0 +1,42 @@
import React from 'react';
import { shallow } from 'enzyme';
import { Collapsible } from '@edx/paragon';
import FilePopoverContent from 'components/FilePopoverContent';
import FileInfo from './FileInfo';
import FileCard from './FileCard';
jest.mock('components/FilePopoverContent', () => 'FilePopoverContent');
jest.mock('./FileInfo', () => 'FileInfo');
describe('File Preview Card component', () => {
const props = {
file: {
name: 'test-file-name.pdf',
description: 'test-file description',
downloadUrl: 'destination/test-file-name.pdf',
},
};
const children = (<h1>some children</h1>);
let el;
beforeEach(() => {
el = shallow(<FileCard {...props}>{children}</FileCard>);
});
test('snapshot', () => {
expect(el).toMatchSnapshot();
});
describe('Component', () => {
test('collapsible title is name header', () => {
const title = el.find(Collapsible).prop('title');
expect(title).toEqual(<h3>{props.file.name}</h3>);
});
test('forwards children into preview-panel', () => {
const previewPanelChildren = el.find('.preview-panel').children();
expect(previewPanelChildren.at(0).equals(
<FileInfo><FilePopoverContent file={props.file} /></FileInfo>,
));
expect(previewPanelChildren.at(1).equals(children)).toEqual(true);
});
});
});

View File

@@ -0,0 +1,49 @@
import React from 'react';
import PropTypes from 'prop-types';
import {
Button,
OverlayTrigger,
Popover,
} from '@edx/paragon';
import { InfoOutline } from '@edx/paragon/icons';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import messages from './messages';
/**
* <FileInfo />
*/
export const FileInfo = ({ onClick, children }) => (
<OverlayTrigger
trigger="focus"
placement="right-end"
flip
overlay={(
<Popover className="overlay-help-popover">
<Popover.Content>{children}</Popover.Content>
</Popover>
)}
>
<Button
size="small"
variant="tertiary"
onClick={onClick}
iconAfter={InfoOutline}
>
<FormattedMessage {...messages.fileInfo} />
</Button>
</OverlayTrigger>
);
FileInfo.defaultProps = {
onClick: () => {},
};
FileInfo.propTypes = {
onClick: PropTypes.func,
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
PropTypes.node,
]).isRequired,
};
export default FileInfo;

View File

@@ -0,0 +1,25 @@
import React from 'react';
import { shallow } from 'enzyme';
import { Popover } from '@edx/paragon';
import FileInfo from './FileInfo';
describe('File Preview Card component', () => {
const children = (<h1>some Children</h1>);
const props = { onClick: jest.fn().mockName('this.props.onClick') };
let el;
beforeEach(() => {
el = shallow(<FileInfo {...props}>{children}</FileInfo>);
});
test('snapshot', () => {
expect(el).toMatchSnapshot();
});
describe('Component', () => {
test('overlay with passed children', () => {
const { overlay } = el.at(0).props();
expect(overlay.type).toEqual(Popover);
expect(overlay.props.children).toEqual(<Popover.Content>{children}</Popover.Content>);
});
});
});

View File

@@ -0,0 +1,17 @@
import React from 'react';
import PropTypes from 'prop-types';
const ImageRenderer = ({ url, fileName }) => (<img alt={fileName} className="image-renderer" src={url} />);
ImageRenderer.supportedTypes = ['jpg', 'jpeg', 'png', 'bmp'];
ImageRenderer.defaultProps = {
fileName: '',
};
ImageRenderer.propTypes = {
url: PropTypes.string.isRequired,
fileName: PropTypes.string,
};
export default ImageRenderer;

View File

@@ -0,0 +1,25 @@
import React from 'react';
import { shallow } from 'enzyme';
import ImageRenderer from './ImageRenderer';
describe('Image Renderer Component', () => {
const props = {
url: 'some_url.pdf',
};
let el;
beforeEach(() => {
el = shallow(<ImageRenderer {...props} />);
});
test('snapshot', () => {
expect(el).toMatchSnapshot();
});
describe('Component', () => {
const supportedTypes = ['jpg', 'jpeg', 'png', 'bmp'];
test('static supported types is expected', () => {
expect(ImageRenderer.supportedTypes).toEqual(supportedTypes);
});
});
});

View File

@@ -0,0 +1,147 @@
import React from 'react';
import PropTypes from 'prop-types';
import { pdfjs, Document, Page } from 'react-pdf';
import {
Icon, Form, ActionRow, IconButton,
} from '@edx/paragon';
import { ChevronLeft, ChevronRight } from '@edx/paragon/icons';
import pdfjsWorker from 'react-pdf/node_modules/pdfjs-dist/build/pdf.worker.entry';
import 'react-pdf/dist/esm/Page/AnnotationLayer.css';
pdfjs.GlobalWorkerOptions.workerSrc = pdfjsWorker;
/**
* <PDFRenderer />
*/
export class PDFRenderer extends React.Component {
static supportedTypes = ['pdf'];
static INITIAL_STATE = {
pageNumber: 1,
numPages: 1,
relativeHeight: 0,
};
constructor(props) {
super(props);
this.state = { ...PDFRenderer.INITIAL_STATE };
this.wrapperRef = React.createRef();
this.onDocumentLoadSuccess = this.onDocumentLoadSuccess.bind(this);
this.onDocumentLoadError = this.onDocumentLoadError.bind(this);
this.onLoadPageSuccess = this.onLoadPageSuccess.bind(this);
this.onPrevPageButtonClick = this.onPrevPageButtonClick.bind(this);
this.onNextPageButtonClick = this.onNextPageButtonClick.bind(this);
this.onInputPageChange = this.onInputPageChange.bind(this);
}
onDocumentLoadSuccess = ({ numPages }) => {
this.setState({ numPages });
};
onLoadPageSuccess = (page) => {
const pageWidth = page.view[2];
const pageHeight = page.view[3];
const wrapperHeight = this.wrapperRef.current.getBoundingClientRect().width;
const relativeHeight = (wrapperHeight * pageHeight) / pageWidth;
if (relativeHeight !== this.state.relativeHeight) {
this.setState({ relativeHeight });
}
};
onDocumentLoadError = (error) => {
// eslint-disable-next-line no-console
console.error(error);
};
onInputPageChange = ({ target: { value } }) => {
this.setPageNumber(parseInt(value, 10));
}
onPrevPageButtonClick = () => {
this.setPageNumber(this.state.pageNumber - 1);
}
onNextPageButtonClick = () => {
this.setPageNumber(this.state.pageNumber + 1);
}
setPageNumber(pageNumber) {
if (pageNumber > 0 && pageNumber <= this.state.numPages) {
this.setState({ pageNumber });
}
}
get hasNext() {
return this.state.pageNumber < this.state.numPages;
}
get hasPrev() {
return this.state.pageNumber > 1;
}
render() {
return (
<div ref={this.wrapperRef} className="pdf-renderer">
<Document
file={this.props.url}
onLoadSuccess={this.onDocumentLoadSuccess}
onLoadError={this.onDocumentLoadError}
>
{/* <Outline /> */}
<div
className="page-wrapper"
style={{
height: this.state.relativeHeight,
}}
>
<Page
pageNumber={this.state.pageNumber}
onLoadSuccess={this.onLoadPageSuccess}
/>
</div>
</Document>
<ActionRow className="d-flex justify-content-center m-0">
<IconButton
size="inline"
alt="previous pdf page"
iconAs={Icon}
src={ChevronLeft}
disabled={!this.hasPrev}
onClick={this.onPrevPageButtonClick}
/>
<Form.Group className="d-flex align-items-center m-0">
<Form.Label isInline>Page </Form.Label>
<Form.Control
type="number"
min={0}
max={this.state.numPages}
value={this.state.pageNumber}
onChange={this.onInputPageChange}
/>
<Form.Label isInline> of {this.state.numPages}</Form.Label>
</Form.Group>
<IconButton
size="inline"
alt="next pdf page"
iconAs={Icon}
src={ChevronRight}
disabled={!this.hasNext}
onClick={this.onNextPageButtonClick}
/>
</ActionRow>
</div>
);
}
}
PDFRenderer.defaultProps = {};
PDFRenderer.propTypes = {
url: PropTypes.string.isRequired,
};
export default PDFRenderer;

View File

@@ -0,0 +1,225 @@
import React from 'react';
import { shallow } from 'enzyme';
import { Document, Page } from 'react-pdf';
import { Form, IconButton } from '@edx/paragon';
import PDFRenderer from './PDFRenderer';
jest.mock('react-pdf', () => ({
pdfjs: { GlobalWorkerOptions: {} },
Document: () => 'Document',
Page: () => 'Page',
}));
describe('PDF Renderer Component', () => {
const props = {
url: 'some_url.pdf',
};
let el;
describe('snapshots', () => {
beforeEach(() => {
el = shallow(<PDFRenderer {...props} />);
el.instance().onDocumentLoadSuccess = jest
.fn()
.mockName('onDocumentLoadSuccess');
el.instance().onDocumentLoadError = jest
.fn()
.mockName('onDocumentLoadError');
el.instance().onLoadPageSuccess = jest.fn().mockName('onLoadPageSuccess');
});
test('snapshot', () => {
el.instance().onPrevPageButtonClick = jest
.fn()
.mockName('onPrevPageButtonClick');
el.instance().onNextPageButtonClick = jest
.fn()
.mockName('onNextPageButtonClick');
el.instance().onInputPageChange = jest.fn().mockName('onInputPageChange');
expect(el.instance().render()).toMatchSnapshot();
});
});
describe('Component', () => {
const numPages = 99;
const pageNumber = 234;
const supportedTypes = ['pdf'];
beforeEach(() => {
el = shallow(<PDFRenderer {...props} />);
});
describe('render', () => {
describe('Top-level document', () => {
let documentEl;
beforeEach(() => { documentEl = el.find(Document); });
it('displays file from props.url', () => {
expect(documentEl.props().file).toEqual(props.url);
});
it('calls this.onDocumentLoadSuccess onLoadSuccess', () => {
expect(documentEl.props().onLoadSuccess).toEqual(el.instance().onDocumentLoadSuccess);
});
it('calls this.onDocumentLoadError onLoadError', () => {
expect(documentEl.props().onLoadError).toEqual(el.instance().onDocumentLoadError);
});
});
describe('Page', () => {
let pageProps;
beforeEach(() => {
el.instance().setState({ pageNumber });
pageProps = el.find(Page).props();
});
it('loads pageNumber from state', () => {
expect(pageProps.pageNumber).toEqual(pageNumber);
});
it('calls onLoadPageSuccess onLoadSuccess', () => {
expect(pageProps.onLoadSuccess).toEqual(el.instance().onLoadPageSuccess);
});
});
describe('pagination ActionRow', () => {
describe('Previous page button', () => {
let hasPrev;
beforeEach(() => {
hasPrev = jest.spyOn(el.instance(), 'hasPrev', 'get').mockReturnValue(false);
});
const btn = () => shallow(el.instance().render()).find(IconButton).at(0).props();
test('disabled iff not this.hasPrev', () => {
expect(btn().disabled).toEqual(true);
hasPrev.mockReturnValue(true);
expect(btn().disabled).toEqual(false);
});
it('calls onPrevPageButtonClick onClick', () => {
expect(btn().onClick).toEqual(el.instance().onPrevPageButtonClick);
});
});
describe('page indicator', () => {
const control = () => el.find(Form.Control).at(0).props();
const labels = () => {
const flat = el.find({ isInline: true });
return [0, 1].map(i => flat.at(i).text());
};
beforeEach(() => { el.instance().setState({ numPages, pageNumber }); });
test('labels: Page <state.pageNumber> of <state.numPages>', () => {
expect(`${labels()[0]}${control().value}${labels()[1]}`).toEqual(
`Page ${pageNumber} of ${numPages}`,
);
});
it('loads max from state.numPages', () => expect(control().max).toEqual(numPages));
it('loads value from state.pageNumber', () => {
expect(control().value).toEqual(pageNumber);
});
it('calls onInputPageChange onChange', () => {
expect(control().onChange).toEqual(el.instance().onInputPageChange);
});
});
describe('Next page button', () => {
let hasNext;
beforeEach(() => {
hasNext = jest.spyOn(el.instance(), 'hasNext', 'get').mockReturnValue(false);
});
const btn = () => shallow(el.instance().render()).find(IconButton).at(1).props();
test('disabled iff not this.hasNext', () => {
expect(btn().disabled).toEqual(true);
hasNext.mockReturnValue(true);
expect(btn().disabled).toEqual(false);
});
it('calls onNextPageButtonClick onClick', () => {
expect(btn().onClick).toEqual(el.instance().onNextPageButtonClick);
});
});
});
});
test('static supported types is expected', () => {
expect(PDFRenderer.supportedTypes).toEqual(supportedTypes);
});
describe('behavior', () => {
test('initial state', () => {
expect(el.instance().state).toEqual(PDFRenderer.INITIAL_STATE);
});
describe('onDocumentLoadSuccess', () => {
test('loads numPages into state', () => {
el.instance().onDocumentLoadSuccess({ numPages });
expect(el.instance().state.numPages).toEqual(numPages);
});
});
describe('onLoadPageSuccess', () => {
const [pageHeight, pageWidth] = [23, 34];
const page = { view: [1, 2, pageWidth, pageHeight] };
const wrapperWidth = 20;
const expected = (wrapperWidth * pageHeight) / pageWidth;
beforeEach(() => {
el.instance().wrapperRef = {
current: {
getBoundingClientRect: () => ({ width: wrapperWidth }),
},
};
});
it('sets relative height if it has changes', () => {
el.instance().onLoadPageSuccess(page);
expect(el.instance().state.relativeHeight).toEqual(expected);
});
it('does not try to set height if has not changes', () => {
el.instance().setState({ relativeHeight: expected });
el.instance().setState = jest.fn();
el.instance().onLoadPageSuccess(page);
expect(el.instance().setState).not.toHaveBeenCalled();
});
});
describe('setPageNumber inheritors', () => {
beforeEach(() => {
el.instance().setPageNumber = jest.fn();
el.instance().setState({ pageNumber });
});
describe('onInputChange', () => {
it('calls setPageNumber with int value of event target value', () => {
el.instance().onInputPageChange({ target: { value: '23' } });
expect(el.instance().setPageNumber).toHaveBeenCalledWith(23);
});
});
describe('onPrevPageButtonClick', () => {
it('calls setPageNumber with state.pageNumber - 1', () => {
el.instance().onPrevPageButtonClick();
expect(el.instance().setPageNumber).toHaveBeenCalledWith(pageNumber - 1);
});
});
describe('onNextPageButtonClick', () => {
it('calls setPageNumber with state.pageNumber + 1', () => {
el.instance().onNextPageButtonClick();
expect(el.instance().setPageNumber).toHaveBeenCalledWith(pageNumber + 1);
});
});
});
describe('setPageNumber', () => {
it('calls setState with pageNumber iff valid', () => {
el.instance().setState({ numPages });
const setState = jest.spyOn(el.instance(), 'setState');
el.instance().setPageNumber(0);
expect(setState).not.toHaveBeenCalled();
el.instance().setPageNumber(numPages + 1);
expect(setState).not.toHaveBeenCalled();
el.instance().setPageNumber(2);
expect(setState).toHaveBeenCalledWith({ pageNumber: 2 });
});
});
describe('hasNext getter', () => {
it('returns true iff state.pageNumber < state.numPages', () => {
el.instance().setState({ pageNumber: 1, numPages: 1 });
expect(el.instance().hasNext).toEqual(false);
el.instance().setState({ pageNumber: 1, numPages: 2 });
expect(el.instance().hasNext).toEqual(true);
});
});
describe('hasPrev getter', () => {
it('returns true iff state.pageNumber > 1', () => {
el.instance().setState({ pageNumber: 1 });
expect(el.instance().hasPrev).toEqual(false);
el.instance().setState({ pageNumber: 2 });
expect(el.instance().hasPrev).toEqual(true);
});
});
});
test('static supported types is expected', () => {
expect(PDFRenderer.supportedTypes).toEqual(supportedTypes);
});
});
});

View File

@@ -0,0 +1,37 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`File Preview Card component snapshot 1`] = `
<Card
className="file-card"
key="test-file-name.pdf"
>
<Collapsible
className="file-collapsible"
defaultOpen={true}
title={
<h3>
test-file-name.pdf
</h3>
}
>
<div
className="preview-panel"
>
<FileInfo>
<FilePopoverContent
file={
Object {
"description": "test-file description",
"downloadUrl": "destination/test-file-name.pdf",
"name": "test-file-name.pdf",
}
}
/>
</FileInfo>
<h1>
some children
</h1>
</div>
</Collapsible>
</Card>
`;

View File

@@ -0,0 +1,33 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`File Preview Card component snapshot 1`] = `
<OverlayTrigger
flip={true}
overlay={
<Popover
className="overlay-help-popover"
>
<Popover.Content>
<h1>
some Children
</h1>
</Popover.Content>
</Popover>
}
placement="right-end"
trigger="focus"
>
<Button
iconAfter={[MockFunction icons.InfoOutline]}
onClick={[MockFunction this.props.onClick]}
size="small"
variant="tertiary"
>
<FormattedMessage
defaultMessage="File info"
description="Popover trigger button text for file preview card"
id="ora-grading.InfoPopover.fileInfo"
/>
</Button>
</OverlayTrigger>
`;

View File

@@ -0,0 +1,9 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Image Renderer Component snapshot 1`] = `
<img
alt=""
className="image-renderer"
src="some_url.pdf"
/>
`;

View File

@@ -0,0 +1,69 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`PDF Renderer Component snapshots snapshot 1`] = `
<div
className="pdf-renderer"
>
<Document
file="some_url.pdf"
onLoadError={[MockFunction onDocumentLoadError]}
onLoadSuccess={[MockFunction onDocumentLoadSuccess]}
>
<div
className="page-wrapper"
style={
Object {
"height": 0,
}
}
>
<Page
onLoadSuccess={[MockFunction onLoadPageSuccess]}
pageNumber={1}
/>
</div>
</Document>
<ActionRow
className="d-flex justify-content-center m-0"
>
<IconButton
alt="previous pdf page"
disabled={true}
iconAs="Icon"
onClick={[MockFunction onPrevPageButtonClick]}
size="inline"
src={[MockFunction icons.ChevronLeft]}
/>
<Form.Group
className="d-flex align-items-center m-0"
>
<Form.Label
isInline={true}
>
Page
</Form.Label>
<Form.Control
max={1}
min={0}
onChange={[MockFunction onInputPageChange]}
type="number"
value={1}
/>
<Form.Label
isInline={true}
>
of
1
</Form.Label>
</Form.Group>
<IconButton
alt="next pdf page"
disabled={true}
iconAs="Icon"
onClick={[MockFunction onNextPageButtonClick]}
size="inline"
src={[MockFunction icons.ChevronRight]}
/>
</ActionRow>
</div>
`;

View File

@@ -0,0 +1,3 @@
export { default as FileCard } from './FileCard';
export { default as ImageRenderer } from './ImageRenderer';
export { default as PDFRenderer } from './PDFRenderer';

View File

@@ -0,0 +1,11 @@
import { defineMessages } from '@edx/frontend-platform/i18n';
const messages = defineMessages({
fileInfo: {
id: 'ora-grading.InfoPopover.fileInfo',
defaultMessage: 'File info',
description: 'Popover trigger button text for file preview card',
},
});
export default messages;

View File

@@ -21,7 +21,7 @@ exports[`Info Popover Component snapshot 1`] = `
alt="Display more info"
className="esg-help-icon"
iconAs="Icon"
onClick={[Function]}
onClick={[MockFunction this.props.onClick]}
src={[MockFunction icons.InfoOutline]}
/>
</OverlayTrigger>

View File

@@ -15,7 +15,7 @@ import messages from './messages';
/**
* <InfoPopover />
*/
export const InfoPopover = ({ children, intl }) => (
export const InfoPopover = ({ onClick, children, intl }) => (
<OverlayTrigger
trigger="focus"
placement="right-end"
@@ -31,13 +31,16 @@ export const InfoPopover = ({ children, intl }) => (
src={InfoOutline}
alt={intl.formatMessage(messages.altText)}
iconAs={Icon}
onClick={() => {}}
onClick={onClick}
/>
</OverlayTrigger>
);
InfoPopover.defaultProps = {};
InfoPopover.defaultProps = {
onClick: () => {},
};
InfoPopover.propTypes = {
onClick: PropTypes.func,
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
PropTypes.node,

View File

@@ -6,15 +6,15 @@ import { InfoPopover } from '.';
describe('Info Popover Component', () => {
const child = <div>Children component</div>;
test('snapshot', () => {
expect(shallow(<InfoPopover intl={{ formatMessage }}>{child}</InfoPopover>)).toMatchSnapshot();
const onClick = jest.fn().mockName('this.props.onClick');
let el;
beforeEach(() => {
el = shallow(<InfoPopover onClick={onClick} intl={{ formatMessage }}>{child}</InfoPopover>);
});
test('snapshot', () => {
expect(el).toMatchSnapshot();
});
describe('Component', () => {
let el;
beforeEach(() => {
el = shallow(<InfoPopover intl={{ formatMessage }}>{child}</InfoPopover>);
});
test('Test component render', () => {
expect(el.length).toEqual(1);
expect(el.find('.esg-help-icon').length).toEqual(1);

View File

@@ -0,0 +1,69 @@
import React from 'react';
import PropTypes from 'prop-types';
import { StrictDict } from 'utils';
import { FileTypes } from 'data/constants/files';
import { FileCard, PDFRenderer, ImageRenderer } from 'components/FilePreview';
import './PreviewDisplay.scss';
/**
* <PreviewDisplay />
*/
export class PreviewDisplay extends React.Component {
static RENDERERS = StrictDict({
[FileTypes.pdf]: PDFRenderer,
[FileTypes.jpg]: ImageRenderer,
[FileTypes.jpeg]: ImageRenderer,
[FileTypes.bmp]: ImageRenderer,
[FileTypes.png]: ImageRenderer,
});
static SUPPORTED_TYPES = Object.keys(PreviewDisplay.RENDERERS);
constructor(props) {
super(props);
this.isSupported = this.isSupported.bind(this);
this.fileType = this.fileType.bind(this);
}
get supportedFiles() {
return this.props.files.filter(this.isSupported);
}
isSupported(file) {
return PreviewDisplay.SUPPORTED_TYPES.includes(this.fileType(file.name));
}
fileType(fileName) {
return fileName.split('.').pop();
}
render() {
return (
<>
{this.supportedFiles.map((file) => {
const Renderer = PreviewDisplay.RENDERERS[this.fileType(file.name)];
return (
<FileCard key={file.downloadUrl} file={file}>
<Renderer fileName={file.name} url={file.downloadUrl} />
</FileCard>
);
})}
</>
);
}
}
PreviewDisplay.defaultProps = {
files: [],
};
PreviewDisplay.propTypes = {
files: PropTypes.arrayOf(PropTypes.shape({
name: PropTypes.string,
downloadUrl: PropTypes.string,
})),
};
export default PreviewDisplay;

View File

@@ -0,0 +1,13 @@
.preview-panel {
.image-renderer {
width: 100%;
height: auto;
}
.pdf-renderer {
.react-pdf__Page__canvas {
width: 100% !important;
height: auto !important;
}
}
}

View File

@@ -0,0 +1,83 @@
import React from 'react';
import { shallow } from 'enzyme';
import { FileTypes } from 'data/constants/files';
import { FileCard, ImageRenderer, PDFRenderer } from 'components/FilePreview';
import { PreviewDisplay } from './PreviewDisplay';
jest.mock('components/FilePreview', () => ({
FileCard: () => 'FileCard',
PDFRenderer: () => 'PDFRenderer',
ImageRenderer: () => 'ImageRenderer',
}));
describe('PreviewDisplay', () => {
describe('component', () => {
const supportedTypes = [
FileTypes.pdf,
FileTypes.jpg,
FileTypes.jpeg,
FileTypes.bmp,
FileTypes.png,
];
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',
},
],
};
let el;
beforeEach(() => {
el = shallow(<PreviewDisplay {...props} />);
});
describe('snapshot', () => {
test('files does not exist', () => {
expect(el).toMatchSnapshot();
});
test('files exited for props', () => {
el.setProps({ files: [] });
expect(el.instance().render()).toMatchSnapshot();
});
});
describe('component', () => {
test('only renders compatible files', () => {
const cards = el.find(FileCard);
expect(cards.length).toEqual(supportedTypes.length);
[0, 1, 2, 3, 4].forEach(index => {
expect(
cards.at(index).prop('file'),
).toEqual(props.files[index]);
});
});
describe('uses the correct renderers', () => {
const loadRenderer = (index) => (
el.find(FileCard).at(index).children().at(0)
);
const checkFile = (index, expectedRenderer) => {
const file = props.files[index];
const renderer = loadRenderer(index);
expect(renderer.type()).toEqual(expectedRenderer);
expect(renderer.props()).toEqual({
url: file.downloadUrl,
fileName: file.name,
});
};
test(FileTypes.pdf, () => checkFile(0, PDFRenderer));
test(FileTypes.jpg, () => checkFile(1, ImageRenderer));
test(FileTypes.jpeg, () => checkFile(2, ImageRenderer));
test(FileTypes.bmp, () => checkFile(3, ImageRenderer));
test(FileTypes.png, () => checkFile(4, ImageRenderer));
});
});
});
});

View File

@@ -7,10 +7,10 @@
height: fit-content;
.submission-files {
padding: map-get($spacers, 3);
margin-bottom: map-get($spacers, 2);
.submission-files-title {
padding: map-get($spacers, 3);
border-radius: calc(0.375rem - 1px);
border-bottom: 1px solid transparent;
transition: border-color 100ms ease 150ms;
@@ -33,7 +33,8 @@
}
.submission-files-body {
padding: map-get($spacers, 3) 0;
padding: map-get($spacers, 3);
padding-top: 0;
thead {
display: none;

View File

@@ -0,0 +1,83 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`PreviewDisplay component snapshot files does not exist 1`] = `
<Fragment>
<FileCard
file={
Object {
"description": "file description 0",
"downloadUrl": "/url-path/fake_file_0.pdf",
"name": "fake_file_0.pdf",
}
}
key="/url-path/fake_file_0.pdf"
>
<PDFRenderer
fileName="fake_file_0.pdf"
url="/url-path/fake_file_0.pdf"
/>
</FileCard>
<FileCard
file={
Object {
"description": "file description 1",
"downloadUrl": "/url-path/fake_file_1.jpg",
"name": "fake_file_1.jpg",
}
}
key="/url-path/fake_file_1.jpg"
>
<ImageRenderer
fileName="fake_file_1.jpg"
url="/url-path/fake_file_1.jpg"
/>
</FileCard>
<FileCard
file={
Object {
"description": "file description 2",
"downloadUrl": "/url-path/fake_file_2.jpeg",
"name": "fake_file_2.jpeg",
}
}
key="/url-path/fake_file_2.jpeg"
>
<ImageRenderer
fileName="fake_file_2.jpeg"
url="/url-path/fake_file_2.jpeg"
/>
</FileCard>
<FileCard
file={
Object {
"description": "file description 3",
"downloadUrl": "/url-path/fake_file_3.bmp",
"name": "fake_file_3.bmp",
}
}
key="/url-path/fake_file_3.bmp"
>
<ImageRenderer
fileName="fake_file_3.bmp"
url="/url-path/fake_file_3.bmp"
/>
</FileCard>
<FileCard
file={
Object {
"description": "file description 4",
"downloadUrl": "/url-path/fake_file_4.png",
"name": "fake_file_4.png",
}
}
key="/url-path/fake_file_4.png"
>
<ImageRenderer
fileName="fake_file_4.png"
url="/url-path/fake_file_4.png"
/>
</FileCard>
</Fragment>
`;
exports[`PreviewDisplay component snapshot files exited for props 1`] = `<React.Fragment />`;

View File

@@ -19,6 +19,9 @@ exports[`ResponseDisplay component snapshot file upload disabled without respons
<SubmissionFiles
files={Array []}
/>
<PreviewDisplay
files={Array []}
/>
</div>
`;
@@ -42,6 +45,22 @@ exports[`ResponseDisplay component snapshot file upload enable with valid respon
]
}
/>
<PreviewDisplay
files={
Array [
Object {
"description": "description for the file",
"downloadURL": "/valid-url-wink-wink",
"name": "some file name.jpg",
},
Object {
"description": "description for this file",
"downloadURL": "/url-2",
"name": "file number 2.jpg",
},
]
}
/>
<Card>
<Card.Body>
parsed html (sanitized (some text response here))
@@ -57,5 +76,8 @@ exports[`ResponseDisplay component snapshot file upload enable without response
<SubmissionFiles
files={Array []}
/>
<PreviewDisplay
files={Array []}
/>
</div>
`;

View File

@@ -1,24 +1,12 @@
import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import InfoPopover from 'components/InfoPopover';
import messages from './messages';
import FilePopoverContent from 'components/FilePopoverContent';
export const FilePopoverCell = ({ row: { original } }) => (
<InfoPopover>
<div className="help-popover-option">
<strong><FormattedMessage {...messages.filePopoverNameTitle} /></strong>
<br />
{original.name}
</div>
<div className="help-popover-option">
<strong><FormattedMessage {...messages.filePopoverDescriptionTitle} /></strong>
<br />
{original.description}
</div>
<FilePopoverContent file={original} />
</InfoPopover>
);

View File

@@ -1,9 +1,11 @@
import React from 'react';
import { shallow } from 'enzyme';
import FilePopoverContent from 'components/FilePopoverContent';
import FilePopoverCell from './FilePopoverCell';
jest.mock('components/InfoPopover', () => 'InfoPopover');
jest.mock('components/FilePopoverContent', () => 'FilePopoverContent');
describe('FilePopoverCell', () => {
describe('component', () => {
@@ -27,8 +29,8 @@ describe('FilePopoverCell', () => {
describe('behavior', () => {
test('content', () => {
const { original } = props.row;
expect(el.text()).toContain(original.name);
expect(el.text()).toContain(original.description);
const content = el.find(FilePopoverContent);
expect(content.props()).toEqual({ file: original });
});
});
});

View File

@@ -2,31 +2,14 @@
exports[`FilePopoverCell component snapshot 1`] = `
<InfoPopover>
<div
className="help-popover-option"
>
<strong>
<FormattedMessage
defaultMessage="File Name"
description="Popover title for file name"
id="ora-grading.ResponseDisplay.FilePopoverCell.filePopoverNameTitle"
/>
</strong>
<br />
some file name
</div>
<div
className="help-popover-option"
>
<strong>
<FormattedMessage
defaultMessage="File Description"
description="Popover title for file description"
id="ora-grading.ResponseDisplay.FilePopoverCell.filePopoverDescriptionTitle"
/>
</strong>
<br />
long descriptive text...
</div>
<FilePopoverContent
file={
Object {
"description": "long descriptive text...",
"downloadURL": "this-url-is.working",
"name": "some file name",
}
}
/>
</InfoPopover>
`;

View File

@@ -12,6 +12,7 @@ import { selectors } from 'data/redux';
import { fileUploadResponseOptions } from 'data/services/lms/constants';
import SubmissionFiles from './SubmissionFiles';
import PreviewDisplay from './PreviewDisplay';
import './ResponseDisplay.scss';
@@ -42,6 +43,7 @@ export class ResponseDisplay extends React.Component {
return (
<div className="response-display">
{this.allowFileUpload && <SubmissionFiles files={this.submittedFiles} />}
{this.allowFileUpload && <PreviewDisplay files={this.submittedFiles} />}
{
/* eslint-disable react/no-array-index-key */
this.textContents.map((textContent, index) => (

View File

@@ -0,0 +1,11 @@
import { StrictDict } from 'utils';
export const FileTypes = StrictDict({
pdf: 'pdf',
jpg: 'jpg',
jpeg: 'jpeg',
png: 'png',
bmp: 'bmp',
});
export default FileTypes;

View File

@@ -9,11 +9,13 @@ Phasellus porttitor vel magna et auctor. Nulla porttitor convallis aliquam. Done
const descriptiveText = (fileName) => `This is some descriptive text for (${fileName}). Phasellus tempor eros aliquam ipsum molestie, vitae varius lectus tempus. Morbi iaculis, libero euismod vehicula rutrum, nisi leo volutpat diam, quis commodo ex nunc ut odio. Pellentesque condimentum feugiat erat ac vulputate. Pellentesque porta rutrum sagittis. Curabitur vulputate tempus accumsan. Fusce bibendum gravida metus a scelerisque. Mauris fringilla orci non lobortis commodo. Quisque iaculis, quam a tincidunt vehicula, erat nisi accumsan quam, eu cursus ligula magna id odio. Nulla porttitor, lorem gravida vehicula tristique, sapien metus tristique ex, id tincidunt sapien justo nec sapien. Maecenas luctus, nisl vestibulum scelerisque pharetra, ligula orci vulputate turpis, in ultrices mauris dolor eu enim. Suspendisse quis nibh nec augue semper maximus. Morbi maximus eleifend magna.`;
const allFiles = [
'presentation.pdf',
'example.jpg',
'diagram.png',
'notes.doc',
'recording.wav',
'edX_2021_Internal_BrandTMGuidelines_V1.0.9.pdf',
'irs_p5563.pdf',
'mit_Cohen_GRL16.pdf',
'sample.bmp',
'sample.jpg',
'sample.png',
'sample.jpeg',
];
const getFiles = (submissionUUID) => {
@@ -23,9 +25,9 @@ const getFiles = (submissionUUID) => {
for (let i = 0; i < numFiles; i++) {
const fileName = `${submissionUUID}_${allFiles[i]}`;
files.push({
name: fileName,
name: allFiles[i],
description: descriptiveText(fileName),
downloadURL: `/download/${fileName}/`,
downloadUrl: allFiles[i],
});
}
return files;

View File

@@ -29,6 +29,21 @@ jest.mock('@edx/frontend-platform/auth', () => ({
getLoginRedirectUrl: jest.fn(),
}));
jest.mock('react-pdf', () => ({
Document: () => <div>Document</div>,
Image: () => <div>Image</div>,
Page: () => <div>Page</div>,
PDFViewer: jest.fn(() => null),
StyleSheet: { create: () => {} },
Text: () => <div>Text</div>,
View: () => <div>View</div>,
pdfjs: { GlobalWorkerOptions: {} },
}));
/*
jest.mock('react-pdf/node_modules/pdfjs-dist/build/pdf.worker.entry', () => (
jest.requireActual('react-pdf/dist/umd/entry.jest')
));
*/
const configureStore = () => redux.createStore(
reducers,
redux.compose(redux.applyMiddleware(thunk)),
@@ -153,9 +168,8 @@ const checkLoadedResponses = async (currentIndex) => {
};
describe('ESG app integration tests', () => {
beforeAll(() => mockApi());
test('initialState', async () => {
mockApi();
await renderEl();
expect(state.app).toEqual(
jest.requireActual('data/redux/app/reducer').initialState,
@@ -177,12 +191,10 @@ describe('ESG app integration tests', () => {
});
describe('table selection', () => {
beforeAll(async () => {
it('loads selected submission ids', async () => {
await renderEl();
await initialize();
await makeTableSelections();
});
it('loads selected submission ids', () => {
expect(state.grading.selected).toEqual(submissionUUIDs);
});
test('app flags, { showReview: true, isGrading: false, showRubric: false }', () => {