import { $, $$, removeEl } from '../utils/dom';
import { assertNotNull, assertNotUndefined } from '../utils/assert';
import { fetchMock } from '../../test/fetch-mock';
import { fixEventListeners } from '../../test/fix-event-listeners';
import { fireEvent, waitFor } from '@testing-library/dom';
import { promises } from 'fs';
import { join } from 'path';
import { setupImageUpload } from '../upload';
/* eslint-disable camelcase */
const scrapeResponse = {
description: 'test',
images: [
{ url: 'http://localhost/images/1', camo_url: 'http://localhost/images/1' },
{ url: 'http://localhost/images/2', camo_url: 'http://localhost/images/2' },
],
source_url: 'http://localhost/images',
author_name: 'test',
};
const nullResponse = null;
const errorResponse = {
errors: ['Error 1', 'Error 2'],
};
/* eslint-enable camelcase */
const tagSets = ['', 'a tag', 'safe', 'one, two, three', 'safe, explicit', 'safe, explicit, three', 'safe, two, three'];
const tagErrorCounts = [1, 2, 1, 1, 2, 1, 0];
describe('Image upload form', () => {
let mockPng: File;
let mockWebm: File;
beforeAll(async () => {
const mockPngPath = join(__dirname, 'upload-test.png');
const mockWebmPath = join(__dirname, 'upload-test.webm');
mockPng = new File([(await promises.readFile(mockPngPath, { encoding: null })).buffer], 'upload-test.png', {
type: 'image/png',
});
mockWebm = new File([(await promises.readFile(mockWebmPath, { encoding: null })).buffer], 'upload-test.webm', {
type: 'video/webm',
});
});
beforeAll(() => {
fetchMock.enableMocks();
});
afterAll(() => {
fetchMock.disableMocks();
});
fixEventListeners(window);
let form: HTMLFormElement;
let imgPreviews: HTMLDivElement;
let fileField: HTMLInputElement;
let remoteUrl: HTMLInputElement;
let scraperError: HTMLDivElement;
let fetchButton: HTMLButtonElement;
let tagsEl: HTMLTextAreaElement;
let taginputEl: HTMLDivElement;
let sourceEl: HTMLInputElement;
let descrEl: HTMLTextAreaElement;
let submitButton: HTMLButtonElement;
const assertFetchButtonIsDisabled = () => {
if (!fetchButton.hasAttribute('disabled')) throw new Error('fetchButton is not disabled');
};
const assertSubmitButtonIsDisabled = () => {
if (!submitButton.hasAttribute('disabled')) throw new Error('submitButton is not disabled');
};
const assertSubmitButtonIsEnabled = () => {
if (submitButton.hasAttribute('disabled')) throw new Error('submitButton is disabled');
};
beforeEach(() => {
document.documentElement.insertAdjacentHTML(
'beforeend',
`
`,
);
form = assertNotNull($('form'));
imgPreviews = assertNotNull($('#js-image-upload-previews'));
fileField = assertNotUndefined($$('.js-scraper')[0]);
remoteUrl = assertNotUndefined($$('.js-scraper')[1]);
scraperError = assertNotUndefined($$('.js-scraper')[2]);
tagsEl = assertNotNull($('.js-image-tags-input'));
taginputEl = assertNotNull($('.js-taginput'));
sourceEl = assertNotNull($('.js-source-url'));
descrEl = assertNotNull($('.js-image-descr-input'));
fetchButton = assertNotNull($('#js-scraper-preview'));
submitButton = assertNotNull($('.actions > .button'));
setupImageUpload();
fetchMock.resetMocks();
});
afterEach(() => {
removeEl(form);
});
it('should disable fetch button on empty source', () => {
fireEvent.input(remoteUrl, { target: { value: '' } });
expect(fetchButton.disabled).toBe(true);
});
it('should enable fetch button on non-empty source', () => {
fireEvent.input(remoteUrl, { target: { value: 'http://localhost/images/1' } });
expect(fetchButton.disabled).toBe(false);
});
it('should create a preview element when an image file is uploaded', () => {
fireEvent.change(fileField, { target: { files: [mockPng] } });
return waitFor(() => {
assertFetchButtonIsDisabled();
expect(imgPreviews.querySelectorAll('img')).toHaveLength(1);
});
});
it('should create a preview element when a Matroska video file is uploaded', () => {
fireEvent.change(fileField, { target: { files: [mockWebm] } });
return waitFor(() => {
assertFetchButtonIsDisabled();
expect(imgPreviews.querySelectorAll('video')).toHaveLength(1);
});
});
it('should block navigation away after an image file is attached, but not after form submission', async () => {
fireEvent.change(fileField, { target: { files: [mockPng] } });
await waitFor(() => {
assertFetchButtonIsDisabled();
expect(imgPreviews.querySelectorAll('img')).toHaveLength(1);
});
const failedUnloadEvent = new Event('beforeunload', { cancelable: true });
expect(fireEvent(window, failedUnloadEvent)).toBe(false);
await new Promise(resolve => {
form.addEventListener('submit', event => {
event.preventDefault();
resolve();
});
fireEvent.submit(form);
});
const succeededUnloadEvent = new Event('beforeunload', { cancelable: true });
expect(fireEvent(window, succeededUnloadEvent)).toBe(true);
});
it('should scrape images when the fetch button is clicked', async () => {
fetchMock.mockResolvedValue(new Response(JSON.stringify(scrapeResponse), { status: 200 }));
fireEvent.input(remoteUrl, { target: { value: 'http://localhost/images/1' } });
await new Promise(resolve => {
tagsEl.addEventListener('addtag', (event: Event) => {
expect((event as CustomEvent).detail).toEqual({ name: 'artist:test' });
resolve();
});
fireEvent.keyDown(remoteUrl, { keyCode: 13 });
});
await waitFor(() => expect(fetch).toHaveBeenCalledTimes(1));
await waitFor(() => expect(imgPreviews.querySelectorAll('img')).toHaveLength(2));
expect(scraperError.innerHTML).toEqual('');
expect(sourceEl.value).toEqual('http://localhost/images');
expect(descrEl.value).toEqual('test');
});
it('should show null scrape result', () => {
fetchMock.mockResolvedValue(new Response(JSON.stringify(nullResponse), { status: 200 }));
fireEvent.input(remoteUrl, { target: { value: 'http://localhost/images/1' } });
fireEvent.click(fetchButton);
return waitFor(() => {
expect(fetch).toHaveBeenCalledTimes(1);
expect(imgPreviews.querySelectorAll('img')).toHaveLength(0);
expect(scraperError.innerText).toEqual('No image found at that address.');
});
});
it('should show error scrape result', () => {
fetchMock.mockResolvedValue(new Response(JSON.stringify(errorResponse), { status: 200 }));
fireEvent.input(remoteUrl, { target: { value: 'http://localhost/images/1' } });
fireEvent.click(fetchButton);
return waitFor(() => {
expect(fetch).toHaveBeenCalledTimes(1);
expect(imgPreviews.querySelectorAll('img')).toHaveLength(0);
expect(scraperError.innerText).toEqual('Error 1 Error 2');
});
});
async function submitForm(frm: HTMLFormElement): Promise {
return new Promise(resolve => {
function onSubmit() {
frm.removeEventListener('submit', onSubmit);
resolve(true);
}
frm.addEventListener('submit', onSubmit);
if (!fireEvent.submit(frm)) {
frm.removeEventListener('submit', onSubmit);
resolve(false);
}
});
}
it('should prevent form submission if tag checks fail', async () => {
for (let i = 0; i < tagSets.length; i += 1) {
taginputEl.innerText = tagSets[i];
if (await submitForm(form)) {
// form submit succeeded
await waitFor(() => {
assertSubmitButtonIsDisabled();
const succeededUnloadEvent = new Event('beforeunload', { cancelable: true });
expect(fireEvent(window, succeededUnloadEvent)).toBe(true);
});
} else {
// form submit prevented
const frm = form;
await waitFor(() => {
assertSubmitButtonIsEnabled();
expect(frm.querySelectorAll('.help-block')).toHaveLength(tagErrorCounts[i]);
});
}
}
});
});