test-agent
Advanced tools
Comparing version 0.0.10 to 0.1.0
var Reporter = require(__dirname + '/../mocha/reporter'); | ||
/** | ||
* The usual merge function. | ||
* Takes multiple objects and merges | ||
* them in order into a new object. | ||
* | ||
* If I need this elsewhere should probably be a utility. | ||
* | ||
* @param {...Object} args any number of objects to merge. | ||
* @return {Object} result of merges. | ||
*/ | ||
function merge() { | ||
var args = Array.prototype.slice.call(arguments), | ||
result = {}; | ||
args.forEach(function(object) { | ||
var key; | ||
for (key in object) { | ||
if (object.hasOwnProperty(key)) { | ||
result[key] = object[key]; | ||
} | ||
} | ||
}); | ||
return result; | ||
} | ||
/** | ||
* REQUIRES: responder | ||
@@ -8,2 +34,5 @@ * | ||
* to stream reports to the servers console. | ||
* | ||
* @constructor | ||
* @param {Object} options see mocha/reporter for options. | ||
*/ | ||
@@ -16,7 +45,20 @@ function Mocha(options) { | ||
/** | ||
* Title for simulated syntax error test failures. | ||
* | ||
* @this | ||
* @type String | ||
*/ | ||
syntaxErrorTitle: 'Syntax Error', | ||
enhance: function enhance(server) { | ||
server.on('test data', this._onTestData.bind(this)); | ||
server.on('error', this._onError.bind(this)); | ||
this.reporter.on('start', this._onRunnerStart.bind(this, server)); | ||
}, | ||
_onError: function _onError(data) { | ||
this.savedError = data; | ||
}, | ||
_onTestData: function _onTestData(data, socket) { | ||
@@ -26,4 +68,54 @@ this.reporter.respond(data); | ||
/** | ||
* Emits a fake test event for a syntax error. | ||
* | ||
* @this | ||
* @param {Object} error error object. | ||
* @param {String} error.message error message. | ||
* @param {String} error.filename file error occurred in. | ||
* @param {String} error.lineno line number of error. | ||
*/ | ||
emitSyntaxError: function emitError(error) { | ||
var baseEvent, | ||
errorMessage, | ||
errObject; | ||
baseEvent = { | ||
title: this.syntaxErrorTitle, | ||
fullTitle: this.syntaxErrorTitle | ||
}; | ||
errorMessage = [ | ||
error.message, | ||
'in file', | ||
error.filename + '#' + error.lineno | ||
].join(' '); | ||
errObject = { | ||
message: errorMessage, | ||
stack: errorMessage, | ||
//best guess | ||
type: 'syntax error', | ||
//make mocha reporters happy | ||
expected: null, | ||
actual: null | ||
}; | ||
this.reporter.respond(['test', baseEvent]); | ||
this.reporter.respond(['fail', merge(baseEvent, { | ||
err: errObject, | ||
state: 'failed' | ||
})]); | ||
this.reporter.respond(['test end', merge(baseEvent, { | ||
state: 'failed' | ||
})]); | ||
}, | ||
_onRunnerStart: function _onRunnerStart(server, runner) { | ||
server.emit('test runner', runner); | ||
if (this.savedError) { | ||
this.emitSyntaxError(this.savedError); | ||
this.savedError = undefined; | ||
} | ||
} | ||
@@ -30,0 +122,0 @@ |
@@ -20,6 +20,27 @@ /** | ||
enhance: function enhance(server) { | ||
server.on('test runner', this.growl.bind(this, server)); | ||
server.on('test runner', this.reportOnTests.bind(this, server)); | ||
server.on('error', this.reportOnError.bind(this, server)); | ||
}, | ||
growl: function growl(server, proxy) { | ||
reportOnError: function reportOnError(server, data) { | ||
var file = data.filename, | ||
line = data.lineno, | ||
message = data.message, | ||
details, | ||
notification; | ||
notification = [ | ||
message, 'in file', | ||
file, '#' + line | ||
].join(' '); | ||
this.notify( | ||
notification, { | ||
title: 'Syntax Error', | ||
image: this.images.fail | ||
} | ||
); | ||
}, | ||
reportOnTests: function reportOnTests(server, proxy) { | ||
var notify = this.notify, | ||
@@ -26,0 +47,0 @@ images = this.images, |
@@ -35,2 +35,4 @@ (function(window) { | ||
this.testRunner = options.testRunner; | ||
//event proxy | ||
this.sandbox.on('error', this.emit.bind(this, 'sandbox error')); | ||
}; | ||
@@ -67,2 +69,3 @@ | ||
var self = this; | ||
this.sandbox.run(function onSandboxRun() { | ||
@@ -69,0 +72,0 @@ self.loader.targetWindow = this; |
@@ -40,2 +40,5 @@ (function(window) { | ||
this.element = options.element || document.querySelector(selector); | ||
this.errorElement = document.createElement('div'); | ||
this.errorElement.className = 'error ' + this.HIDDEN; | ||
this.element.appendChild(this.errorElement); | ||
this.queue = {}; | ||
@@ -46,6 +49,16 @@ }; | ||
TestUi.prototype = { | ||
HIDDEN: 'hidden', | ||
templates: { | ||
testList: '<ul class="test-agent"></ul>', | ||
testList: '<ul class="test-list"></ul>', | ||
testItem: '<li data-url="%s">%s</li>', | ||
testRun: '<button class="run-tests">Execute</button>' | ||
testRun: '<button class="run-tests">Execute</button>', | ||
error: [ | ||
'<h1>Critical Error</h1>', | ||
'<p><span class="error">%0s</span> in file ', | ||
'<span class="file">', | ||
'<a href="%1s">%1s</a>', | ||
'</span> line #', | ||
'<span class="line">%2s</span>' | ||
].join('') | ||
}, | ||
@@ -56,7 +69,35 @@ | ||
this.worker.on('config', this.onConfig.bind(this)); | ||
this.worker.on('sandbox', this.onSandbox.bind(this)); | ||
this.worker.on('sandbox error', this.onSandboxError.bind(this)); | ||
}, | ||
onSandbox: function onSandbox() { | ||
var error = this.errorElement; | ||
if (error) { | ||
if (error.className.indexOf(this.HIDDEN) === -1) { | ||
error.className += ' ' + this.HIDDEN; | ||
} | ||
} | ||
}, | ||
onSandboxError: function onSandboxError(data) { | ||
var element = this.element, | ||
error = this.errorElement, | ||
message = data.message, | ||
file = data.filename, | ||
line = data.lineno; | ||
error.className = error.className.replace(' hidden', ''); | ||
error.innerHTML = format( | ||
this.templates.error, | ||
message, | ||
file, | ||
line | ||
); | ||
}, | ||
onConfig: function onConfig(data) { | ||
//purge elements | ||
var elements = this.element.getElementsByTagName('*'), | ||
var elements = this.element.getElementsByTagName('test-list'), | ||
element, | ||
@@ -63,0 +104,0 @@ templates = this.templates, |
@@ -116,2 +116,5 @@ (function(window) { | ||
//url is cached we are good | ||
if (callback) { | ||
callback(); | ||
} | ||
return; | ||
@@ -118,0 +121,0 @@ } |
@@ -32,3 +32,4 @@ (function(window) { | ||
MochaReporter.console.log = function consoleLogShim() { | ||
var args = Array.prototype.slice.call(arguments); | ||
var args = Array.prototype.slice.call(arguments), | ||
message = TestAgent.format.apply(TestAgent, arguments); | ||
//real console log | ||
@@ -62,3 +63,3 @@ log.apply(this, arguments); | ||
MochaReporter.send( | ||
JSON.stringify(['log', {messages: messages, stack: stack}]) | ||
JSON.stringify(['log', {messages: [message], stack: stack}]) | ||
); | ||
@@ -65,0 +66,0 @@ }; |
(function(exports) { | ||
'use strict'; | ||
if (typeof(exports.TestAgent) === 'undefined') { | ||
@@ -5,0 +5,0 @@ exports.TestAgent = {}; |
@@ -10,80 +10,97 @@ (function(window) { | ||
var Sandbox = window.TestAgent.Sandbox = function Sandbox(url) { | ||
TestAgent.Responder.call(this); | ||
this.url = url; | ||
}; | ||
Sandbox.prototype = { | ||
var proto = Sandbox.prototype = Object.create( | ||
TestAgent.Responder.prototype | ||
); | ||
_element: null, | ||
proto._element = null; | ||
/** | ||
* @type Boolean | ||
* | ||
* True when sandbox is ready | ||
*/ | ||
ready: false, | ||
/** | ||
* @type Boolean | ||
* | ||
* True when sandbox is ready | ||
*/ | ||
proto.ready = false; | ||
/** | ||
* URL for the iframe sandbox. | ||
* | ||
* @type String | ||
*/ | ||
url: null, | ||
/** | ||
* URL for the iframe sandbox. | ||
* | ||
* @type String | ||
*/ | ||
proto.url = null; | ||
/** | ||
* Returns iframe element. | ||
* | ||
* | ||
* @type DOMElement | ||
*/ | ||
getElement: function getElement() { | ||
var iframe; | ||
if (!this._element) { | ||
iframe = this._element = window.document.createElement('iframe'); | ||
iframe.src = this.url + '?time=' + String(Date.now()); | ||
} | ||
return this._element; | ||
}, | ||
/** | ||
* Returns iframe element. | ||
* | ||
* | ||
* @type DOMElement | ||
*/ | ||
proto.getElement = function getElement() { | ||
var iframe; | ||
if (!this._element) { | ||
iframe = this._element = window.document.createElement('iframe'); | ||
iframe.src = this.url + '?time=' + String(Date.now()); | ||
} | ||
return this._element; | ||
}; | ||
run: function run(callback) { | ||
//cleanup old sandboxes | ||
this.destroy(); | ||
proto.run = function run(callback) { | ||
//cleanup old sandboxes | ||
this.destroy(); | ||
var element = this.getElement(), | ||
self = this; | ||
var element = this.getElement(), | ||
iframeWindow, | ||
self = this; | ||
//this must come before the listener | ||
window.document.body.appendChild(element); | ||
element.contentWindow.addEventListener('DOMContentLoaded', function() { | ||
self.ready = true; | ||
callback.call(this); | ||
//this must come before the listener | ||
window.document.body.appendChild(element); | ||
iframeWindow = element.contentWindow; | ||
iframeWindow.onerror = function(message, file, line) { | ||
self.emit('error', { | ||
message: message, | ||
//remove cache busting string | ||
filename: file.split('?time=')[0], | ||
lineno: line | ||
}); | ||
}, | ||
}; | ||
destroy: function destroy() { | ||
var el; | ||
iframeWindow.addEventListener('DOMContentLoaded', function() { | ||
self.ready = true; | ||
self.emit('ready', this); | ||
callback.call(this); | ||
}); | ||
if (!this.ready) { | ||
return false; | ||
} | ||
return iframeWindow; | ||
}; | ||
proto.destroy = function destroy() { | ||
var el; | ||
this.ready = false; | ||
if (!this.ready) { | ||
return false; | ||
} | ||
el = this.getElement(); | ||
el.parentNode.removeChild(el); | ||
this.ready = false; | ||
el = this.getElement(); | ||
el.parentNode.removeChild(el); | ||
return true; | ||
}, | ||
getWindow: function getWindow() { | ||
if (!this.ready) { | ||
return false; | ||
} | ||
return true; | ||
}; | ||
return this.getElement().contentWindow; | ||
proto.getWindow = function getWindow() { | ||
if (!this.ready) { | ||
return false; | ||
} | ||
return this.getElement().contentWindow; | ||
}; | ||
}(this)); | ||
{ | ||
"name": "test-agent", | ||
"version": "0.0.10", | ||
"version": "0.1.0", | ||
"author": "James Lal", | ||
@@ -5,0 +5,0 @@ "description": "execute client side tests from browser report back to cli", |
@@ -0,1 +1,385 @@ | ||
// Copyright Joyent, Inc. and other Node contributors. | ||
// | ||
// Permission is hereby granted, free of charge, to any person obtaining a | ||
// copy of this software and associated documentation files (the | ||
// "Software"), to deal in the Software without restriction, including | ||
// without limitation the rights to use, copy, modify, merge, publish, | ||
// distribute, sublicense, and/or sell copies of the Software, and to permit | ||
// persons to whom the Software is furnished to do so, subject to the | ||
// following conditions: | ||
// | ||
// The above copyright notice and this permission notice shall be included | ||
// in all copies or substantial portions of the Software. | ||
// | ||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS | ||
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | ||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN | ||
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, | ||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR | ||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE | ||
// USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
(function(window) { | ||
if (typeof(window.TestAgent) === 'undefined') { | ||
window.TestAgent = {}; | ||
} | ||
var exports = window.TestAgent; | ||
var formatRegExp = /%[sdj%]/g; | ||
exports.format = function(f) { | ||
if (typeof f !== 'string') { | ||
var objects = []; | ||
for (var i = 0; i < arguments.length; i++) { | ||
objects.push(inspect(arguments[i])); | ||
} | ||
return objects.join(' '); | ||
} | ||
var i = 1; | ||
var args = arguments; | ||
var len = args.length; | ||
var str = String(f).replace(formatRegExp, function(x) { | ||
if (x === '%%') return '%'; | ||
if (i >= len) return x; | ||
switch (x) { | ||
case '%s': return String(args[i++]); | ||
case '%d': return Number(args[i++]); | ||
case '%j': return JSON.stringify(args[i++]); | ||
default: | ||
return x; | ||
} | ||
}); | ||
for (var x = args[i]; i < len; x = args[++i]) { | ||
if (x === null || typeof x !== 'object') { | ||
str += ' ' + x; | ||
} else { | ||
str += ' ' + inspect(x); | ||
} | ||
} | ||
return str; | ||
}; | ||
/** | ||
* Echos the value of a value. Trys to print the value out | ||
* in the best way possible given the different types. | ||
* | ||
* @param {Object} obj The object to print out. | ||
* @param {Boolean} showHidden Flag that shows hidden (not enumerable) | ||
* properties of objects. | ||
* @param {Number} depth Depth in which to descend in object. Default is 2. | ||
* @param {Boolean} colors Flag to turn on ANSI escape codes to color the | ||
* output. Default is false (no coloring). | ||
*/ | ||
function inspect(obj, showHidden, depth, colors) { | ||
var ctx = { | ||
showHidden: showHidden, | ||
seen: [], | ||
stylize: colors ? stylizeWithColor : stylizeNoColor | ||
}; | ||
return formatValue(ctx, obj, (typeof depth === 'undefined' ? 2 : depth)); | ||
} | ||
exports.inspect = inspect; | ||
// http://en.wikipedia.org/wiki/ANSI_escape_code#graphics | ||
var colors = { | ||
'bold' : [1, 22], | ||
'italic' : [3, 23], | ||
'underline' : [4, 24], | ||
'inverse' : [7, 27], | ||
'white' : [37, 39], | ||
'grey' : [90, 39], | ||
'black' : [30, 39], | ||
'blue' : [34, 39], | ||
'cyan' : [36, 39], | ||
'green' : [32, 39], | ||
'magenta' : [35, 39], | ||
'red' : [31, 39], | ||
'yellow' : [33, 39] | ||
}; | ||
// Don't use 'blue' not visible on cmd.exe | ||
var styles = { | ||
'special': 'cyan', | ||
'number': 'yellow', | ||
'boolean': 'yellow', | ||
'undefined': 'grey', | ||
'null': 'bold', | ||
'string': 'green', | ||
'date': 'magenta', | ||
// "name": intentionally not styling | ||
'regexp': 'red' | ||
}; | ||
function stylizeWithColor(str, styleType) { | ||
var style = styles[styleType]; | ||
if (style) { | ||
return '\033[' + colors[style][0] + 'm' + str + | ||
'\033[' + colors[style][1] + 'm'; | ||
} else { | ||
return str; | ||
} | ||
} | ||
function stylizeNoColor(str, styleType) { | ||
return str; | ||
} | ||
function formatValue(ctx, value, recurseTimes) { | ||
// Provide a hook for user-specified inspect functions. | ||
// Check that value is an object with an inspect function on it | ||
if (value && typeof value.inspect === 'function' && | ||
// Filter out the util module, it's inspect function is special | ||
value.inspect !== exports.inspect && | ||
// Also filter out any prototype objects using the circular check. | ||
!(value.constructor && value.constructor.prototype === value)) { | ||
return value.inspect(recurseTimes); | ||
} | ||
// Primitive types cannot have properties | ||
var primitive = formatPrimitive(ctx, value); | ||
if (primitive) { | ||
return primitive; | ||
} | ||
// Look up the keys of the object. | ||
var visibleKeys = Object.keys(value); | ||
var keys = ctx.showHidden ? Object.getOwnPropertyNames(value) : visibleKeys; | ||
// Some type of object without properties can be shortcutted. | ||
if (keys.length === 0) { | ||
if (typeof value === 'function') { | ||
var name = value.name ? ': ' + value.name : ''; | ||
return ctx.stylize('[Function' + name + ']', 'special'); | ||
} | ||
if (isRegExp(value)) { | ||
return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); | ||
} | ||
if (isDate(value)) { | ||
return ctx.stylize(Date.prototype.toString.call(value), 'date'); | ||
} | ||
if (isError(value)) { | ||
return formatError(value); | ||
} | ||
} | ||
var base = '', array = false, braces = ['{', '}']; | ||
// Make Array say that they are Array | ||
if (isArray(value)) { | ||
array = true; | ||
braces = ['[', ']']; | ||
} | ||
// Make functions say that they are functions | ||
if (typeof value === 'function') { | ||
var n = value.name ? ': ' + value.name : ''; | ||
base = ' [Function' + n + ']'; | ||
} | ||
// Make RegExps say that they are RegExps | ||
if (isRegExp(value)) { | ||
base = ' ' + RegExp.prototype.toString.call(value); | ||
} | ||
// Make dates with properties first say the date | ||
if (isDate(value)) { | ||
base = ' ' + Date.prototype.toUTCString.call(value); | ||
} | ||
// Make error with message first say the error | ||
if (isError(value)) { | ||
base = ' ' + formatError(value); | ||
} | ||
if (keys.length === 0 && (!array || value.length == 0)) { | ||
return braces[0] + base + braces[1]; | ||
} | ||
if (recurseTimes < 0) { | ||
if (isRegExp(value)) { | ||
return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); | ||
} else { | ||
return ctx.stylize('[Object]', 'special'); | ||
} | ||
} | ||
ctx.seen.push(value); | ||
var output; | ||
if (array) { | ||
output = formatArray(ctx, value, recurseTimes, visibleKeys, keys); | ||
} else { | ||
output = keys.map(function(key) { | ||
return formatProperty( | ||
ctx, value, recurseTimes, visibleKeys, key, array | ||
); | ||
}); | ||
} | ||
ctx.seen.pop(); | ||
return reduceToSingleString(output, base, braces); | ||
} | ||
function formatPrimitive(ctx, value) { | ||
switch (typeof value) { | ||
case 'undefined': | ||
return ctx.stylize('undefined', 'undefined'); | ||
case 'string': | ||
var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '') | ||
.replace(/'/g, "\\'") | ||
.replace(/\\"/g, '"') + '\''; | ||
return ctx.stylize(simple, 'string'); | ||
case 'number': | ||
return ctx.stylize('' + value, 'number'); | ||
case 'boolean': | ||
return ctx.stylize('' + value, 'boolean'); | ||
} | ||
// For some reason typeof null is "object", so special case here. | ||
if (value === null) { | ||
return ctx.stylize('null', 'null'); | ||
} | ||
} | ||
function formatError(value) { | ||
return '[' + Error.prototype.toString.call(value) + ']'; | ||
} | ||
function formatArray(ctx, value, recurseTimes, visibleKeys, keys) { | ||
var output = []; | ||
for (var i = 0, l = value.length; i < l; ++i) { | ||
if (Object.prototype.hasOwnProperty.call(value, String(i))) { | ||
output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, | ||
String(i), true)); | ||
} else { | ||
output.push(''); | ||
} | ||
} | ||
keys.forEach(function(key) { | ||
if (!key.match(/^\d+$/)) { | ||
output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, | ||
key, true)); | ||
} | ||
}); | ||
return output; | ||
} | ||
function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) { | ||
var name, str, desc; | ||
desc = Object.getOwnPropertyDescriptor(value, key) || { value: value[key] }; | ||
if (desc.get) { | ||
if (desc.set) { | ||
str = ctx.stylize('[Getter/Setter]', 'special'); | ||
} else { | ||
str = ctx.stylize('[Getter]', 'special'); | ||
} | ||
} else { | ||
if (desc.set) { | ||
str = ctx.stylize('[Setter]', 'special'); | ||
} | ||
} | ||
if (visibleKeys.indexOf(key) < 0) { | ||
name = '[' + key + ']'; | ||
} | ||
if (!str) { | ||
if (ctx.seen.indexOf(desc.value) < 0) { | ||
if (recurseTimes === null) { | ||
str = formatValue(ctx, desc.value, null); | ||
} else { | ||
str = formatValue(ctx, desc.value, recurseTimes - 1); | ||
} | ||
if (str.indexOf('\n') > -1) { | ||
if (array) { | ||
str = str.split('\n').map(function(line) { | ||
return ' ' + line; | ||
}).join('\n').substr(2); | ||
} else { | ||
str = '\n' + str.split('\n').map(function(line) { | ||
return ' ' + line; | ||
}).join('\n'); | ||
} | ||
} | ||
} else { | ||
str = ctx.stylize('[Circular]', 'special'); | ||
} | ||
} | ||
if (typeof name === 'undefined') { | ||
if (array && key.match(/^\d+$/)) { | ||
return str; | ||
} | ||
name = JSON.stringify('' + key); | ||
if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) { | ||
name = name.substr(1, name.length - 2); | ||
name = ctx.stylize(name, 'name'); | ||
} else { | ||
name = name.replace(/'/g, "\\'") | ||
.replace(/\\"/g, '"') | ||
.replace(/(^"|"$)/g, "'"); | ||
name = ctx.stylize(name, 'string'); | ||
} | ||
} | ||
return name + ': ' + str; | ||
} | ||
function reduceToSingleString(output, base, braces) { | ||
var numLinesEst = 0; | ||
var length = output.reduce(function(prev, cur) { | ||
numLinesEst++; | ||
if (cur.indexOf('\n') >= 0) numLinesEst++; | ||
return prev + cur.length + 1; | ||
}, 0); | ||
if (length > 60) { | ||
return braces[0] + | ||
(base === '' ? '' : base + '\n ') + | ||
' ' + | ||
output.join(',\n ') + | ||
' ' + | ||
braces[1]; | ||
} | ||
return braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1]; | ||
} | ||
// NOTE: These type checking functions intentionally don't use `instanceof` | ||
// because it is fragile and can be easily faked with `Object.create()`. | ||
function isArray(ar) { | ||
return Array.isArray(ar) || | ||
(typeof ar === 'object' && objectToString(ar) === '[object Array]'); | ||
} | ||
function isRegExp(re) { | ||
return typeof re === 'object' && objectToString(re) === '[object RegExp]'; | ||
} | ||
function isDate(d) { | ||
return typeof d === 'object' && objectToString(d) === '[object Date]'; | ||
} | ||
function isError(e) { | ||
return typeof e === 'object' && objectToString(e) === '[object Error]'; | ||
} | ||
function objectToString(o) { | ||
return Object.prototype.toString.call(o); | ||
} | ||
}(this)); | ||
(function(exports) { | ||
@@ -304,2 +688,5 @@ 'use strict'; | ||
//url is cached we are good | ||
if (callback) { | ||
callback(); | ||
} | ||
return; | ||
@@ -346,81 +733,98 @@ } | ||
var Sandbox = window.TestAgent.Sandbox = function Sandbox(url) { | ||
TestAgent.Responder.call(this); | ||
this.url = url; | ||
}; | ||
Sandbox.prototype = { | ||
var proto = Sandbox.prototype = Object.create( | ||
TestAgent.Responder.prototype | ||
); | ||
_element: null, | ||
proto._element = null; | ||
/** | ||
* @type Boolean | ||
* | ||
* True when sandbox is ready | ||
*/ | ||
ready: false, | ||
/** | ||
* @type Boolean | ||
* | ||
* True when sandbox is ready | ||
*/ | ||
proto.ready = false; | ||
/** | ||
* URL for the iframe sandbox. | ||
* | ||
* @type String | ||
*/ | ||
url: null, | ||
/** | ||
* URL for the iframe sandbox. | ||
* | ||
* @type String | ||
*/ | ||
proto.url = null; | ||
/** | ||
* Returns iframe element. | ||
* | ||
* | ||
* @type DOMElement | ||
*/ | ||
getElement: function getElement() { | ||
var iframe; | ||
if (!this._element) { | ||
iframe = this._element = window.document.createElement('iframe'); | ||
iframe.src = this.url + '?time=' + String(Date.now()); | ||
} | ||
return this._element; | ||
}, | ||
/** | ||
* Returns iframe element. | ||
* | ||
* | ||
* @type DOMElement | ||
*/ | ||
proto.getElement = function getElement() { | ||
var iframe; | ||
if (!this._element) { | ||
iframe = this._element = window.document.createElement('iframe'); | ||
iframe.src = this.url + '?time=' + String(Date.now()); | ||
} | ||
return this._element; | ||
}; | ||
run: function run(callback) { | ||
//cleanup old sandboxes | ||
this.destroy(); | ||
proto.run = function run(callback) { | ||
//cleanup old sandboxes | ||
this.destroy(); | ||
var element = this.getElement(), | ||
self = this; | ||
var element = this.getElement(), | ||
iframeWindow, | ||
self = this; | ||
//this must come before the listener | ||
window.document.body.appendChild(element); | ||
element.contentWindow.addEventListener('DOMContentLoaded', function() { | ||
self.ready = true; | ||
callback.call(this); | ||
//this must come before the listener | ||
window.document.body.appendChild(element); | ||
iframeWindow = element.contentWindow; | ||
iframeWindow.onerror = function(message, file, line) { | ||
self.emit('error', { | ||
message: message, | ||
//remove cache busting string | ||
filename: file.split('?time=')[0], | ||
lineno: line | ||
}); | ||
}, | ||
}; | ||
destroy: function destroy() { | ||
var el; | ||
iframeWindow.addEventListener('DOMContentLoaded', function() { | ||
self.ready = true; | ||
self.emit('ready', this); | ||
callback.call(this); | ||
}); | ||
if (!this.ready) { | ||
return false; | ||
} | ||
return iframeWindow; | ||
}; | ||
proto.destroy = function destroy() { | ||
var el; | ||
this.ready = false; | ||
if (!this.ready) { | ||
return false; | ||
} | ||
el = this.getElement(); | ||
el.parentNode.removeChild(el); | ||
this.ready = false; | ||
el = this.getElement(); | ||
el.parentNode.removeChild(el); | ||
return true; | ||
}, | ||
getWindow: function getWindow() { | ||
if (!this.ready) { | ||
return false; | ||
} | ||
return true; | ||
}; | ||
return this.getElement().contentWindow; | ||
proto.getWindow = function getWindow() { | ||
if (!this.ready) { | ||
return false; | ||
} | ||
return this.getElement().contentWindow; | ||
}; | ||
}(this)); | ||
(function(window) { | ||
@@ -774,3 +1178,4 @@ | ||
MochaReporter.console.log = function consoleLogShim() { | ||
var args = Array.prototype.slice.call(arguments); | ||
var args = Array.prototype.slice.call(arguments), | ||
message = TestAgent.format.apply(TestAgent, arguments); | ||
//real console log | ||
@@ -804,3 +1209,3 @@ log.apply(this, arguments); | ||
MochaReporter.send( | ||
JSON.stringify(['log', {messages: messages, stack: stack}]) | ||
JSON.stringify(['log', {messages: [message], stack: stack}]) | ||
); | ||
@@ -943,2 +1348,4 @@ }; | ||
this.testRunner = options.testRunner; | ||
//event proxy | ||
this.sandbox.on('error', this.emit.bind(this, 'sandbox error')); | ||
}; | ||
@@ -975,2 +1382,3 @@ | ||
var self = this; | ||
this.sandbox.run(function onSandboxRun() { | ||
@@ -1162,2 +1570,26 @@ self.loader.targetWindow = this; | ||
function ErrorReporting() { | ||
}; | ||
ErrorReporting.prototype = { | ||
enhance: function enhance(worker) { | ||
worker.on('sandbox error', this.onSandboxError.bind(this, worker)); | ||
}, | ||
onSandboxError: function onSandboxError(worker, data) { | ||
worker.send('error', data); | ||
} | ||
}; | ||
Worker.ErrorReporting = ErrorReporting; | ||
}(this)); | ||
(function(window) { | ||
'use strict'; | ||
var Worker = window.TestAgent.BrowserWorker; | ||
Worker.Config = function Config(options) { | ||
@@ -1227,2 +1659,5 @@ if (typeof(options) === 'undefined') { | ||
this.element = options.element || document.querySelector(selector); | ||
this.errorElement = document.createElement('div'); | ||
this.errorElement.className = 'error ' + this.HIDDEN; | ||
this.element.appendChild(this.errorElement); | ||
this.queue = {}; | ||
@@ -1233,6 +1668,16 @@ }; | ||
TestUi.prototype = { | ||
HIDDEN: 'hidden', | ||
templates: { | ||
testList: '<ul class="test-agent"></ul>', | ||
testList: '<ul class="test-list"></ul>', | ||
testItem: '<li data-url="%s">%s</li>', | ||
testRun: '<button class="run-tests">Execute</button>' | ||
testRun: '<button class="run-tests">Execute</button>', | ||
error: [ | ||
'<h1>Critical Error</h1>', | ||
'<p><span class="error">%0s</span> in file ', | ||
'<span class="file">', | ||
'<a href="%1s">%1s</a>', | ||
'</span> line #', | ||
'<span class="line">%2s</span>' | ||
].join('') | ||
}, | ||
@@ -1243,7 +1688,35 @@ | ||
this.worker.on('config', this.onConfig.bind(this)); | ||
this.worker.on('sandbox', this.onSandbox.bind(this)); | ||
this.worker.on('sandbox error', this.onSandboxError.bind(this)); | ||
}, | ||
onSandbox: function onSandbox() { | ||
var error = this.errorElement; | ||
if (error) { | ||
if (error.className.indexOf(this.HIDDEN) === -1) { | ||
error.className += ' ' + this.HIDDEN; | ||
} | ||
} | ||
}, | ||
onSandboxError: function onSandboxError(data) { | ||
var element = this.element, | ||
error = this.errorElement, | ||
message = data.message, | ||
file = data.filename, | ||
line = data.lineno; | ||
error.className = error.className.replace(' hidden', ''); | ||
error.innerHTML = format( | ||
this.templates.error, | ||
message, | ||
file, | ||
line | ||
); | ||
}, | ||
onConfig: function onConfig(data) { | ||
//purge elements | ||
var elements = this.element.getElementsByTagName('*'), | ||
var elements = this.element.getElementsByTagName('test-list'), | ||
element, | ||
@@ -1250,0 +1723,0 @@ templates = this.templates, |
@@ -14,4 +14,5 @@ { | ||
"/test/test-agent/sandbox-test.js", | ||
"/test/test-agent/inspect-test.js", | ||
"/test/test-agent/websocket-client-test.js" | ||
] | ||
} |
@@ -1,7 +0,9 @@ | ||
(function(window){ | ||
(function(window) { | ||
var worker = new TestAgent.BrowserWorker({ | ||
sandbox: '/test-agent/sandbox.html', | ||
}); | ||
var worker; | ||
worker = new TestAgent.BrowserWorker({ | ||
sandbox: '/test-agent/sandbox.html' | ||
}); | ||
worker.use(TestAgent.BrowserWorker.Config, { | ||
@@ -17,2 +19,3 @@ url: '/test-agent/config.json' | ||
worker.use(TestAgent.BrowserWorker.TestUi); | ||
worker.use(TestAgent.BrowserWorker.ErrorReporting); | ||
@@ -25,7 +28,7 @@ worker.on({ | ||
'open': function(){ | ||
'open': function() { | ||
console.log('socket open'); | ||
}, | ||
'close': function(){ | ||
'close': function() { | ||
console.log('lost client trying to reconnect'); | ||
@@ -32,0 +35,0 @@ } |
112
437940
14796