JSTD adapter for running e2e tests

Couple of changes into angular.scenario runner:
 - add autotest config (runs tests when document ready)
 - update ObjectModel (forwards events)
 - use only one ObjectModel instance for all outputters
 - expose error msg and line number in ObjectModel.Spec and ObjectModel.Step
 - fix generating spec.ids
 - fix 'html' output so that it does not mutate ObjectModel

Couple of changes into docs / generator:
 - rename copy -> copyTpl
 - move docs/static into docs/examples (to avoid conflict with jstd proxy)

Running all docs e2e tests:
========================================================
1/ compile angular-scenario, jstd-scenario-adapter
>> rake compile

2/ build docs
>> rake docs

3/ start jstd server
>> ./server-scenario.sh

4/ capture some browser

5/ run node server to serve static content
>> node ../lib/nodeserver/server.js

6/ run tests
>> ./test-scenario.sh
This commit is contained in:
Vojta Jina
2011-05-19 17:33:25 +02:00
committed by Igor Minar
parent 9f56af9c15
commit 1abdc097b2
35 changed files with 1034 additions and 121 deletions

View File

@@ -1,8 +1,13 @@
<a name="0.9.16"><a/>
# <angular/> 0.9.16 weather-control (in-progress) #
### Features
- we can run scenario tests with jstd (from command line and in multiple browsers)
### Breaking changes
- html scenario runner requires ng:autotest option to start tests automatically
<a name="0.9.15"><a/>
# <angular/> 0.9.15 lethal-stutter (2011-04-11) #

View File

@@ -53,7 +53,7 @@ ANGULAR_SCENARIO = [
'src/scenario/output/Html.js',
'src/scenario/output/Json.js',
'src/scenario/output/Xml.js',
'src/scenario/output/Object.js',
'src/scenario/output/Object.js'
]
BUILD_DIR = 'build'
@@ -94,6 +94,30 @@ task :compile_scenario => :init do
end
end
desc 'Compile JSTD Scenario Adapter'
task :compile_jstd_scenario_adapter => :init do
deps = [
'src/jstd-scenario-adapter/angular.prefix',
'src/jstd-scenario-adapter/Adapter.js',
'src/jstd-scenario-adapter/angular.suffix',
]
concat = 'cat ' + deps.flatten.join(' ')
File.open(path_to('jstd-scenario-adapter.js'), 'w') do |f|
f.write(%x{#{concat}})
end
# TODO(vojta) use jstd configuration when implemented
# (instead of including jstd-adapter-config.js)
File.open(path_to('jstd-scenario-adapter-config.js'), 'w') do |f|
f.write("/**\r\n" +
" * Configuration for jstd scenario adapter \n */\n" +
"var jstdScenarioAdapter = {\n relativeUrlPrefix: '/build/docs/'\n};\n")
end
end
desc 'Generate IE css js patch'
task :generate_ie_compat => :init do
@@ -152,7 +176,7 @@ end
desc 'Compile JavaScript'
task :compile => [:init, :compile_scenario, :generate_ie_compat] do
task :compile => [:init, :compile_scenario, :compile_jstd_scenario_adapter, :generate_ie_compat] do
deps = [
'src/angular.prefix',
@@ -195,7 +219,9 @@ task :package => [:clean, :compile, :docs] do
path_to('angular.js'),
path_to('angular.min.js'),
path_to('angular-ie-compat.js'),
path_to('angular-scenario.js')
path_to('angular-scenario.js'),
path_to('jstd-scenario-adapter.js'),
path_to('jstd-scenario-adapter-config.js'),
].each do |src|
dest = src.gsub(/^[^\/]+\//, '').gsub(/((\.min)?\.js)$/, "-#{version}\\1")
FileUtils.cp(src, pkg_dir + '/' + dest)

View File

@@ -34,8 +34,8 @@ In this example we have a simple app which consist of two screens:
The two partials are defined in the following URLs:
* {@link ./static/settings.html}
* {@link ./static/welcome.html}
* {@link ./examples/settings.html}
* {@link ./examples/welcome.html}
<doc:example>
@@ -44,8 +44,8 @@ The two partials are defined in the following URLs:
AppCntl.$inject = ['$route']
function AppCntl($route) {
// define routes
$route.when("", {template:'./static/welcome.html', controller:WelcomeCntl});
$route.when("/settings", {template:'./static/settings.html', controller:SettingsCntl});
$route.when("", {template:'./examples/welcome.html', controller:WelcomeCntl});
$route.when("/settings", {template:'./examples/settings.html', controller:SettingsCntl});
$route.parent(this);
// initialize the model to something useful

View File

@@ -25,22 +25,22 @@ var writes = callback.chain(function(){
var metadata = ngdoc.metadata(docs);
writer.output('docs-keywords.js', ['NG_PAGES=', JSON.stringify(metadata).replace(/{/g, '\n{'), ';'], writes.waitFor());
writer.copyDir('img', writes.waitFor());
writer.copyDir('static', writes.waitFor());
writer.copy('index.html', writes.waitFor());
writer.copy('docs.js', writes.waitFor());
writer.copy('docs.css', writes.waitFor());
writer.copy('doc_widgets.js', writes.waitFor());
writer.copy('doc_widgets.css', writes.waitFor());
writer.copy('docs-scenario.html', writes.waitFor());
writer.copyDir('examples', writes.waitFor());
writer.copyTpl('index.html', writes.waitFor());
writer.copyTpl('docs.js', writes.waitFor());
writer.copyTpl('docs.css', writes.waitFor());
writer.copyTpl('doc_widgets.js', writes.waitFor());
writer.copyTpl('doc_widgets.css', writes.waitFor());
writer.copyTpl('docs-scenario.html', writes.waitFor());
writer.output('docs-scenario.js', ngdoc.scenarios(docs), writes.waitFor());
writer.output('sitemap.xml', new SiteMap(docs).render(), writes.waitFor());
writer.output('robots.txt', 'Sitemap: http://docs.angularjs.org/sitemap.xml\n', writes.waitFor());
writer.copy('syntaxhighlighter/shBrushJScript.js', writes.waitFor());
writer.copy('syntaxhighlighter/shBrushXml.js', writes.waitFor());
writer.copy('syntaxhighlighter/shCore.css', writes.waitFor());
writer.copy('syntaxhighlighter/shCore.js', writes.waitFor());
writer.copy('syntaxhighlighter/shThemeDefault.css', writes.waitFor());
writer.copy('jquery.min.js', writes.waitFor());
writer.copyTpl('syntaxhighlighter/shBrushJScript.js', writes.waitFor());
writer.copyTpl('syntaxhighlighter/shBrushXml.js', writes.waitFor());
writer.copyTpl('syntaxhighlighter/shCore.css', writes.waitFor());
writer.copyTpl('syntaxhighlighter/shCore.js', writes.waitFor());
writer.copyTpl('syntaxhighlighter/shThemeDefault.css', writes.waitFor());
writer.copyTpl('jquery.min.js', writes.waitFor());
});
writes.onDone(function(){
console.log('DONE. Generated ' + docs.length + ' pages in ' +

View File

@@ -2,7 +2,7 @@
<html xmlns:ng="http://angularjs.org" wiki:ng="http://angularjs.org">
<head>
<title>&lt;angular/&gt; Docs Scenario Runner</title>
<script type="text/javascript" src="../angular-scenario.js" ng:autobind></script>
<script type="text/javascript" src="../angular-scenario.js" ng:autotest></script>
<script type="text/javascript" src="docs-scenario.js"></script>
</head>
<body>

View File

@@ -49,7 +49,7 @@ exports.makeDir = function (path, callback) {
})();
};
exports.copy = function(filename, callback){
exports.copyTpl = function(filename, callback) {
copy('docs/src/templates/' + filename, OUTPUT_DIR + filename, callback);
};

View File

@@ -2,7 +2,7 @@
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>Personal Log Scenario Runner</title>
<script type="text/javascript" src="../../../src/scenario/angular-bootstrap.js"></script>
<script type="text/javascript" src="../../../src/scenario/angular-bootstrap.js" ng:autotest></script>
<script type="text/javascript" src="personalLogScenario.js"></script>
</head>
<body>

View File

@@ -0,0 +1,10 @@
server: http://localhost:9877
load:
- build/angular-scenario.js
- build/jstd-scenario-adapter-config.js
- build/jstd-scenario-adapter.js
- build/docs/docs-scenario.js
proxy:
- {matcher: "*", server: "http://localhost:8000"}

View File

@@ -13,11 +13,13 @@ load:
- test/testabilityPatch.js
- src/scenario/Scenario.js
- src/scenario/output/*.js
- src/jstd-scenario-adapter/*.js
- src/scenario/*.js
- src/angular-mocks.js
- test/mocks.js
- test/scenario/*.js
- test/scenario/output/*.js
- test/jstd-scenario-adapter/*.js
- test/*.js
- test/service/*.js
- example/personalLog/test/*.js

View File

@@ -1,7 +1,7 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<script type="text/javascript" src="../build/angular-scenario.js"></script>
<script type="text/javascript" src="../build/angular-scenario.js" ng:autotest></script>
<script type="text/javascript" src="widgets-scenario.js"></script>
</head>
<body>

View File

@@ -1,7 +1,7 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<script type="text/javascript" src="../src/scenario/angular-bootstrap.js"></script>
<script type="text/javascript" src="../src/scenario/angular-bootstrap.js" ng:autotest></script>
<script type="text/javascript" src="widgets-scenario.js"></script>
</head>
<body>

3
server-scenario.sh Executable file
View File

@@ -0,0 +1,3 @@
#!/bin/bash
java -jar lib/jstestdriver/JsTestDriver.jar --port 9877 --browserTimeout 90000 --config jsTestDriver-scenario.conf

View File

@@ -0,0 +1,175 @@
/**
* JSTestDriver adapter for angular scenario tests
*
* Example of jsTestDriver.conf for running scenario tests with JSTD:
<pre>
server: http://localhost:9877
load:
- lib/angular-scenario.js
- lib/jstd-scenario-adapter-config.js
- lib/jstd-scenario-adapter.js
# your test files go here #
proxy:
- {matcher: "/your-prefix/*", server: "http://localhost:8000/"}
</pre>
*
* For more information on how to configure jstd proxy, see {@link http://code.google.com/p/js-test-driver/wiki/Proxy}
* Note the order of files - it's important !
*
* Example of jstd-scenario-adapter-config.js
<pre>
var jstdScenarioAdapter = {
relativeUrlPrefix: '/your-prefix/'
};
</pre>
*
* Whenever you use <code>browser().navigateTo('relativeUrl')</code> in your scenario test, the relativeUrlPrefix will be prepended.
* You have to configure this to work together with JSTD proxy.
*
* Let's assume you are using the above configuration (jsTestDriver.conf and jstd-scenario-adapter-config.js):
* Now, when you call <code>browser().navigateTo('index.html')</code> in your scenario test, the browser will open /your-prefix/index.html.
* That matches the proxy, so JSTD will proxy this request to http://localhost:8000/index.html.
*/
/**
* Custom type of test case
*
* @const
* @see jstestdriver.TestCaseInfo
*/
var SCENARIO_TYPE = 'scenario';
/**
* Plugin for JSTestDriver
* Connection point between scenario's jstd output and jstestdriver.
*
* @see jstestdriver.PluginRegistrar
*/
function JstdPlugin() {
var nop = function() {};
this.reportResult = nop;
this.reportEnd = nop;
this.runScenario = nop;
this.name = 'Angular Scenario Adapter';
/**
* Called for each JSTD TestCase
*
* Handles only SCENARIO_TYPE test cases. There should be only one fake TestCase.
* Runs all scenario tests (under one fake TestCase) and report all results to JSTD.
*
* @param {jstestdriver.TestRunConfiguration} configuration
* @param {Function} onTestDone
* @param {Function} onAllTestsComplete
* @returns {boolean} True if this type of test is handled by this plugin, false otherwise
*/
this.runTestConfiguration = function(configuration, onTestDone, onAllTestsComplete) {
if (configuration.getTestCaseInfo().getType() != SCENARIO_TYPE) return false;
this.reportResult = onTestDone;
this.reportEnd = onAllTestsComplete;
this.runScenario();
return true;
};
this.getTestRunsConfigurationFor = function(testCaseInfos, expressions, testRunsConfiguration) {
testRunsConfiguration.push(
new jstestdriver.TestRunConfiguration(
new jstestdriver.TestCaseInfo(
'Angular Scenario Tests', function() {}, SCENARIO_TYPE), []));
return true;
};
}
/**
* Singleton instance of the plugin
* Accessed using closure by:
* - jstd output (reports to this plugin)
* - initScenarioAdapter (register the plugin to jstd)
*/
var plugin = new JstdPlugin();
/**
* Initialise scenario jstd-adapter
* (only if jstestdriver is defined)
*
* @param {Object} jstestdriver Undefined when run from browser (without jstd)
* @param {Function} initScenarioAndRun Function that inits scenario and runs all the tests
* @param {Object=} config Configuration object, supported properties:
* - relativeUrlPrefix: prefix for all relative links when navigateTo()
*/
function initScenarioAdapter(jstestdriver, initScenarioAndRun, config) {
if (jstestdriver) {
// create and register ScenarioPlugin
jstestdriver.pluginRegistrar.register(plugin);
plugin.runScenario = initScenarioAndRun;
/**
* HACK (angular.scenario.Application.navigateTo)
*
* We need to navigate to relative urls when running from browser (without JSTD),
* because we want to allow running scenario tests without creating its own virtual host.
* For example: http://angular.local/build/docs/docs-scenario.html
*
* On the other hand, when running with JSTD, we need to navigate to absolute urls,
* because of JSTD proxy. (proxy, because of same domain policy)
*
* So this hack is applied only if running with JSTD and change all relative urls to absolute.
*/
var appProto = angular.scenario.Application.prototype,
navigateTo = appProto.navigateTo,
relativeUrlPrefix = config && config.relativeUrlPrefix || '/';
appProto.navigateTo = function(url, loadFn, errorFn) {
if (url.charAt(0) != '/' && url.charAt(0) != '#' &&
url != 'about:blank' && !url.match(/^https?/)) {
url = relativeUrlPrefix + url;
}
return navigateTo.call(this, url, loadFn, errorFn);
};
}
}
/**
* Builds proper TestResult object from given model spec
*
* TODO(vojta) report error details
*
* @param {angular.scenario.ObjectModel.Spec} spec
* @returns {jstestdriver.TestResult}
*/
function createTestResultFromSpec(spec) {
var map = {
success: 'PASSED',
error: 'ERROR',
failure: 'FAILED'
};
return new jstestdriver.TestResult(
spec.fullDefinitionName,
spec.name,
jstestdriver.TestResult.RESULT[map[spec.status]],
spec.error || '',
spec.line || '',
spec.duration);
}
/**
* Generates JSTD output (jstestdriver.TestResult)
*/
angular.scenario.output('jstd', function(context, runner, model) {
model.on('SpecEnd', function(spec) {
plugin.reportResult(createTestResultFromSpec(spec));
});
model.on('RunnerEnd', function() {
plugin.reportEnd();
});
});

View File

@@ -0,0 +1,24 @@
/**
* The MIT License
*
* Copyright (c) 2010 Adam Abrons and Misko Hevery http://getangular.com
*
* 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) {

View File

@@ -0,0 +1,2 @@
initScenarioAdapter(window.jstestdriver, angular.scenario.setUpAndRun, window.jstdScenarioAdapter);
})(window);

View File

@@ -37,6 +37,9 @@ angular.scenario.Describe = function(descName, parent) {
// Shared Unique ID generator for every describe block
angular.scenario.Describe.id = 0;
// Shared Unique ID generator for every it (spec)
angular.scenario.Describe.specId = 0;
/**
* Defines a block to execute before each it or nested describe.
*
@@ -93,6 +96,7 @@ angular.scenario.Describe.prototype.xdescribe = angular.noop;
*/
angular.scenario.Describe.prototype.it = function(name, body) {
this.its.push({
id: angular.scenario.Describe.specId++,
definition: this,
only: this.only,
name: name,

View File

@@ -4,21 +4,26 @@
* @param {Object} runner The scenario Runner instance to connect to.
*
* TODO(esprehn): Every output type creates one of these, but we probably
* want one glonal shared instance. Need to handle events better too
* want one global shared instance. Need to handle events better too
* so the HTML output doesn't need to do spec model.getSpec(spec.id)
* silliness.
*
* TODO(vojta) refactor on, emit methods (from all objects) - use inheritance
*/
angular.scenario.ObjectModel = function(runner) {
var self = this;
this.specMap = {};
this.listeners = [];
this.value = {
name: '',
children: {}
};
runner.on('SpecBegin', function(spec) {
var block = self.value;
var block = self.value,
definitions = [];
angular.forEach(self.getDefinitionPath(spec), function(def) {
if (!block.children[def.name]) {
block.children[def.name] = {
@@ -29,49 +34,78 @@ angular.scenario.ObjectModel = function(runner) {
};
}
block = block.children[def.name];
definitions.push(def.name);
});
self.specMap[spec.id] = block.specs[spec.name] =
new angular.scenario.ObjectModel.Spec(spec.id, spec.name);
var it = self.specMap[spec.id] =
block.specs[spec.name] =
new angular.scenario.ObjectModel.Spec(spec.id, spec.name, definitions);
// forward the event
self.emit('SpecBegin', it);
});
runner.on('SpecError', function(spec, error) {
var it = self.getSpec(spec.id);
it.status = 'error';
it.error = error;
// forward the event
self.emit('SpecError', it, error);
});
runner.on('SpecEnd', function(spec) {
var it = self.getSpec(spec.id);
complete(it);
// forward the event
self.emit('SpecEnd', it);
});
runner.on('StepBegin', function(spec, step) {
var it = self.getSpec(spec.id);
it.steps.push(new angular.scenario.ObjectModel.Step(step.name));
var step = new angular.scenario.ObjectModel.Step(step.name);
it.steps.push(step);
// forward the event
self.emit('StepBegin', it, step);
});
runner.on('StepEnd', function(spec, step) {
var it = self.getSpec(spec.id);
if (it.getLastStep().name !== step.name)
throw 'Events fired in the wrong order. Step names don\' match.';
complete(it.getLastStep());
var step = it.getLastStep();
if (step.name !== step.name)
throw 'Events fired in the wrong order. Step names don\'t match.';
complete(step);
// forward the event
self.emit('StepEnd', it, step);
});
runner.on('StepFailure', function(spec, step, error) {
var it = self.getSpec(spec.id);
var item = it.getLastStep();
item.error = error;
if (!it.status) {
it.status = item.status = 'failure';
}
var it = self.getSpec(spec.id),
modelStep = it.getLastStep();
modelStep.setErrorStatus('failure', error, step.line());
it.setStatusFromStep(modelStep);
// forward the event
self.emit('StepFailure', it, modelStep, error);
});
runner.on('StepError', function(spec, step, error) {
var it = self.getSpec(spec.id);
var item = it.getLastStep();
it.status = 'error';
item.status = 'error';
item.error = error;
var it = self.getSpec(spec.id),
modelStep = it.getLastStep();
modelStep.setErrorStatus('error', error, step.line());
it.setStatusFromStep(modelStep);
// forward the event
self.emit('StepError', it, modelStep, error);
});
runner.on('RunnerEnd', function() {
self.emit('RunnerEnd');
});
function complete(item) {
@@ -81,6 +115,36 @@ angular.scenario.ObjectModel = function(runner) {
}
};
/**
* Adds a listener for an event.
*
* @param {string} eventName Name of the event to add a handler for
* @param {Function} listener Function that will be called when event is fired
*/
angular.scenario.ObjectModel.prototype.on = function(eventName, listener) {
eventName = eventName.toLowerCase();
this.listeners[eventName] = this.listeners[eventName] || [];
this.listeners[eventName].push(listener);
};
/**
* Emits an event which notifies listeners and passes extra
* arguments.
*
* @param {string} eventName Name of the event to fire.
*/
angular.scenario.ObjectModel.prototype.emit = function(eventName) {
var self = this,
args = Array.prototype.slice.call(arguments, 1),
eventName = eventName.toLowerCase();
if (this.listeners[eventName]) {
angular.forEach(this.listeners[eventName], function(listener) {
listener.apply(self, args);
});
}
};
/**
* Computes the path of definition describe blocks that wrap around
* this spec.
@@ -113,12 +177,14 @@ angular.scenario.ObjectModel.prototype.getSpec = function(id) {
*
* @param {string} id Id of the spec
* @param {string} name Name of the spec
* @param {Array<string>=} definitionNames List of all describe block names that wrap this spec
*/
angular.scenario.ObjectModel.Spec = function(id, name) {
angular.scenario.ObjectModel.Spec = function(id, name, definitionNames) {
this.id = id;
this.name = name;
this.startTime = new Date().getTime();
this.steps = [];
this.fullDefinitionName = (definitionNames || []).join(' ');
};
/**
@@ -142,6 +208,19 @@ angular.scenario.ObjectModel.Spec.prototype.getLastStep = function() {
return this.steps[this.steps.length-1];
};
/**
* Set status of the Spec from given Step
*
* @param {angular.scenario.ObjectModel.Step} step
*/
angular.scenario.ObjectModel.Spec.prototype.setStatusFromStep = function(step) {
if (!this.status || step.status == 'error') {
this.status = step.status;
this.error = step.error;
this.line = step.line;
}
};
/**
* A single step inside a Spec.
*
@@ -151,3 +230,16 @@ angular.scenario.ObjectModel.Step = function(name) {
this.name = name;
this.startTime = new Date().getTime();
};
/**
* Helper method for setting all error status related properties
*
* @param {string} status
* @param {string} error
* @param {string} line
*/
angular.scenario.ObjectModel.Step.prototype.setErrorStatus = function(status, error, line) {
this.status = status;
this.error = error;
this.line = line;
};

View File

@@ -1,5 +1,8 @@
/**
* Runner for scenarios.
* Runner for scenarios
*
* Has to be initialized before any test is loaded,
* because it publishes the API into window (global space).
*/
angular.scenario.Runner = function($window) {
this.listeners = [];

View File

@@ -87,17 +87,20 @@ angular.scenario.matcher = angular.scenario.matcher || function(name, fn) {
};
/**
* Initialization function for the scenario runner.
* Initialize the scenario runner and run !
*
* @param {angular.scenario.Runner} $scenario The runner to setup
* @param {Object} config Config options
* Access global window and document object
* Access $runner through closure
*
* @param {Object=} config Config options
*/
function angularScenarioInit($scenario, config) {
angular.scenario.setUpAndRun = function (config) {
var href = window.location.href;
var body = _jQuery(document.body);
var output = [];
var objModel = new angular.scenario.ObjectModel($runner);
if (config.scenario_output) {
if (config && config.scenario_output) {
output = config.scenario_output.split(',');
}
@@ -105,7 +108,7 @@ function angularScenarioInit($scenario, config) {
if (!output.length || indexOf(output,name) != -1) {
var context = body.append('<div></div>').find('div:last');
context.attr('id', name);
fn.call({}, context, $scenario);
fn.call({}, context, $runner, objModel);
}
});
@@ -121,12 +124,12 @@ function angularScenarioInit($scenario, config) {
var appFrame = body.append('<div id="application"></div>').find('#application');
var application = new angular.scenario.Application(appFrame);
$scenario.on('RunnerEnd', function() {
$runner.on('RunnerEnd', function() {
appFrame.css('display', 'none');
appFrame.find('iframe').attr('src', 'about:blank');
});
$scenario.on('RunnerError', function(error) {
$runner.on('RunnerError', function(error) {
if (window.console) {
console.log(formatException(error));
} else {
@@ -135,8 +138,8 @@ function angularScenarioInit($scenario, config) {
}
});
$scenario.run(application);
}
$runner.run(application);
};
/**
* Iterates through list with iterator function that must call the

View File

@@ -23,7 +23,8 @@
try {
if (previousOnLoad) previousOnLoad();
} catch(e) {}
angularScenarioInit($scenario, angularJsConfig(document));
var config = angularJsConfig(document);
if (config.autotest) angular.scenario.setUpAndRun(config);
};
addCSS("../../css/angular-scenario.css");
@@ -52,8 +53,7 @@
// Create the runner (which also sets up the global API)
document.write(
'<script type="text/javascript">' +
'var $scenario = new angular.scenario.Runner(window, angular.scenario.SpecRunner);' +
'</script>'
);
' var $runner = new angular.scenario.Runner(window);' +
'</script>');
})(window.onload);

View File

@@ -1,7 +1,10 @@
var $scenario = new angular.scenario.Runner(window);
var $runner = new angular.scenario.Runner(window),
config = angularJsConfig(document);
if (config.autotest) {
jqLiteWrap(document).ready(function() {
angularScenarioInit($scenario, angularJsConfig(document));
angular.scenario.setUpAndRun(config);
});
}
})(window, document);

View File

@@ -4,8 +4,9 @@
* TODO(esprehn): This should be refactored now that ObjectModel exists
* to use angular bindings for the UI.
*/
angular.scenario.output('html', function(context, runner) {
var model = new angular.scenario.ObjectModel(runner);
angular.scenario.output('html', function(context, runner, model) {
var specUiMap = {},
lastStepUiMap = {};
context.append(
'<div id="header">' +
@@ -22,7 +23,7 @@ angular.scenario.output('html', function(context, runner) {
);
runner.on('InteractiveWait', function(spec, step) {
var ui = model.getSpec(spec.id).getLastStep().ui;
var ui = lastStepUiMap[spec.id];
ui.find('.test-title').
html('waiting for you to <a href="javascript:resume()">resume</a>.');
});
@@ -58,59 +59,62 @@ angular.scenario.output('html', function(context, runner) {
name.removeClass('closed').addClass('open');
}
});
model.getSpec(spec.id).ui = ui;
specUiMap[spec.id] = ui;
});
runner.on('SpecError', function(spec, error) {
var ui = model.getSpec(spec.id).ui;
var ui = specUiMap[spec.id];
ui.append('<pre></pre>');
ui.find('> pre').text(formatException(error));
});
runner.on('SpecEnd', function(spec) {
var ui = specUiMap[spec.id];
spec = model.getSpec(spec.id);
spec.ui.removeClass('status-pending');
spec.ui.addClass('status-' + spec.status);
spec.ui.find("> .test-info .timer-result").text(spec.duration + "ms");
ui.removeClass('status-pending');
ui.addClass('status-' + spec.status);
ui.find("> .test-info .timer-result").text(spec.duration + "ms");
if (spec.status === 'success') {
spec.ui.find('> .test-info .test-name').addClass('closed');
spec.ui.find('> .scrollpane .test-actions').hide();
ui.find('> .test-info .test-name').addClass('closed');
ui.find('> .scrollpane .test-actions').hide();
}
updateTotals(spec.status);
});
runner.on('StepBegin', function(spec, step) {
var ui = specUiMap[spec.id];
spec = model.getSpec(spec.id);
step = spec.getLastStep();
spec.ui.find('> .scrollpane .test-actions').
append('<li class="status-pending"></li>');
step.ui = spec.ui.find('> .scrollpane .test-actions li:last');
step.ui.append(
ui.find('> .scrollpane .test-actions').append('<li class="status-pending"></li>');
var stepUi = lastStepUiMap[spec.id] = ui.find('> .scrollpane .test-actions li:last');
stepUi.append(
'<div class="timer-result"></div>' +
'<div class="test-title"></div>'
);
step.ui.find('> .test-title').text(step.name);
var scrollpane = step.ui.parents('.scrollpane');
stepUi.find('> .test-title').text(step.name);
var scrollpane = stepUi.parents('.scrollpane');
scrollpane.attr('scrollTop', scrollpane.attr('scrollHeight'));
});
runner.on('StepFailure', function(spec, step, error) {
var ui = model.getSpec(spec.id).getLastStep().ui;
var ui = lastStepUiMap[spec.id];
addError(ui, step.line, error);
});
runner.on('StepError', function(spec, step, error) {
var ui = model.getSpec(spec.id).getLastStep().ui;
var ui = lastStepUiMap[spec.id];
addError(ui, step.line, error);
});
runner.on('StepEnd', function(spec, step) {
var stepUi = lastStepUiMap[spec.id];
spec = model.getSpec(spec.id);
step = spec.getLastStep();
step.ui.find('.timer-result').text(step.duration + 'ms');
step.ui.removeClass('status-pending');
step.ui.addClass('status-' + step.status);
var scrollpane = spec.ui.find('> .scrollpane');
stepUi.find('.timer-result').text(step.duration + 'ms');
stepUi.removeClass('status-pending');
stepUi.addClass('status-' + step.status);
var scrollpane = specUiMap[spec.id].find('> .scrollpane');
scrollpane.attr('scrollTop', scrollpane.attr('scrollHeight'));
});

View File

@@ -1,10 +1,8 @@
/**
* Generates JSON output into a context.
*/
angular.scenario.output('json', function(context, runner) {
var model = new angular.scenario.ObjectModel(runner);
runner.on('RunnerEnd', function() {
angular.scenario.output('json', function(context, runner, model) {
model.on('RunnerEnd', function() {
context.text(angular.toJson(model.value));
});
});

View File

@@ -1,6 +1,6 @@
/**
* Creates a global value $result with the result of the runner.
*/
angular.scenario.output('object', function(context, runner) {
runner.$window.$result = new angular.scenario.ObjectModel(runner).value;
angular.scenario.output('object', function(context, runner, model) {
runner.$window.$result = model.value;
});

View File

@@ -1,10 +1,9 @@
/**
* Generates XML output into a context.
*/
angular.scenario.output('xml', function(context, runner) {
var model = new angular.scenario.ObjectModel(runner);
angular.scenario.output('xml', function(context, runner, model) {
var $ = function(args) {return new context.init(args);};
runner.on('RunnerEnd', function() {
model.on('RunnerEnd', function() {
var scenario = $('<scenario></scenario>');
context.append(scenario);
serializeXml(scenario, model.value);

7
test-scenario.sh Executable file
View File

@@ -0,0 +1,7 @@
#!/bin/bash
tests=$1
if [[ $tests = "" ]]; then
tests="all"
fi
java -jar lib/jstestdriver/JsTestDriver.jar --tests "$tests" --config jsTestDriver-scenario.conf --reset

View File

@@ -0,0 +1,320 @@
describe('jstd-adapter', function() {
var fakeJSTD = { pluginRegistrar: { register: function() {} } },
originalNavigateTo = angular.scenario.Application.prototype.navigateTo;
/**
* Reverts hack on angular.scenario.Application.navigateTo
* We should revert this hack after any single call of initScenarioAdapter,
* so that it doesn't influence other tests...
*/
function revertNavigateToHack() {
angular.scenario.Application.prototype.navigateTo = originalNavigateTo;
}
/**
* Helper for building angular.scenario.ObjectModel.Spec
* @returns {angular.scenario.ObjectModel.Spec}
*/
function buildSpec(status, name, duration, definitions, error, line) {
var spec = new angular.scenario.ObjectModel.Spec(
'fake-id', name || 'name', definitions || ['desc1', 'desc2']);
spec.duration = duration || 10;
spec.status = status || 'success';
spec.error = error || '';
spec.line = line || '';
return spec;
}
/**
* Helper for building angular.scenario.ObjectModel.Spec with error and error line
* @returns {angular.scenario.ObjectModel.Spec}
*/
function buildErrorSpec(error, line, status, name) {
return buildSpec(status || 'error', name, null, null, error, line);
}
/**
* Helper for building TestConfiguration
* @returns {jstestdriver.TestRunConfiguration}
*/
function buildTestConf(type) {
return new jstestdriver.TestRunConfiguration(
new jstestdriver.TestCaseInfo('Fake test - ' + Math.random(), function(){}, type), null);
}
/**
* Helper for building SCENARIO TestConfiguration
* @returns {jstestdriver.TestRunConfiguration}
*/
function buildScenarioTestConf() {
return buildTestConf(SCENARIO_TYPE);
}
describe('initScenarioAdapter', function() {
afterEach(revertNavigateToHack);
it('should create and register plugin if jstestdriver defined', function() {
spyOn(fakeJSTD.pluginRegistrar, 'register');
initScenarioAdapter(fakeJSTD);
expect(fakeJSTD.pluginRegistrar.register).toHaveBeenCalled();
expect(fakeJSTD.pluginRegistrar.register.mostRecentCall.args[0] instanceof JstdPlugin);
});
it('should do nothing if jstestdriver not defined', function() {
expect(function() {
initScenarioAdapter(undefined);
}).not.toThrow();
});
it('should set setUpAndRun callback to plugin', function() {
var runFn = jasmine.createSpy('setUpAndRun');
plugin.runScenario = null;
initScenarioAdapter(fakeJSTD, runFn);
expect(plugin.runScenario).toBe(runFn);
});
describe('navigateTo', function() {
var fakeJSTD = { pluginRegistrar: { register: function() {} } },
app = new angular.scenario.Application(_jQuery('<div></div>')),
navigateSpy;
beforeEach(function() {
navigateSpy = spyOn(angular.scenario.Application.prototype, 'navigateTo');
});
it('should add url prefix when jstd defined', function() {
initScenarioAdapter(fakeJSTD, null, {relativeUrlPrefix: '/prefix/'});
app.navigateTo('test.html');
expect(navigateSpy).toHaveBeenCalled();
expect(navigateSpy.mostRecentCall.args[0]).toEqual('/prefix/test.html');
});
it('should add forward-slash as default url prefix when jstd defined', function() {
initScenarioAdapter(fakeJSTD);
app.navigateTo('test.html');
expect(navigateSpy).toHaveBeenCalled();
expect(navigateSpy.mostRecentCall.args[0]).toEqual('/test.html');
});
it('should not change url when jstd not defined', function() {
initScenarioAdapter(null);
app.navigateTo('test.html');
expect(navigateSpy).toHaveBeenCalled();
expect(navigateSpy.mostRecentCall.args[0]).toEqual('test.html');
});
it('should not change hash url', function() {
initScenarioAdapter(fakeJSTD);
app.navigateTo('#/index.html/a');
expect(navigateSpy).toHaveBeenCalled();
expect(navigateSpy.mostRecentCall.args[0]).toEqual('#/index.html/a');
});
it('should not change absolute url', function() {
initScenarioAdapter(fakeJSTD);
app.navigateTo('/index.html/a');
expect(navigateSpy).toHaveBeenCalled();
expect(navigateSpy.mostRecentCall.args[0]).toEqual('/index.html/a');
});
it('should not change "about:blank" url', function() {
initScenarioAdapter(fakeJSTD);
app.navigateTo('about:blank');
expect(navigateSpy).toHaveBeenCalled();
expect(navigateSpy.mostRecentCall.args[0]).toEqual('about:blank');
});
it('should not change url with domain', function() {
initScenarioAdapter(fakeJSTD);
app.navigateTo('http://www.google.com');
expect(navigateSpy).toHaveBeenCalled();
expect(navigateSpy.mostRecentCall.args[0]).toEqual('http://www.google.com');
});
});
});
describe('JstdPlugin', function() {
var p;
beforeEach(function() {
p = new JstdPlugin();
});
describe('runTestConfiguration', function() {
var initScenarioSpy, onTestSpy, onAllTestsSpy, spec, modelSpec;
beforeEach(function() {
initScenarioSpy = jasmine.createSpy('initScenarioAndRun');
onTestSpy = jasmine.createSpy('onOneTest');
onAllTestsSpy = jasmine.createSpy('onAllTests');
p.runScenario = initScenarioSpy;
spec = {id: 'fake', name: 'Spec Name'};
modelSpec = new angular.scenario.ObjectModel.Spec(spec.id, spec.name);
});
it('should ignore non scenario test cases', function() {
expect(p.runTestConfiguration(buildTestConf(), onTestSpy, onAllTestsSpy)).toBe(false);
expect(p.runTestConfiguration(buildTestConf('async'), onTestSpy, onAllTestsSpy)).toBe(false);
expect(initScenarioSpy).not.toHaveBeenCalled();
expect(onTestSpy).not.toHaveBeenCalled();
expect(onAllTestsSpy).not.toHaveBeenCalled();
});
it('should return true when scenario test case', function() {
expect(p.runTestConfiguration(buildScenarioTestConf(), onTestSpy, onAllTestsSpy)).toBe(true);
});
it('should call initAndRunTests when scenario test case', function() {
p.runTestConfiguration(buildScenarioTestConf(), onTestSpy, onAllTestsSpy);
expect(initScenarioSpy).toHaveBeenCalled();
});
});
describe('getTestRunsConfigurationFor', function() {
it('should add TestRunConfiguration with SCENARIO_TYPE TestCase', function() {
var configurations = [];
p.getTestRunsConfigurationFor(null, null, configurations);
expect(configurations.length).toBe(1);
expect(configurations[0] instanceof jstestdriver.TestRunConfiguration).toBe(true);
expect(configurations[0].getTestCaseInfo().getType()).toEqual(SCENARIO_TYPE);
});
it('should always return true', function() {
expect(p.getTestRunsConfigurationFor(null, null, [])).toBe(true);
});
});
});
describe('createTestResultFromSpec', function() {
it('should return jstestdriver.TestResult instance', function() {
expect(createTestResultFromSpec(buildSpec()) instanceof jstestdriver.TestResult).toBe(true);
});
it('should set proper test name', function() {
expect(createTestResultFromSpec(buildSpec()).testName).toEqual('name');
});
it('should set duration', function() {
expect(createTestResultFromSpec(buildSpec()).time).toEqual(10);
});
it('should set test case - full definition name', function() {
var spec = buildSpec();
expect(createTestResultFromSpec(spec).testCaseName).toEqual(spec.fullDefinitionName);
});
it('should set passed result when success', function() {
expect(createTestResultFromSpec(buildSpec('success')).result)
.toEqual(jstestdriver.TestResult.RESULT.PASSED);
});
it('should set error result when error', function() {
expect(createTestResultFromSpec(buildSpec('error')).result)
.toEqual(jstestdriver.TestResult.RESULT.ERROR);
});
it('should set failed result when failure', function() {
expect(createTestResultFromSpec(buildSpec('failure')).result)
.toEqual(jstestdriver.TestResult.RESULT.FAILED);
});
it('should set error message when error/failure', function() {
expect(createTestResultFromSpec(buildErrorSpec('error-message')).message)
.toEqual('error-message');
});
it('should log line number when error/failure', function() {
expect(createTestResultFromSpec(buildErrorSpec('msg', 'line-number')).log)
.toEqual('line-number');
});
});
describe('angular.scenario.output.jstd', function() {
var model;
beforeEach(function() {
var runner = new angular.scenario.testing.MockRunner(),
context = _jQuery("<div></div>");
plugin = new JstdPlugin();
model = new angular.scenario.ObjectModel(runner);
angular.scenario.output.jstd(context, runner, model);
spyOn(plugin, 'reportEnd');
spyOn(plugin, 'reportResult');
});
it('should report end of all tests', function() {
model.emit('RunnerEnd');
expect(plugin.reportEnd).toHaveBeenCalled();
});
it('should report jstestdriver.TestResult', function() {
model.emit('SpecEnd', buildSpec());
expect(plugin.reportResult).toHaveBeenCalled();
expect(plugin.reportResult.argsForCall[0][0] instanceof jstestdriver.TestResult).toBe(true);
});
});
// couple of higher level tests (wiring objects together)
describe('HIGHER LEVEL', function() {
var initScenarioSpy, onTestSpy, onAllTestsSpy, model;
beforeEach(function() {
plugin = new JstdPlugin();
initScenarioSpy = jasmine.createSpy('initScenarioAndRun');
onTestSpy = jasmine.createSpy('onOneTest');
onAllTestsSpy = jasmine.createSpy('onAllTests');
var runner = new angular.scenario.testing.MockRunner(),
context = _jQuery("<div></div>");
model = new angular.scenario.ObjectModel(runner);
angular.scenario.output.jstd(context, runner, model);
initScenarioAdapter(fakeJSTD, initScenarioSpy);
plugin.runTestConfiguration(buildScenarioTestConf(), onTestSpy, onAllTestsSpy);
});
afterEach(revertNavigateToHack);
it('should report and of test suite', function() {
model.emit('RunnerEnd');
expect(onAllTestsSpy).toHaveBeenCalled();
});
it('should report success test result', function() {
model.emit('SpecEnd', buildSpec('success', 'name'));
expect(onTestSpy).toHaveBeenCalled();
var result = onTestSpy.argsForCall[0][0];
expect(result instanceof jstestdriver.TestResult).toBe(true);
expect(result.testName).toEqual('name');
expect(result.result).toEqual(jstestdriver.TestResult.RESULT.PASSED);
});
it('should report error test result', function() {
model.emit('SpecEnd', buildSpec('error'));
expect(onTestSpy).toHaveBeenCalled();
var result = onTestSpy.argsForCall[0][0];
expect(result.result).toEqual(jstestdriver.TestResult.RESULT.ERROR);
});
it('should report failed test result', function() {
model.emit('SpecEnd', buildSpec('failure'));
expect(onTestSpy).toHaveBeenCalled();
var result = onTestSpy.argsForCall[0][0];
expect(result.result).toEqual(jstestdriver.TestResult.RESULT.FAILED);
});
});
});

View File

@@ -107,4 +107,14 @@ describe('angular.scenario.Describe', function() {
var b = new angular.scenario.Describe();
expect(a.id).toNotEqual(b.id);
});
it('should create uniqueIds for each spec', function() {
var d = new angular.scenario.Describe();
d.it('fake', function() {});
d.it('fake', function() {});
expect(d.its[0].id).toBeDefined();
expect(d.its[1].id).toBeDefined();
expect(d.its[0].id).not.toEqual(d.its[1].id);
});
});

View File

@@ -3,18 +3,36 @@ describe('angular.scenario.ObjectModel', function() {
var runner;
var spec, step;
beforeEach(function() {
spec = {
name: 'test spec',
function buildSpec(id, name, definitions) {
var spec = {
id: id,
name: name,
definition: {
id: 10,
name: 'describe 1'
name: definitions.shift()
}
};
step = {
name: 'test step',
line: function() { return ''; }
var currentDef = spec.definition;
forEach(definitions, function(defName) {
currentDef.parent = {
name: defName
};
currentDef = currentDef.parent;
});
return spec;
}
function buildStep(name, line) {
return {
name: name || 'test step',
line: function() { return line || ''; }
};
}
beforeEach(function() {
spec = buildSpec(1, 'test spec', ['describe 1']);
step = buildStep();
runner = new angular.scenario.testing.MockRunner();
model = new angular.scenario.ObjectModel(runner);
});
@@ -27,23 +45,28 @@ describe('angular.scenario.ObjectModel', function() {
});
it('should add spec and create describe blocks on SpecBegin event', function() {
runner.emit('SpecBegin', {
name: 'test spec',
definition: {
id: 10,
name: 'describe 2',
parent: {
id: 12,
name: 'describe 1'
}
}
});
runner.emit('SpecBegin', buildSpec(1, 'test spec', ['describe 2', 'describe 1']));
expect(model.value.children['describe 1']).toBeDefined();
expect(model.value.children['describe 1'].children['describe 2']).toBeDefined();
expect(model.value.children['describe 1'].children['describe 2'].specs['test spec']).toBeDefined();
});
it('should set fullDefinitionName on SpecBegin event', function() {
runner.emit('SpecBegin', buildSpec(1, 'fake spec', ['describe 2']));
var spec = model.getSpec(1);
expect(spec.fullDefinitionName).toBeDefined();
expect(spec.fullDefinitionName).toEqual('describe 2');
});
it('should set fullDefinitionName on SpecBegin event (join more names by space)', function() {
runner.emit('SpecBegin', buildSpec(1, 'fake spec', ['describe 2', 'describe 1']));
var spec = model.getSpec(1);
expect(spec.fullDefinitionName).toEqual('describe 1 describe 2');
});
it('should add step to spec on StepBegin', function() {
runner.emit('SpecBegin', spec);
runner.emit('StepBegin', spec, step);
@@ -109,4 +132,200 @@ describe('angular.scenario.ObjectModel', function() {
expect(model.value.children['describe 1'].specs['test spec'].status).toEqual('error');
});
describe('events', function() {
var Spec = angular.scenario.ObjectModel.Spec,
Step = angular.scenario.ObjectModel.Step,
callback;
beforeEach(function() {
callback = jasmine.createSpy('listener');
});
it('should provide method for registering a listener', function() {
expect(model.on).toBeDefined();
expect(model.on instanceof Function).toBe(true);
});
it('should forward SpecBegin event', function() {
model.on('SpecBegin', callback);
runner.emit('SpecBegin', spec);
expect(callback).toHaveBeenCalled();
});
it('should forward SpecBegin event with ObjectModel.Spec as a param', function() {
model.on('SpecBegin', callback);
runner.emit('SpecBegin', spec);
expect(callback.mostRecentCall.args[0] instanceof Spec).toBe(true);
expect(callback.mostRecentCall.args[0].name).toEqual(spec.name);
});
it('should forward SpecError event', function() {
model.on('SpecError', callback);
runner.emit('SpecBegin', spec);
runner.emit('SpecError', spec, {});
expect(callback).toHaveBeenCalled();
});
it('should forward SpecError event with ObjectModel.Spec and error as a params', function() {
var error = {};
model.on('SpecError', callback);
runner.emit('SpecBegin', spec);
runner.emit('SpecError', spec, error);
var param = callback.mostRecentCall.args[0];
expect(param instanceof Spec).toBe(true);
expect(param.name).toEqual(spec.name);
expect(param.status).toEqual('error');
expect(param.error).toBe(error);
});
it('should forward SpecEnd event', function() {
model.on('SpecEnd', callback);
runner.emit('SpecBegin', spec);
runner.emit('SpecEnd', spec);
expect(callback).toHaveBeenCalled();
});
it('should forward SpecEnd event with ObjectModel.Spec as a param', function() {
model.on('SpecEnd', callback);
runner.emit('SpecBegin', spec);
runner.emit('SpecEnd', spec);
expect(callback.mostRecentCall.args[0] instanceof Spec).toBe(true);
expect(callback.mostRecentCall.args[0].name).toEqual(spec.name);
});
it('should forward StepBegin event', function() {
model.on('StepBegin', callback);
runner.emit('SpecBegin', spec);
runner.emit('StepBegin', spec, step);
expect(callback).toHaveBeenCalled();
});
it('should forward StepBegin event with Spec and Step as params', function() {
model.on('StepBegin', callback);
runner.emit('SpecBegin', spec);
runner.emit('StepBegin', spec, step);
var params = callback.mostRecentCall.args;
expect(params[0] instanceof Spec).toBe(true);
expect(params[0].name).toEqual(spec.name);
expect(params[1] instanceof Step).toBe(true);
});
it('should forward StepError event', function() {
model.on('StepError', callback);
runner.emit('SpecBegin', spec);
runner.emit('StepBegin', spec, step);
runner.emit('StepError', spec, step, {});
expect(callback).toHaveBeenCalled();
});
it('should forward StepError event with Spec, Step and error as params', function() {
var error = {};
model.on('StepError', callback);
runner.emit('SpecBegin', spec);
runner.emit('StepBegin', spec, step);
runner.emit('StepError', spec, step, error);
var params = callback.mostRecentCall.args;
expect(params[0] instanceof Spec).toBe(true);
expect(params[0].name).toEqual(spec.name);
expect(params[1] instanceof Step).toBe(true);
expect(params[1].status).toEqual('error');
expect(params[2]).toBe(error);
});
it('should forward StepFailure event', function() {
model.on('StepFailure', callback);
runner.emit('SpecBegin', spec);
runner.emit('StepBegin', spec, step);
runner.emit('StepFailure', spec, step, {});
expect(callback).toHaveBeenCalled();
});
it('should forward StepFailure event with Spec, Step and error as params', function() {
var error = {};
model.on('StepFailure', callback);
runner.emit('SpecBegin', spec);
runner.emit('StepBegin', spec, step);
runner.emit('StepFailure', spec, step, error);
var params = callback.mostRecentCall.args;
expect(params[0] instanceof Spec).toBe(true);
expect(params[0].name).toEqual(spec.name);
expect(params[1] instanceof Step).toBe(true);
expect(params[1].status).toEqual('failure');
expect(params[2]).toBe(error);
});
it('should forward StepEnd event', function() {
model.on('StepEnd', callback);
runner.emit('SpecBegin', spec);
runner.emit('StepBegin', spec, step);
runner.emit('StepEnd', spec, step);
expect(callback).toHaveBeenCalled();
});
it('should forward StepEnd event with Spec and Step as params', function() {
model.on('StepEnd', callback);
runner.emit('SpecBegin', spec);
runner.emit('StepBegin', spec, step);
runner.emit('StepEnd', spec, step);
var params = callback.mostRecentCall.args;
expect(params[0] instanceof Spec).toBe(true);
expect(params[0].name).toEqual(spec.name);
expect(params[1] instanceof Step).toBe(true);
});
it('should forward RunnerEnd event', function() {
model.on('RunnerEnd', callback);
runner.emit('RunnerEnd');
expect(callback).toHaveBeenCalled();
});
it('should set error of first failure', function() {
var error = 'first-error',
step2 = buildStep();
model.on('SpecEnd', function(spec) {
expect(spec.error).toBeDefined();
expect(spec.error).toBe(error);
});
runner.emit('SpecBegin', spec);
runner.emit('StepBegin', spec, step);
runner.emit('StepFailure', spec, step, error);
runner.emit('StepBegin', spec, step2);
runner.emit('StepFailure', spec, step2, 'second-error');
runner.emit('SpecEnd', spec);
});
it('should set line number of first failure', function() {
var step = buildStep('fake', 'first-line'),
step2 = buildStep('fake2', 'second-line');
model.on('SpecEnd', function(spec) {
expect(spec.line).toBeDefined();
expect(spec.line).toBe('first-line');
});
runner.emit('SpecBegin', spec);
runner.emit('StepBegin', spec, step);
runner.emit('StepFailure', spec, step, null);
runner.emit('StepBegin', spec, step2);
runner.emit('StepFailure', spec, step2, null);
runner.emit('SpecEnd', spec);
});
});
});

View File

@@ -1,5 +1,5 @@
describe('angular.scenario.output.html', function() {
var runner, spec, listeners;
var runner, model, spec, listeners;
var ui, context;
beforeEach(function() {
@@ -22,8 +22,9 @@ describe('angular.scenario.output.html', function() {
line: function() { return 'unknown:-1'; }
};
runner = new angular.scenario.testing.MockRunner();
model = new angular.scenario.ObjectModel(runner);
context = _jQuery("<div></div>");
ui = angular.scenario.output.html(context, runner);
ui = angular.scenario.output.html(context, runner, model);
});
it('should create nested describe context', function() {

View File

@@ -1,13 +1,14 @@
describe('angular.scenario.output.json', function() {
var output, context;
var runner, $window;
var runner, model, $window;
var spec, step;
beforeEach(function() {
$window = {};
context = _jQuery('<div></div>');
runner = new angular.scenario.testing.MockRunner();
output = angular.scenario.output.json(context, runner);
model = new angular.scenario.ObjectModel(runner);
output = angular.scenario.output.json(context, runner, model);
spec = {
name: 'test spec',
definition: {

View File

@@ -1,13 +1,14 @@
describe('angular.scenario.output.object', function() {
var output;
var runner, $window;
var runner, model, $window;
var spec, step;
beforeEach(function() {
$window = {};
runner = new angular.scenario.testing.MockRunner();
model = new angular.scenario.ObjectModel(runner);
runner.$window = $window;
output = angular.scenario.output.object(null, runner);
output = angular.scenario.output.object(null, runner, model);
spec = {
name: 'test spec',
definition: {

View File

@@ -1,13 +1,14 @@
describe('angular.scenario.output.json', function() {
var output, context;
var runner, $window;
var runner, model, $window;
var spec, step;
beforeEach(function() {
$window = {};
context = _jQuery('<div></div>');
runner = new angular.scenario.testing.MockRunner();
output = angular.scenario.output.xml(context, runner);
model = new angular.scenario.ObjectModel(runner);
output = angular.scenario.output.xml(context, runner, model);
spec = {
name: 'test spec',
definition: {