From 656b5aab906e5749db9b5a080c6a83b95f50fd91 Mon Sep 17 00:00:00 2001 From: christopherthielen Date: Thu, 11 Sep 2014 21:50:20 -0500 Subject: [PATCH] fix(uiSref): nagivate to state when url is "" fix($state.href): generate href for state with url: "" test(uiSref): add tests for html5Mode Made resolve values ignore any keys that we're actively trying to resolve, when they also exist in some parent state. Made ui-sref ignore a single preventDefault that comes from the directive when there is no href (and the ui-sref is on an ) Closes #1363 --- src/state.js | 2 +- src/stateDirectives.js | 8 +++- test/stateDirectivesSpec.js | 78 ++++++++++++++++++++++++++----------- 3 files changed, 63 insertions(+), 25 deletions(-) diff --git a/src/state.js b/src/state.js index 3330a8655..6a33feb58 100644 --- a/src/state.js +++ b/src/state.js @@ -1116,7 +1116,7 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) { var nav = (state && options.lossy) ? state.navigable : state; - if (!nav || !nav.url) { + if (!nav || nav.url === undefined || nav.url === null) { return null; } return $urlRouter.href(nav.url, filterByKeys(objectKeys(state.params), params || {}), { diff --git a/src/stateDirectives.js b/src/stateDirectives.js index 3b5831885..ba730ce29 100644 --- a/src/stateDirectives.js +++ b/src/stateDirectives.js @@ -86,6 +86,7 @@ function $StateRefDirective($state, $timeout) { link: function(scope, element, attrs, uiSrefActive) { var ref = parseStateRef(attrs.uiSref, $state.current.name); var params = null, url = null, base = stateContext(element) || $state.$current; + var newHref = null, isAnchor = element.prop("tagName") === "A"; var isForm = element[0].nodeName === "FORM"; var attr = isForm ? "action" : "href", nav = true; @@ -102,7 +103,7 @@ function $StateRefDirective($state, $timeout) { if (newVal) params = newVal; if (!nav) return; - var newHref = $state.href(ref.state, params, options); + newHref = $state.href(ref.state, params, options); var activeDirective = uiSrefActive[1] || uiSrefActive[0]; if (activeDirective) { @@ -134,8 +135,11 @@ function $StateRefDirective($state, $timeout) { }); e.preventDefault(); + // if the state has no URL, ignore one preventDefault from the directive. + var ignorePreventDefaultCount = isAnchor && !newHref ? 1: 0; e.preventDefault = function() { - $timeout.cancel(transition); + if (ignorePreventDefaultCount-- <= 0) + $timeout.cancel(transition); }; } }); diff --git a/test/stateDirectivesSpec.js b/test/stateDirectivesSpec.js index 962a5471a..1e9e1bead 100644 --- a/test/stateDirectivesSpec.js +++ b/test/stateDirectivesSpec.js @@ -1,10 +1,11 @@ describe('uiStateRef', function() { - var el, template, scope, document; + var timeoutFlush, el, el2, template, scope, document, _locationProvider; beforeEach(module('ui.router')); - beforeEach(module(function($stateProvider) { + beforeEach(module(function($stateProvider, $locationProvider) { + _locationProvider = $locationProvider; $stateProvider.state('top', { url: '' }).state('contacts', { @@ -74,29 +75,30 @@ describe('uiStateRef', function() { })); }); - describe('links', function() { - var timeoutFlush, el2; - beforeEach(inject(function($rootScope, $compile, $timeout) { - el = angular.element('Details'); - el2 = angular.element('Top'); - scope = $rootScope; - scope.contact = { id: 5 }; - scope.$apply(); + function buildDOM($rootScope, $compile, $timeout) { + el = angular.element('Details'); + el2 = angular.element('Top'); + scope = $rootScope; + scope.contact = { id: 5 }; + scope.$apply(); - $compile(el)(scope); - $compile(el2)(scope); - scope.$digest(); + $compile(el)(scope); + $compile(el2)(scope); + scope.$digest(); - timeoutFlush = function() { - try { - $timeout.flush(); - } catch (e) { - // Angular 1.0.8 throws 'No deferred tasks to be flushed' if there is nothing in queue. - // Behave as Angular >=1.1.5 and do nothing in such case. - } + timeoutFlush = function () { + try { + $timeout.flush(); + } catch (e) { + // Angular 1.0.8 throws 'No deferred tasks to be flushed' if there is nothing in queue. + // Behave as Angular >=1.1.5 and do nothing in such case. } - })); + } + }; + + describe('links', function() { + beforeEach(inject(buildDOM)); it('should generate the correct href', function() { expect(el.attr('href')).toBe('#/contacts/5'); @@ -217,7 +219,7 @@ describe('uiStateRef', function() { expect($state.current.name).toEqual('top'); expect($stateParams).toEqualData({}); })); - + it('should allow passing params to current state', inject(function($compile, $rootScope, $state) { $state.current.name = 'contacts.item.detail'; @@ -243,6 +245,38 @@ describe('uiStateRef', function() { })); }); + describe('links in html5 mode', function() { + beforeEach(function() { + _locationProvider.html5Mode(true); + }); + + beforeEach(inject(buildDOM)); + + it('should generate the correct href', function() { + expect(el.attr('href')).toBe('/contacts/5'); + expect(el2.attr('href')).toBe(''); + }); + + it('should update the href when parameters change', function() { + expect(el.attr('href')).toBe('/contacts/5'); + scope.contact.id = 6; + scope.$apply(); + expect(el.attr('href')).toBe('/contacts/6'); + }); + + it('should transition states when the url is empty', inject(function($state, $stateParams, $q) { + // Odd, in html5Mode, the initial state isn't matching on empty url, but does match if top.url is "/". +// expect($state.$current.name).toEqual('top'); + + triggerClick(el2); + timeoutFlush(); + $q.flush(); + + expect($state.current.name).toEqual('top'); + expect($stateParams).toEqualData({}); + })); + }); + describe('forms', function() { var el, scope;