From a480308065606fa9e34023d226d252e87731f57f Mon Sep 17 00:00:00 2001 From: TechQuery Date: Tue, 11 Sep 2018 14:20:08 +0800 Subject: [PATCH] [ Add ] Support Assets encoded in DataURI [ Optimize ] Simplify core logic of the loader --- package.json | 5 - src/core/loader.js | 84 ++++++---- src/core/utility.js | 62 ++++++++ tests/unit/lib/qunit.css | 4 +- tests/unit/lib/qunit.js | 325 ++++++++++++++++++++++++++++----------- 5 files changed, 349 insertions(+), 131 deletions(-) create mode 100644 src/core/utility.js diff --git a/package.json b/package.json index 002ddb69..49e98fc0 100644 --- a/package.json +++ b/package.json @@ -82,10 +82,5 @@ "wdio-qunit-framework": "mucaho/wdio-qunit-framework#master", "wdio-sauce-service": "^0.3.1", "webdriverio": "^4.6.2" - }, - "browserify": { - "transform": [ - "brfs" - ] } } diff --git a/src/core/loader.js b/src/core/loader.js index bf972d1b..4b9a7143 100644 --- a/src/core/loader.js +++ b/src/core/loader.js @@ -1,11 +1,11 @@ -var Crafty = require('../core/core.js'); +var Crafty = require('../core/core.js'), Utility = require('./utility'); module.exports = { /**@ * #Crafty.assets * @category Assets * @kind Property - * + * * An object containing every asset used in the current Crafty game. * The key is the URL and the value is the `Audio` or `Image` object. * @@ -23,7 +23,7 @@ module.exports = { * #Crafty.paths * @category Assets * @kind Method - * + * * @sign public void Crafty.paths([Object paths]) * @param paths - Object containing paths for audio and images folders * @@ -69,7 +69,7 @@ module.exports = { * #Crafty.asset * @category Assets * @kind Method - * + * * @trigger NewAsset - After setting new asset - Object - key and value of new added asset. * @sign public void Crafty.asset(String key, Object asset) * @param key - asset url. @@ -152,7 +152,7 @@ module.exports = { * #Crafty.load * @category Assets * @kind Method - * + * * @sign public void Crafty.load(Object assets, Function onLoad[, Function onProgress[, Function onError]]) * @param assets - Object JSON formatted (or JSON string), with assets to load (accepts sounds, images and sprites) * @param onLoad - Callback when the assets are loaded @@ -250,9 +250,6 @@ module.exports = { (data.sprites ? Object.keys(data.sprites).length : 0), current, fileUrl, obj, type, asset, paths = Crafty.paths(), - getExt = function(f) { - return f.substr(f.lastIndexOf('.') + 1).toLowerCase(); - }, getFilePath = function(type,f) { return (f.search("://") === -1 ? (type === "audio" ? paths.audio + f : paths.images + f) : f); }, @@ -260,11 +257,24 @@ module.exports = { isAsset = function(a) { return Crafty.asset(a) || null; }, + // jshint ignore:start isSupportedAudio = function(f) { - return Crafty.support.audio && Crafty.audio.supports(getExt(f)); + + return Crafty.support.audio && Crafty.audio.supports( + Utility.fileTypeOf( f ).type + ); }, + // jshint ignore:end isValidImage = function(f) { - return Crafty.imageWhitelist.indexOf(getExt(f)) !== -1; + + return -1 < Crafty.imageWhitelist.indexOf( + Utility.fileTypeOf( f ).type + ); + }, + shortURLOf = function (URI) { + + return (Utility.fileTypeOf( URI ).schema === 'data') ? + URL.createObjectURL( Utility.toBlob( URI ) ) : URI; }, onImgLoad = function(obj,url) { obj.onload = pro; @@ -319,36 +329,47 @@ module.exports = { obj = null; if (type === "audio") { - if (typeof current === "object") { - var files = []; - for (var i in current) { - fileUrl = getFilePath(type, current[i]); - if (!isAsset(fileUrl) && isSupportedAudio(current[i]) && !Crafty.audio.sounds[asset]) - files.push(fileUrl); - } - if (files.length > 0) - obj = Crafty.audio.add(asset, files); - } else if (typeof current === "string") { - fileUrl = getFilePath(type, current); - if (!isAsset(fileUrl) && isSupportedAudio(current) && !Crafty.audio.sounds[asset]) - obj = Crafty.audio.add(asset, fileUrl); - } + + current = (typeof current === "object") ? + current : {'': current + ''}; + // jshint ignore:start + var files = Object.keys( current ).filter(function (key) { + + var fileUrl = getFilePath(type, current[key]); + + if ( + !isAsset( fileUrl ) && + isSupportedAudio( current[key] ) && + !Crafty.audio.sounds[asset] + ) + return shortURLOf( fileUrl ); + }); + + if ( files[0] ) obj = Crafty.audio.add(asset, files); + // jshint ignore:end //extract actual audio obj if audio creation was successfull - if (obj) - obj = obj.obj; + if ( obj ) obj = obj.obj; //addEventListener is supported on IE9 , Audio as well if (obj && obj.addEventListener) obj.addEventListener('canplaythrough', pro, false); } else { - asset = (type === "sprites" ? asset : current); + asset = (type === "sprites") ? asset : current; + fileUrl = getFilePath(type, asset); - if (!isAsset(fileUrl) && isValidImage(asset)) { - obj = new Image(); + + if (!isAsset( fileUrl ) && isValidImage( asset )) { + + obj = new Image(); fileUrl = shortURLOf( fileUrl ); + if (type === "sprites") - Crafty.sprite(current.tile, current.tileh, fileUrl, current.map, - current.paddingX, current.paddingY, current.paddingAroundBorder); + Crafty.sprite( + current.tile, current.tileh, fileUrl, current.map, + current.paddingX, current.paddingY, current.paddingAroundBorder + ); + Crafty.asset(fileUrl, obj); + onImgLoad(obj, fileUrl); } } @@ -363,7 +384,6 @@ module.exports = { // If we aren't trying to handle *any* of the files, that's as complete as it gets! if (total === 0 && oncomplete) oncomplete(); - }, /**@ * #Crafty.removeAssets diff --git a/src/core/utility.js b/src/core/utility.js new file mode 100644 index 00000000..81df3f6a --- /dev/null +++ b/src/core/utility.js @@ -0,0 +1,62 @@ +exports.blobOf = function blobOf(URI) { + + var XHR = new XMLHttpRequest(); + + XHR.responseType = 'blob'; + + XHR.open('GET', URI); + + return new Promise(function (resolve, reject) { // jshint ignore:line + + XHR.onload = function () { resolve( this.response ); }; + + XHR.onerror = reject; + + XHR.send(); + }); +}; + +var DataURI = /^data:(.+?\/(.+?))?(;base64)?,(\S+)/; + +exports.fileTypeOf = function fileTypeOf(URI) { + + var schema = /^(?:(\w+):)?.+?(?:\.(\w+))?$/.exec( URI ); + + switch ( schema[1] ) { + case 'data': return { + schema: 'data', type: DataURI.exec( URI )[2] + }; + case 'blob': return exports.blobOf( URI ).then(function (blob) { + return { + schema: 'blob', type: blob.type + }; + }); + default: return { + schema: schema[1], type: schema[2] + }; + } +}; + +var BlobBuilder = window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder; + +exports.toBlob = function toBlob(dataURI) { + + dataURI = DataURI.exec( dataURI ); + + var type = dataURI[1], base64 = dataURI[3], data = dataURI[4]; + + data = base64 ? window.atob( data ) : data; + + var aBuffer = new ArrayBuffer( data.length ); + + var uBuffer = new Uint8Array( aBuffer ); + + for (var i = 0; data[i]; i++) uBuffer[i] = data.charCodeAt( i ); + + if (! BlobBuilder) + return new window.Blob([aBuffer], {type: type}); + + var builder = new BlobBuilder(); builder.append( aBuffer ); + + return builder.getBlob( type ); +}; diff --git a/tests/unit/lib/qunit.css b/tests/unit/lib/qunit.css index 1cf1609a..224c9358 100644 --- a/tests/unit/lib/qunit.css +++ b/tests/unit/lib/qunit.css @@ -1,12 +1,12 @@ /*! - * QUnit 2.3.2 + * QUnit 2.4.1 * https://qunitjs.com/ * * Copyright jQuery Foundation and other contributors * Released under the MIT license * https://jquery.org/license * - * Date: 2017-04-18T02:19Z + * Date: 2017-10-22T05:12Z */ /** Font Family and Sizes */ diff --git a/tests/unit/lib/qunit.js b/tests/unit/lib/qunit.js index 03334cb6..bdba631f 100644 --- a/tests/unit/lib/qunit.js +++ b/tests/unit/lib/qunit.js @@ -1,12 +1,12 @@ /*! - * QUnit 2.3.2 + * QUnit 2.4.1 * https://qunitjs.com/ * * Copyright jQuery Foundation and other contributors * Released under the MIT license * https://jquery.org/license * - * Date: 2017-04-18T02:19Z + * Date: 2017-10-22T05:12Z */ (function (global$1) { 'use strict'; @@ -14,6 +14,7 @@ global$1 = 'default' in global$1 ? global$1['default'] : global$1; var window = global$1.window; + var self$1 = global$1.self; var console = global$1.console; var setTimeout = global$1.setTimeout; var clearTimeout = global$1.clearTimeout; @@ -226,10 +227,8 @@ case "Function": case "Symbol": return type.toLowerCase(); - } - - if ((typeof obj === "undefined" ? "undefined" : _typeof(obj)) === "object") { - return "object"; + default: + return typeof obj === "undefined" ? "undefined" : _typeof(obj); } } @@ -587,7 +586,13 @@ return arguments.length === 2 || innerEquiv.apply(this, [].slice.call(arguments, 1)); } - return innerEquiv; + return function () { + var result = innerEquiv.apply(undefined, arguments); + + // Release any retained objects + pairs.length = 0; + return result; + }; })(); /** @@ -635,7 +640,13 @@ tests: [], childModules: [], testsRun: 0, - unskippedTestsRun: 0 + unskippedTestsRun: 0, + hooks: { + before: [], + beforeEach: [], + afterEach: [], + after: [] + } }, callbacks: {}, @@ -1316,7 +1327,7 @@ return TestReport; }(); - var focused = false; + var focused$1 = false; function Test(settings) { var i, l; @@ -1324,12 +1335,30 @@ ++Test.count; this.expected = null; - extend(this, settings); this.assertions = []; this.semaphore = 0; this.module = config.currentModule; this.stack = sourceFromStacktrace(3); this.steps = []; + this.timeout = undefined; + + // If a module is skipped, all its tests and the tests of the child suites + // should be treated as skipped even if they are defined as `only` or `todo`. + // As for `todo` module, all its tests will be treated as `todo` except for + // tests defined as `skip` which will be left intact. + // + // So, if a test is defined as `todo` and is inside a skipped module, we should + // then treat that test as if was defined as `skip`. + if (this.module.skip) { + settings.skip = true; + settings.todo = false; + + // Skipped tests should be left intact + } else if (this.module.todo && !settings.skip) { + settings.todo = true; + } + + extend(this, settings); this.testReport = new TestReport(settings.testName, this.module.suiteReport, { todo: settings.todo, @@ -1359,6 +1388,13 @@ this.async = false; this.expected = 0; } else { + if (typeof this.callback !== "function") { + var method = this.todo ? "todo" : "test"; + + // eslint-disable-next-line max-len + throw new TypeError("You must provide a function as a test callback to QUnit." + method + "(\"" + settings.testName + "\")"); + } + this.assert = new Assert(this); } } @@ -1441,6 +1477,12 @@ function runTest(test) { promise = test.callback.call(test.testEnvironment, test.assert); test.resolvePromise(promise); + + // If the test has a "lock" on it, but the timeout is 0, then we push a + // failure as the test should be synchronous. + if (test.timeout === 0 && test.semaphore !== 0) { + pushFailure("Test did not finish synchronously even though assert.timeout( 0 ) was used.", sourceFromStacktrace(2)); + } } }, @@ -1449,22 +1491,27 @@ }, queueHook: function queueHook(hook, hookName, hookOwner) { - var promise, - test = this; - return function runHook() { + var _this = this; + + var callHook = function callHook() { + var promise = hook.call(_this.testEnvironment, _this.assert); + _this.resolvePromise(promise, hookName); + }; + + var runHook = function runHook() { if (hookName === "before") { if (hookOwner.unskippedTestsRun !== 0) { return; } - test.preserveEnvironment = true; + _this.preserveEnvironment = true; } if (hookName === "after" && hookOwner.unskippedTestsRun !== numberOfUnskippedTests(hookOwner) - 1 && config.queue.length > 2) { return; } - config.current = test; + config.current = _this; if (config.notrycatch) { callHook(); return; @@ -1472,16 +1519,14 @@ try { callHook(); } catch (error) { - test.pushFailure(hookName + " failed on " + test.testName + ": " + (error.message || error), extractStacktrace(error, 0)); - } - - function callHook() { - promise = hook.call(test.testEnvironment, test.assert); - test.resolvePromise(promise, hookName); + _this.pushFailure(hookName + " failed on " + _this.testName + ": " + (error.message || error), extractStacktrace(error, 0)); } }; + + return runHook; }, + // Currently only used for module level hooks, can be used to add global level ones hooks: function hooks(handler) { var hooks = []; @@ -1490,8 +1535,11 @@ if (module.parentModule) { processHooks(test, module.parentModule); } - if (module.hooks && objectType(module.hooks[handler]) === "function") { - hooks.push(test.queueHook(module.hooks[handler], handler, module)); + + if (module.hooks[handler].length) { + for (var i = 0; i < module.hooks[handler].length; i++) { + hooks.push(test.queueHook(module.hooks[handler][i], handler, module)); + } } } @@ -1499,9 +1547,11 @@ if (!this.skip) { processHooks(this, this.module); } + return hooks; }, + finish: function finish() { config.current = this; if (config.requireExpects && this.expected === null) { @@ -1654,13 +1704,16 @@ result: resultInfo.result, message: resultInfo.message, actual: resultInfo.actual, - expected: resultInfo.expected, testId: this.testId, negative: resultInfo.negative || false, runtime: now() - this.started, todo: !!this.todo }; + if (hasOwn.call(resultInfo, "expected")) { + details.expected = resultInfo.expected; + } + if (!resultInfo.result) { source = resultInfo.source || sourceFromStacktrace(); @@ -1686,7 +1739,6 @@ result: false, message: message || "error", actual: actual || null, - expected: null, source: source }); }, @@ -1859,7 +1911,7 @@ // Will be exposed as QUnit.test function test(testName, callback) { - if (focused) { + if (focused$1) { return; } @@ -1872,7 +1924,7 @@ } function todo(testName, callback) { - if (focused) { + if (focused$1) { return; } @@ -1887,7 +1939,7 @@ // Will be exposed as QUnit.skip function skip(testName) { - if (focused) { + if (focused$1) { return; } @@ -1901,12 +1953,12 @@ // Will be exposed as QUnit.only function only(testName, callback) { - if (focused) { + if (focused$1) { return; } config.queue.length = 0; - focused = true; + focused$1 = true; var newTest = new Test({ testName: testName, @@ -1918,20 +1970,29 @@ // Put a hold on processing and return a function that will release it. function internalStop(test) { - var released = false; - test.semaphore += 1; config.blocking = true; // Set a recovery timeout, if so configured. - if (config.testTimeout && defined.setTimeout) { - clearTimeout(config.timeout); - config.timeout = setTimeout(function () { - pushFailure("Test timed out", sourceFromStacktrace(2)); - internalRecover(test); - }, config.testTimeout); + if (defined.setTimeout) { + var timeoutDuration = void 0; + + if (typeof test.timeout === "number") { + timeoutDuration = test.timeout; + } else if (typeof config.testTimeout === "number") { + timeoutDuration = config.testTimeout; + } + + if (typeof timeoutDuration === "number" && timeoutDuration > 0) { + clearTimeout(config.timeout); + config.timeout = setTimeout(function () { + pushFailure("Test took longer than " + timeoutDuration + "ms; test timed out.", sourceFromStacktrace(2)); + internalRecover(test); + }, timeoutDuration); + } } + var released = false; return function resume() { if (released) { return; @@ -2058,10 +2119,19 @@ // Assert helpers - // Documents a "step", which is a string value, in a test as a passing assertion + createClass(Assert, [{ + key: "timeout", + value: function timeout(duration) { + if (typeof duration !== "number") { + throw new Error("You must pass a number as the duration to assert.timeout"); + } + this.test.timeout = duration; + } - createClass(Assert, [{ + // Documents a "step", which is a string value, in a test as a passing assertion + + }, { key: "step", value: function step(message) { var result = !!message; @@ -2137,7 +2207,7 @@ }, { key: "push", value: function push(result, actual, expected, message, negative) { - Logger.warn("assert.push is deprecated and will be removed in QUnit 3.0." + " Please use assert.pushResult instead (http://api.qunitjs.com/pushResult/)."); + Logger.warn("assert.push is deprecated and will be removed in QUnit 3.0." + " Please use assert.pushResult instead (https://api.qunitjs.com/assert/pushResult)."); var currentAssert = this instanceof Assert ? this : config.current.assert; return currentAssert.pushResult({ @@ -2427,6 +2497,11 @@ }); QUnit.config.autostart = false; } + + // For Web/Service Workers + if (self$1 && self$1.WorkerGlobalScope && self$1 instanceof self$1.WorkerGlobalScope) { + self$1.QUnit = QUnit; + } } var SuiteReport = function () { @@ -2567,6 +2642,7 @@ return false; } + var focused = false; var QUnit = {}; var globalSuite = new SuiteReport(); @@ -2583,13 +2659,16 @@ QUnit.isLocal = !(defined.document && window.location.protocol !== "file:"); // Expose the current QUnit version - QUnit.version = "2.3.2"; + QUnit.version = "2.4.1"; - function createModule(name, testEnvironment) { + function createModule(name, testEnvironment, modifiers) { var parentModule = moduleStack.length ? moduleStack.slice(-1)[0] : null; var moduleName = parentModule !== null ? [parentModule.name, name].join(" > ") : name; var parentSuite = parentModule ? parentModule.suiteReport : globalSuite; + var skip$$1 = parentModule !== null && parentModule.skip || modifiers.skip; + var todo$$1 = parentModule !== null && parentModule.todo || modifiers.todo; + var module = { name: moduleName, parentModule: parentModule, @@ -2598,7 +2677,14 @@ testsRun: 0, unskippedTestsRun: 0, childModules: [], - suiteReport: new SuiteReport(name, parentSuite) + suiteReport: new SuiteReport(name, parentSuite), + + // Pass along `skip` and `todo` properties from parent module, in case + // there is one, to childs. And use own otherwise. + // This property will be used to mark own tests and tests of child suites + // as either `skipped` or `todo`. + skip: skip$$1, + todo: skip$$1 ? false : todo$$1 }; var env = {}; @@ -2613,53 +2699,108 @@ return module; } - extend(QUnit, { - on: on, + function processModule(name, options, executeNow) { + var modifiers = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; - // Call on start of module test to prepend name to all tests - module: function module(name, testEnvironment, executeNow) { - if (arguments.length === 2) { - if (objectType(testEnvironment) === "function") { - executeNow = testEnvironment; - testEnvironment = undefined; - } + var module = createModule(name, options, modifiers); + + // Move any hooks to a 'hooks' object + var testEnvironment = module.testEnvironment; + var hooks = module.hooks = {}; + + setHookFromEnvironment(hooks, testEnvironment, "before"); + setHookFromEnvironment(hooks, testEnvironment, "beforeEach"); + setHookFromEnvironment(hooks, testEnvironment, "afterEach"); + setHookFromEnvironment(hooks, testEnvironment, "after"); + + function setHookFromEnvironment(hooks, environment, name) { + var potentialHook = environment[name]; + hooks[name] = typeof potentialHook === "function" ? [potentialHook] : []; + delete environment[name]; + } + + var moduleFns = { + before: setHookFunction(module, "before"), + beforeEach: setHookFunction(module, "beforeEach"), + afterEach: setHookFunction(module, "afterEach"), + after: setHookFunction(module, "after") + }; + + var currentModule = config.currentModule; + if (objectType(executeNow) === "function") { + moduleStack.push(module); + config.currentModule = module; + executeNow.call(module.testEnvironment, moduleFns); + moduleStack.pop(); + module = module.parentModule || currentModule; + } + + config.currentModule = module; + } + + // TODO: extract this to a new file alongside its related functions + function module$1(name, options, executeNow) { + if (focused) { + return; + } + + if (arguments.length === 2) { + if (objectType(options) === "function") { + executeNow = options; + options = undefined; } + } - var module = createModule(name, testEnvironment); + processModule(name, options, executeNow); + } - // Move any hooks to a 'hooks' object - if (module.testEnvironment) { - module.hooks = { - before: module.testEnvironment.before, - beforeEach: module.testEnvironment.beforeEach, - afterEach: module.testEnvironment.afterEach, - after: module.testEnvironment.after - }; + module$1.only = function () { + if (focused) { + return; + } + + config.modules.length = 0; + config.queue.length = 0; + + module$1.apply(undefined, arguments); + + focused = true; + }; + + module$1.skip = function (name, options, executeNow) { + if (focused) { + return; + } - delete module.testEnvironment.before; - delete module.testEnvironment.beforeEach; - delete module.testEnvironment.afterEach; - delete module.testEnvironment.after; + if (arguments.length === 2) { + if (objectType(options) === "function") { + executeNow = options; + options = undefined; } + } - var moduleFns = { - before: setHook(module, "before"), - beforeEach: setHook(module, "beforeEach"), - afterEach: setHook(module, "afterEach"), - after: setHook(module, "after") - }; + processModule(name, options, executeNow, { skip: true }); + }; - var currentModule = config.currentModule; - if (objectType(executeNow) === "function") { - moduleStack.push(module); - config.currentModule = module; - executeNow.call(module.testEnvironment, moduleFns); - moduleStack.pop(); - module = module.parentModule || currentModule; + module$1.todo = function (name, options, executeNow) { + if (focused) { + return; + } + + if (arguments.length === 2) { + if (objectType(options) === "function") { + executeNow = options; + options = undefined; } + } - config.currentModule = module; - }, + processModule(name, options, executeNow, { todo: true }); + }; + + extend(QUnit, { + on: on, + + module: module$1, test: test, @@ -2796,13 +2937,9 @@ ProcessingQueue.advance(); } - function setHook(module, hookName) { - if (!module.hooks) { - module.hooks = {}; - } - - return function (callback) { - module.hooks[hookName] = callback; + function setHookFunction(module, hookName) { + return function setHook(callback) { + module.hooks[hookName].push(callback); }; } @@ -3156,7 +3293,8 @@ // Skip inherited or undefined properties if (hasOwn.call(params, key) && params[key] !== undefined) { - // Output a parameter for each value of this key (but usually just one) + // Output a parameter for each value of this key + // (but usually just one) arrValue = [].concat(params[key]); for (i = 0; i < arrValue.length; i++) { querystring += encodeURIComponent(key); @@ -3577,7 +3715,8 @@ if (config.altertitle && document$$1.title) { // Show ✖ for good, ✔ for bad suite result in title - // use escape sequences in case file gets loaded with non-utf-8-charset + // use escape sequences in case file gets loaded with non-utf-8 + // charset document$$1.title = [stats.failedTests ? "\u2716" : "\u2714", document$$1.title.replace(/^[\u2714\u2716] /i, "")].join(" "); } @@ -3615,7 +3754,7 @@ if (running) { bad = QUnit.config.reorder && details.previousFailure; - running.innerHTML = (bad ? "Rerunning previously failed test:
" : "Running:
") + getNameHtml(details.name, details.module); + running.innerHTML = [bad ? "Rerunning previously failed test:
" : "Running:
", getNameHtml(details.name, details.module)].join(""); } }); @@ -4746,7 +4885,9 @@ line = text.substring(lineStart, lineEnd + 1); lineStart = lineEnd + 1; - if (lineHash.hasOwnProperty ? lineHash.hasOwnProperty(line) : lineHash[line] !== undefined) { + var lineHashExists = lineHash.hasOwnProperty ? lineHash.hasOwnProperty(line) : lineHash[line] !== undefined; + + if (lineHashExists) { chars += String.fromCharCode(lineHash[line]); } else { chars += String.fromCharCode(lineArrayLength);