Added spinner/upload message when uploading an image to a post's content (#8986)

This commit is contained in:
Nick Taylor 2020-06-30 13:52:47 -04:00 committed by GitHub
parent 62f89f9c7c
commit efc3a124a0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 103 additions and 48 deletions

View file

@ -2,7 +2,7 @@ import { h, Component } from 'preact';
import { generateMainImage } from '../actions';
import { validateFileInputs } from '../../packs/validateFileInputs';
import { ClipboardButton } from './ClipboardButton';
import { Button } from '@crayons';
import { Button, Spinner } from '@crayons';
function isNativeAndroid() {
return (
@ -41,6 +41,7 @@ export class ImageUploader extends Component {
uploadError: false,
uploadErrorMessage: null,
showImageCopiedMessage: false,
uploadingImage: false,
};
onUploadError = (error) => {
@ -48,6 +49,7 @@ export class ImageUploader extends Component {
insertionImageUrls: [],
uploadError: true,
uploadErrorMessage: error.message,
uploadingImage: false,
});
};
@ -76,13 +78,16 @@ export class ImageUploader extends Component {
handleInsertionImageUpload = (e) => {
const { files } = e.target;
this.clearUploadError();
const validFileInputs = validateFileInputs();
if (validFileInputs && files.length > 0) {
if (files.length > 0 && validateFileInputs()) {
const payload = { image: files };
this.setState({ showImageCopiedMessage: false });
this.setState({
uploadError: false,
uploadErrorMessage: null,
uploadingImage: true,
insertionImageUrls: [],
showImageCopiedMessage: false,
});
generateMainImage(
payload,
@ -94,6 +99,7 @@ export class ImageUploader extends Component {
handleInsertImageUploadSuccess = (response) => {
this.setState({
uploadingImage: false,
insertionImageUrls: response.links,
});
};
@ -107,43 +113,43 @@ export class ImageUploader extends Component {
this.setState({ showImageCopiedMessage: true });
}
clearUploadError() {
this.setState({
uploadError: false,
uploadErrorMessage: null,
});
}
render() {
const {
insertionImageUrls,
uploadError,
uploadErrorMessage,
showImageCopiedMessage,
uploadingImage,
} = this.state;
return (
<div className="flex items-center">
<Button
className="mr-2 fw-normal"
variant="ghost"
contentType="icon-left"
icon={ImageIcon}
tabIndex="-1"
>
Upload image
<input
type="file"
id="image-upload-field"
onChange={this.handleInsertionImageUpload}
className="w-100 h-100 absolute left-0 right-0 top-0 bottom-0 overflow-hidden opacity-0 cursor-pointer"
multiple
accept="image/*"
data-max-file-size-mb="25"
{uploadingImage ? (
<span class="lh-base pl-3 border-0 py-2 inline-block">
<Spinner /> Uploading...
</span>
) : (
<Button
className="mr-2 fw-normal"
variant="ghost"
contentType="icon-left"
icon={ImageIcon}
tabIndex="-1"
aria-label="Upload an image"
/>
</Button>
>
Upload image
<input
type="file"
id="image-upload-field"
onChange={this.handleInsertionImageUpload}
className="w-100 h-100 absolute left-0 right-0 top-0 bottom-0 overflow-hidden opacity-0 cursor-pointer"
multiple
accept="image/*"
data-max-file-size-mb="25"
tabIndex="-1"
aria-label="Upload an image"
/>
</Button>
)}
{insertionImageUrls.length > 0 && (
<ClipboardButton

View file

@ -1,5 +1,10 @@
import { h } from 'preact';
import { render, fireEvent, waitForElement } from '@testing-library/preact';
import {
render,
fireEvent,
waitForElement,
waitForElementToBeRemoved,
} from '@testing-library/preact';
import { axe } from 'jest-axe';
import fetch from 'jest-fetch-mock';
import { ImageUploader } from '../ImageUploader';
@ -8,14 +13,6 @@ import '@testing-library/jest-dom';
global.fetch = fetch;
describe('<ImageUploader />', () => {
const fakeLinksResponse = JSON.stringify({
links: ['/i/fake-link.jpg'],
});
const fakeErrorMessage = {
message: 'Some Fake Error',
};
it('should have no a11y violations', async () => {
const { container } = render(<ImageUploader />);
const results = await axe(container);
@ -29,30 +26,74 @@ describe('<ImageUploader />', () => {
expect(uploadInput.getAttribute('type')).toEqual('file');
});
it('displays text to copy after upload', async () => {
const { getByTitle, getByDisplayValue, getByLabelText } = render(
<ImageUploader />,
it('displays the upload spinner during upload', async () => {
fetch.mockResponse(
JSON.stringify({
links: ['/i/fake-link.jpg'],
}),
);
const { getByLabelText, queryByText } = render(<ImageUploader />);
const inputEl = getByLabelText(/Upload an image/i);
const file = new File(['(⌐□_□)'], 'chucknorris.png', {
type: 'image/png',
});
fireEvent.change(inputEl, { target: { files: [file] } });
const uploadingImage = await waitForElement(() =>
queryByText(/uploading.../i),
);
expect(uploadingImage).toBeDefined();
});
it('displays text to copy after upload', async () => {
fetch.mockResponse(
JSON.stringify({
links: ['/i/fake-link.jpg'],
}),
);
const {
getByTitle,
getByDisplayValue,
getByLabelText,
queryByText,
} = render(<ImageUploader />);
const inputEl = getByLabelText(/Upload an image/i);
const file = new File(['(⌐□_□)'], 'chucknorris.png', {
type: 'image/png',
});
fetch.mockResponse(fakeLinksResponse);
fireEvent.change(inputEl, { target: { files: [file] } });
let uploadingImage = await waitForElement(() =>
queryByText(/uploading.../i),
);
expect(uploadingImage).toBeDefined();
expect(inputEl.files[0]).toEqual(file);
expect(inputEl.files).toHaveLength(1);
await waitForElement(() => getByTitle(/copy markdown for image/i));
await waitForElementToBeRemoved(() => queryByText(/uploading.../i));
getByTitle(/copy markdown for image/i);
getByDisplayValue(/fake-link.jpg/i);
});
// TODO: 'Copied!' is always in the DOM, and so we cannot test that the visual implications of the copy when clicking on the copy icon
it('displays an error when one occurs', async () => {
const { getByText, getByLabelText } = render(<ImageUploader />);
fetch.mockReject({
message: 'Some Fake Error',
});
const { getByText, getByLabelText, queryByText } = render(
<ImageUploader />,
);
const inputEl = getByLabelText(/Upload an image/i);
// Check the input validation settings
@ -63,13 +104,19 @@ describe('<ImageUploader />', () => {
type: 'image/png',
});
fetch.mockReject(fakeErrorMessage);
fireEvent.change(inputEl, {
target: {
files: [file],
},
});
let uploadingImage = await waitForElement(() =>
queryByText(/uploading.../i),
);
expect(uploadingImage).toBeDefined();
await waitForElementToBeRemoved(() => queryByText(/uploading.../i));
await waitForElement(() => getByText(/some fake error/i));
});
});

View file

@ -0,0 +1 @@
export * from './Spinner';

View file

@ -3,3 +3,4 @@ export * from '@crayons/ButtonGroup';
export * from '@crayons/Dropdown';
export * from '@crayons/formElements';
export * from '@crayons/Modal';
export * from '@crayons/Spinner';