diff --git a/Rakefile b/Rakefile index 9c18da9dc728..36ec7675448f 100644 --- a/Rakefile +++ b/Rakefile @@ -237,7 +237,7 @@ end ## # generates css snippet from a given files and optionally applies simple minification rules # -def gen_css(cssFile, minify = false) +def gen_css(cssFile, minify = true) css = '' File.open(cssFile, 'r') do |f| css = f.read diff --git a/css/angular-scenario.css b/css/angular-scenario.css index b8d25c1c08d0..ab4c3180f00d 100644 --- a/css/angular-scenario.css +++ b/css/angular-scenario.css @@ -195,6 +195,12 @@ body { background-color: #efefef; } +#specs .description { + background-color: #EFEFEF; + padding-left: 20px; + font-style: italic; +} + #application { border: 1px solid #BABAD1; } diff --git a/gen_jstd_configs.js b/gen_jstd_configs.js index 842cfe2100d6..c8227ff12b43 100755 --- a/gen_jstd_configs.js +++ b/gen_jstd_configs.js @@ -31,8 +31,8 @@ fs.readFile('angularFiles.js', function(err, data) { fs.writeFile('./jsTestDriver-coverage.conf', prefix + combine(angularFiles.jstd, angularFiles.jstdExclude) + '\n\nplugin:\n- name: "coverage"\n' + - 'jar: "lib/jstestdriver/coverage.jar"\n' + - 'module: "com.google.jstestdriver.coverage.CoverageModule"'); + ' jar: "lib/jstestdriver/coverage.jar"\n' + + ' module: "com.google.jstestdriver.coverage.CoverageModule"'); }); function combine(load, exclude) { diff --git a/src/ng/browser.js b/src/ng/browser.js index 3925348317ce..245b6417ce6d 100644 --- a/src/ng/browser.js +++ b/src/ng/browser.js @@ -29,7 +29,8 @@ function Browser(window, document, $log, $sniffer) { history = window.history, setTimeout = window.setTimeout, clearTimeout = window.clearTimeout, - pendingDeferIds = {}; + pendingDeferIds = {}, + useHtml5HistoryMode = false; self.isMock = false; @@ -151,7 +152,7 @@ function Browser(window, document, $log, $sniffer) { if (url) { if (lastBrowserUrl == url) return; lastBrowserUrl = url; - if ($sniffer.history) { + if ($sniffer.history && useHtml5HistoryMode) { if (replace) history.replaceState(null, '', url); else { history.pushState(null, '', url); @@ -212,7 +213,7 @@ function Browser(window, document, $log, $sniffer) { // changed by push/replaceState // html5 history api - popstate event - if ($sniffer.history) jqLite(window).bind('popstate', fireUrlChange); + if ($sniffer.history && useHtml5HistoryMode) jqLite(window).bind('popstate', fireUrlChange); // hashchange event if ($sniffer.hashchange) jqLite(window).bind('hashchange', fireUrlChange); // polling @@ -352,6 +353,15 @@ function Browser(window, document, $log, $sniffer) { return false; }; + self.html5HistoryMode = function(mode) { + if (isDefined(mode)) { + useHtml5HistoryMode = mode; + return this; + } else { + return useHtml5HistoryMode; + } + }; + } function $BrowserProvider(){ diff --git a/src/ng/location.js b/src/ng/location.js index cf50952d53c3..3c8b9752ba75 100644 --- a/src/ng/location.js +++ b/src/ng/location.js @@ -514,6 +514,9 @@ function $LocationProvider(){ initUrlParts = matchUrl(initUrl), appBaseUrl; + // Apply the html5Mode setting to the $browser service + $browser.html5HistoryMode(html5Mode); + if (html5Mode) { basePath = $browser.baseHref() || '/'; pathPrefix = pathPrefixFromBase(basePath); diff --git a/src/ng/sniffer.js b/src/ng/sniffer.js index a7d73716b05d..81efef0bc54c 100644 --- a/src/ng/sniffer.js +++ b/src/ng/sniffer.js @@ -13,16 +13,38 @@ * This is very simple implementation of testing browser's features. */ function $SnifferProvider() { + var historyPushStateUpdatesURL; + this.$get = ['$window', function($window) { - var eventSupport = {}, - android = int((/android (\d+)/.exec(lowercase($window.navigator.userAgent)) || [])[1]); + var eventSupport = {}; + + // Android has history.pushState, but it does not update location correctly + // so let's not use the history API at all. + // http://code.google.com/p/android/issues/detail?id=17471 + // https://github.com/angular/angular.js/issues/904 + // + // Detect this issue by checking whether browser history.pushState changes URL + // rather then relying on browser userAgent string. + if(historyPushStateUpdatesURL === undefined){ + historyPushStateUpdatesURL = !!($window.history && $window.history.pushState); + + if(historyPushStateUpdatesURL){ + var urlToken = '__ANGULARJS__'; + var originalHref = $window.location.href; + var originalTitle = $window.document.title; + + $window.history.pushState(null, '', urlToken); + historyPushStateUpdatesURL = $window.location.href.indexOf(urlToken) > -1; + + if (historyPushStateUpdatesURL) + { + $window.history.replaceState(null, originalTitle, originalHref); + } + } + } return { - // Android has history.pushState, but it does not update location correctly - // so let's not use the history API at all. - // http://code.google.com/p/android/issues/detail?id=17471 - // https://github.com/angular/angular.js/issues/904 - history: !!($window.history && $window.history.pushState && !(android < 4)), + history: !!historyPushStateUpdatesURL, hashchange: 'onhashchange' in $window && // IE8 compatible mode lies (!$window.document.documentMode || $window.document.documentMode > 7), diff --git a/src/ngScenario/Application.js b/src/ngScenario/Application.js index 2ca6c8fdf555..f37af5a90583 100644 --- a/src/ngScenario/Application.js +++ b/src/ngScenario/Application.js @@ -81,27 +81,43 @@ angular.scenario.Application.prototype.navigateTo = function(url, loadFn, errorF * @param {function()} action The callback to execute. function($window, $document) * $document is a jQuery wrapped document. */ -angular.scenario.Application.prototype.executeAction = function(action) { - var self = this; - var $window = this.getWindow_(); - if (!$window.document) { - throw 'Sandbox Error: Application document not accessible.'; - } - if (!$window.angular) { - return action.call(this, $window, _jQuery($window.document)); - } - angularInit($window.document, function(element) { - var $injector = $window.angular.element(element).injector(); - var $element = _jQuery(element); +angular.scenario.Application.prototype.executeAction = function(action) +{ + var self = this; + var $window = this.getWindow_(); + if (!$window.document) { + throw 'Sandbox Error: Application document not accessible.'; + } + + if (!$window.angular) { + return action.call(this, $window, _jQuery($window.document)); + } + + var initialisedWithNgAppDirective = false; + + // angularInit calls this function only if ng-app directive is detected + var initFn = function(element) { + initialisedWithNgAppDirective = true; + + var $injector = $window.angular.element(element).injector(); + var $element = _jQuery(element); + + $element.injector = function() { + return $injector; + }; + + $injector.invoke(function($browser) { + $browser.notifyWhenNoOutstandingRequests(function() { + action.call(self, $window, $element); + }); + }); + }; - $element.injector = function() { - return $injector; - }; + // original implementation searches for ng-app and attaches to angular if found + angularInit($window.document, initFn); - $injector.invoke(function($browser){ - $browser.notifyWhenNoOutstandingRequests(function() { - action.call(self, $window, $element); - }); - }); - }); + if (!initialisedWithNgAppDirective) { + // ng-app not found so manually attaching to the root of document + initFn($window.document); + } }; diff --git a/src/ngScenario/Describe.js b/src/ngScenario/Describe.js index 4d52e9d58486..a95f341ab7c2 100644 --- a/src/ngScenario/Describe.js +++ b/src/ngScenario/Describe.js @@ -6,14 +6,16 @@ * * @param {string} descName Name of the block * @param {Object} parent describe or undefined if the root. + * @param {string} descDescription Description of the block */ -angular.scenario.Describe = function(descName, parent) { +angular.scenario.Describe = function(descName, parent, descDescription) { this.only = parent && parent.only; this.beforeEachFns = []; this.afterEachFns = []; this.its = []; this.children = []; this.name = descName; + this.description = descDescription; this.parent = parent; this.id = angular.scenario.Describe.id++; @@ -65,9 +67,10 @@ angular.scenario.Describe.prototype.afterEach = function(body) { * * @param {string} name Name of the block. Appended to the parent block's name. * @param {function()} body Body of the block. + * @param {string} description Description of the block. Appended to the parent block's name. */ -angular.scenario.Describe.prototype.describe = function(name, body) { - var child = new angular.scenario.Describe(name, this); +angular.scenario.Describe.prototype.describe = function(name, body, description) { + var child = new angular.scenario.Describe(name, this, description); this.children.push(child); body.call(child); }; diff --git a/src/ngScenario/Runner.js b/src/ngScenario/Runner.js index 06ad3aa184de..769f2396ddcf 100644 --- a/src/ngScenario/Runner.js +++ b/src/ngScenario/Runner.js @@ -12,10 +12,11 @@ angular.scenario.Runner = function($window) { this.rootDescribe = new angular.scenario.Describe(); this.currentDescribe = this.rootDescribe; this.api = { - it: this.it, + it: this.it, iit: this.iit, xit: angular.noop, describe: this.describe, + describeStart: this.describeStart, ddescribe: this.ddescribe, xdescribe: angular.noop, beforeEach: this.beforeEach, @@ -76,6 +77,27 @@ angular.scenario.Runner.prototype.describe = function(name, body) { }); }; +/** + * Defines a describe block of a spec, but allows the body to callback when the describe has been processed. + * + * @see Describe.js + * + * @param {string} name Name of the block + * @param {function()} body Body of the block + * @param {string} description Description of the block + */ +angular.scenario.Runner.prototype.describeStart = function(name, body, description) { + var self = this; + this.currentDescribe.describe(name, function() { + var parentDescribe = self.currentDescribe; + var endDescribeCallback = function() {self.currentDescribe = parentDescribe;}; + + self.currentDescribe = this; + body.call(this, endDescribeCallback); + }, + description); +}; + /** * Same as describe, but makes ddescribe the only blocks to run. * diff --git a/src/ngScenario/output/Html.js b/src/ngScenario/output/Html.js index 7f69a734db20..39a99d1d0459 100644 --- a/src/ngScenario/output/Html.js +++ b/src/ngScenario/output/Html.js @@ -133,11 +133,20 @@ angular.scenario.output('html', function(context, runner, model) { currentContext.find('> .test-children').append( '