Commit 72b03f14 authored by Mihail Dunaev's avatar Mihail Dunaev
Browse files

refactor(ui): refactor FileUpload molecule based on review decisions

re #41
parent 9034b188
......@@ -29,14 +29,9 @@ const FileUploadsPage = () => (
conversion={{
converting: loading,
completed: !!data,
// display either upload error or form validation error
error: uploadError,
}}
formError={
errors.manuscriptUrl &&
touched.manuscriptUrl &&
new Error(errors.manuscriptUrl)
}
formError={errors.manuscriptUrl && touched.manuscriptUrl}
onDrop={([file]) =>
uploadFile({ variables: { file } }).then(
// save file URL to form on success
......
......@@ -23,18 +23,16 @@ const Icon = ({ children, size, color, ...props }) => {
}
return (
<Container color={color} role="img" size={size} {...props}>
<SelectedIcon color={color} size={size} {...props} />
<SelectedIcon {...props} />
</Container>
)
}
Icon.propTypes = {
size: PropTypes.number,
color: PropTypes.string,
}
Icon.defaultProps = {
size: 3,
color: 'black',
}
......
An upload icon
An upload icon (you must specify a size).
```js
<Icon>Upload</Icon>
<Icon size={6}>Upload</Icon>
```
The size can be changed.
Upload failure icon.
```js
<Icon size={6}>UploadFailure</Icon>
```
Some icons accept special parameters.
Some icons accept additional parameters.
```js
<Icon size={6} percentage={100}>
......
import React from 'react'
import PropTypes from 'prop-types'
import styled, { keyframes } from 'styled-components'
import styled, { keyframes, withTheme } from 'styled-components'
function animateUpload(animate) {
if (animate === true) {
......@@ -38,21 +38,18 @@ function percentageToDasharray(percentage, circleRadius) {
return `${lineLength}, ${spaceLength}`
}
const Upload = props => {
const { color, size, percentage, ...otherProps } = props
const Upload = ({ color, percentage, ...otherProps }) => {
const dashArray = percentageToDasharray(percentage, 24)
let animate = true
return (
<AnimationRoot>
<svg
height={size}
onClick={() => {
animateUpload(animate)
animate = !animate
}}
version="1.1"
viewBox="0 0 52 52"
width={size}
xmlns="http://www.w3.org/2000/svg"
{...otherProps}
>
......@@ -68,8 +65,7 @@ const Upload = props => {
<g id="icons" transform="translate(-22.000000, -22.000000)">
<g id="dropzone-upload" transform="translate(24.000000, 24.000000)">
<polygon
fill="#0288D1"
// fill={color}
fill={otherProps.theme.colorPrimary}
id="Shape"
points="20 31 28 31 28 23 33 23 24 14 15 23 20 23"
/>
......@@ -78,7 +74,7 @@ const Upload = props => {
cy="24"
id="Oval"
r="24"
// stroke={color}
// stroke={otherProps.theme.colorTextDividers}
stroke="#E0E0E0"
strokeWidth="3"
/>
......@@ -87,7 +83,7 @@ const Upload = props => {
cy="24"
id="Progress-circle"
r="24"
stroke="#0288D1"
stroke={otherProps.theme.colorPrimary}
strokeDasharray={dashArray}
strokeWidth="2"
/>
......@@ -101,14 +97,12 @@ const Upload = props => {
Upload.propTypes = {
color: PropTypes.string,
size: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
percentage: PropTypes.number,
}
Upload.defaultProps = {
color: 'currentColor',
size: '24',
percentage: 0,
}
export default Upload
export default withTheme(Upload)
import React from 'react'
import PropTypes from 'prop-types'
import { withTheme } from 'styled-components'
const UploadFailure = props => {
const { color, size, ...otherProps } = props
......@@ -23,8 +24,7 @@ const UploadFailure = props => {
>
<g
id="icons"
stroke="#CF0C4E"
// stroke={color}
stroke={otherProps.theme.colorError}
strokeWidth="3"
transform="translate(-214.000000, -22.000000)"
>
......@@ -41,12 +41,10 @@ const UploadFailure = props => {
UploadFailure.propTypes = {
color: PropTypes.string,
size: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
}
UploadFailure.defaultProps = {
color: 'currentColor',
size: '24',
}
export default UploadFailure
export default withTheme(UploadFailure)
import React from 'react'
import PropTypes from 'prop-types'
import { withTheme } from 'styled-components'
const UploadSuccess = props => {
const { color, size, ...otherProps } = props
......@@ -23,8 +24,7 @@ const UploadSuccess = props => {
>
<g
id="icons"
stroke="#0288D1"
// stroke={color}
stroke={otherProps.theme.colorPrimary}
strokeWidth="3"
transform="translate(-118.000000, -22.000000)"
>
......@@ -40,12 +40,10 @@ const UploadSuccess = props => {
UploadSuccess.propTypes = {
color: PropTypes.string,
size: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
}
UploadSuccess.defaultProps = {
color: 'currentColor',
size: '24',
}
export default UploadSuccess
export default withTheme(UploadSuccess)
......@@ -5,15 +5,12 @@ import styled from 'styled-components'
import Dropzone from 'react-dropzone'
import { ErrorText, Action, th } from '@pubsweet/ui'
import { get } from 'lodash'
import { toClass } from 'recompose'
import Icon from '../atoms/Icon'
const StyledDropzone = styled(
toClass(({ hasError, saveInnerRef, ...rest }) => (
<Dropzone ref={saveInnerRef} {...rest} />
)),
)`
const StyledDropzone = styled(({ hasError, saveInnerRef, ...rest }) => (
<Dropzone ref={saveInnerRef} {...rest} />
))`
border-style: dashed;
border-color: ${({ hasError = false }) =>
hasError ? th('colorError') : th('colorBorder')};
......@@ -27,7 +24,7 @@ const CentredFlex = styled(Flex)`
align-items: center;
`
const DropzoneContent = ({ conversion, formError, dropzoneOpen }) => {
const DropzoneContent = ({ conversion, formError, dropzoneOpen, ...props }) => {
if (conversion.error) {
const errorMessage = get(
conversion,
......@@ -37,7 +34,7 @@ const DropzoneContent = ({ conversion, formError, dropzoneOpen }) => {
return (
<div>
<Icon size={6}>UploadFailure</Icon>
<ErrorText name="dropzoneMessage">
<ErrorText data-test-id="dropzoneMessage">
{errorMessage}. Try to <Action onClick={dropzoneOpen}>upload</Action>{' '}
your Manuscript again.
</ErrorText>
......@@ -48,7 +45,7 @@ const DropzoneContent = ({ conversion, formError, dropzoneOpen }) => {
return (
<div>
<Icon size={6}>UploadFailure</Icon>
<ErrorText name="dropzoneMessage">
<ErrorText data-test-id="dropzoneMessage">
Please <Action onClick={dropzoneOpen}>upload</Action> your Manuscript.
</ErrorText>
</div>
......@@ -58,7 +55,7 @@ const DropzoneContent = ({ conversion, formError, dropzoneOpen }) => {
return (
<div>
<Icon size={6}>Upload</Icon>
<Instruction name="dropzoneMessage">
<Instruction data-test-id="dropzoneMessage">
Manuscript is uploading
</Instruction>
</div>
......@@ -68,7 +65,7 @@ const DropzoneContent = ({ conversion, formError, dropzoneOpen }) => {
return (
<div>
<Icon size={6}>UploadSuccess</Icon>
<Instruction name="dropzoneMessage">
<Instruction data-test-id="dropzoneMessage">
Success! <Action to="/manuscript">Preview</Action> or{' '}
<Action onClick={dropzoneOpen}>replace</Action> your Manuscript.
</Instruction>
......@@ -78,7 +75,7 @@ const DropzoneContent = ({ conversion, formError, dropzoneOpen }) => {
return (
<div>
<Icon size={6}>Upload</Icon>
<Instruction name="dropzoneMessage">
<Instruction data-test-id="dropzoneMessage">
<Action onClick={dropzoneOpen}>Upload</Action> your manuscript or drag
it here.
</Instruction>
......@@ -117,13 +114,14 @@ FileUpload.propTypes = {
conversion: PropTypes.shape({
completed: PropTypes.bool,
error: PropTypes.instanceOf(Error),
converting: PropTypes.bool,
}),
formError: PropTypes.instanceOf(Error),
formError: PropTypes.bool,
}
FileUpload.defaultProps = {
conversion: {},
formError: null,
formError: false,
}
export default FileUpload
import React from 'react'
import { mount } from 'enzyme'
import { MemoryRouter } from 'react-router-dom'
import { ThemeProvider } from 'styled-components'
import theme from '@pubsweet/elife-theme'
import FileUpload from './FileUpload'
function makeCheerioWrapper(props) {
return mount(
<ThemeProvider theme={theme}>
<MemoryRouter>
<FileUpload onDrop={jest.fn()} {...props} />
</MemoryRouter>
</ThemeProvider>,
)
.render()
.find('[data-test-id=dropzoneMessage]')
}
const manuscriptUpload = 'Upload your manuscript or drag it here.'
const noManuscriptError = 'Please upload your Manuscript.'
const badManuscriptError = 'Try to upload your Manuscript again.'
const manuscriptUploadSuccess = 'Success! Preview or replace your Manuscript.'
const manuscriptUploading = 'Manuscript is uploading'
it('displays upload manuscript if nothing is set', () => {
const dropzoneContentWrapper = makeCheerioWrapper()
expect(dropzoneContentWrapper.text()).toBe(manuscriptUpload)
})
it('displays error if formError is set', () => {
const dropzoneContentWrapper = makeCheerioWrapper({ formError: true })
expect(dropzoneContentWrapper.text()).toBe(noManuscriptError)
})
it('displays error if conversion.error is set', () => {
const uploadError = new Error('Bad file type')
const dropzoneContentWrapper = makeCheerioWrapper({
conversion: { error: uploadError },
})
expect(dropzoneContentWrapper.text()).toBe(
`${uploadError.message}. ${badManuscriptError}`,
)
})
it('displays success if conversion.completed is set', () => {
const dropzoneContentWrapper = makeCheerioWrapper({
conversion: { completed: true },
})
expect(dropzoneContentWrapper.text()).toBe(manuscriptUploadSuccess)
})
it('displays uploading if conversion.converting is set', () => {
const dropzoneContentWrapper = makeCheerioWrapper({
conversion: { converting: true },
})
expect(dropzoneContentWrapper.text()).toBe(manuscriptUploading)
})
module.exports = {
rootDir: '../',
setupTestFrameworkScriptFile: '<rootDir>/test/helpers/jest-setup.js',
testMatch: ['<rootDir>/test/**/*.test.js'],
testMatch: ['<rootDir>/**/*.test.js'],
transformIgnorePatterns: ['/node_modules/(?!@?pubsweet)'],
moduleNameMapper: {
'\\.s?css$': 'identity-obj-proxy',
},
}
......@@ -123,109 +123,3 @@ test('Submission form details are saved to server on submit', async t => {
.expect(Selector(authorDetails.institutionField).value)
.eql('iTunes U', 'Institution has been saved')
})
const noManuscriptError = 'Please upload your Manuscript.'
const badManuscriptError =
'Network error: Response not successful: Received status code 400'
const manuscriptUploadSuccess = 'Success! Preview or replace your Manuscript.'
test('Clicking next without uploading manuscript on page 2 generates error', async t => {
await t.navigateTo(dashboard.url)
await t.ctx.localStorageSet(t.ctx.token)
await t.navigateTo(dashboard.url).click('[data-test-id=submit]')
await t.click('[data-test-id=next]')
await t.click('[data-test-id=next]')
await t
.expect(Selector('[name=dropzoneMessage]').innerText)
.eql(noManuscriptError)
})
test('Uploading the wrong type of file for manuscript will show error', async t => {
await t.navigateTo(dashboard.url)
await t.ctx.localStorageSet(t.ctx.token)
await t.navigateTo(dashboard.url).click('[data-test-id=submit]')
await t.click('[data-test-id=next]')
await t
.setFilesToUpload(
'[data-test-id=upload]>input',
'./fixtures/dummy-manuscript.pdf',
)
.then(arg => {
throw new Error('No error was thrown when uploading bad manuscript type')
})
.catch(e => {
if (e.errMsg !== badManuscriptError) throw e
})
})
test('Uploading a correct file for manuscript shows success', async t => {
await t.navigateTo(dashboard.url)
await t.ctx.localStorageSet(t.ctx.token)
await t.navigateTo(dashboard.url).click('[data-test-id=submit]')
await t.click('[data-test-id=next]')
await t
.setFilesToUpload(
'[data-test-id=upload]>input',
'./fixtures/dummy-manuscript.docx',
)
.wait(1000)
.expect(Selector('[name=dropzoneMessage]').innerText)
.eql(manuscriptUploadSuccess)
})
test('Uploading a correct file for manuscript after no-manuscript error shows success', async t => {
await t.navigateTo(dashboard.url)
await t.ctx.localStorageSet(t.ctx.token)
await t.navigateTo(dashboard.url).click('[data-test-id=submit]')
await t.click('[data-test-id=next]')
await t.click('[data-test-id=next]')
await t
.setFilesToUpload(
'[data-test-id=upload]>input',
'./fixtures/dummy-manuscript.docx',
)
.wait(1000)
.expect(Selector('[name=dropzoneMessage]').innerText)
.eql(manuscriptUploadSuccess)
})
test('Uploading correct manuscript type after uploading wrong manuscript type shows success', async t => {
await t.navigateTo(dashboard.url)
await t.ctx.localStorageSet(t.ctx.token)
await t.navigateTo(dashboard.url).click('[data-test-id=submit]')
await t.click('[data-test-id=next]')
await t
.setFilesToUpload('[data-test-id=upload]>input', [
'./fixtures/dummy-manuscript.pdf',
'./fixtures/dummy-manuscript.docx',
])
.wait(1000)
.expect(Selector('[name=dropzoneMessage]').innerText)
.eql(manuscriptUploadSuccess)
})
test('Reuploading manuscript after success shows success again', async t => {
await t.navigateTo(dashboard.url)
await t.ctx.localStorageSet(t.ctx.token)
await t.navigateTo(dashboard.url).click('[data-test-id=submit]')
await t.click('[data-test-id=next]')
await t
.setFilesToUpload('[data-test-id=upload]>input', [
'./fixtures/dummy-manuscript.docx',
'./fixtures/dummy-manuscript.docx',
])
.wait(1000)
.expect(Selector('[name=dropzoneMessage]').innerText)
.eql(manuscriptUploadSuccess)
})
test.skip('Preview a manuscript works', async t => {
/* TODO */
})
......@@ -4506,6 +4506,10 @@ har-validator@~5.0.3:
ajv "^5.1.0"
har-schema "^2.0.0"
harmony-reflect@^1.4.6:
version "1.6.0"
resolved "https://registry.yarnpkg.com/harmony-reflect/-/harmony-reflect-1.6.0.tgz#9c28a77386ec225f7b5d370f9861ba09c4eea58f"
has-ansi@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91"
......@@ -4863,6 +4867,12 @@ icss-utils@^2.1.0:
dependencies:
postcss "^6.0.1"
identity-obj-proxy@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz#94d2bda96084453ef36fbc5aaec37e0f79f1fc14"
dependencies:
harmony-reflect "^1.4.6"
ieee754@^1.1.4:
version "1.1.11"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.11.tgz#c16384ffe00f5b7835824e67b6f2bd44a5229455"
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment