From eacb92768267f5d7aecd9e8791d792b4ccffeb5b Mon Sep 17 00:00:00 2001 From: Amelia Magee Date: Thu, 5 Sep 2024 22:21:36 +0100 Subject: [PATCH 1/7] Restore input.files after wiping input.value on DropZoneComponent --- js/DropZone/DropZoneComponent.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/js/DropZone/DropZoneComponent.js b/js/DropZone/DropZoneComponent.js index faf19ab36f..16b7a9c1a4 100644 --- a/js/DropZone/DropZoneComponent.js +++ b/js/DropZone/DropZoneComponent.js @@ -138,6 +138,13 @@ class DropZoneComponent { input.value = ''; input.type = ''; input.type = 'file'; + + let rawFiles = this.instanceManager.getFiles(id); + const dataTransfer = new DataTransfer(); + for (let i = 0; i < rawFiles.length; i++) { + dataTransfer.items.add(rawFiles[i].raw); + } + input.files = dataTransfer.files; }); // visually hide input - this should ideally be done in the CSS also to prevent a From 44aee8d8e4ed10d96f2ae84486c871413b8e349e Mon Sep 17 00:00:00 2001 From: Amelia Magee Date: Thu, 5 Sep 2024 22:47:52 +0100 Subject: [PATCH 2/7] Add a test for ensuring that input.files is not wiped alongside input.value, since we are only clearing to trigger the change event. --- tests/js/web/DropZone/DropZoneComponentTest.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/js/web/DropZone/DropZoneComponentTest.js b/tests/js/web/DropZone/DropZoneComponentTest.js index 790f127520..d90ba39ae6 100644 --- a/tests/js/web/DropZone/DropZoneComponentTest.js +++ b/tests/js/web/DropZone/DropZoneComponentTest.js @@ -148,6 +148,19 @@ describe('DropZoneComponent', () => { expect($fileInput.val()).to.equal(''); }); + it('should retain the input node files on change', () => { + const change = new Event('change'); + + const rawFile = new File(['Lorem ipsum dolor'], 'example.txt', { type: 'text/plain' }); + const dataTransfer = new DataTransfer(); + dataTransfer.items.add(rawFile); + $fileInput[0].files = dataTransfer.files; + + dropZoneComponent.processInputNode($fileInput[0], 0, options.showInputNode); + $fileInput[0].dispatchEvent(change); + expect($fileInput[0].files).to.equal(dataTransfer.files); + }); + it('should not hide the input if specified in options', () => { const options = { showInputNode: true, inputNodeId: 'fileInput' }; const display = $fileInput.css('display'); From 9d0a16495c254c4fc73de330556af7ff4d817838 Mon Sep 17 00:00:00 2001 From: Amelia Magee Date: Thu, 5 Sep 2024 22:55:15 +0100 Subject: [PATCH 3/7] Update test to ensure we add any pre-existing files to the check --- tests/js/web/DropZone/DropZoneComponentTest.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/js/web/DropZone/DropZoneComponentTest.js b/tests/js/web/DropZone/DropZoneComponentTest.js index d90ba39ae6..98a121e934 100644 --- a/tests/js/web/DropZone/DropZoneComponentTest.js +++ b/tests/js/web/DropZone/DropZoneComponentTest.js @@ -153,6 +153,12 @@ describe('DropZoneComponent', () => { const rawFile = new File(['Lorem ipsum dolor'], 'example.txt', { type: 'text/plain' }); const dataTransfer = new DataTransfer(); + //add any pre-existing files to the dataTransfer object + for (let i = 0; i < $fileInput[0].files.length; i++) { + const preExistingFile = $fileInput[0].files[i]; + dataTransfer.items.add(preExistingFile); + } + //add our "new" file to the dataTransfer object dataTransfer.items.add(rawFile); $fileInput[0].files = dataTransfer.files; From 17f43df4fcf98bdf2b826777ccb40d0952db07d8 Mon Sep 17 00:00:00 2001 From: Amelia Magee Date: Tue, 10 Sep 2024 23:20:24 +0100 Subject: [PATCH 4/7] Add PUPPETEER_ARGS for '--no-sandbox' on mochify to bypass non-required sandbox environment --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 2452f57a3c..89cf55dc75 100644 --- a/package.json +++ b/package.json @@ -10,11 +10,11 @@ }, "scripts": { "start": "docker compose up -d && grunt", - "coverage": "mochify --transform [ babelify ] --plugin [ mochify-istanbul --exclude '**/+(tests|node_modules|libs)/**/*' --report lcov --dir ./coverage --instrumenter babel-istanbul] --reporter spec ./tests/harness/common './tests/js/web/**/*Test.js'", + "coverage": "PUPPETEER_ARGS='--no-sandbox' PUPPETEER_EXECUTABLE_PATH=$(which chromium) mochify --transform [ babelify ] --plugin [ mochify-istanbul --exclude '**/+(tests|node_modules|libs)/**/*' --report lcov --dir ./coverage --instrumenter babel-istanbul] --reporter spec ./tests/harness/common './tests/js/web/**/*Test.js'", "test": "npm run test:headless", "build:tests": "grunt javascript:tests", "build:tests:watch": "grunt javascript:tests:watch", - "test:headless": "mochify --reporter spec --transform [ babelify ] ./tests/harness/common './tests/js/web/**/*Test.js'", + "test:headless": "PUPPETEER_ARGS='--no-sandbox' PUPPETEER_EXECUTABLE_PATH=$(which chromium) mochify --reporter spec --transform [ babelify ] ./tests/harness/common './tests/js/web/**/*Test.js'", "test:browser": "npm run build:tests && open ./tests/js/web/index.html", "test:browser:watch": "npm run build:tests:watch" }, From 36d87fddf4bd146530132cfe87d8d478d1fd998f Mon Sep 17 00:00:00 2001 From: Amelia Magee Date: Wed, 11 Sep 2024 01:56:00 +0100 Subject: [PATCH 5/7] Update js tests to fully use the DataTransfer --- js/DropZone/DropZoneComponent.js | 11 ++++- .../js/web/DropZone/DropZoneComponentTest.js | 44 ++++++++++++++----- 2 files changed, 43 insertions(+), 12 deletions(-) diff --git a/js/DropZone/DropZoneComponent.js b/js/DropZone/DropZoneComponent.js index 16b7a9c1a4..7ffa915eb1 100644 --- a/js/DropZone/DropZoneComponent.js +++ b/js/DropZone/DropZoneComponent.js @@ -141,10 +141,17 @@ class DropZoneComponent { let rawFiles = this.instanceManager.getFiles(id); const dataTransfer = new DataTransfer(); + let wasFile = false; for (let i = 0; i < rawFiles.length; i++) { - dataTransfer.items.add(rawFiles[i].raw); + if (rawFiles[i].raw instanceof File){ + dataTransfer.items.add(rawFiles[i].raw); + wasFile = true; + } + } + if (wasFile){ + // this guard exists as some js tests do not provide a file type as the input value. + input.files = dataTransfer.files; } - input.files = dataTransfer.files; }); // visually hide input - this should ideally be done in the CSS also to prevent a diff --git a/tests/js/web/DropZone/DropZoneComponentTest.js b/tests/js/web/DropZone/DropZoneComponentTest.js index 98a121e934..779799c0f9 100644 --- a/tests/js/web/DropZone/DropZoneComponentTest.js +++ b/tests/js/web/DropZone/DropZoneComponentTest.js @@ -149,22 +149,46 @@ describe('DropZoneComponent', () => { }); it('should retain the input node files on change', () => { + // we need to explicitly set type="file" here. Originally it is configured as type="text" presumably to avoid the same security issue explained below + $fileInput = $(''); const change = new Event('change'); - - const rawFile = new File(['Lorem ipsum dolor'], 'example.txt', { type: 'text/plain' }); - const dataTransfer = new DataTransfer(); - //add any pre-existing files to the dataTransfer object + const getDTFileList = (fileInput, ...appendFiles) => { + const dataTransfer = new DataTransfer(); + if (fileInput.files && fileInput.files.length > 0) { + // add all our File objects to prepopulate the DataTransfer + for (let i = 0; i < fileInput.files.length; i++) { + dataTransfer.items.add(fileInput.files[i]); + } + } + appendFiles.forEach((appendFile)=>{ + dataTransfer.items.add(appendFile); + }); + return dataTransfer.files; + } + /** + * There is actually no way to programatically add a File object to Input.files other than to duplicate the DataTransfer process... + * This is due to strict browser security measures. Yes it is a bit of a hack, but there are simply no other options for initalising our input. + * Feel free to refactor this should a future version of ES add this functionality in some way. + */ + const file = new File(['Lorem ipsum dolor'], 'example.txt', { type: 'text/plain' }); + $fileInput[0].files = getDTFileList($fileInput[0], file); + + let rawFiles = []; for (let i = 0; i < $fileInput[0].files.length; i++) { - const preExistingFile = $fileInput[0].files[i]; - dataTransfer.items.add(preExistingFile); + $fileInput[0].files[i] + rawFiles.push({'raw': $fileInput[0].files[i]}) } - //add our "new" file to the dataTransfer object - dataTransfer.items.add(rawFile); - $fileInput[0].files = dataTransfer.files; + instanceManager.getFiles.returns(rawFiles); dropZoneComponent.processInputNode($fileInput[0], 0, options.showInputNode); $fileInput[0].dispatchEvent(change); - expect($fileInput[0].files).to.equal(dataTransfer.files); + //debugger; + const dataTransferFile = getDTFileList($fileInput[0])[0]; + const inputFile = $fileInput[0].files[0]; + expect(inputFile.name).to.equal(dataTransferFile.name); + expect(inputFile.lastModified).to.equal(dataTransferFile.lastModified); + expect(inputFile.size).to.equal(dataTransferFile.size); + expect(inputFile.type).to.equal(dataTransferFile.type); }); it('should not hide the input if specified in options', () => { From 4a50a0373d02c641d9db72a9eb763501dbf39f8b Mon Sep 17 00:00:00 2001 From: Amelia Magee Date: Wed, 11 Sep 2024 02:26:47 +0100 Subject: [PATCH 6/7] Remove puppeteer changes as some test require the sandbox --- package.json | 4 ++-- tests/js/web/Notifications/FaviconEditorTest.js | 16 +--------------- 2 files changed, 3 insertions(+), 17 deletions(-) diff --git a/package.json b/package.json index 89cf55dc75..2452f57a3c 100644 --- a/package.json +++ b/package.json @@ -10,11 +10,11 @@ }, "scripts": { "start": "docker compose up -d && grunt", - "coverage": "PUPPETEER_ARGS='--no-sandbox' PUPPETEER_EXECUTABLE_PATH=$(which chromium) mochify --transform [ babelify ] --plugin [ mochify-istanbul --exclude '**/+(tests|node_modules|libs)/**/*' --report lcov --dir ./coverage --instrumenter babel-istanbul] --reporter spec ./tests/harness/common './tests/js/web/**/*Test.js'", + "coverage": "mochify --transform [ babelify ] --plugin [ mochify-istanbul --exclude '**/+(tests|node_modules|libs)/**/*' --report lcov --dir ./coverage --instrumenter babel-istanbul] --reporter spec ./tests/harness/common './tests/js/web/**/*Test.js'", "test": "npm run test:headless", "build:tests": "grunt javascript:tests", "build:tests:watch": "grunt javascript:tests:watch", - "test:headless": "PUPPETEER_ARGS='--no-sandbox' PUPPETEER_EXECUTABLE_PATH=$(which chromium) mochify --reporter spec --transform [ babelify ] ./tests/harness/common './tests/js/web/**/*Test.js'", + "test:headless": "mochify --reporter spec --transform [ babelify ] ./tests/harness/common './tests/js/web/**/*Test.js'", "test:browser": "npm run build:tests && open ./tests/js/web/index.html", "test:browser:watch": "npm run build:tests:watch" }, diff --git a/tests/js/web/Notifications/FaviconEditorTest.js b/tests/js/web/Notifications/FaviconEditorTest.js index ead99ab76f..caac27f6b8 100644 --- a/tests/js/web/Notifications/FaviconEditorTest.js +++ b/tests/js/web/Notifications/FaviconEditorTest.js @@ -145,21 +145,7 @@ describe('FaviconEditor', () => { faviconEditor = new FaviconEditor($root[0]); faviconEditor.init(); - // Override serializer to return raw image data, we cannot use the default - // canvas.toDataURL(...) here as the png compression is different when - // in the browser / headless test environments - faviconEditor.setSerializer((canvas, ctx) => { - const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); - const debugCvs = document.createElement('canvas'); - const debugCtx = debugCvs.getContext('2d'); - - debugCtx.width = canvas.width; - debugCtx.height = canvas.height; - debugCtx.putImageData(imageData, 0, 0); - actual = debugCvs.toDataURL('image/png'); - - return [...imageData.data]; - }); + canvas.toDataURL('image/png'); faviconEditor.addCircleNotification('red', 5) .then((data) => { From 70c9524cd6d446e483ff2396be0a8b4b185dbb2b Mon Sep 17 00:00:00 2001 From: Amelia Magee Date: Wed, 11 Sep 2024 02:35:11 +0100 Subject: [PATCH 7/7] Revert Favicon change --- tests/js/web/Notifications/FaviconEditorTest.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/tests/js/web/Notifications/FaviconEditorTest.js b/tests/js/web/Notifications/FaviconEditorTest.js index caac27f6b8..ead99ab76f 100644 --- a/tests/js/web/Notifications/FaviconEditorTest.js +++ b/tests/js/web/Notifications/FaviconEditorTest.js @@ -145,7 +145,21 @@ describe('FaviconEditor', () => { faviconEditor = new FaviconEditor($root[0]); faviconEditor.init(); - canvas.toDataURL('image/png'); + // Override serializer to return raw image data, we cannot use the default + // canvas.toDataURL(...) here as the png compression is different when + // in the browser / headless test environments + faviconEditor.setSerializer((canvas, ctx) => { + const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); + const debugCvs = document.createElement('canvas'); + const debugCtx = debugCvs.getContext('2d'); + + debugCtx.width = canvas.width; + debugCtx.height = canvas.height; + debugCtx.putImageData(imageData, 0, 0); + actual = debugCvs.toDataURL('image/png'); + + return [...imageData.data]; + }); faviconEditor.addCircleNotification('red', 5) .then((data) => {