import '@lion/ui/define/lion-input-file.js'; import { Required } from '@lion/ui/form-core.js'; import { getInputMembers } from '@lion/ui/input-test-helpers.js'; import { getLocalizeManager } from '@lion/ui/localize-no-side-effects.js'; import { localizeTearDown } from '@lion/ui/localize-test-helpers.js'; import { expect, fixture as _fixture, html, oneEvent, elementUpdated } from '@open-wc/testing'; import sinon from 'sinon'; /** * @typedef {import('../src/LionInputFile.js').LionInputFile} LionInputFile * @typedef {import('../types/input-file.js').InputFile} InputFile * @typedef {import('../types/input-file.js').SystemFile} SystemFile * @typedef {import('lit').TemplateResult} TemplateResult */ const fixture = /** @type {(arg: TemplateResult|string) => Promise} */ (_fixture); const filesListChanged = (/** @type {LionInputFile} */ el, /** @type { CustomEvent } */ ev) => { // eslint-disable-next-line no-param-reassign el.uploadResponse = [...ev.detail.newFiles]; }; function mimicSelectFile( /** @type {LionInputFile} */ formControl, /** @type {InputFile[]} */ mockFiles, ) { // @ts-expect-error [allow-protected-in-tests] formControl._processFiles(mockFiles); // TODO: doesn't this set latest file.name only to formControl._inputNode.value? mockFiles.forEach(file => { // @ts-expect-error [allow-protected-in-tests] Object.defineProperty(formControl._inputNode, 'value', { value: `C:\\fakepath\\${file.name}`, writable: true, }); }); // @ts-expect-error [allow-protected-in-tests] Object.defineProperty(formControl._inputNode, 'files', { value: mockFiles, writable: true }); // @ts-expect-error [allow-protected-in-tests] formControl._inputNode.dispatchEvent(new Event('input', { bubbles: true })); } const file = /** @type {InputFile} */ ( new File(['foo'], 'foo.txt', { type: 'text/plain', }) ); const file2 = /** @type {InputFile} */ ( new File(['bar'], 'bar.txt', { type: 'text/plain', }) ); const file3 = /** @type {InputFile} */ ( new File(['foo3'], 'foo3.txt', { type: 'text/plain', }) ); const file4 = /** @type {InputFile} */ ( new File(['foo4'], 'foo4.txt', { type: 'text/plain', }) ); describe('lion-input-file', () => { const localizeManager = getLocalizeManager(); afterEach(localizeTearDown); it('has a type of "file"', async () => { const el = await fixture(html``); // @ts-expect-error [allow-protected-in-tests] const { _inputNode } = getInputMembers(el); expect(_inputNode.type).to.equal('file'); }); it('should add single file and dispatch "file-list-changed" event with newly added file', async () => { const el = await fixture(html``); const fileListChangedEventPromise = oneEvent(el, 'file-list-changed'); mimicSelectFile(el, [file]); const fileListChangedEvent = await fileListChangedEventPromise; // @ts-expect-error [allow-protected-in-tests] expect(el._selectedFilesMetaData.length).to.equal(1); expect(el.uploadResponse.length).to.equal(1); expect(fileListChangedEvent).to.exist; expect(fileListChangedEvent.detail.newFiles.length).to.equal(1); expect(fileListChangedEvent.detail.newFiles[0].name).to.equal('foo.txt'); }); it('should select 1 file', async () => { const el = await fixture(html``); // @ts-expect-error [allow-protected-in-test] const processedFilesSpy = sinon.spy(el, '_processFiles'); // @ts-expect-error [allow-protected-in-test] await el._onChange({ target: { files: [file] } }); expect(processedFilesSpy).have.been.calledOnce; processedFilesSpy.restore(); }); it('should retain selected file when "Cancel" button is clicked in system file explorer dialog', async () => { const el = await fixture(html``); mimicSelectFile(el, [file]); await el.updateComplete; // @ts-expect-error [allow-protected-in-test] expect(el._selectedFilesMetaData.length).to.equal(1); expect(el.uploadResponse.length).to.equal(1); // when cancel is clicked, native input value is blank which means modelValue is blank el.modelValue = []; await el.updateComplete; // @ts-expect-error [allow-protected-in-test] expect(el._selectedFilesMetaData.length).to.equal(1); }); it('has an attribute focused when focused', async () => { const el = await fixture(html` `); // @ts-expect-error [allow-protected-in-test] el._buttonNode.focus(); await el.updateComplete; expect(el.hasAttribute('focused')).to.be.true; // @ts-expect-error [allow-protected-in-test] el._buttonNode.blur(); await el.updateComplete; expect(el.hasAttribute('focused')).to.be.false; }); it('should set touched property true on change', async () => { const el = await fixture(html` `); expect(el.touched).to.be.false; // @ts-expect-error [allow-protected-in-test] await el._onChange({ target: { files: [file] } }); expect(el.touched).to.be.true; }); it('should replace previous file when new file is selected', async () => { const el = await fixture(html``); mimicSelectFile(el, [file]); await el.updateComplete; // @ts-expect-error [allow-protected-in-test] expect(el._selectedFilesMetaData.length).to.equal(1); // @ts-expect-error [allow-protected-in-test] expect(el._selectedFilesMetaData[0].systemFile.name).to.equal('foo.txt'); mimicSelectFile(el, [file2]); await el.updateComplete; // @ts-expect-error [allow-protected-in-test] expect(el._selectedFilesMetaData.length).to.equal(1); // @ts-expect-error [allow-protected-in-test] expect(el._selectedFilesMetaData[0].systemFile.name).to.equal('bar.txt'); }); describe('structure', async () => { it('can has a button label by default', async () => { const el = await fixture(html``); // @ts-expect-error [allow-protected-in-test] expect(el._buttonNode.textContent).to.equal('Select file'); }); it('can has a button label by default when multiple', async () => { const el = await fixture(html``); // @ts-expect-error [allow-protected-in-test] expect(el._buttonNode.textContent).to.equal('Select files'); }); it('will update the button label on locale change', async () => { const el = await fixture(html``); // @ts-expect-error [allow-protected-in-test] expect(el._buttonNode.textContent).to.equal('Select file'); localizeManager.locale = 'nl-NL'; await localizeManager.loadingComplete; await el.updateComplete; // @ts-expect-error [allow-protected-in-test] expect(el._buttonNode.textContent).to.equal('Selecteer bestand'); }); it('can overwrite the button label', async () => { const el = await fixture(html``); // @ts-expect-error [allow-protected-in-test] expect(el._buttonNode.textContent).to.equal('Foo'); el.buttonLabel = 'Bar'; await el.updateComplete; // @ts-expect-error [allow-protected-in-test] expect(el._buttonNode.textContent).to.equal('Bar'); }); }); describe('invalid file types', async () => { const fileWrongType = /** @type {InputFile} */ ( new File(['foobar'], 'foobar.txt', { type: 'xxxxx', }) ); it('error should not be there when the file extensions are accepted', async () => { const el = await fixture(html` `); mimicSelectFile(el, [fileWrongType]); await el.updateComplete; expect(el.hasFeedbackFor.length).to.equal(0); }); it('should not be added to the selected list', async () => { const el = await fixture(html` `); mimicSelectFile(el, [fileWrongType]); await el.updateComplete; // @ts-expect-error [allow-protected-in-test] expect(el._selectedFilesMetaData.length).to.equal(1); // @ts-expect-error [allow-protected-in-test] expect(el._selectedFilesMetaData[0].failedProp?.length).to.equal(1); // @ts-expect-error [allow-protected-in-test] expect(el._selectedFilesMetaData[0].validationFeedback).to.exist; // @ts-expect-error [allow-protected-in-test] expect(el._selectedFilesMetaData[0].status).to.equal('FAIL'); expect(el.uploadResponse[0].status).to.equal('FAIL'); }); it('error message should use main type when "/*" is used', async () => { const el = await fixture(html` `); mimicSelectFile(el, [fileWrongType]); await el.updateComplete; // @ts-expect-error [allow-protected-in-test] el._selectedFilesMetaData[0].validationFeedback?.forEach(error => { expect(error.message).to.deep.equal('Please select a(n) text file with max 500MB.'); }); }); it('error message should use main type when "text/plain" is used', async () => { const el = await fixture(html` `); mimicSelectFile(el, [fileWrongType]); await el.updateComplete; // @ts-expect-error [allow-protected-in-test] el._selectedFilesMetaData[0].validationFeedback?.forEach(error => { expect(error.message).to.deep.equal('Please select a(n) text file with max 500MB.'); }); }); it('error message should use sub type when e.g. "text/html" is used', async () => { const el = await fixture(html` `); mimicSelectFile(el, [fileWrongType]); await el.updateComplete; // @ts-expect-error [allow-protected-in-test] el._selectedFilesMetaData[0].validationFeedback?.forEach(error => { expect(error.message).to.deep.equal('Please select a(n) .html file with max 500MB.'); }); }); it('error message should use the first sub type when e.g. "image/svg+xml" is used', async () => { const el = await fixture(html` `); mimicSelectFile(el, [fileWrongType]); await el.updateComplete; // @ts-expect-error [allow-protected-in-test] el._selectedFilesMetaData[0].validationFeedback?.forEach(error => { expect(error.message).to.deep.equal('Please select a(n) .svg file with max 500MB.'); }); }); it('can reflect multiple types in the error message', async () => { const el = await fixture(html` `); mimicSelectFile(el, [fileWrongType]); await el.updateComplete; // @ts-expect-error [allow-protected-in-test] el._selectedFilesMetaData[0].validationFeedback?.forEach(error => { expect(error.message).to.deep.equal('Please select a .html or .csv file with max 500MB.'); }); }); it('can reflect multiple types in the error message also with a space " "', async () => { const el = await fixture(html` `); mimicSelectFile(el, [fileWrongType]); await el.updateComplete; // @ts-expect-error [allow-protected-in-test] el._selectedFilesMetaData[0].validationFeedback?.forEach(error => { expect(error.message).to.deep.equal('Please select a .html or .csv file with max 500MB.'); }); }); it('can reflect multiple types in the error message with preference to extensions', async () => { const el = await fixture(html` `); mimicSelectFile(el, [fileWrongType]); await el.updateComplete; // @ts-expect-error [allow-protected-in-test] el._selectedFilesMetaData[0].validationFeedback?.forEach(error => { expect(error.message).to.deep.equal('Please select a(n) .jpg file with max 500MB.'); }); }); }); describe('invalid file extensions', async () => { const fileWrongType = /** @type {InputFile} */ ( new File(['foobar'], 'foobar.txt', { type: 'xxxxx', }) ); it('should not be added to the selected list', async () => { const el = await fixture(html` `); mimicSelectFile(el, [fileWrongType]); await el.updateComplete; // @ts-expect-error [allow-protected-in-test] expect(el._selectedFilesMetaData.length).to.equal(1); // @ts-expect-error [allow-protected-in-test] expect(el._selectedFilesMetaData[0].failedProp?.length).to.equal(1); // @ts-expect-error [allow-protected-in-test] expect(el._selectedFilesMetaData[0].validationFeedback).to.exist; // @ts-expect-error [allow-protected-in-test] expect(el._selectedFilesMetaData[0].status).to.equal('FAIL'); expect(el.uploadResponse[0].status).to.equal('FAIL'); }); it('error message should add the file extension to the validator message', async () => { const el = await fixture(html` `); mimicSelectFile(el, [fileWrongType]); await el.updateComplete; // @ts-expect-error [allow-protected-in-test] el._selectedFilesMetaData[0].validationFeedback?.forEach(error => { expect(error.message).to.equal('Please select a(n) .jpg file with max 500MB.'); }); }); it('error message should add all file extensions to the validator message', async () => { const el = await fixture(html` `); mimicSelectFile(el, [fileWrongType]); await el.updateComplete; // @ts-expect-error [allow-protected-in-test] el._selectedFilesMetaData[0].validationFeedback?.forEach(error => { expect(error.message).to.equal('Please select a .jpg, .png or .pdf file with max 500MB.'); }); }); it('error message should add all file extensions to the validator message also works without spaces " "', async () => { const el = await fixture(html` `); mimicSelectFile(el, [fileWrongType]); await el.updateComplete; // @ts-expect-error [allow-protected-in-test] el._selectedFilesMetaData[0].validationFeedback?.forEach(error => { expect(error.message).to.equal('Please select a .jpg, .png or .pdf file with max 500MB.'); }); }); }); describe('invalid file sizes', async () => { // Size of this file is 4 bytes const fileWrongSize = /** @type {InputFile} */ (new File(['foobar'], 'foobar.txt')); it('should not be added to the selected list', async () => { const el = await fixture(html` `); mimicSelectFile(el, [fileWrongSize]); await el.updateComplete; // @ts-expect-error [allow-protected-in-test] expect(el._selectedFilesMetaData.length).to.equal(1); // @ts-expect-error [allow-protected-in-test] expect(el._selectedFilesMetaData[0].failedProp?.length).to.equal(1); // @ts-expect-error [allow-protected-in-test] expect(el._selectedFilesMetaData[0].validationFeedback).to.exist; // @ts-expect-error [allow-protected-in-test] expect(el._selectedFilesMetaData[0].status).to.equal('FAIL'); expect(el.uploadResponse[0].status).to.equal('FAIL'); }); it('error message should show only the max file size if no type/extension restrictions are defined', async () => { const el = await fixture(html` `); mimicSelectFile(el, [fileWrongSize]); await el.updateComplete; // @ts-expect-error [allow-protected-in-test] el._selectedFilesMetaData[0].validationFeedback?.forEach(error => { expect(error.message).to.equal('Please select a file with max 2 bytes.'); }); }); it('error message should show the correct max file size if type/extension restrictions are defined', async () => { const el = await fixture(html` `); mimicSelectFile(el, [fileWrongSize]); await el.updateComplete; // @ts-expect-error [allow-protected-in-test] el._selectedFilesMetaData[0].validationFeedback?.forEach(error => { expect(error.message).to.equal('Please select a .jpg, .png or .pdf file with max 2 bytes.'); }); }); }); it('should send "file-list-changed" event if selecting files succeeded partially', async () => { const el = await fixture(html``); // Size of this file is 4 bytes const fileWrongSize = /** @type {InputFile} */ (new File(['foobar'], 'foobar.txt')); setTimeout(() => { mimicSelectFile(el, [fileWrongSize, file2]); }); const fileListChangedEvent = await oneEvent(el, 'file-list-changed'); // @ts-expect-error [allow-protected-in-test] expect(el._selectedFilesMetaData.length).to.equal(2); // @ts-expect-error [allow-protected-in-test] expect(el._selectedFilesMetaData[0].status).to.equal('FAIL'); expect(el.uploadResponse[0].status).to.equal('FAIL'); expect(fileListChangedEvent.detail.newFiles.length).to.equal(1); }); it('should update downloadurl for successful files', async () => { const el = await fixture(html``); setTimeout(() => { mimicSelectFile(el, [file]); }); const fileListChangedEvent = await oneEvent(el, 'file-list-changed'); filesListChanged(el, fileListChangedEvent); // @ts-expect-error [allow-protected-in-test] expect(el._selectedFilesMetaData[0].downloadUrl).to.exist; }); describe('format', () => { it('modelValue is an array of files', async () => { const el = await fixture(html` `); mimicSelectFile(el, [file]); await el.updateComplete; expect(el.modelValue).to.deep.equal([file]); }); it('view value is a string', async () => { const el = await fixture(html` `); mimicSelectFile(el, [file]); await el.updateComplete; expect(el.value).to.equal('C:\\fakepath\\foo.txt'); // @ts-expect-error [allow-protected-in-tests] expect(el._inputNode.value).to.equal('C:\\fakepath\\foo.txt'); }); it('formattedValue is a string', async () => { const el = await fixture(html` `); mimicSelectFile(el, [file]); await el.updateComplete; expect(el.formattedValue).to.equal('C:\\fakepath\\foo.txt'); }); it('serializedValue is an array of files', async () => { const el = await fixture(html` `); mimicSelectFile(el, [file]); await el.updateComplete; expect(el.serializedValue).to.deep.equal([file]); }); it('fires `model-value-changed` for every programmatic modelValue change', async () => { const el = await fixture(html` `); let counter = 0; let isTriggeredByUser = false; el.addEventListener('model-value-changed', event => { counter += 1; isTriggeredByUser = /** @type {CustomEvent} */ (event).detail.isTriggeredByUser; }); el.modelValue = [file]; expect(counter).to.equal(1); expect(isTriggeredByUser).to.be.false; // TODO: should no change mean no event? // el.modelValue = [file]; // expect(counter).to.equal(1); el.modelValue = [file, file2]; expect(counter).to.equal(2); }); it('fires `model-value-changed` for every user input, adding `isTriggeredByUser` in event detail', async () => { const el = await fixture(html` `); let counter = 0; let isTriggeredByUser = false; el.addEventListener('model-value-changed', event => { counter += 1; isTriggeredByUser = /** @type {CustomEvent} */ (event).detail.isTriggeredByUser; }); mimicSelectFile(el, [file]); await el.updateComplete; expect(counter).to.equal(1); expect(isTriggeredByUser).to.be.true; mimicSelectFile(el, [file, file2]); await el.updateComplete; expect(counter).to.equal(2); }); }); describe('multiple file select', () => { it('should add multiple files', async () => { const el = await fixture(html` `); mimicSelectFile(el, [file, file2]); await el.updateComplete; // @ts-expect-error [allow-protected-in-test] expect(el._selectedFilesMetaData.length).to.equal(2); // @ts-expect-error [allow-protected-in-test] expect(el._selectedFilesMetaData[0].systemFile.name).to.equal('foo.txt'); // @ts-expect-error [allow-protected-in-test] expect(el._selectedFilesMetaData[1].systemFile.name).to.equal('bar.txt'); expect(el.uploadResponse.length).to.equal(2); expect(el.uploadResponse[0].name).to.equal('foo.txt'); expect(el.uploadResponse[1].name).to.equal('bar.txt'); }); it('should add new files and retain previous files', async () => { const el = await fixture(html` `); setTimeout(() => { mimicSelectFile(el, [file, file2]); }); const fileListChangedEvent = await oneEvent(el, 'file-list-changed'); filesListChanged(el, fileListChangedEvent); await elementUpdated(el); // @ts-expect-error [allow-protected-in-test] expect(el._selectedFilesMetaData.length).to.equal(2); expect(el.modelValue.length).to.equal(2); setTimeout(() => { mimicSelectFile(el, [file3, file4]); }); const fileListChangedEvent1 = await oneEvent(el, 'file-list-changed'); filesListChanged(el, fileListChangedEvent1); await elementUpdated(el); // @ts-expect-error [allow-protected-in-test] expect(el._selectedFilesMetaData.length).to.equal(4); expect(el.modelValue.length).to.equal(4); }); it('should add multiple files and dispatch file-list-changed event ONLY with newly added file', async () => { const el = await fixture(html` `); setTimeout(() => { mimicSelectFile(el, [file, file2]); }); const fileListChangedEvent = await oneEvent(el, 'file-list-changed'); filesListChanged(el, fileListChangedEvent); setTimeout(() => { mimicSelectFile(el, [file3, file4]); }); const fileListChangedEvent1 = await oneEvent(el, 'file-list-changed'); filesListChanged(el, fileListChangedEvent1); expect(fileListChangedEvent1).to.exist; // @ts-expect-error [allow-protected-in-test] expect(el._selectedFilesMetaData.length).to.equal(4); expect(fileListChangedEvent1.detail.newFiles.length).to.equal(2); expect(fileListChangedEvent1.detail.newFiles[0].name).to.equal('foo3.txt'); expect(fileListChangedEvent1.detail.newFiles[1].name).to.equal('foo4.txt'); }); it('should not allow duplicate files to be selected and show notification message', async () => { const el = await fixture(html` `); setTimeout(() => { mimicSelectFile(el, [file]); }); // create condition to also show the feedback el.prefilled = true; const fileListChangedEvent = await oneEvent(el, 'file-list-changed'); // @ts-expect-error [allow-protected-in-test] expect(el._selectedFilesMetaData.length).to.equal(1); // @ts-expect-error [allow-protected-in-test] expect(el._selectedFilesMetaData[0].systemFile.name).to.equal('foo.txt'); expect(fileListChangedEvent).to.exist; expect(fileListChangedEvent.detail.newFiles.length).to.equal(1); expect(fileListChangedEvent.detail.newFiles[0].name).to.equal('foo.txt'); const fileDuplicate = /** @type {InputFile} */ ( new File(['foo'], 'foo.txt', { type: 'text/plain', }) ); mimicSelectFile(el, [fileDuplicate]); await el.updateComplete; // @ts-expect-error [allow-protected-in-test] expect(el._selectedFilesMetaData.length).to.equal(1); expect(el.hasFeedbackFor).to.deep.equals(['info'], 'hasFeedbackFor'); expect(el.showsFeedbackFor).to.deep.equals(['info'], 'showsFeedbackFor'); }); it('should add valid files and skip invalid ones', async () => { const fileWrongSize = /** @type {InputFile} */ (new File(['foobar'], 'foobar.txt')); const fileWrongType = /** @type {InputFile} */ ( new File(['foobar'], 'foobar.txt', { type: 'xxxxx', }) ); const el = await fixture(html` `); setTimeout(() => { mimicSelectFile(el, [file, fileWrongSize, file2, fileWrongType]); }); const fileListChangedEvent = await oneEvent(el, 'file-list-changed'); // @ts-expect-error [allow-protected-in-test] expect(el._selectedFilesMetaData.length).to.equal(4); expect(fileListChangedEvent).to.exist; expect(fileListChangedEvent.detail.newFiles.length).to.equal(2); expect(fileListChangedEvent.detail.newFiles[0].name).to.equal('foo.txt'); expect(fileListChangedEvent.detail.newFiles[1].name).to.equal('bar.txt'); }); }); describe('status and error', () => { /** * @type {LionInputFile} */ let el; beforeEach(async () => { el = await fixture(html``); }); it('should set uploadResponse data to _selectedFilesMetaData for rendering error and status', async () => { mimicSelectFile(el, [file]); el.uploadResponse = [{ name: 'foo.txt', status: 'LOADING', errorMessage: '500' }]; await el.updateComplete; // @ts-expect-error [allow-protected-in-test] expect(el._selectedFilesMetaData[0].status).to.equal('LOADING'); // @ts-ignore expect(el._selectedFilesMetaData[0].validationFeedback[0].message).to.equal('500'); }); it('should not fire file-list-changed event if invalid file is selected', async () => { const filePdf = /** @type {InputFile} */ ( new File(['foo'], 'foo.pdf', { type: 'application/pdf', }) ); // @ts-ignore const fileListChangedSpy = sinon.spy(el, '_dispatchFileListChangeEvent'); mimicSelectFile(el, [filePdf]); expect(fileListChangedSpy).have.not.been.called; fileListChangedSpy.restore(); }); it('updates showFeedbackFor when a wrong file has been added/removed', async () => { const fileWrongType = /** @type {InputFile} */ ( new File(['foobar'], 'foobar.txt', { type: 'xxxxx', }) ); // create condition to also show the feedback el.prefilled = true; mimicSelectFile(el, [fileWrongType]); await el.updateComplete; expect(el.hasFeedbackFor).to.deep.equals(['error'], 'hasFeedbackFor'); expect(el.showsFeedbackFor).to.deep.equals(['error'], 'showsFeedbackFor'); // on change the showsFeedbackFor should be empty again mimicSelectFile(el, [file]); await el.updateComplete; expect(el.hasFeedbackFor).to.deep.equals([]); expect(el.showsFeedbackFor).to.deep.equals([]); }); }); describe('validations when used with lion-form', () => { it('should throw error with Required validator', async () => { const el = await fixture(html` `); el.touched = true; el.dirty = true; await el.updateComplete; await el.feedbackComplete; // @ts-ignore input type="file" is a specific input member const { _feedbackNode } = getInputMembers(el); expect(_feedbackNode.feedbackData?.[0].message).to.equal('FooBar'); expect(el.hasFeedbackFor.includes('error')).to.be.true; }); it('reset method should remove File from modelValue but keep uploadResponse', async () => { const uploadResponse = [ { name: 'file1.txt', status: 'SUCCESS', errorMessage: '', downloadUrl: '/downloadFile', }, ]; const el = await fixture(html` `); // @ts-expect-error [allow-protected-in-test] el.uploadResponse = uploadResponse; await el.updateComplete; mimicSelectFile(el, [file]); await el.updateComplete; await el.reset(); expect(el.modelValue).to.deep.equal([]); // @ts-expect-error [allow-protected-in-test] expect(el._selectedFilesMetaData).to.deep.equal([]); expect(el.uploadResponse).to.deep.equal(uploadResponse); }); it('clear method should remove File from modelValue and uploadResponse', async () => { const uploadResponse = [ { name: 'file1.txt', status: 'SUCCESS', errorMessage: '', downloadUrl: '/downloadFile', }, ]; const el = await fixture(html` `); // @ts-expect-error [allow-protected-in-test] el.uploadResponse = uploadResponse; setTimeout(() => { mimicSelectFile(el, [file]); }); await oneEvent(el, 'file-list-changed'); await el.clear(); expect(el.modelValue).to.deep.equal([]); expect(el.uploadResponse).to.deep.equal([]); // @ts-expect-error [allow-protected-in-test] expect(el._selectedFilesMetaData).to.deep.equal([]); }); }); describe('file select component with prefilled state', () => { /** * @type {LionInputFile} */ let el; const uploadResponse = [ { name: 'file1.txt', status: 'SUCCESS', errorMessage: '', downloadUrl: '/downloadFile', }, { name: 'file2.txt', status: 'FAIL', errorMessage: 'something went wrong', }, ]; beforeEach(async () => { el = await fixture(html` `); // @ts-expect-error [allow-protected-in-test] el.uploadResponse = uploadResponse; await el.updateComplete; }); it('should update the _selectedFilesMetaData according to uploadResponse', () => { // @ts-expect-error [allow-protected-in-test] expect(el._selectedFilesMetaData.length).to.equal(2); // @ts-expect-error [allow-protected-in-test] expect(el._selectedFilesMetaData[0].systemFile.name).to.equal('file1.txt'); // @ts-expect-error [allow-protected-in-test] expect(el._selectedFilesMetaData[0].status).to.equal('SUCCESS'); // @ts-expect-error [allow-protected-in-test] expect(el._selectedFilesMetaData[0].downloadUrl).to.equal('/downloadFile'); // @ts-expect-error [allow-protected-in-test] expect(el._selectedFilesMetaData[1].validationFeedback[0].message).to.equal( 'something went wrong', ); }); it('should remove file on click of cross button', async () => { /** * @type {Partial} */ const removedFile = { name: 'file2.txt', status: 'FAIL', systemFile: { name: 'file2.txt' }, response: { name: 'file2.txt', status: 'FAIL' }, }; // @ts-expect-error [allow-protected-in-test] const removeFileSpy = sinon.spy(el, '_removeFile'); // assertion for displayed file list to be same // @ts-expect-error [allow-protected-in-test] expect(el._selectedFilesMetaData.length).to.equal(2); // @ts-expect-error [allow-protected-in-test] expect(el._selectedFilesMetaData[1].systemFile.name).to.equal('file2.txt'); // @ts-expect-error [allow-protected-in-test] el._fileListNode.dispatchEvent( new CustomEvent('file-remove-requested', { detail: { removedFile, status: removedFile.status, uploadResponse: removedFile.response, }, }), ); await el.updateComplete; expect(removeFileSpy).have.been.calledOnce; // @ts-expect-error [allow-protected-in-test] expect(el._selectedFilesMetaData.length).to.equal(1); removeFileSpy.restore(); }); it('should fire file-removed event with uploadResponse in the details', async () => { /** * @type {Partial} */ const removedFile = { name: 'file2.txt', status: 'FAIL', systemFile: { name: 'file2.txt' }, response: { name: 'file2.txt', status: 'FAIL' }, }; setTimeout(() => { // @ts-ignore ignore file typing el._removeFile(removedFile); }); await el.updateComplete; const removeFileEvent = await oneEvent(el, 'file-removed'); // assertion for event data expect(removeFileEvent).to.exist; expect(removeFileEvent.detail.removedFile).to.deep.equal({ name: 'file2.txt', status: 'FAIL', systemFile: { name: 'file2.txt', }, response: { name: 'file2.txt', status: 'FAIL', }, }); expect(removeFileEvent.detail.status).to.deep.equal('FAIL'); expect(removeFileEvent.detail.uploadResponse).to.deep.equal({ name: 'file2.txt', status: 'FAIL', }); }); }); describe('drag and drop', () => { /** * @type {LionInputFile} */ let el; beforeEach(async () => { el = await fixture(html` `); await el.updateComplete; }); it('should set "is-dragging" on dragenter', async () => { const dropzone = el.shadowRoot?.querySelector('.input-file__drop-zone'); dropzone?.dispatchEvent(new Event('dragenter', { bubbles: true })); await el.updateComplete; expect(el.hasAttribute('is-dragging')).to.equal(true); }); it('should set "is-dragging" on dragover', async () => { const dropzone = el.shadowRoot?.querySelector('.input-file__drop-zone'); dropzone?.dispatchEvent(new Event('dragover', { bubbles: true })); await el.updateComplete; expect(el.hasAttribute('is-dragging')).to.equal(true); }); it('should remove "is-dragging" on dragleave', async () => { const dropzone = el.shadowRoot?.querySelector('.input-file__drop-zone'); dropzone?.dispatchEvent(new Event('dragenter', { bubbles: true })); await el.updateComplete; dropzone?.dispatchEvent(new Event('dragleave', { bubbles: true })); await el.updateComplete; expect(el.hasAttribute('is-dragging')).to.equal(false); }); it('should remove "is-dragging" on drop', async () => { const dropzone = el.shadowRoot?.querySelector('.input-file__drop-zone'); dropzone?.dispatchEvent(new Event('dragenter', { bubbles: true })); await el.updateComplete; window.dispatchEvent(new Event('drop', { bubbles: true })); await el.updateComplete; expect(el.hasAttribute('is-dragging')).to.equal(false); }); it('should update modelValue on drop', async () => { const list = new DataTransfer(); // @ts-ignore list.items.add(file); const droppedFiles = list.files; // @ts-expect-error [allow-protected-in-test] await el._processDroppedFiles({ // @ts-ignore dataTransfer: { files: droppedFiles, items: [{ name: 'test.txt' }] }, preventDefault: () => {}, }); await el.updateComplete; expect(el.modelValue.length).to.equal(1); const list2 = new DataTransfer(); // @ts-ignore list2.items.add(file2); // @ts-expect-error [allow-protected-in-test] await el._processDroppedFiles({ // @ts-ignore dataTransfer: { files: list2.files, items: [{ name: 'test2.txt' }] }, preventDefault: () => {}, }); await el.updateComplete; expect(el.modelValue.length).to.equal(2); }); it('should call _processFiles method', async () => { const list = new DataTransfer(); // @ts-ignore list.items.add(file); const droppedFiles = list.files; // @ts-ignore const _processFilesSpy = sinon.spy(el, '_processFiles'); // @ts-expect-error [allow-protected-in-test] await el._processDroppedFiles({ // @ts-ignore dataTransfer: { files: droppedFiles, items: [{ name: 'test.txt' }] }, preventDefault: () => {}, }); expect(_processFilesSpy).have.been.calledOnce; _processFilesSpy.restore(); }); }); describe('uploadOnSelect as true', () => { it('should not remove file on click of cross button and fire file-removed event', async () => { /** * @type {Partial} */ const removeFile = { name: 'foo.txt', status: 'SUCCESS', systemFile: { name: 'foo.txt' }, response: { name: 'foo.txt', status: 'SUCCESS' }, }; const el = await fixture(html` `); el.modelValue = [file]; mimicSelectFile(el, [file]); await el.updateComplete; setTimeout(() => { // @ts-ignore ignore file typing el._removeFile(removeFile); }); const removeFileEvent = await oneEvent(el, 'file-removed'); // assertion for displayed file list to be same // @ts-expect-error [allow-protected-in-test] expect(el._selectedFilesMetaData.length).to.equal(1); // @ts-expect-error [allow-protected-in-test] expect(el._selectedFilesMetaData[0].systemFile.name).to.equal('foo.txt'); // assertion for event data 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', }); }); it('should remove file from displayed list if not available in uploadResponse array', async () => { const uploadResponse = [ { name: 'file1.txt', status: 'SUCCESS', errorMessage: '', downloadUrl: '/downloadFile', }, { name: 'file2.txt', status: 'FAIL', errorMessage: 'something went wrong', }, ]; const el = await fixture(html` `); // @ts-expect-error [allow-protected-in-test] el.uploadResponse = uploadResponse; await el.updateComplete; // assertion for displayed file list to be same // @ts-expect-error [allow-protected-in-test] expect(el._selectedFilesMetaData.length).to.equal(2); el.uploadResponse = [ { name: 'file1.txt', status: 'SUCCESS', errorMessage: '', downloadUrl: '/downloadFile', }, ]; await el.updateComplete; // assertion for displayed file list to be same // @ts-expect-error [allow-protected-in-test] expect(el._selectedFilesMetaData.length).to.equal(1); // @ts-expect-error [allow-protected-in-test] expect(el._selectedFilesMetaData[0].systemFile.name).to.equal('file1.txt'); // @ts-expect-error [allow-protected-in-tests] expect(el._inputNode.value).to.equal(''); }); }); describe('Accessibility', async () => { it('is accessible', async () => { const el = await fixture(html``); await expect(el).to.be.accessible(); }); it('is accessible when a file is selected', async () => { const uploadResponse = [ { name: 'file1.txt', status: 'SUCCESS', errorMessage: '', downloadUrl: '/downloadFile', }, ]; const el = await fixture(html` `); // @ts-expect-error [allow-protected-in-test] el.uploadResponse = uploadResponse; await expect(el).to.be.accessible(); }); it('is accessible with an selected file and disabled', async () => { const uploadResponse = [ { name: 'file1.txt', status: 'SUCCESS', errorMessage: '', downloadUrl: '/downloadFile', }, ]; const el = await fixture(html` `); // @ts-expect-error [allow-protected-in-test] el.uploadResponse = uploadResponse; await expect(el).to.be.accessible(); }); describe('has correct aria-roles', async () => { it('select-button has aria-labelledby set to itself and the label', async () => { const el = await fixture(html` `); // @ts-expect-error [allow-protected-in-test] expect(el._buttonNode?.getAttribute('aria-labelledby')).to.contain( // @ts-expect-error [allow-protected-in-test] `select-button-${el._inputId}`, ); // @ts-expect-error [allow-protected-in-test] expect(el._buttonNode?.getAttribute('aria-labelledby')).to.contain( // @ts-expect-error [allow-protected-in-test] `label-${el._inputId}`, ); }); it('select-button has aria-describedby set to the help-text, after and the feedback message', async () => { const uploadResponse = [ { name: 'file1.txt', status: 'SUCCESS', errorMessage: '', downloadUrl: '/downloadFile', }, ]; const el = await fixture(html` `); // @ts-expect-error [allow-protected-in-test] el.uploadResponse = uploadResponse; // @ts-expect-error [allow-protected-in-test] expect(el._buttonNode?.getAttribute('aria-describedby')).to.contain( // @ts-expect-error [allow-protected-in-test] `help-text-${el._inputId}`, ); // @ts-expect-error [allow-protected-in-test] expect(el._buttonNode?.getAttribute('aria-describedby')).to.contain( // @ts-expect-error [allow-protected-in-test] `feedback-${el._inputId}`, ); // @ts-expect-error [allow-protected-in-test] expect(el._buttonNode?.getAttribute('aria-describedby')).to.contain( // @ts-expect-error [allow-protected-in-test] `after-${el._inputId}`, ); }); it('after contains upload name of file when uploadOnSelect is true and status is SUCCESS', async () => { const uploadResponse = [ { name: 'file1.txt', status: 'SUCCESS', errorMessage: '', downloadUrl: '/downloadFile', }, ]; const el = await fixture(html` `); expect(el.querySelector('[slot="after"]')?.textContent).to.equal('No files uploaded.'); // @ts-expect-error el.uploadResponse = uploadResponse; await el.updateComplete; expect(el.querySelector('[slot="after"]')?.textContent).to.equal( 'Uploaded file: file1.txt', ); }); it('after contains upload name of file when uploadOnSelect is false and status is SUCCESS', async () => { const uploadResponse = [ { name: 'file1.txt', status: 'SUCCESS', errorMessage: '', downloadUrl: '/downloadFile', }, ]; const el = await fixture(html` `); expect(el.querySelector('[slot="after"]')?.textContent).to.equal('No files selected.'); // @ts-expect-error el.uploadResponse = uploadResponse; await el.updateComplete; expect(el.querySelector('[slot="after"]')?.textContent).to.equal( 'Selected file: file1.txt', ); }); it('after contains upload validator message of file when FAIL', async () => { const uploadResponse = [ { name: 'file1.txt', status: 'FAIL', errorMessage: 'something went wrong', downloadUrl: '/downloadFile', }, ]; const el = await fixture(html` `); expect(el.querySelector('[slot="after"]')?.textContent).to.equal('No files selected.'); // @ts-expect-error el.uploadResponse = uploadResponse; await el.updateComplete; expect(el.querySelector('[slot="after"]')?.textContent).to.equal('something went wrong'); }); it('after contains upload status of files when uploadOnSelect is true and multiple files have been selected', async () => { const uploadResponse = [ { name: 'file1.txt', status: 'SUCCESS', errorMessage: '', downloadUrl: '/downloadFile', }, { name: 'file2.txt', status: 'FAIL', errorMessage: 'something went wrong', downloadUrl: '/downloadFile', }, ]; const el = await fixture(html` `); // @ts-expect-error el.uploadResponse = uploadResponse; await el.updateComplete; expect(el.querySelector('[slot="after"]')?.textContent?.trim()).to.equal( 'Uploaded files: 2 files. "something went wrong", for file2.txt.', ); }); it('after contains upload status of files when uploadOnSelect is false and multiple files have been selected', async () => { const uploadResponse = [ { name: 'file1.txt', status: 'SUCCESS', errorMessage: '', downloadUrl: '/downloadFile', }, { name: 'file2.txt', status: 'FAIL', errorMessage: 'something went wrong', downloadUrl: '/downloadFile', }, ]; const el = await fixture(html` `); // @ts-expect-error el.uploadResponse = uploadResponse; await el.updateComplete; expect(el.querySelector('[slot="after"]')?.textContent?.trim()).to.equal( 'Selected files: 2 files. "something went wrong", for file2.txt.', ); }); }); }); });