lion/packages/ui/components/input-file/test/lion-selected-file-list.test.js
Danny Moerkerke 8b9c8050e5
Fix/upload response (#2000)
* feat(input-file): create input-file component

* chore: improvements after review

* chore: update after review

* chore: update translations

* chore: - fixed demo with form submit, submit was not prevented
       - fixed checking allowed file extensions
       - fixed clicking on select file button in drag and drop area

* chore: since the input-file does not upload files itself but enables user to select files, I replaced "upload" and "upload" with "select" and "selected" where applicable

* chore: - removed unused properties allowedFileTypes and allowedFileExtensions from lion-input-file
       - cleaned up docs

* chore: - changed type Array.<type> to Array<type>
       - removed redundant type definition

* fix: - FocusMixin: moved registering events for from connectedCallback to firstUpdated since _focusableNode is sometimes not available yet
     - SlotMixin: changed updated to update in since slots were rendered too late (related to previous fix in FocusMixin.js)

* fix: renamed lion-uploaded-file-list.js to lion-selected-file-list.js

* fix: fixed test for lion-selected-file-list

* fix: fixed typ

* wip

* fix: - fixed issue with multiple file selection where element would not select valid files after invalid ones
     - added getMessage method to FileValidation that returns empty string to prevent message being shown that error message must be configured
     - fixed tests

* chore: replaced `uploadOnFormSubmit` with `uploadOnSelect` and flipped the default value to false. When `uploadOnSelect` is set to true, the file will be uploaded as soon as it is selected.

* fix: - replaced `uploadOnFormSubmit` with `uploadOnSelect` and flipped the default value to false. When `uploadOnSelect` is set to true, the file will be uploaded as soon as it is selected.
     - fixed issue where a valid file was not selected and added to the file list if it was preceded by an invalid file

* chore: removed redundant README.md

* fix: fixed failing test

* chore: added missing type annotation

* chore: annotated event param as optional

* chore: corrected property names in demo and removed demo of prefilled state

* chore: renamed `_fileSelectResponse` back to `uploadResponse` and restored demo of prefilled state

---------

Co-authored-by: gvangeest <gerjan.van.geest@ing.com>
Co-authored-by: Thijs Louisse <Thijs.Louisse@ing.com>
2023-06-07 12:57:34 +02:00

207 lines
6.5 KiB
JavaScript

import '@lion/ui/define/lion-selected-file-list.js';
import { expect, fixture as _fixture, html, oneEvent } from '@open-wc/testing';
import sinon from 'sinon';
/**
* @typedef {import('../src/LionSelectedFileList.js').LionSelectedFileList} LionSelectedFileList
* @typedef {import('../types/input-file.js').InputFile} InputFile
* @typedef {import('lit').TemplateResult} TemplateResult
*/
const fixture = /** @type {(arg: TemplateResult|string) => Promise<LionSelectedFileList>} */ (
_fixture
);
describe('lion-selected-file-list', () => {
/**
* @type {Partial<InputFile>}
*/
const fileSuccess = {
name: 'foo.txt',
status: 'SUCCESS',
downloadUrl: '#foo',
systemFile: {
name: 'foo.txt',
type: 'text/plain',
size: 1000,
status: 'SUCCESS',
},
response: {
name: 'foo',
type: 'text/plain',
size: 1000,
status: 'SUCCESS',
},
};
/**
* @type {Partial<InputFile>}
*/
const fileLoading = {
status: 'LOADING',
systemFile: {
name: 'bar.txt',
type: 'text/plain',
size: 100,
status: 'LOADING',
},
};
/**
* @type {Partial<InputFile>}
*/
const fileFail = {
systemFile: {
name: 'foobar.txt',
type: 'text/plain',
size: 1000,
status: 'FAIL',
},
status: 'FAIL',
validationFeedback: [{ message: 'foobar', type: 'error' }],
};
it('when empty show nothing', async () => {
const el = await fixture(html`<lion-selected-file-list></lion-selected-file-list>`);
expect(el.children.length).to.equal(0);
});
it('can have 1 item', async () => {
const el = await fixture(html`
<lion-selected-file-list .fileList="${[fileSuccess]}"></lion-selected-file-list>
`);
const fileItems = el.shadowRoot?.querySelectorAll('.selected__list__item');
expect(fileItems?.length).to.equal(1);
});
it('shows a list when multiple', async () => {
const el = await fixture(html`
<lion-selected-file-list
.fileList="${[fileSuccess, fileLoading]}"
.multiple="${true}"
></lion-selected-file-list>
`);
const fileItems = el.shadowRoot?.querySelectorAll('.selected__list__item');
expect(fileItems?.length).to.equal(2);
const fileList = el.shadowRoot?.querySelector('.selected__list');
expect(fileList?.tagName).to.equal('UL');
});
it('displays an anchor when status="SUCCESS" and a downloadUrl is available', async () => {
const el = await fixture(html`
<lion-selected-file-list
.fileList="${[fileSuccess, fileLoading, fileFail]}"
.multiple="${true}"
></lion-selected-file-list>
`);
const fileItems = el.shadowRoot?.querySelectorAll('.selected__list__item');
// @ts-ignore
expect(fileItems[0].querySelector('.selected__list__item__label__link')).to.exist;
// @ts-ignore
expect(fileItems[1].querySelector('.selected__list__item__label__link')).to.not.exist;
// @ts-ignore
expect(fileItems[2].querySelector('.selected__list__item__label__link')).to.not.exist;
});
it('displays a feedback message when status="FAIL"', async () => {
const el = await fixture(html`
<lion-selected-file-list .fileList="${[fileFail]}"></lion-selected-file-list>
`);
const fileItems = el.shadowRoot?.querySelectorAll('.selected__list__item');
const feedback = fileItems ? fileItems[0].querySelector('lion-validation-feedback') : undefined;
// @ts-ignore
expect(feedback).to.exist;
expect(feedback).shadowDom.to.equal('foobar');
});
it('should call removeFile method on click of remove button', async () => {
const el = await fixture(html`
<lion-selected-file-list .fileList="${[fileSuccess, fileLoading]}"></lion-selected-file-list>
`);
/**
* @type {HTMLButtonElement | null | undefined}
*/
const removeButton = el.shadowRoot?.querySelector('.selected__list__item__remove-button');
const removeFileSpy = sinon.spy(el, /** @type {keyof LionSelectedFileList} */ ('_removeFile'));
removeButton?.click();
expect(removeFileSpy).have.been.calledOnce;
removeFileSpy.restore();
});
it('should fire file-remove-requested event with removed file data', async () => {
const el = await fixture(html`
<lion-selected-file-list .fileList="${[fileSuccess, fileLoading]}"></lion-selected-file-list>
`);
/**
* @type {Partial<InputFile>}
*/
const removedFile = {
name: el.fileList[0].name,
status: el.fileList[0].status,
systemFile: {
name: el.fileList[0].name,
},
response: {
name: el.fileList[0].name,
status: el.fileList[0].status,
},
};
setTimeout(() => {
// @ts-ignore ignore file typing
el._removeFile(removedFile);
});
const removeFileEvent = await oneEvent(el, 'file-remove-requested');
expect(removeFileEvent).to.exist;
expect(removeFileEvent.detail.removedFile).to.deep.equal({
name: 'foo.txt',
status: 'SUCCESS',
systemFile: {
name: 'foo.txt',
},
response: {
name: 'foo.txt',
status: 'SUCCESS',
},
});
expect(removeFileEvent.detail.status).to.deep.equal('SUCCESS');
expect(removeFileEvent.detail.uploadResponse).to.deep.equal({
name: 'foo.txt',
status: 'SUCCESS',
});
});
describe('Accessibility', async () => {
it('is accessible', async () => {
const el = await fixture(
html`<lion-selected-file-list
.fileList="${[fileSuccess, fileLoading, fileFail]}"
></lion-selected-file-list>`,
);
await expect(el).to.be.accessible();
});
it(`adds aria-live="polite" to the feedback slot on focus, aria-live="assertive" to the feedback slot on blur,
so error messages appearing on blur will be read before those of the next input`, async () => {
const el = await fixture(html`
<lion-selected-file-list .fileList="${[fileFail]}"></lion-selected-file-list>
`);
const fileFeedback = el?.shadowRoot?.querySelectorAll('[id^="file-feedback"]')[0];
const removeButton = /** @type {HTMLButtonElement} */ (
el.shadowRoot?.querySelector('.selected__list__item__remove-button')
);
// @ts-ignore
expect(fileFeedback.getAttribute('aria-live')).to.equal('assertive');
removeButton?.focus();
// @ts-ignore
expect(fileFeedback.getAttribute('aria-live')).to.equal('polite');
removeButton?.blur();
// @ts-ignore
expect(fileFeedback.getAttribute('aria-live')).to.equal('assertive');
});
});
});