From 8e43e7d41a010e61f139021aa6603d63f779f880 Mon Sep 17 00:00:00 2001 From: jelhan Date: Fri, 23 Jun 2017 14:54:44 +0200 Subject: [PATCH] exposes test helper form-data and follow FormData specification --- test-support/helpers/form-data.js | 106 ++++++++++++++++++++++ tests/helpers/form-data.js | 38 -------- tests/unit/test-helpers/form-data-test.js | 65 +++++++++++++ tests/unit/uploader-test.js | 13 ++- 4 files changed, 179 insertions(+), 43 deletions(-) create mode 100644 test-support/helpers/form-data.js delete mode 100644 tests/helpers/form-data.js create mode 100644 tests/unit/test-helpers/form-data-test.js diff --git a/test-support/helpers/form-data.js b/test-support/helpers/form-data.js new file mode 100644 index 0000000..8db7256 --- /dev/null +++ b/test-support/helpers/form-data.js @@ -0,0 +1,106 @@ +/* + * Allows to inject and remove a FormData polyfill which supports get() and + * getAll() methods. + * + * Except for Chrome (>= 50) and Firefox (>= 39) major browser engines implement + * only a very basic subset of FormData specification. Especially current Safari, + * IE, Edge and PhantomJS does not support any methods to retrieve data of a + * FormData object. + * This is a hard limitation in testing. E.g. in an ember-cli-mirage route handler + * you are not able to retrieve values of the request. + * + * Implementation follows FormData specification: + * https://xhr.spec.whatwg.org/#interface-formdata + */ + +import { isArray } from '@ember/array'; + +function TestableFormData() { + this._data = {}; +} + +/* + * Injects FormData polyfill by overriding window.FormData if current browser + * engine does not implement FormData.get method. + * Overriding window.FormData could be forced by passing `true` as first argument. + */ +TestableFormData.inject = function(force) { + if ( + window && + (force || typeof window.FormData.get === 'undefined') + ) { + this.OldFormData = window.FormData; + window.FormData = TestableFormData; + } +}; + +TestableFormData.remove = function() { + if (window && this.OldFormData) { + window.FormData = this.OldFormData; + delete this.OldFormData; + } +}; + +/* + * FormData.append() + * The append(name, value) and append(name, blobValue, filename) methods, when + * invoked, must run these steps: + * 1. Let value be value if given, and blobValue otherwise. + * 2. Let entry be the result of creating an entry with name, value, and + * filename if given. + * 3. Append entry to context object’s list of entries. + * Note: The reason there is an argument named value as well as blobValue is + * due to a limitation of the editing software used to write the XMLHttpRequest + * Standard. + * https://xhr.spec.whatwg.org/#dom-formdata-append + */ +TestableFormData.prototype.append = function(name, value, filename) { + if (!isArray(this._data[name])) { + this._data[name] = []; + } + /* + * To create an entry for name, value, and optionally a filename, run these steps: + * 3. If value is a Blob object and not a File object, then set value to a + * new File object, representing the same bytes, whose name attribute + * value is "blob". + * 4. If value is (now) a File object and filename is given, then set value + * to a new File object, representing the same bytes, whose name attribute + * value is filename. + * https://xhr.spec.whatwg.org/#create-an-entry + */ + if ( + // it's a Blob + value instanceof Blob && + // but it's not a File yet + !(value instanceof File) && + // File is supported by current engine + typeof File === 'function' + ) { + value = new File([value], filename || 'blob'); + } + this._data[name].push(value); +}; + +/* + * FormData.get() + * The get(name) method, when invoked, must return the value of the first entry + * whose name is name, and null otherwise. + * https://xhr.spec.whatwg.org/#dom-formdata-get + */ +TestableFormData.prototype.get = function(name) { + let values = this._data[name]; + return ( isArray(values) && values.length > 0 ) ? values[0] : null; +}; + +/* + * FormData.getAll() + * The getAll(name) method, when invoked, must return the values of all entries + * whose name is name, in list order, and the empty sequence otherwise. + * https://xhr.spec.whatwg.org/#dom-formdata-getall + */ +TestableFormData.prototype.getAll = function(name) { + let value = this._data[name]; + return isArray(value) ? value : []; +}; + +export default TestableFormData; diff --git a/tests/helpers/form-data.js b/tests/helpers/form-data.js deleted file mode 100644 index 516c18a..0000000 --- a/tests/helpers/form-data.js +++ /dev/null @@ -1,38 +0,0 @@ -import { isArray } from '@ember/array'; -function TestableFormData() { - this.data = {} -} - -TestableFormData.inject = function() { - if (window) { - this.OldFormData = window.FormData; - window.FormData = TestableFormData; - } -} - -TestableFormData.remove = function() { - if (window && this.OldFormData) { - window.FormData = this.OldFormData; - delete this.OldFormData; - } -} - -TestableFormData.prototype.append = function(key, value) { - // FormData expects the key for arrays to be postfixed with empty brackets - // This same key is used each time a new item is added. - let matches = key.match(/^(.*)\[\]$/); - - if (matches) { - const arrayKey = matches.reverse()[0]; - - if (!isArray(this.data[arrayKey])) { - this.data[arrayKey] = []; - } - - this.data[arrayKey].push(value); - } else { - this.data[key] = value; - } -} - -export default TestableFormData; diff --git a/tests/unit/test-helpers/form-data-test.js b/tests/unit/test-helpers/form-data-test.js new file mode 100644 index 0000000..d474d40 --- /dev/null +++ b/tests/unit/test-helpers/form-data-test.js @@ -0,0 +1,65 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; +import TestableFormData from 'dummy/tests/helpers/form-data'; +import { isArray } from '@ember/array'; +import { warn } from '@ember/debug'; + +module("test helper | FormData", function(hooks) { + setupTest(hooks); + + test('supports empty value', function(assert) { + let formData = new TestableFormData(); + + assert.strictEqual(formData.get('not-existing'), null, 'get returns null'); + assert.ok(isArray(formData.getAll('not-existing')), 'getAll returns an array'); + assert.equal(formData.getAll('not-existing').length, 0, 'array returned by getAll is empty'); + }); + + test('supports single value', function(assert) { + let formData = new TestableFormData(); + formData.append('foo', 'a'); + + assert.strictEqual(formData.get('foo'), 'a', 'get returns value'); + assert.ok(isArray(formData.getAll('foo')), 'getAll returns an array'); + assert.equal(formData.getAll('foo')[0], 'a', 'array returned by getAll contains value'); + }); + + test('supports multiple values', function(assert) { + let formData = new TestableFormData(); + formData.append('foo', 'a'); + formData.append('foo', 'b'); + + assert.strictEqual(formData.get('foo'), 'a', 'get returns value which was set as first'); + assert.ok(isArray(formData.getAll('foo')), 'getAll returns an array'); + assert.equal(formData.getAll('foo')[0], 'a', 'array returned by getAll contains first value'); + assert.equal(formData.getAll('foo')[1], 'b', 'array returned by getAll contains second value'); + }) + + test('supports appending Blob', function(assert) { + let formData = new TestableFormData(); + formData.append('foo', new Blob([])); + + assert.ok(isArray(formData.getAll('foo')), 'getAll returns an array'); + assert.ok(formData.getAll('foo')[0] instanceof Blob, 'array returned by getAll contains value'); + }); + + test('supports appending File', function(assert) { + if (typeof File !== 'function') { + warn('Skipping File tests since File not supported by current engine'); + assert.expect(0); + return; + } + + let formData = new TestableFormData(); + let fileObject = new File([], 'untouched'); + formData.append('foo', new Blob([])); + formData.append('foo', fileObject); + formData.append('foo', new Blob([]), 'test.jpg'); + + assert.ok(formData.getAll('foo')[0] instanceof File, 'converts Blob to File object'); + assert.equal(formData.getAll('foo')[0].name, 'blob', 'sets name to blob if no name is given'); + assert.strictEqual(formData.getAll('foo')[1], fileObject, 'File object does not get changed'); + assert.ok(formData.getAll('foo')[2] instanceof File, 'converts Blob to File object'); + assert.equal(formData.getAll('foo')[2].name, 'test.jpg', 'supports specifing name'); + }); +}); diff --git a/tests/unit/uploader-test.js b/tests/unit/uploader-test.js index ce88daa..db9aad7 100644 --- a/tests/unit/uploader-test.js +++ b/tests/unit/uploader-test.js @@ -4,8 +4,9 @@ import { computed } from '@ember/object'; import $ from 'jquery'; import Uploader from 'ember-uploader/uploaders/uploader'; import test from 'ember-sinon-qunit/test-support/test'; -import TestableFormData from '../helpers/form-data'; +import TestableFormData from 'dummy/tests/helpers/form-data'; import { startMirage } from 'dummy/initializers/ember-cli-mirage'; +import { isArray } from '@ember/array'; let file; @@ -77,11 +78,13 @@ module('EmberUploader.Uploader', function(hooks) { let uploader = Uploader.extend({ paramName: 'files' }).create(); + let formData = uploader.createFormData([1, 2, 3]); - let formData = uploader.createFormData([1,2,3]); - assert.equal(formData.data['files'][0], 1); - assert.equal(formData.data['files'][1], 2); - assert.equal(formData.data['files'][2], 3); + assert.strictEqual(formData.get('files'), null, 'does not set to name without empty brackets'); + + let files = formData.getAll('files[]'); + assert.ok(isArray(files)); + assert.deepEqual(files, [1, 2, 3]) }); test("uploads to the given url", function(assert) {