Skip to content

Commit 656b5aa

Browse files
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 <a> directive when there is no href (and the ui-sref is on an <a>) Closes #1363
1 parent 0569016 commit 656b5aa

File tree

3 files changed

+63
-25
lines changed

3 files changed

+63
-25
lines changed

src/state.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1116,7 +1116,7 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
11161116

11171117
var nav = (state && options.lossy) ? state.navigable : state;
11181118

1119-
if (!nav || !nav.url) {
1119+
if (!nav || nav.url === undefined || nav.url === null) {
11201120
return null;
11211121
}
11221122
return $urlRouter.href(nav.url, filterByKeys(objectKeys(state.params), params || {}), {

src/stateDirectives.js

+6-2
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ function $StateRefDirective($state, $timeout) {
8686
link: function(scope, element, attrs, uiSrefActive) {
8787
var ref = parseStateRef(attrs.uiSref, $state.current.name);
8888
var params = null, url = null, base = stateContext(element) || $state.$current;
89+
var newHref = null, isAnchor = element.prop("tagName") === "A";
8990
var isForm = element[0].nodeName === "FORM";
9091
var attr = isForm ? "action" : "href", nav = true;
9192

@@ -102,7 +103,7 @@ function $StateRefDirective($state, $timeout) {
102103
if (newVal) params = newVal;
103104
if (!nav) return;
104105

105-
var newHref = $state.href(ref.state, params, options);
106+
newHref = $state.href(ref.state, params, options);
106107

107108
var activeDirective = uiSrefActive[1] || uiSrefActive[0];
108109
if (activeDirective) {
@@ -134,8 +135,11 @@ function $StateRefDirective($state, $timeout) {
134135
});
135136
e.preventDefault();
136137

138+
// if the state has no URL, ignore one preventDefault from the <a> directive.
139+
var ignorePreventDefaultCount = isAnchor && !newHref ? 1: 0;
137140
e.preventDefault = function() {
138-
$timeout.cancel(transition);
141+
if (ignorePreventDefaultCount-- <= 0)
142+
$timeout.cancel(transition);
139143
};
140144
}
141145
});

test/stateDirectivesSpec.js

+56-22
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
describe('uiStateRef', function() {
22

3-
var el, template, scope, document;
3+
var timeoutFlush, el, el2, template, scope, document, _locationProvider;
44

55
beforeEach(module('ui.router'));
66

7-
beforeEach(module(function($stateProvider) {
7+
beforeEach(module(function($stateProvider, $locationProvider) {
8+
_locationProvider = $locationProvider;
89
$stateProvider.state('top', {
910
url: ''
1011
}).state('contacts', {
@@ -74,29 +75,30 @@ describe('uiStateRef', function() {
7475
}));
7576
});
7677

77-
describe('links', function() {
78-
var timeoutFlush, el2;
7978

80-
beforeEach(inject(function($rootScope, $compile, $timeout) {
81-
el = angular.element('<a ui-sref="contacts.item.detail({ id: contact.id })">Details</a>');
82-
el2 = angular.element('<a ui-sref="top">Top</a>');
83-
scope = $rootScope;
84-
scope.contact = { id: 5 };
85-
scope.$apply();
79+
function buildDOM($rootScope, $compile, $timeout) {
80+
el = angular.element('<a ui-sref="contacts.item.detail({ id: contact.id })">Details</a>');
81+
el2 = angular.element('<a ui-sref="top">Top</a>');
82+
scope = $rootScope;
83+
scope.contact = { id: 5 };
84+
scope.$apply();
8685

87-
$compile(el)(scope);
88-
$compile(el2)(scope);
89-
scope.$digest();
86+
$compile(el)(scope);
87+
$compile(el2)(scope);
88+
scope.$digest();
9089

91-
timeoutFlush = function() {
92-
try {
93-
$timeout.flush();
94-
} catch (e) {
95-
// Angular 1.0.8 throws 'No deferred tasks to be flushed' if there is nothing in queue.
96-
// Behave as Angular >=1.1.5 and do nothing in such case.
97-
}
90+
timeoutFlush = function () {
91+
try {
92+
$timeout.flush();
93+
} catch (e) {
94+
// Angular 1.0.8 throws 'No deferred tasks to be flushed' if there is nothing in queue.
95+
// Behave as Angular >=1.1.5 and do nothing in such case.
9896
}
99-
}));
97+
}
98+
};
99+
100+
describe('links', function() {
101+
beforeEach(inject(buildDOM));
100102

101103
it('should generate the correct href', function() {
102104
expect(el.attr('href')).toBe('#/contacts/5');
@@ -217,7 +219,7 @@ describe('uiStateRef', function() {
217219
expect($state.current.name).toEqual('top');
218220
expect($stateParams).toEqualData({});
219221
}));
220-
222+
221223
it('should allow passing params to current state', inject(function($compile, $rootScope, $state) {
222224
$state.current.name = 'contacts.item.detail';
223225

@@ -243,6 +245,38 @@ describe('uiStateRef', function() {
243245
}));
244246
});
245247

248+
describe('links in html5 mode', function() {
249+
beforeEach(function() {
250+
_locationProvider.html5Mode(true);
251+
});
252+
253+
beforeEach(inject(buildDOM));
254+
255+
it('should generate the correct href', function() {
256+
expect(el.attr('href')).toBe('/contacts/5');
257+
expect(el2.attr('href')).toBe('');
258+
});
259+
260+
it('should update the href when parameters change', function() {
261+
expect(el.attr('href')).toBe('/contacts/5');
262+
scope.contact.id = 6;
263+
scope.$apply();
264+
expect(el.attr('href')).toBe('/contacts/6');
265+
});
266+
267+
it('should transition states when the url is empty', inject(function($state, $stateParams, $q) {
268+
// Odd, in html5Mode, the initial state isn't matching on empty url, but does match if top.url is "/".
269+
// expect($state.$current.name).toEqual('top');
270+
271+
triggerClick(el2);
272+
timeoutFlush();
273+
$q.flush();
274+
275+
expect($state.current.name).toEqual('top');
276+
expect($stateParams).toEqualData({});
277+
}));
278+
});
279+
246280
describe('forms', function() {
247281
var el, scope;
248282

0 commit comments

Comments
 (0)