@@ -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
similarity index 100%
rename from docs/static/settings.html
rename to docs/examples/settings.html
diff --git a/docs/static/welcome.html b/docs/examples/welcome.html
similarity index 100%
rename from docs/static/welcome.html
rename to docs/examples/welcome.html
diff --git a/docs/src/gen-docs.js b/docs/src/gen-docs.js
index 83e339425b3b..464916b1a718 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 bc244d5d9259..fcc70431f73b 100644
--- a/docs/src/templates/docs-scenario.html
+++ b/docs/src/templates/docs-scenario.html
@@ -2,7 +2,7 @@
<angular/> Docs Scenario Runner
-
+
diff --git a/docs/src/writer.js b/docs/src/writer.js
index 3251b9cdbb55..cf54e1a3aa94 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 7129c2281590..2dd776db4517 100644
--- a/example/personalLog/scenario/runner.html
+++ b/example/personalLog/scenario/runner.html
@@ -2,7 +2,7 @@
Personal Log Scenario Runner
-
+
diff --git a/example/personalLog/test/personalLogSpec.js b/example/personalLog/test/personalLogSpec.js
index 7502ff2147be..3e6935a3d9be 100644
--- a/example/personalLog/test/personalLogSpec.js
+++ b/example/personalLog/test/personalLogSpec.js
@@ -11,7 +11,7 @@ describe('example.personalLog.LogCtrl', function() {
beforeEach(function() {
logCtrl = createNotesCtrl();
});
-
+
it('should initialize notes with an empty array', function() {
expect(logCtrl.logs).toEqual([]);
@@ -28,7 +28,7 @@ describe('example.personalLog.LogCtrl', function() {
it('should add newMsg to logs as a log entry', function() {
logCtrl.newMsg = 'first log message';
logCtrl.addLog();
-
+
expect(logCtrl.logs.length).toBe(1);
expect(logCtrl.logs[0].msg).toBe('first log message');
diff --git a/jsTestDriver-scenario.conf b/jsTestDriver-scenario.conf
new file mode 100644
index 000000000000..1ad7d32f2250
--- /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 204594d4bf01..901803b73046 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 f5f76fde477a..78cd7e57d7dd 100644
--- a/scenario/Runner-compiled.html
+++ b/scenario/Runner-compiled.html
@@ -1,7 +1,7 @@
-
+
diff --git a/scenario/Runner.html b/scenario/Runner.html
index f715b8e5f4ac..fa3ccf23b937 100644
--- a/scenario/Runner.html
+++ b/scenario/Runner.html
@@ -1,7 +1,7 @@
-
+
diff --git a/scenario/datastore-scenarios.js b/scenario/datastore-scenarios.js
index 6038070be69b..a844ac5377c7 100644
--- a/scenario/datastore-scenarios.js
+++ b/scenario/datastore-scenarios.js
@@ -1,8 +1,8 @@
angular.scenarioDef.datastore = {
$before:[
- {Given:"dataset",
+ {Given:"dataset",
dataset:{
- Book:[{$id:'moby', name:"Moby Dick"},
+ Book:[{$id:'moby', name:"Moby Dick"},
{$id:'gadsby', name:'Great Gadsby'}]
}
},
@@ -10,10 +10,10 @@ angular.scenarioDef.datastore = {
],
checkLoadBook:[
{Then:"drainRequestQueue"},
-
+
{Then:"text", at:"{{book.$id}}", should_be:"moby"},
{Then:"text", at:"li[$index=0] {{book.name}}", should_be:"Great Gahdsby"},
{Then:"text", at:"li[$index=0] {{book.name}}", should_be:"Moby Dick"},
-
+
]
};
diff --git a/server-scenario.sh b/server-scenario.sh
new file mode 100755
index 000000000000..3f7c42d65b87
--- /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/Browser.js b/src/Browser.js
index 554397626031..b10c43cf34ca 100644
--- a/src/Browser.js
+++ b/src/Browser.js
@@ -7,10 +7,14 @@ var XHR = window.XMLHttpRequest || function () {
try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch (e3) {}
throw new Error("This browser does not support XMLHttpRequest.");
};
+
+// default xhr headers
var XHR_HEADERS = {
- "Content-Type": "application/x-www-form-urlencoded",
- "Accept": "application/json, text/plain, */*",
- "X-Requested-With": "XMLHttpRequest"
+ DEFAULT: {
+ "Accept": "application/json, text/plain, */*",
+ "X-Requested-With": "XMLHttpRequest"
+ },
+ POST: {'Content-Type': 'application/x-www-form-urlencoded'}
};
/**
@@ -103,8 +107,9 @@ function Browser(window, document, body, XHR, $log) {
} else {
var xhr = new XHR();
xhr.open(method, url, true);
- forEach(extend(XHR_HEADERS, headers || {}), function(value, key){
- if (value) xhr.setRequestHeader(key, value);
+ forEach(extend({}, XHR_HEADERS.DEFAULT, XHR_HEADERS[uppercase(method)] || {}, headers || {}),
+ function(value, key) {
+ if (value) xhr.setRequestHeader(key, value);
});
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
diff --git a/src/jstd-scenario-adapter/Adapter.js b/src/jstd-scenario-adapter/Adapter.js
new file mode 100644
index 000000000000..fd9674e1a5fb
--- /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:
+
+ 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/"}
+
+ *
+ * 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
+
+ var jstdScenarioAdapter = {
+ relativeUrlPrefix: '/your-prefix/'
+ };
+
+ *
+ * Whenever you use browser().navigateTo('relativeUrl') 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 browser().navigateTo('index.html') 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 000000000000..ab8a71525852
--- /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 000000000000..6134fb010679
--- /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/Application.js b/src/scenario/Application.js
index 988dae90d76a..b2c372de73d5 100644
--- a/src/scenario/Application.js
+++ b/src/scenario/Application.js
@@ -37,35 +37,6 @@ angular.scenario.Application.prototype.getWindow_ = function() {
return contentWindow;
};
-/**
- * Checks that a URL would return a 2xx success status code. Callback is called
- * with no arguments on success, or with an error on failure.
- *
- * Warning: This requires the server to be able to respond to HEAD requests
- * and not modify the state of your application.
- *
- * @param {string} url Url to check
- * @param {Function} callback function(error) that is called with result.
- */
-angular.scenario.Application.prototype.checkUrlStatus_ = function(url, callback) {
- var self = this;
- _jQuery.ajax({
- url: url.replace(/#.*/, ''), //IE encodes and sends the url fragment, so we must strip it
- type: 'HEAD',
- complete: function(request) {
- if (request.status < 200 || request.status >= 300) {
- if (!request.status) {
- callback.call(self, 'Sandbox Error: Cannot access ' + url);
- } else {
- callback.call(self, request.status + ' ' + request.statusText);
- }
- } else {
- callback.call(self);
- }
- }
- });
-};
-
/**
* Changes the location of the frame.
*
@@ -87,21 +58,16 @@ angular.scenario.Application.prototype.navigateTo = function(url, loadFn, errorF
this.executeAction(loadFn);
} else {
frame.css('display', 'none').attr('src', 'about:blank');
- this.checkUrlStatus_(url, function(error) {
- if (error) {
- return errorFn(error);
+ this.context.find('#test-frames').append('