aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md5
-rw-r--r--Rakefile32
-rw-r--r--docs/cookbook.deeplinking.ngdoc8
-rw-r--r--docs/examples/settings.html (renamed from docs/static/settings.html)0
-rw-r--r--docs/examples/welcome.html (renamed from docs/static/welcome.html)0
-rw-r--r--docs/src/gen-docs.js26
-rw-r--r--docs/src/templates/docs-scenario.html2
-rw-r--r--docs/src/writer.js2
-rw-r--r--example/personalLog/scenario/runner.html2
-rw-r--r--jsTestDriver-scenario.conf10
-rw-r--r--jsTestDriver.conf2
-rw-r--r--scenario/Runner-compiled.html2
-rw-r--r--scenario/Runner.html2
-rwxr-xr-xserver-scenario.sh3
-rw-r--r--src/jstd-scenario-adapter/Adapter.js175
-rw-r--r--src/jstd-scenario-adapter/angular.prefix24
-rw-r--r--src/jstd-scenario-adapter/angular.suffix2
-rw-r--r--src/scenario/Describe.js4
-rw-r--r--src/scenario/ObjectModel.js132
-rw-r--r--src/scenario/Runner.js5
-rw-r--r--src/scenario/Scenario.js23
-rw-r--r--src/scenario/angular-bootstrap.js8
-rw-r--r--src/scenario/angular.suffix9
-rw-r--r--src/scenario/output/Html.js48
-rw-r--r--src/scenario/output/Json.js6
-rw-r--r--src/scenario/output/Object.js4
-rw-r--r--src/scenario/output/Xml.js5
-rwxr-xr-xtest-scenario.sh7
-rw-r--r--test/jstd-scenario-adapter/AdapterSpecs.js320
-rw-r--r--test/scenario/DescribeSpec.js10
-rw-r--r--test/scenario/ObjectModelSpec.js257
-rw-r--r--test/scenario/output/HtmlSpec.js5
-rw-r--r--test/scenario/output/jsonSpec.js5
-rw-r--r--test/scenario/output/objectSpec.js5
-rw-r--r--test/scenario/output/xmlSpec.js5
35 files changed, 1034 insertions, 121 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index daa7537d..1856124a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,7 +1,12 @@
<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/>
diff --git a/Rakefile b/Rakefile
index fff9ce1c..31307c4f 100644
--- a/Rakefile
+++ b/Rakefile
@@ -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)
diff --git a/docs/cookbook.deeplinking.ngdoc b/docs/cookbook.deeplinking.ngdoc
index 5270eb16..7d69ee84 100644
--- a/docs/cookbook.deeplinking.ngdoc
+++ b/docs/cookbook.deeplinking.ngdoc
@@ -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
diff --git a/docs/static/settings.html b/docs/examples/settings.html
index 2fa5dca8..2fa5dca8 100644
--- a/docs/static/settings.html
+++ b/docs/examples/settings.html
diff --git a/docs/static/welcome.html b/docs/examples/welcome.html
index b085123d..b085123d 100644
--- a/docs/static/welcome.html
+++ b/docs/examples/welcome.html
diff --git a/docs/src/gen-docs.js b/docs/src/gen-docs.js
index 83e33942..464916b1 100644
--- a/docs/src/gen-docs.js
+++ b/docs/src/gen-docs.js
@@ -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 ' +
diff --git a/docs/src/templates/docs-scenario.html b/docs/src/templates/docs-scenario.html
index bc244d5d..fcc70431 100644
--- a/docs/src/templates/docs-scenario.html
+++ b/docs/src/templates/docs-scenario.html
@@ -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>
diff --git a/docs/src/writer.js b/docs/src/writer.js
index 3251b9cd..cf54e1a3 100644
--- a/docs/src/writer.js
+++ b/docs/src/writer.js
@@ -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);
};
diff --git a/example/personalLog/scenario/runner.html b/example/personalLog/scenario/runner.html
index 7129c228..2dd776db 100644
--- a/example/personalLog/scenario/runner.html
+++ b/example/personalLog/scenario/runner.html
@@ -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>
diff --git a/jsTestDriver-scenario.conf b/jsTestDriver-scenario.conf
new file mode 100644
index 00000000..1ad7d32f
--- /dev/null
+++ b/jsTestDriver-scenario.conf
@@ -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"}
diff --git a/jsTestDriver.conf b/jsTestDriver.conf
index 204594d4..901803b7 100644
--- a/jsTestDriver.conf
+++ b/jsTestDriver.conf
@@ -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
diff --git a/scenario/Runner-compiled.html b/scenario/Runner-compiled.html
index f5f76fde..78cd7e57 100644
--- a/scenario/Runner-compiled.html
+++ b/scenario/Runner-compiled.html
@@ -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>
diff --git a/scenario/Runner.html b/scenario/Runner.html
index f715b8e5..fa3ccf23 100644
--- a/scenario/Runner.html
+++ b/scenario/Runner.html
@@ -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>
diff --git a/server-scenario.sh b/server-scenario.sh
new file mode 100755
index 00000000..3f7c42d6
--- /dev/null
+++ b/server-scenario.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+java -jar lib/jstestdriver/JsTestDriver.jar --port 9877 --browserTimeout 90000 --config jsTestDriver-scenario.conf
diff --git a/src/jstd-scenario-adapter/Adapter.js b/src/jstd-scenario-adapter/Adapter.js
new file mode 100644
index 00000000..fd9674e1
--- /dev/null
+++ b/src/jstd-scenario-adapter/Adapter.js
@@ -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();
+ });
+});
diff --git a/src/jstd-scenario-adapter/angular.prefix b/src/jstd-scenario-adapter/angular.prefix
new file mode 100644
index 00000000..ab8a7152
--- /dev/null
+++ b/src/jstd-scenario-adapter/angular.prefix
@@ -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) {
diff --git a/src/jstd-scenario-adapter/angular.suffix b/src/jstd-scenario-adapter/angular.suffix
new file mode 100644
index 00000000..6134fb01
--- /dev/null
+++ b/src/jstd-scenario-adapter/angular.suffix
@@ -0,0 +1,2 @@
+initScenarioAdapter(window.jstestdriver, angular.scenario.setUpAndRun, window.jstdScenarioAdapter);
+})(window);
diff --git a/src/scenario/Describe.js b/src/scenario/Describe.js
index 50cfded6..c6484f2f 100644
--- a/src/scenario/Describe.js
+++ b/src/scenario/Describe.js
@@ -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,
diff --git a/src/scenario/ObjectModel.js b/src/scenario/ObjectModel.js
index 263aa5f9..bff14461 100644
--- a/src/scenario/ObjectModel.js
+++ b/src/scenario/ObjectModel.js
@@ -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) {
@@ -82,6 +116,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(' ');
};
/**
@@ -143,6 +209,19 @@ angular.scenario.ObjectModel.Spec.prototype.getLastStep = function() {
};
/**
+ * 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.
*
* @param {string} step Name of the step
@@ -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;
+};
diff --git a/src/scenario/Runner.js b/src/scenario/Runner.js
index 583c8beb..51a81d6a 100644
--- a/src/scenario/Runner.js
+++ b/src/scenario/Runner.js
@@ -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 = [];
diff --git a/src/scenario/Scenario.js b/src/scenario/Scenario.js
index 87dad5b5..6879d787 100644
--- a/src/scenario/Scenario.js
+++ b/src/scenario/Scenario.js
@@ -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
diff --git a/src/scenario/angular-bootstrap.js b/src/scenario/angular-bootstrap.js
index 68dc393e..264ce718 100644
--- a/src/scenario/angular-bootstrap.js
+++ b/src/scenario/angular-bootstrap.js
@@ -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);
diff --git a/src/scenario/angular.suffix b/src/scenario/angular.suffix
index 3ab796d2..014c2cc9 100644
--- a/src/scenario/angular.suffix
+++ b/src/scenario/angular.suffix
@@ -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);
+
diff --git a/src/scenario/output/Html.js b/src/scenario/output/Html.js
index 6e2e20f3..ccf7db6f 100644
--- a/src/scenario/output/Html.js
+++ b/src/scenario/output/Html.js
@@ -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'));
});
diff --git a/src/scenario/output/Json.js b/src/scenario/output/Json.js
index 2c852496..1e3bef6b 100644
--- a/src/scenario/output/Json.js
+++ b/src/scenario/output/Json.js
@@ -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));
});
});
diff --git a/src/scenario/output/Object.js b/src/scenario/output/Object.js
index 3257cfd7..1f29af9c 100644
--- a/src/scenario/output/Object.js
+++ b/src/scenario/output/Object.js
@@ -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;
});
diff --git a/src/scenario/output/Xml.js b/src/scenario/output/Xml.js
index e8c7f0e3..1a4c362c 100644
--- a/src/scenario/output/Xml.js
+++ b/src/scenario/output/Xml.js
@@ -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);
diff --git a/test-scenario.sh b/test-scenario.sh
new file mode 100755
index 00000000..8ecf8b03
--- /dev/null
+++ b/test-scenario.sh
@@ -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
diff --git a/test/jstd-scenario-adapter/AdapterSpecs.js b/test/jstd-scenario-adapter/AdapterSpecs.js
new file mode 100644
index 00000000..ccf08e3d
--- /dev/null
+++ b/test/jstd-scenario-adapter/AdapterSpecs.js
@@ -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);
+ });
+ });
+});
diff --git a/test/scenario/DescribeSpec.js b/test/scenario/DescribeSpec.js
index 6fcee731..0322b2d4 100644
--- a/test/scenario/DescribeSpec.js
+++ b/test/scenario/DescribeSpec.js
@@ -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);
+ });
});
diff --git a/test/scenario/ObjectModelSpec.js b/test/scenario/ObjectModelSpec.js
index 8b83a52f..cb84b33f 100644
--- a/test/scenario/ObjectModelSpec.js
+++ b/test/scenario/ObjectModelSpec.js
@@ -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);
+ });
+ });
});
diff --git a/test/scenario/output/HtmlSpec.js b/test/scenario/output/HtmlSpec.js
index f973397e..6694bb45 100644
--- a/test/scenario/output/HtmlSpec.js
+++ b/test/scenario/output/HtmlSpec.js
@@ -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() {
diff --git a/test/scenario/output/jsonSpec.js b/test/scenario/output/jsonSpec.js
index afc74a21..2c56b297 100644
--- a/test/scenario/output/jsonSpec.js
+++ b/test/scenario/output/jsonSpec.js
@@ -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: {
diff --git a/test/scenario/output/objectSpec.js b/test/scenario/output/objectSpec.js
index 73c3dcf9..9fc2f7d4 100644
--- a/test/scenario/output/objectSpec.js
+++ b/test/scenario/output/objectSpec.js
@@ -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: {
diff --git a/test/scenario/output/xmlSpec.js b/test/scenario/output/xmlSpec.js
index fbfabcc4..a0e92639 100644
--- a/test/scenario/output/xmlSpec.js
+++ b/test/scenario/output/xmlSpec.js
@@ -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: {