diff --git a/Gruntfile.js b/Gruntfile.js index 15d5d861..fbf49a7b 100755 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -10,13 +10,13 @@ module.exports = function(grunt) { ["clean:dev", "concat:css", "concat:js", "copy:htmlDev", "copy:staticDev", "chmod:build", "watch"]); grunt.registerTask("test", - "A persistent task which creates a test build whenever source files are modified.", - ["clean:dev", "concat:cssTest", "concat:jsTest", "copy:htmlTest", "copy:staticTest", "chmod:build", "watch"]); + "A task which runs all the tests in test/tests.", + ["clean:test", "concat:jsTest", "copy:htmlTest", "chmod:build", "exec:tests"]); grunt.registerTask("prod", "Creates a production-ready build. Use the --msg flag to add a compile message.", ["eslint", "exec:stats", "clean", "jsdoc", "concat", "copy:htmlDev", "copy:htmlProd", "copy:htmlInline", - "copy:staticDev", "copy:staticProd", "cssmin", "uglify:prod", "inline", "htmlmin", "chmod"]); + "copy:staticDev", "copy:staticProd", "cssmin", "uglify:prod", "inline", "htmlmin", "chmod", "test"]); grunt.registerTask("docs", "Compiles documentation in the /docs directory.", @@ -138,6 +138,7 @@ module.exports = function(grunt) { "src/js/lib/vkbeautify.js", "src/js/lib/Sortable.js", "src/js/lib/bootstrap-colorpicker.js", + "src/js/lib/es6-promise.auto.js", "src/js/lib/xpath.js", // Custom libraries @@ -171,11 +172,9 @@ module.exports = function(grunt) { var jsTestFiles = [].concat( jsIncludes, [ - "src/js/lib/vuejs/vue.min.js", - "src/js/test/*.js", - "src/js/test/tests/**/*", - // Start the test runner app! - "src/js/test/views/html/main.js", + "test/TestRegister.js", + "test/tests/**/*.js", + "test/TestRunner.js", ] ); @@ -219,7 +218,11 @@ module.exports = function(grunt) { config: ["src/js/config/**/*.js"], views: ["src/js/views/**/*.js"], operations: ["src/js/operations/**/*.js"], - tests: ["src/js/test/**/*.js"], + tests: [ + "test/**/*.js", + "!test/PhantomRunner.js", + "!test/NodeRunner.js", + ], }, jsdoc: { options: { @@ -238,6 +241,7 @@ module.exports = function(grunt) { }, clean: { dev: ["build/dev/*"], + test: ["build/test/*"], prod: ["build/prod/*"], docs: ["docs/*", "!docs/*.conf.json", "!docs/*.ico"], }, @@ -261,22 +265,6 @@ module.exports = function(grunt) { ], dest: "build/dev/styles.css" }, - cssTest: { - options: { - banner: banner.replace(/\/\*\*/g, "/*!"), - process: function(content, srcpath) { - // Change special comments from /** to /*! to comply with cssmin - content = content.replace(/^\/\*\* /g, "/*! "); - return grunt.template.process(content); - } - }, - src: [ - "src/css/lib/**/*.css", - "src/css/structure/**/*.css", - "src/css/themes/classic.css" - ], - dest: "build/test/styles.css" - }, js: { options: { banner: '"use strict";\n' @@ -289,7 +277,7 @@ module.exports = function(grunt) { banner: '"use strict";\n' }, src: jsTestFiles, - dest: "build/test/scripts.js" + dest: "build/test/tests.js" } }, copy: { @@ -348,21 +336,6 @@ module.exports = function(grunt) { } ] }, - staticTest: { - files: [ - { - expand: true, - cwd: "src/static/", - src: [ - "**/*", - "**/.*", - "!stats.txt", - "!ga.html" - ], - dest: "build/test/" - } - ] - }, staticProd: { files: [ { @@ -478,6 +451,9 @@ module.exports = function(grunt) { } }, exec: { + tests: { + command: "node test/NodeRunner.js", + }, repoSize: { command: [ "git ls-files | wc -l | xargs printf '\n%b\ttracked files\n'", @@ -537,15 +513,15 @@ module.exports = function(grunt) { }, js: { files: "src/js/**/*.js", - tasks: ["concat:js", "concat:jsTest", "chmod:build"] + tasks: ["concat:js", "chmod:build"] }, html: { files: "src/html/**/*.html", - tasks: ["copy:htmlDev", "copy:htmlTest", "chmod:build"] + tasks: ["copy:htmlDev", "chmod:build"] }, static: { files: ["src/static/**/*", "src/static/**/.*"], - tasks: ["copy:staticDev", "copy:staticTest", "chmod:build"] + tasks: ["copy:staticDev", "chmod:build"] }, grunt: { files: "Gruntfile.js", diff --git a/package.json b/package.json index 96c81e3b..f8f17fe4 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "grunt-exec": "~1.0.1", "grunt-inline-alt": "~0.3.10", "grunt-jsdoc": "^2.1.0", - "ink-docstrap": "^1.1.4" + "ink-docstrap": "^1.1.4", + "phantomjs-prebuilt": "^2.1.14" } } diff --git a/src/html/test.html b/src/html/test.html index f38a7251..6c434e52 100755 --- a/src/html/test.html +++ b/src/html/test.html @@ -25,72 +25,10 @@ - CyberChef Test Runner - - + CyberChef - - - - - - -
-
-

CyberChef Test Runner

-
- -
- -
-
- - +
+ diff --git a/src/js/.eslintrc.json b/src/js/.eslintrc.json index 3ad149d5..7ea169f3 100755 --- a/src/js/.eslintrc.json +++ b/src/js/.eslintrc.json @@ -111,8 +111,8 @@ "SeasonalWaiter": false, "WindowWaiter": false, - /* test */ - "Vue": false, - "TestRegister": false + /* tests */ + "TestRegister": false, + "TestRunner": false } } diff --git a/src/js/lib/es6-promise.auto.js b/src/js/lib/es6-promise.auto.js new file mode 100644 index 00000000..632b8695 --- /dev/null +++ b/src/js/lib/es6-promise.auto.js @@ -0,0 +1,1159 @@ +/*! + * @overview es6-promise - a tiny implementation of Promises/A+. + * @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald) + * @license Licensed under MIT license + * See https://raw.githubusercontent.com/stefanpenner/es6-promise/master/LICENSE + * @version 4.0.5 + */ + +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global.ES6Promise = factory()); +}(this, (function () { 'use strict'; + +function objectOrFunction(x) { + return typeof x === 'function' || typeof x === 'object' && x !== null; +} + +function isFunction(x) { + return typeof x === 'function'; +} + +var _isArray = undefined; +if (!Array.isArray) { + _isArray = function (x) { + return Object.prototype.toString.call(x) === '[object Array]'; + }; +} else { + _isArray = Array.isArray; +} + +var isArray = _isArray; + +var len = 0; +var vertxNext = undefined; +var customSchedulerFn = undefined; + +var asap = function asap(callback, arg) { + queue[len] = callback; + queue[len + 1] = arg; + len += 2; + if (len === 2) { + // If len is 2, that means that we need to schedule an async flush. + // If additional callbacks are queued before the queue is flushed, they + // will be processed by this flush that we are scheduling. + if (customSchedulerFn) { + customSchedulerFn(flush); + } else { + scheduleFlush(); + } + } +}; + +function setScheduler(scheduleFn) { + customSchedulerFn = scheduleFn; +} + +function setAsap(asapFn) { + asap = asapFn; +} + +var browserWindow = typeof window !== 'undefined' ? window : undefined; +var browserGlobal = browserWindow || {}; +var BrowserMutationObserver = browserGlobal.MutationObserver || browserGlobal.WebKitMutationObserver; +var isNode = typeof self === 'undefined' && typeof process !== 'undefined' && ({}).toString.call(process) === '[object process]'; + +// test for web worker but not in IE10 +var isWorker = typeof Uint8ClampedArray !== 'undefined' && typeof importScripts !== 'undefined' && typeof MessageChannel !== 'undefined'; + +// node +function useNextTick() { + // node version 0.10.x displays a deprecation warning when nextTick is used recursively + // see https://github.com/cujojs/when/issues/410 for details + return function () { + return process.nextTick(flush); + }; +} + +// vertx +function useVertxTimer() { + if (typeof vertxNext !== 'undefined') { + return function () { + vertxNext(flush); + }; + } + + return useSetTimeout(); +} + +function useMutationObserver() { + var iterations = 0; + var observer = new BrowserMutationObserver(flush); + var node = document.createTextNode(''); + observer.observe(node, { characterData: true }); + + return function () { + node.data = iterations = ++iterations % 2; + }; +} + +// web worker +function useMessageChannel() { + var channel = new MessageChannel(); + channel.port1.onmessage = flush; + return function () { + return channel.port2.postMessage(0); + }; +} + +function useSetTimeout() { + // Store setTimeout reference so es6-promise will be unaffected by + // other code modifying setTimeout (like sinon.useFakeTimers()) + var globalSetTimeout = setTimeout; + return function () { + return globalSetTimeout(flush, 1); + }; +} + +var queue = new Array(1000); +function flush() { + for (var i = 0; i < len; i += 2) { + var callback = queue[i]; + var arg = queue[i + 1]; + + callback(arg); + + queue[i] = undefined; + queue[i + 1] = undefined; + } + + len = 0; +} + +function attemptVertx() { + try { + var r = require; + var vertx = r('vertx'); + vertxNext = vertx.runOnLoop || vertx.runOnContext; + return useVertxTimer(); + } catch (e) { + return useSetTimeout(); + } +} + +var scheduleFlush = undefined; +// Decide what async method to use to triggering processing of queued callbacks: +if (isNode) { + scheduleFlush = useNextTick(); +} else if (BrowserMutationObserver) { + scheduleFlush = useMutationObserver(); +} else if (isWorker) { + scheduleFlush = useMessageChannel(); +} else if (browserWindow === undefined && typeof require === 'function') { + scheduleFlush = attemptVertx(); +} else { + scheduleFlush = useSetTimeout(); +} + +function then(onFulfillment, onRejection) { + var _arguments = arguments; + + var parent = this; + + var child = new this.constructor(noop); + + if (child[PROMISE_ID] === undefined) { + makePromise(child); + } + + var _state = parent._state; + + if (_state) { + (function () { + var callback = _arguments[_state - 1]; + asap(function () { + return invokeCallback(_state, child, callback, parent._result); + }); + })(); + } else { + subscribe(parent, child, onFulfillment, onRejection); + } + + return child; +} + +/** + `Promise.resolve` returns a promise that will become resolved with the + passed `value`. It is shorthand for the following: + + ```javascript + let promise = new Promise(function(resolve, reject){ + resolve(1); + }); + + promise.then(function(value){ + // value === 1 + }); + ``` + + Instead of writing the above, your code now simply becomes the following: + + ```javascript + let promise = Promise.resolve(1); + + promise.then(function(value){ + // value === 1 + }); + ``` + + @method resolve + @static + @param {Any} value value that the returned promise will be resolved with + Useful for tooling. + @return {Promise} a promise that will become fulfilled with the given + `value` +*/ +function resolve(object) { + /*jshint validthis:true */ + var Constructor = this; + + if (object && typeof object === 'object' && object.constructor === Constructor) { + return object; + } + + var promise = new Constructor(noop); + _resolve(promise, object); + return promise; +} + +var PROMISE_ID = Math.random().toString(36).substring(16); + +function noop() {} + +var PENDING = void 0; +var FULFILLED = 1; +var REJECTED = 2; + +var GET_THEN_ERROR = new ErrorObject(); + +function selfFulfillment() { + return new TypeError("You cannot resolve a promise with itself"); +} + +function cannotReturnOwn() { + return new TypeError('A promises callback cannot return that same promise.'); +} + +function getThen(promise) { + try { + return promise.then; + } catch (error) { + GET_THEN_ERROR.error = error; + return GET_THEN_ERROR; + } +} + +function tryThen(then, value, fulfillmentHandler, rejectionHandler) { + try { + then.call(value, fulfillmentHandler, rejectionHandler); + } catch (e) { + return e; + } +} + +function handleForeignThenable(promise, thenable, then) { + asap(function (promise) { + var sealed = false; + var error = tryThen(then, thenable, function (value) { + if (sealed) { + return; + } + sealed = true; + if (thenable !== value) { + _resolve(promise, value); + } else { + fulfill(promise, value); + } + }, function (reason) { + if (sealed) { + return; + } + sealed = true; + + _reject(promise, reason); + }, 'Settle: ' + (promise._label || ' unknown promise')); + + if (!sealed && error) { + sealed = true; + _reject(promise, error); + } + }, promise); +} + +function handleOwnThenable(promise, thenable) { + if (thenable._state === FULFILLED) { + fulfill(promise, thenable._result); + } else if (thenable._state === REJECTED) { + _reject(promise, thenable._result); + } else { + subscribe(thenable, undefined, function (value) { + return _resolve(promise, value); + }, function (reason) { + return _reject(promise, reason); + }); + } +} + +function handleMaybeThenable(promise, maybeThenable, then$$) { + if (maybeThenable.constructor === promise.constructor && then$$ === then && maybeThenable.constructor.resolve === resolve) { + handleOwnThenable(promise, maybeThenable); + } else { + if (then$$ === GET_THEN_ERROR) { + _reject(promise, GET_THEN_ERROR.error); + } else if (then$$ === undefined) { + fulfill(promise, maybeThenable); + } else if (isFunction(then$$)) { + handleForeignThenable(promise, maybeThenable, then$$); + } else { + fulfill(promise, maybeThenable); + } + } +} + +function _resolve(promise, value) { + if (promise === value) { + _reject(promise, selfFulfillment()); + } else if (objectOrFunction(value)) { + handleMaybeThenable(promise, value, getThen(value)); + } else { + fulfill(promise, value); + } +} + +function publishRejection(promise) { + if (promise._onerror) { + promise._onerror(promise._result); + } + + publish(promise); +} + +function fulfill(promise, value) { + if (promise._state !== PENDING) { + return; + } + + promise._result = value; + promise._state = FULFILLED; + + if (promise._subscribers.length !== 0) { + asap(publish, promise); + } +} + +function _reject(promise, reason) { + if (promise._state !== PENDING) { + return; + } + promise._state = REJECTED; + promise._result = reason; + + asap(publishRejection, promise); +} + +function subscribe(parent, child, onFulfillment, onRejection) { + var _subscribers = parent._subscribers; + var length = _subscribers.length; + + parent._onerror = null; + + _subscribers[length] = child; + _subscribers[length + FULFILLED] = onFulfillment; + _subscribers[length + REJECTED] = onRejection; + + if (length === 0 && parent._state) { + asap(publish, parent); + } +} + +function publish(promise) { + var subscribers = promise._subscribers; + var settled = promise._state; + + if (subscribers.length === 0) { + return; + } + + var child = undefined, + callback = undefined, + detail = promise._result; + + for (var i = 0; i < subscribers.length; i += 3) { + child = subscribers[i]; + callback = subscribers[i + settled]; + + if (child) { + invokeCallback(settled, child, callback, detail); + } else { + callback(detail); + } + } + + promise._subscribers.length = 0; +} + +function ErrorObject() { + this.error = null; +} + +var TRY_CATCH_ERROR = new ErrorObject(); + +function tryCatch(callback, detail) { + try { + return callback(detail); + } catch (e) { + TRY_CATCH_ERROR.error = e; + return TRY_CATCH_ERROR; + } +} + +function invokeCallback(settled, promise, callback, detail) { + var hasCallback = isFunction(callback), + value = undefined, + error = undefined, + succeeded = undefined, + failed = undefined; + + if (hasCallback) { + value = tryCatch(callback, detail); + + if (value === TRY_CATCH_ERROR) { + failed = true; + error = value.error; + value = null; + } else { + succeeded = true; + } + + if (promise === value) { + _reject(promise, cannotReturnOwn()); + return; + } + } else { + value = detail; + succeeded = true; + } + + if (promise._state !== PENDING) { + // noop + } else if (hasCallback && succeeded) { + _resolve(promise, value); + } else if (failed) { + _reject(promise, error); + } else if (settled === FULFILLED) { + fulfill(promise, value); + } else if (settled === REJECTED) { + _reject(promise, value); + } +} + +function initializePromise(promise, resolver) { + try { + resolver(function resolvePromise(value) { + _resolve(promise, value); + }, function rejectPromise(reason) { + _reject(promise, reason); + }); + } catch (e) { + _reject(promise, e); + } +} + +var id = 0; +function nextId() { + return id++; +} + +function makePromise(promise) { + promise[PROMISE_ID] = id++; + promise._state = undefined; + promise._result = undefined; + promise._subscribers = []; +} + +function Enumerator(Constructor, input) { + this._instanceConstructor = Constructor; + this.promise = new Constructor(noop); + + if (!this.promise[PROMISE_ID]) { + makePromise(this.promise); + } + + if (isArray(input)) { + this._input = input; + this.length = input.length; + this._remaining = input.length; + + this._result = new Array(this.length); + + if (this.length === 0) { + fulfill(this.promise, this._result); + } else { + this.length = this.length || 0; + this._enumerate(); + if (this._remaining === 0) { + fulfill(this.promise, this._result); + } + } + } else { + _reject(this.promise, validationError()); + } +} + +function validationError() { + return new Error('Array Methods must be provided an Array'); +}; + +Enumerator.prototype._enumerate = function () { + var length = this.length; + var _input = this._input; + + for (var i = 0; this._state === PENDING && i < length; i++) { + this._eachEntry(_input[i], i); + } +}; + +Enumerator.prototype._eachEntry = function (entry, i) { + var c = this._instanceConstructor; + var resolve$$ = c.resolve; + + if (resolve$$ === resolve) { + var _then = getThen(entry); + + if (_then === then && entry._state !== PENDING) { + this._settledAt(entry._state, i, entry._result); + } else if (typeof _then !== 'function') { + this._remaining--; + this._result[i] = entry; + } else if (c === Promise) { + var promise = new c(noop); + handleMaybeThenable(promise, entry, _then); + this._willSettleAt(promise, i); + } else { + this._willSettleAt(new c(function (resolve$$) { + return resolve$$(entry); + }), i); + } + } else { + this._willSettleAt(resolve$$(entry), i); + } +}; + +Enumerator.prototype._settledAt = function (state, i, value) { + var promise = this.promise; + + if (promise._state === PENDING) { + this._remaining--; + + if (state === REJECTED) { + _reject(promise, value); + } else { + this._result[i] = value; + } + } + + if (this._remaining === 0) { + fulfill(promise, this._result); + } +}; + +Enumerator.prototype._willSettleAt = function (promise, i) { + var enumerator = this; + + subscribe(promise, undefined, function (value) { + return enumerator._settledAt(FULFILLED, i, value); + }, function (reason) { + return enumerator._settledAt(REJECTED, i, reason); + }); +}; + +/** + `Promise.all` accepts an array of promises, and returns a new promise which + is fulfilled with an array of fulfillment values for the passed promises, or + rejected with the reason of the first passed promise to be rejected. It casts all + elements of the passed iterable to promises as it runs this algorithm. + + Example: + + ```javascript + let promise1 = resolve(1); + let promise2 = resolve(2); + let promise3 = resolve(3); + let promises = [ promise1, promise2, promise3 ]; + + Promise.all(promises).then(function(array){ + // The array here would be [ 1, 2, 3 ]; + }); + ``` + + If any of the `promises` given to `all` are rejected, the first promise + that is rejected will be given as an argument to the returned promises's + rejection handler. For example: + + Example: + + ```javascript + let promise1 = resolve(1); + let promise2 = reject(new Error("2")); + let promise3 = reject(new Error("3")); + let promises = [ promise1, promise2, promise3 ]; + + Promise.all(promises).then(function(array){ + // Code here never runs because there are rejected promises! + }, function(error) { + // error.message === "2" + }); + ``` + + @method all + @static + @param {Array} entries array of promises + @param {String} label optional string for labeling the promise. + Useful for tooling. + @return {Promise} promise that is fulfilled when all `promises` have been + fulfilled, or rejected if any of them become rejected. + @static +*/ +function all(entries) { + return new Enumerator(this, entries).promise; +} + +/** + `Promise.race` returns a new promise which is settled in the same way as the + first passed promise to settle. + + Example: + + ```javascript + let promise1 = new Promise(function(resolve, reject){ + setTimeout(function(){ + resolve('promise 1'); + }, 200); + }); + + let promise2 = new Promise(function(resolve, reject){ + setTimeout(function(){ + resolve('promise 2'); + }, 100); + }); + + Promise.race([promise1, promise2]).then(function(result){ + // result === 'promise 2' because it was resolved before promise1 + // was resolved. + }); + ``` + + `Promise.race` is deterministic in that only the state of the first + settled promise matters. For example, even if other promises given to the + `promises` array argument are resolved, but the first settled promise has + become rejected before the other promises became fulfilled, the returned + promise will become rejected: + + ```javascript + let promise1 = new Promise(function(resolve, reject){ + setTimeout(function(){ + resolve('promise 1'); + }, 200); + }); + + let promise2 = new Promise(function(resolve, reject){ + setTimeout(function(){ + reject(new Error('promise 2')); + }, 100); + }); + + Promise.race([promise1, promise2]).then(function(result){ + // Code here never runs + }, function(reason){ + // reason.message === 'promise 2' because promise 2 became rejected before + // promise 1 became fulfilled + }); + ``` + + An example real-world use case is implementing timeouts: + + ```javascript + Promise.race([ajax('foo.json'), timeout(5000)]) + ``` + + @method race + @static + @param {Array} promises array of promises to observe + Useful for tooling. + @return {Promise} a promise which settles in the same way as the first passed + promise to settle. +*/ +function race(entries) { + /*jshint validthis:true */ + var Constructor = this; + + if (!isArray(entries)) { + return new Constructor(function (_, reject) { + return reject(new TypeError('You must pass an array to race.')); + }); + } else { + return new Constructor(function (resolve, reject) { + var length = entries.length; + for (var i = 0; i < length; i++) { + Constructor.resolve(entries[i]).then(resolve, reject); + } + }); + } +} + +/** + `Promise.reject` returns a promise rejected with the passed `reason`. + It is shorthand for the following: + + ```javascript + let promise = new Promise(function(resolve, reject){ + reject(new Error('WHOOPS')); + }); + + promise.then(function(value){ + // Code here doesn't run because the promise is rejected! + }, function(reason){ + // reason.message === 'WHOOPS' + }); + ``` + + Instead of writing the above, your code now simply becomes the following: + + ```javascript + let promise = Promise.reject(new Error('WHOOPS')); + + promise.then(function(value){ + // Code here doesn't run because the promise is rejected! + }, function(reason){ + // reason.message === 'WHOOPS' + }); + ``` + + @method reject + @static + @param {Any} reason value that the returned promise will be rejected with. + Useful for tooling. + @return {Promise} a promise rejected with the given `reason`. +*/ +function reject(reason) { + /*jshint validthis:true */ + var Constructor = this; + var promise = new Constructor(noop); + _reject(promise, reason); + return promise; +} + +function needsResolver() { + throw new TypeError('You must pass a resolver function as the first argument to the promise constructor'); +} + +function needsNew() { + throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function."); +} + +/** + Promise objects represent the eventual result of an asynchronous operation. The + primary way of interacting with a promise is through its `then` method, which + registers callbacks to receive either a promise's eventual value or the reason + why the promise cannot be fulfilled. + + Terminology + ----------- + + - `promise` is an object or function with a `then` method whose behavior conforms to this specification. + - `thenable` is an object or function that defines a `then` method. + - `value` is any legal JavaScript value (including undefined, a thenable, or a promise). + - `exception` is a value that is thrown using the throw statement. + - `reason` is a value that indicates why a promise was rejected. + - `settled` the final resting state of a promise, fulfilled or rejected. + + A promise can be in one of three states: pending, fulfilled, or rejected. + + Promises that are fulfilled have a fulfillment value and are in the fulfilled + state. Promises that are rejected have a rejection reason and are in the + rejected state. A fulfillment value is never a thenable. + + Promises can also be said to *resolve* a value. If this value is also a + promise, then the original promise's settled state will match the value's + settled state. So a promise that *resolves* a promise that rejects will + itself reject, and a promise that *resolves* a promise that fulfills will + itself fulfill. + + + Basic Usage: + ------------ + + ```js + let promise = new Promise(function(resolve, reject) { + // on success + resolve(value); + + // on failure + reject(reason); + }); + + promise.then(function(value) { + // on fulfillment + }, function(reason) { + // on rejection + }); + ``` + + Advanced Usage: + --------------- + + Promises shine when abstracting away asynchronous interactions such as + `XMLHttpRequest`s. + + ```js + function getJSON(url) { + return new Promise(function(resolve, reject){ + let xhr = new XMLHttpRequest(); + + xhr.open('GET', url); + xhr.onreadystatechange = handler; + xhr.responseType = 'json'; + xhr.setRequestHeader('Accept', 'application/json'); + xhr.send(); + + function handler() { + if (this.readyState === this.DONE) { + if (this.status === 200) { + resolve(this.response); + } else { + reject(new Error('getJSON: `' + url + '` failed with status: [' + this.status + ']')); + } + } + }; + }); + } + + getJSON('/posts.json').then(function(json) { + // on fulfillment + }, function(reason) { + // on rejection + }); + ``` + + Unlike callbacks, promises are great composable primitives. + + ```js + Promise.all([ + getJSON('/posts'), + getJSON('/comments') + ]).then(function(values){ + values[0] // => postsJSON + values[1] // => commentsJSON + + return values; + }); + ``` + + @class Promise + @param {function} resolver + Useful for tooling. + @constructor +*/ +function Promise(resolver) { + this[PROMISE_ID] = nextId(); + this._result = this._state = undefined; + this._subscribers = []; + + if (noop !== resolver) { + typeof resolver !== 'function' && needsResolver(); + this instanceof Promise ? initializePromise(this, resolver) : needsNew(); + } +} + +Promise.all = all; +Promise.race = race; +Promise.resolve = resolve; +Promise.reject = reject; +Promise._setScheduler = setScheduler; +Promise._setAsap = setAsap; +Promise._asap = asap; + +Promise.prototype = { + constructor: Promise, + + /** + The primary way of interacting with a promise is through its `then` method, + which registers callbacks to receive either a promise's eventual value or the + reason why the promise cannot be fulfilled. + + ```js + findUser().then(function(user){ + // user is available + }, function(reason){ + // user is unavailable, and you are given the reason why + }); + ``` + + Chaining + -------- + + The return value of `then` is itself a promise. This second, 'downstream' + promise is resolved with the return value of the first promise's fulfillment + or rejection handler, or rejected if the handler throws an exception. + + ```js + findUser().then(function (user) { + return user.name; + }, function (reason) { + return 'default name'; + }).then(function (userName) { + // If `findUser` fulfilled, `userName` will be the user's name, otherwise it + // will be `'default name'` + }); + + findUser().then(function (user) { + throw new Error('Found user, but still unhappy'); + }, function (reason) { + throw new Error('`findUser` rejected and we're unhappy'); + }).then(function (value) { + // never reached + }, function (reason) { + // if `findUser` fulfilled, `reason` will be 'Found user, but still unhappy'. + // If `findUser` rejected, `reason` will be '`findUser` rejected and we're unhappy'. + }); + ``` + If the downstream promise does not specify a rejection handler, rejection reasons will be propagated further downstream. + + ```js + findUser().then(function (user) { + throw new PedagogicalException('Upstream error'); + }).then(function (value) { + // never reached + }).then(function (value) { + // never reached + }, function (reason) { + // The `PedgagocialException` is propagated all the way down to here + }); + ``` + + Assimilation + ------------ + + Sometimes the value you want to propagate to a downstream promise can only be + retrieved asynchronously. This can be achieved by returning a promise in the + fulfillment or rejection handler. The downstream promise will then be pending + until the returned promise is settled. This is called *assimilation*. + + ```js + findUser().then(function (user) { + return findCommentsByAuthor(user); + }).then(function (comments) { + // The user's comments are now available + }); + ``` + + If the assimliated promise rejects, then the downstream promise will also reject. + + ```js + findUser().then(function (user) { + return findCommentsByAuthor(user); + }).then(function (comments) { + // If `findCommentsByAuthor` fulfills, we'll have the value here + }, function (reason) { + // If `findCommentsByAuthor` rejects, we'll have the reason here + }); + ``` + + Simple Example + -------------- + + Synchronous Example + + ```javascript + let result; + + try { + result = findResult(); + // success + } catch(reason) { + // failure + } + ``` + + Errback Example + + ```js + findResult(function(result, err){ + if (err) { + // failure + } else { + // success + } + }); + ``` + + Promise Example; + + ```javascript + findResult().then(function(result){ + // success + }, function(reason){ + // failure + }); + ``` + + Advanced Example + -------------- + + Synchronous Example + + ```javascript + let author, books; + + try { + author = findAuthor(); + books = findBooksByAuthor(author); + // success + } catch(reason) { + // failure + } + ``` + + Errback Example + + ```js + + function foundBooks(books) { + + } + + function failure(reason) { + + } + + findAuthor(function(author, err){ + if (err) { + failure(err); + // failure + } else { + try { + findBoooksByAuthor(author, function(books, err) { + if (err) { + failure(err); + } else { + try { + foundBooks(books); + } catch(reason) { + failure(reason); + } + } + }); + } catch(error) { + failure(err); + } + // success + } + }); + ``` + + Promise Example; + + ```javascript + findAuthor(). + then(findBooksByAuthor). + then(function(books){ + // found books + }).catch(function(reason){ + // something went wrong + }); + ``` + + @method then + @param {Function} onFulfilled + @param {Function} onRejected + Useful for tooling. + @return {Promise} + */ + then: then, + + /** + `catch` is simply sugar for `then(undefined, onRejection)` which makes it the same + as the catch block of a try/catch statement. + + ```js + function findAuthor(){ + throw new Error('couldn't find that author'); + } + + // synchronous + try { + findAuthor(); + } catch(reason) { + // something went wrong + } + + // async with promises + findAuthor().catch(function(reason){ + // something went wrong + }); + ``` + + @method catch + @param {Function} onRejection + Useful for tooling. + @return {Promise} + */ + 'catch': function _catch(onRejection) { + return this.then(null, onRejection); + } +}; + +function polyfill() { + var local = undefined; + + if (typeof global !== 'undefined') { + local = global; + } else if (typeof self !== 'undefined') { + local = self; + } else { + try { + local = Function('return this')(); + } catch (e) { + throw new Error('polyfill failed because global object is unavailable in this environment'); + } + } + + var P = local.Promise; + + if (P) { + var promiseToString = null; + try { + promiseToString = Object.prototype.toString.call(P.resolve()); + } catch (e) { + // silently ignored + } + + if (promiseToString === '[object Promise]' && !P.cast) { + return; + } + } + + local.Promise = Promise; +} + +// Strange compat.. +Promise.polyfill = polyfill; +Promise.Promise = Promise; + +return Promise; + +}))); + +ES6Promise.polyfill(); +//# sourceMappingURL=es6-promise.auto.map \ No newline at end of file diff --git a/src/js/test/TestRegister.js b/src/js/test/TestRegister.js deleted file mode 100644 index 2e49d75a..00000000 --- a/src/js/test/TestRegister.js +++ /dev/null @@ -1,106 +0,0 @@ -/** - * TestRegister.js - * - * This is so individual files can register their tests in one place, and - * ensure that they will get run by the frontend. - * - * @author tlwr [toby@toby.codes - * - * @copyright Crown Copyright 2017 - * @license Apache-2.0 - * - */ -(function() { - /** - * Add a list of tests to the register. - * - * @class - */ - function TestRegister() { - this.tests = []; - } - - /** - * Add a list of tests to the register. - * - * @param {Object[]} tests - */ - TestRegister.prototype.addTests = function(tests) { - this.tests = this.tests.concat(tests.map(function(test) { - - test.status = "waiting"; - test.output = ""; - - return test; - })); - }; - - /** - * Returns the list of tests. - * - * @returns {Object[]} tests - */ - TestRegister.prototype.getTests = function() { - return this.tests; - }; - - /** - * Runs all the tests in the register and updates the state of each test. - * - */ - TestRegister.prototype.runTests = function() { - this.tests.forEach(function(test, i) { - var chef = new Chef(); - - // This resolve is to not instantly break when async operations are - // supported. Marked as TODO. - Promise.resolve(chef.bake( - test.input, - test.recipeConfig, - {}, - 0, - 0 - )) - .then(function(result) { - if (result.error) { - if (test.expectedError) { - test.status = "passing"; - } else { - test.status = "erroring"; - test.output = [ - "Erroring", - "-------", - result.error.displayStr, - ].join("\n"); - } - } else { - if (test.expectedError) { - test.status = "failing"; - test.output = [ - "Failing", - "-------", - "Expected an error but did not receive one.", - ].join("\n"); - } else if (result.result === test.expectedOutput) { - test.status = "passing"; - } else { - test.status = "failing"; - test.output = [ - "Failing", - "-------", - "Expected", - "-------", - test.expectedOutput, - "Actual", - "-------", - result.result, - ].join("\n"); - } - } - }); - }); - }; - - // Singleton TestRegister, keeping things simple and obvious. - window.TestRegister = new TestRegister(); -})(); diff --git a/src/js/test/views/html/main.js b/src/js/test/views/html/main.js deleted file mode 100644 index aaf364fd..00000000 --- a/src/js/test/views/html/main.js +++ /dev/null @@ -1,58 +0,0 @@ -/** - * main.js - * - * Simple VueJS app for running all the tests and displaying some basic stats. - * @author tlwr [toby@toby.codes] - * @copyright Crown Copyright 2017 - * @license Apache-2.0 - * - */ -(function() { - Vue.component("test-status-icon", { - template: "#test-status-icon-template", - props: ["status"], - - methods: { - getIcon: function() { - var icons = { - waiting: "⌚", - loading: "⚡", - passing: "✔️️", - failing: "❌", - erroring: "☠️", - }; - - return icons[this.status]; - } - }, - }); - - Vue.component("test-stats", { - template: "#test-stats-template", - props: ["tests"], - - methods: { - countTestsWithStatus: function(status) { - return this.tests.filter(function(test) { - return test.status === status; - }).length; - }, - }, - }); - - Vue.component("tests", { - template: "#tests-template", - props: ["tests"], - }); - - window.TestRunner = new Vue({ - el: "main", - data: { - tests: TestRegister.getTests(), - }, - - mounted: function() { - TestRegister.runTests(); - }, - }); -})(); diff --git a/test/NodeRunner.js b/test/NodeRunner.js new file mode 100644 index 00000000..e1c0d9dd --- /dev/null +++ b/test/NodeRunner.js @@ -0,0 +1,24 @@ +/** + * NodeRunner.js + * + * The purpose of this file is to execute via PhantomJS the file + * PhantomRunner.js, because PhantomJS is managed by node. + * + * @author tlwr [toby@toby.codes + * + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + * + */ +var path = require("path"); +var phantomjs = require("phantomjs-prebuilt"); + +var phantomEntryPoint = path.join(__dirname, "PhantomRunner.js"); +var program = phantomjs.exec(phantomEntryPoint); + +program.stdout.pipe(process.stdout); +program.stderr.pipe(process.stderr); + +program.on("exit", function(status) { + process.exit(status); +}); diff --git a/test/PhantomRunner.js b/test/PhantomRunner.js new file mode 100644 index 00000000..26ce0f2c --- /dev/null +++ b/test/PhantomRunner.js @@ -0,0 +1,80 @@ +/** + * PhantomRunner.js + * + * This file navigates to build/test/index.html and logs the test results. + * + * @author tlwr [toby@toby.codes + * + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + * + */ +var page = require("webpage").create(); + +var allTestsPassing = true; +var testStatusCounts = { + total: 0, +}; + +function statusToIcon(status) { + var icons = { + erroring: "🔥", + failing: "❌", + passing: "✔️️", + }; + return icons[status] || "?"; +} + +page.onCallback = function(messageType) { + if (messageType === "testResult") { + var testResult = arguments[1]; + + allTestsPassing = allTestsPassing && testResult.status === "passing"; + var newCount = (testStatusCounts[testResult.status] || 0) + 1; + testStatusCounts[testResult.status] = newCount; + testStatusCounts.total += 1; + + console.log([ + statusToIcon(testResult.status), + testResult.test.name + ].join(" ")); + + if (testResult.output) { + console.log( + testResult.output + .trim() + .replace(/^/, "\t") + .replace(/\n/g, "\n\t") + ); + } + } else if (messageType === "exit") { + + console.log("\n"); + + for (var testStatus in testStatusCounts) { + var count = testStatusCounts[testStatus]; + if (count > 0) { + console.log(testStatus.toUpperCase(), count); + } + } + + if (!allTestsPassing) { + console.log("\n") + console.log("Not all tests are passing"); + } + + phantom.exit(allTestsPassing ? 0 : 1); + } +}; + +page.open("file:///home/toby/Code/CyberChef/build/test/index.html", function(status) { + if (status !== "success") { + console.log("STATUS", status); + phantom.exit(1); + } +}); + +setTimeout(function() { + // Timeout + phantom.exit(1); +}, 10 * 1000); diff --git a/test/TestRegister.js b/test/TestRegister.js new file mode 100644 index 00000000..e3ecbcb3 --- /dev/null +++ b/test/TestRegister.js @@ -0,0 +1,96 @@ +/** + * TestRegister.js + * + * This is so individual files can register their tests in one place, and + * ensure that they will get run by the frontend. + * + * @author tlwr [toby@toby.codes + * + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + * + */ +(function() { + /** + * Add a list of tests to the register. + * + * @class + */ + function TestRegister() { + this.tests = []; + } + + /** + * Add a list of tests to the register. + * + * @param {Object[]} tests + */ + TestRegister.prototype.addTests = function(tests) { + this.tests = this.tests.concat(tests); + }; + + /** + * Returns the list of tests. + * + * @returns {Object[]} tests + */ + TestRegister.prototype.getTests = function() { + return this.tests; + }; + + /** + * Runs all the tests in the register. + * + */ + TestRegister.prototype.runTests = function() { + return Promise.all( + this.tests.map(function(test, i) { + var chef = new Chef(); + + return Promise.resolve(chef.bake( + test.input, + test.recipeConfig, + {}, + 0, + 0 + )) + .then(function(result) { + var ret = { + test: test, + status: null, + output: null, + }; + + if (result.error) { + if (test.expectedError) { + ret.status = "passing"; + } else { + ret.status = "erroring"; + ret.output = result.error.displayStr; + } + } else { + if (test.expectedError) { + ret.status = "failing"; + ret.output = "Expected an error but did not receive one."; + } else if (result.result === test.expectedOutput) { + ret.status = "passing"; + } else { + ret.status = "failing"; + ret.output = [ + "Expected", + "\t" + test.expectedOutput.replace(/\n/g, "\n\t"), + "Received", + "\t" + result.result.replace(/\n/g, "\n\t"), + ].join("\n"); + } + } + + return ret; + }); + }) + ); + }; + + // Singleton TestRegister, keeping things simple and obvious. + window.TestRegister = new TestRegister(); +})(); diff --git a/test/TestRunner.js b/test/TestRunner.js new file mode 100644 index 00000000..5984f731 --- /dev/null +++ b/test/TestRunner.js @@ -0,0 +1,38 @@ +/** + * TestRunner.js + * + * This is for actually running the tests in the test register. + * + * @author tlwr [toby@toby.codes + * + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + * + */ +(function() { + document.addEventListener("DOMContentLoaded", function() { + TestRegister.runTests() + .then(function(results) { + results.forEach(function(testResult) { + if (typeof window.callPhantom === "function") { + window.callPhantom( + "testResult", + testResult + ); + } else { + var output = [ + "----------", + testResult.test.name, + testResult.status, + testResult.output, + ].join("
"); + document.body.innerHTML += "
" + output + "
"; + } + }); + + if (typeof window.callPhantom === "function") { + window.callPhantom("exit"); + } + }); + }); +})(); diff --git a/src/js/test/tests/core.js b/test/tests/core.js similarity index 52% rename from src/js/test/tests/core.js rename to test/tests/core.js index 28ec60a3..fe596cb0 100644 --- a/src/js/test/tests/core.js +++ b/test/tests/core.js @@ -6,47 +6,47 @@ * */ TestRegister.addTests([ - { - name: "Example error", - input: "1\n2\na\n4", - expectedOutput: "1\n2\n3\n4", - recipeConfig: [ - { - op: "Fork", - args: ["\n", "\n", false], - }, - { - op: "To Base", - args: [16], - }, - ], - }, - { - name: "Example non-error when error was expected", - input: "1", - expectedError: true, - recipeConfig: [ - { - op: "To Base", - args: [16], - }, - ], - }, - { - name: "Example fail", - input: "1\n2\na\n4", - expectedOutput: "1\n2\n3\n4", - recipeConfig: [ - { - op: "Fork", - args: ["\n", "\n", true], - }, - { - op: "To Base", - args: [16], - }, - ], - }, + //{ + // name: "Example error", + // input: "1\n2\na\n4", + // expectedOutput: "1\n2\n3\n4", + // recipeConfig: [ + // { + // op: "Fork", + // args: ["\n", "\n", false], + // }, + // { + // op: "To Base", + // args: [16], + // }, + // ], + //}, + //{ + // name: "Example non-error when error was expected", + // input: "1", + // expectedError: true, + // recipeConfig: [ + // { + // op: "To Base", + // args: [16], + // }, + // ], + //}, + //{ + // name: "Example fail", + // input: "1\n2\na\n4", + // expectedOutput: "1\n2\n3\n4", + // recipeConfig: [ + // { + // op: "Fork", + // args: ["\n", "\n", true], + // }, + // { + // op: "To Base", + // args: [16], + // }, + // ], + //}, { name: "Fork: nothing", input: "", diff --git a/src/js/test/tests/operations/Base58.js b/test/tests/operations/Base58.js similarity index 100% rename from src/js/test/tests/operations/Base58.js rename to test/tests/operations/Base58.js diff --git a/src/js/test/tests/operations/MorseCode.js b/test/tests/operations/MorseCode.js similarity index 100% rename from src/js/test/tests/operations/MorseCode.js rename to test/tests/operations/MorseCode.js