mirror of
https://github.com/wahyd4/Libr-mobile.git
synced 2026-08-15 15:56:36 +10:00
929 lines
32 KiB
JavaScript
929 lines
32 KiB
JavaScript
describe('state', function () {
|
|
|
|
var stateProvider, locationProvider, templateParams, ctrlName;
|
|
|
|
beforeEach(module('ui.router', function($locationProvider) {
|
|
locationProvider = $locationProvider;
|
|
$locationProvider.html5Mode(false);
|
|
}));
|
|
|
|
var log, logEvents, logEnterExit;
|
|
function eventLogger(event, to, toParams, from, fromParams) {
|
|
if (logEvents) log += event.name + '(' + to.name + ',' + from.name + ');';
|
|
}
|
|
function callbackLogger(what) {
|
|
return function () {
|
|
if (logEnterExit) log += this.name + '.' + what + ';';
|
|
};
|
|
}
|
|
|
|
var A = { data: {} },
|
|
B = {},
|
|
C = {},
|
|
D = { params: [ 'x', 'y' ] },
|
|
DD = { parent: D, params: [ 'x', 'y', 'z' ] },
|
|
E = { params: [ 'i' ] },
|
|
H = { data: {propA: 'propA', propB: 'propB'} },
|
|
HH = { parent: H },
|
|
HHH = {parent: HH, data: {propA: 'overriddenA', propC: 'propC'} },
|
|
RS = { url: '^/search?term', reloadOnSearch: false },
|
|
AppInjectable = {};
|
|
|
|
beforeEach(module(function ($stateProvider, $provide) {
|
|
angular.forEach([ A, B, C, D, DD, E, H, HH, HHH ], function (state) {
|
|
state.onEnter = callbackLogger('onEnter');
|
|
state.onExit = callbackLogger('onExit');
|
|
});
|
|
stateProvider = $stateProvider;
|
|
|
|
$stateProvider
|
|
.state('A', A)
|
|
.state('B', B)
|
|
.state('C', C)
|
|
.state('D', D)
|
|
.state('DD', DD)
|
|
.state('E', E)
|
|
.state('H', H)
|
|
.state('HH', HH)
|
|
.state('HHH', HHH)
|
|
.state('RS', RS)
|
|
|
|
.state('home', { url: "/" })
|
|
.state('home.item', { url: "front/:id" })
|
|
.state('about', { url: "/about" })
|
|
.state('about.person', { url: "/:person" })
|
|
.state('about.person.item', { url: "/:id" })
|
|
.state('about.sidebar', {})
|
|
.state('about.sidebar.item', {
|
|
url: "/:item",
|
|
templateUrl: function(params) {
|
|
templateParams = params;
|
|
return "/templates/" + params.item + ".html";
|
|
}
|
|
})
|
|
.state('dynamicController', {
|
|
url: "/dynamic/:type",
|
|
template: "test",
|
|
controllerProvider: function($stateParams) {
|
|
ctrlName = $stateParams.type + "Controller";
|
|
return ctrlName;
|
|
}
|
|
})
|
|
.state('home.redirect', {
|
|
url: "redir",
|
|
onEnter: function($state) {
|
|
$state.transitionTo("about");
|
|
}
|
|
})
|
|
.state('resolveFail', {
|
|
url: "/resolve-fail",
|
|
resolve: {
|
|
badness: function($q) {
|
|
return $q.reject("!");
|
|
}
|
|
}
|
|
})
|
|
.state('resolveTimeout', {
|
|
url: "/:foo",
|
|
resolve: {
|
|
value: function ($timeout) {
|
|
return $timeout(function() { log += "Success!"; }, 1);
|
|
}
|
|
}
|
|
})
|
|
|
|
.state('first', { url: '^/first/subpath' })
|
|
.state('second', { url: '^/second' });
|
|
|
|
$provide.value('AppInjectable', AppInjectable);
|
|
}));
|
|
|
|
beforeEach(inject(function ($rootScope) {
|
|
log = '';
|
|
logEvents = logEnterExit = false;
|
|
$rootScope.$on('$stateChangeStart', eventLogger);
|
|
$rootScope.$on('$stateChangeSuccess', eventLogger);
|
|
$rootScope.$on('$stateChangeError', eventLogger);
|
|
$rootScope.$on('$stateNotFound', eventLogger);
|
|
}));
|
|
|
|
|
|
function $get(what) {
|
|
return jasmine.getEnv().currentSpec.$injector.get(what);
|
|
}
|
|
|
|
function initStateTo(state, optionalParams) {
|
|
var $state = $get('$state'), $q = $get('$q');
|
|
$state.transitionTo(state, optionalParams || {});
|
|
$q.flush();
|
|
expect($state.current).toBe(state);
|
|
}
|
|
|
|
describe('provider', function () {
|
|
it ('should ignore Object properties when registering states', function () {
|
|
expect(function() {
|
|
stateProvider.state('toString', { url: "/to-string" });
|
|
}).not.toThrow();
|
|
expect(function() {
|
|
stateProvider.state('watch', { url: "/watch" });
|
|
}).not.toThrow();
|
|
});
|
|
});
|
|
|
|
describe('.transitionTo()', function () {
|
|
it('returns a promise for the target state', inject(function ($state, $q) {
|
|
var trans = $state.transitionTo(A, {});
|
|
$q.flush();
|
|
expect(resolvedValue(trans)).toBe(A);
|
|
}));
|
|
|
|
it('allows transitions by name', inject(function ($state, $q) {
|
|
$state.transitionTo('A', {});
|
|
$q.flush();
|
|
expect($state.current).toBe(A);
|
|
}));
|
|
|
|
it('doesn\'t trigger state change if reloadOnSearch is false', inject(function ($state, $q, $location, $rootScope){
|
|
initStateTo(RS);
|
|
$location.search({term: 'hello'});
|
|
var called;
|
|
$rootScope.$on('$stateChangeStart', function (ev, to, toParams, from, fromParams) {
|
|
called = true
|
|
});
|
|
$q.flush();
|
|
expect($location.search()).toEqual({term: 'hello'});
|
|
expect(called).toBeFalsy();
|
|
}));
|
|
|
|
it('ignores non-applicable state parameters', inject(function ($state, $q) {
|
|
$state.transitionTo('A', { w00t: 'hi mom!' });
|
|
$q.flush();
|
|
expect($state.current).toBe(A);
|
|
}));
|
|
|
|
it('triggers $stateChangeStart', inject(function ($state, $q, $rootScope) {
|
|
initStateTo(E, { i: 'iii' });
|
|
var called;
|
|
$rootScope.$on('$stateChangeStart', function (ev, to, toParams, from, fromParams) {
|
|
expect(from).toBe(E);
|
|
expect(fromParams).toEqual({ i: 'iii' });
|
|
expect(to).toBe(D);
|
|
expect(toParams).toEqual({ x: '1', y: '2' });
|
|
|
|
expect($state.current).toBe(from); // $state not updated yet
|
|
expect($state.params).toEqual(fromParams);
|
|
called = true;
|
|
});
|
|
$state.transitionTo(D, { x: '1', y: '2' });
|
|
$q.flush();
|
|
expect(called).toBeTruthy();
|
|
expect($state.current).toBe(D);
|
|
}));
|
|
|
|
it('can be cancelled by preventDefault() in $stateChangeStart', inject(function ($state, $q, $rootScope) {
|
|
initStateTo(A);
|
|
var called;
|
|
$rootScope.$on('$stateChangeStart', function (ev) {
|
|
ev.preventDefault();
|
|
called = true;
|
|
});
|
|
var promise = $state.transitionTo(B, {});
|
|
$q.flush();
|
|
expect(called).toBeTruthy();
|
|
expect($state.current).toBe(A);
|
|
expect(resolvedError(promise)).toBeTruthy();
|
|
}));
|
|
|
|
it('triggers $stateNotFound', inject(function ($state, $q, $rootScope) {
|
|
initStateTo(E, { i: 'iii' });
|
|
var called;
|
|
$rootScope.$on('$stateNotFound', function (ev, redirect, from, fromParams) {
|
|
expect(from).toBe(E);
|
|
expect(fromParams).toEqual({ i: 'iii' });
|
|
expect(redirect.to).toEqual('never_defined');
|
|
expect(redirect.toParams).toEqual({ x: '1', y: '2' });
|
|
|
|
expect($state.current).toBe(from); // $state not updated yet
|
|
expect($state.params).toEqual(fromParams);
|
|
called = true;
|
|
});
|
|
var message;
|
|
try {
|
|
$state.transitionTo('never_defined', { x: '1', y: '2' });
|
|
} catch(err) {
|
|
message = err.message;
|
|
}
|
|
$q.flush();
|
|
expect(message).toEqual('No such state \'never_defined\'');
|
|
expect(called).toBeTruthy();
|
|
expect($state.current).toBe(E);
|
|
}));
|
|
|
|
it('can be cancelled by preventDefault() in $stateNotFound', inject(function ($state, $q, $rootScope) {
|
|
initStateTo(A);
|
|
var called;
|
|
$rootScope.$on('$stateNotFound', function (ev) {
|
|
ev.preventDefault();
|
|
called = true;
|
|
});
|
|
var promise = $state.transitionTo('never_defined', {});
|
|
$q.flush();
|
|
expect(called).toBeTruthy();
|
|
expect($state.current).toBe(A);
|
|
expect(resolvedError(promise)).toBeTruthy();
|
|
}));
|
|
|
|
it('can be redirected in $stateNotFound', inject(function ($state, $q, $rootScope) {
|
|
initStateTo(A);
|
|
var called;
|
|
$rootScope.$on('$stateNotFound', function (ev, redirect) {
|
|
redirect.to = D;
|
|
redirect.toParams = { x: '1', y: '2' };
|
|
called = true;
|
|
});
|
|
var promise = $state.transitionTo('never_defined', { z: 3 });
|
|
$q.flush();
|
|
expect(called).toBeTruthy();
|
|
expect($state.current).toBe(D);
|
|
expect($state.params).toEqual({ x: '1', y: '2' });
|
|
}));
|
|
|
|
it('can lazy-define a state in $stateNotFound', inject(function ($state, $q, $rootScope) {
|
|
initStateTo(DD, { x: 1, y: 2, z: 3 });
|
|
var called;
|
|
$rootScope.$on('$stateNotFound', function (ev, redirect) {
|
|
stateProvider.state(redirect.to, { parent: DD, params: [ 'x', 'y', 'z', 'w' ]});
|
|
called = true;
|
|
});
|
|
var promise = $state.go('DDD', { w: 4 });
|
|
$q.flush();
|
|
expect(called).toBeTruthy();
|
|
expect($state.current.name).toEqual('DDD');
|
|
expect($state.params).toEqual({ x: '1', y: '2', z: '3', w: '4' });
|
|
}));
|
|
|
|
it('can defer a state transition in $stateNotFound', inject(function ($state, $q, $rootScope) {
|
|
initStateTo(A);
|
|
var called;
|
|
var deferred = $q.defer();
|
|
$rootScope.$on('$stateNotFound', function (ev, redirect) {
|
|
ev.retry = deferred.promise;
|
|
called = true;
|
|
});
|
|
var promise = $state.go('AA', { a: 1 });
|
|
stateProvider.state('AA', { parent: A, params: [ 'a' ]});
|
|
deferred.resolve();
|
|
$q.flush();
|
|
expect(called).toBeTruthy();
|
|
expect($state.current.name).toEqual('AA');
|
|
expect($state.params).toEqual({ a: '1' });
|
|
}));
|
|
|
|
it('can defer and supersede a state transition in $stateNotFound', inject(function ($state, $q, $rootScope) {
|
|
initStateTo(A);
|
|
var called;
|
|
var deferred = $q.defer();
|
|
$rootScope.$on('$stateNotFound', function (ev, redirect) {
|
|
ev.retry = deferred.promise;
|
|
called = true;
|
|
});
|
|
var promise = $state.go('AA', { a: 1 });
|
|
$state.go(B);
|
|
stateProvider.state('AA', { parent: A, params: [ 'a' ]});
|
|
deferred.resolve();
|
|
$q.flush();
|
|
expect(called).toBeTruthy();
|
|
expect($state.current).toEqual(B);
|
|
expect($state.params).toEqual({});
|
|
}));
|
|
|
|
it('triggers $stateChangeSuccess', inject(function ($state, $q, $rootScope) {
|
|
initStateTo(E, { i: 'iii' });
|
|
var called;
|
|
$rootScope.$on('$stateChangeSuccess', function (ev, to, toParams, from, fromParams) {
|
|
expect(from).toBe(E);
|
|
expect(fromParams).toEqual({ i: 'iii' });
|
|
expect(to).toBe(D);
|
|
expect(toParams).toEqual({ x: '1', y: '2' });
|
|
|
|
expect($state.current).toBe(to); // $state has been updated
|
|
expect($state.params).toEqual(toParams);
|
|
called = true;
|
|
});
|
|
$state.transitionTo(D, { x: '1', y: '2' });
|
|
$q.flush();
|
|
expect(called).toBeTruthy();
|
|
expect($state.current).toBe(D);
|
|
}));
|
|
|
|
it('does not trigger $stateChangeSuccess when suppressed, but changes state', inject(function ($state, $q, $rootScope) {
|
|
initStateTo(E, { i: 'iii' });
|
|
var called;
|
|
|
|
$rootScope.$on('$stateChangeSuccess', function (ev, to, toParams, from, fromParams) {
|
|
called = true;
|
|
});
|
|
|
|
$state.transitionTo(D, { x: '1', y: '2' }, { notify: false });
|
|
$q.flush();
|
|
|
|
expect(called).toBeFalsy();
|
|
expect($state.current).toBe(D);
|
|
}));
|
|
|
|
it('does not trigger $stateChangeSuccess when suppressed, but updates params', inject(function ($state, $q, $rootScope) {
|
|
initStateTo(E, { x: 'iii' });
|
|
var called;
|
|
|
|
$rootScope.$on('$stateChangeSuccess', function (ev, to, toParams, from, fromParams) {
|
|
called = true;
|
|
});
|
|
$state.transitionTo(E, { i: '1', y: '2' }, { notify: false });
|
|
$q.flush();
|
|
|
|
expect(called).toBeFalsy();
|
|
expect($state.params.i).toBe('1');
|
|
expect($state.current).toBe(E);
|
|
}));
|
|
|
|
it('is a no-op when passing the current state and identical parameters', inject(function ($state, $q) {
|
|
initStateTo(A);
|
|
var trans = $state.transitionTo(A, {}); // no-op
|
|
expect(trans).toBeDefined(); // but we still get a valid promise
|
|
$q.flush();
|
|
expect(resolvedValue(trans)).toBe(A);
|
|
expect($state.current).toBe(A);
|
|
expect(log).toBe('');
|
|
}));
|
|
|
|
it('aborts pending transitions (last call wins)', inject(function ($state, $q) {
|
|
initStateTo(A);
|
|
logEvents = true;
|
|
|
|
var superseded = $state.transitionTo(B, {});
|
|
$state.transitionTo(C, {});
|
|
$q.flush();
|
|
expect($state.current).toBe(C);
|
|
expect(resolvedError(superseded)).toBeTruthy();
|
|
expect(log).toBe(
|
|
'$stateChangeStart(B,A);' +
|
|
'$stateChangeStart(C,A);' +
|
|
'$stateChangeSuccess(C,A);');
|
|
}));
|
|
|
|
it('aborts pending transitions even when going back to the current state', inject(function ($state, $q) {
|
|
initStateTo(A);
|
|
logEvents = true;
|
|
|
|
var superseded = $state.transitionTo(B, {});
|
|
$state.transitionTo(A, {});
|
|
$q.flush();
|
|
expect($state.current).toBe(A);
|
|
expect(resolvedError(superseded)).toBeTruthy();
|
|
expect(log).toBe('$stateChangeStart(B,A);');
|
|
}));
|
|
|
|
it('aborts pending transitions when aborted from callbacks', inject(function ($state, $q) {
|
|
var superseded = $state.transitionTo('home.redirect');
|
|
$q.flush();
|
|
expect($state.current.name).toBe('about');
|
|
}));
|
|
|
|
it('triggers onEnter and onExit callbacks', inject(function ($state, $q) {
|
|
initStateTo(A);
|
|
logEnterExit = true;
|
|
$state.transitionTo(D, {}); $q.flush();
|
|
log += $state.current.name + ';';
|
|
$state.transitionTo(DD, {}); $q.flush();
|
|
log += $state.current.name + ';';
|
|
$state.transitionTo(A, {}); $q.flush();
|
|
expect(log).toBe(
|
|
'A.onExit;' +
|
|
'D.onEnter;' +
|
|
'D;' +
|
|
'DD.onEnter;' +
|
|
'DD;' +
|
|
'DD.onExit;' +
|
|
'D.onExit;' +
|
|
'A.onEnter;');
|
|
}));
|
|
|
|
it('doesn\'t transition to parent state when child has no URL', inject(function ($state, $q) {
|
|
$state.transitionTo('about.sidebar'); $q.flush();
|
|
expect($state.current.name).toEqual('about.sidebar');
|
|
}));
|
|
|
|
it('notifies on failed relative state resolution', inject(function ($state, $q) {
|
|
$state.transitionTo(DD);
|
|
$q.flush();
|
|
|
|
var err = "Could not resolve '^.Z' from state 'DD'";
|
|
expect(function() { $state.transitionTo("^.Z", null, { relative: $state.$current }); }).toThrow(err);
|
|
}));
|
|
|
|
it('uses the controllerProvider to get controller dynamically', inject(function ($state, $q) {
|
|
$state.transitionTo('dynamicController', { type: "Acme" });
|
|
$q.flush();
|
|
expect(ctrlName).toEqual("AcmeController");
|
|
}));
|
|
});
|
|
|
|
describe('.go()', function () {
|
|
it('transitions to a relative state', inject(function ($state, $q) {
|
|
$state.transitionTo('about.person.item', { id: 5 }); $q.flush();
|
|
$state.go('^.^.sidebar'); $q.flush();
|
|
expect($state.$current.name).toBe('about.sidebar');
|
|
|
|
// Transitions to absolute state
|
|
$state.go("home"); $q.flush();
|
|
expect($state.$current.name).toBe('home');
|
|
|
|
|
|
// Transition to a child state
|
|
$state.go(".item", { id: 5 }); $q.flush();
|
|
expect($state.$current.name).toBe('home.item');
|
|
|
|
// Transition to grandparent's sibling through root
|
|
// (Equivalent to absolute transition, assuming the root is known).
|
|
$state.go("^.^.about"); $q.flush();
|
|
expect($state.$current.name).toBe('about');
|
|
|
|
// Transition to grandchild
|
|
$state.go(".person.item", { person: "bob", id: 13 }); $q.flush();
|
|
expect($state.$current.name).toBe('about.person.item');
|
|
|
|
// Transition to immediate parent
|
|
$state.go("^"); $q.flush();
|
|
expect($state.$current.name).toBe('about.person');
|
|
|
|
// Transition to sibling
|
|
$state.go("^.sidebar"); $q.flush();
|
|
expect($state.$current.name).toBe('about.sidebar');
|
|
}));
|
|
|
|
it('keeps parameters from common ancestor states', inject(function ($state, $stateParams, $q) {
|
|
$state.transitionTo('about.person', { person: 'bob' });
|
|
$q.flush();
|
|
|
|
$state.go('.item', { id: 5 });
|
|
$q.flush();
|
|
|
|
expect($state.$current.name).toBe('about.person.item');
|
|
expect($stateParams).toEqual({ person: 'bob', id: '5' });
|
|
|
|
$state.go('^.^.sidebar');
|
|
$q.flush();
|
|
expect($state.$current.name).toBe('about.sidebar');
|
|
}));
|
|
});
|
|
|
|
describe('.reload()', function () {
|
|
it('should reload the current state with the current parameters', inject(function ($state, $q, $timeout) {
|
|
$state.transitionTo('resolveTimeout', { foo: "bar" });
|
|
$q.flush();
|
|
expect(log).toBe('');
|
|
|
|
$timeout.flush();
|
|
expect(log).toBe('Success!');
|
|
|
|
$state.reload();
|
|
$q.flush();
|
|
$timeout.flush();
|
|
expect(log).toBe('Success!Success!');
|
|
}));
|
|
});
|
|
|
|
describe('.is()', function () {
|
|
it('should return true when the current state is passed', inject(function ($state, $q) {
|
|
$state.transitionTo(A); $q.flush();
|
|
expect($state.is(A)).toBe(true);
|
|
expect($state.is('A')).toBe(true);
|
|
expect($state.is(B)).toBe(false);
|
|
}));
|
|
|
|
it('should return undefined when queried state does not exist', inject(function ($state) {
|
|
expect($state.is('Z')).toBeUndefined();
|
|
}));
|
|
|
|
it('should return true when the current state is passed with matching parameters', inject(function ($state, $q) {
|
|
$state.transitionTo(D, {x: 'foo', y: 'bar'}); $q.flush();
|
|
expect($state.is(D, {x: 'foo', y: 'bar'})).toBe(true);
|
|
expect($state.is('D', {x: 'foo', y: 'bar'})).toBe(true);
|
|
expect($state.is(D, {x: 'bar', y: 'foo'})).toBe(false);
|
|
}));
|
|
});
|
|
|
|
describe('.includes()', function () {
|
|
it('should return true when the current state is passed', inject(function ($state, $q) {
|
|
$state.transitionTo(A); $q.flush();
|
|
expect($state.includes(A)).toBe(true);
|
|
expect($state.includes('A')).toBe(true);
|
|
expect($state.includes(B)).toBe(false);
|
|
}));
|
|
|
|
it('should return true when the current state\'s parent is passed', inject(function ($state, $q) {
|
|
$state.transitionTo('about.person.item'); $q.flush();
|
|
expect($state.includes('about')).toBe(true);
|
|
expect($state.includes('about.person')).toBe(true);
|
|
expect($state.includes('about.sidebar')).toBe(false);
|
|
}));
|
|
|
|
it('should return undefined when queried state does not exist', inject(function ($state) {
|
|
expect($state.includes('Z')).toBeUndefined();
|
|
}));
|
|
|
|
it('should return true when the current state is passed with partial matching parameters', inject(function ($state, $q) {
|
|
$state.transitionTo(D, {x: 'foo', y: 'bar'}); $q.flush();
|
|
expect($state.includes(D, {x: 'foo'})).toBe(true);
|
|
expect($state.includes(D, {y: 'bar'})).toBe(true);
|
|
expect($state.includes('D', {x: 'foo'})).toBe(true);
|
|
expect($state.includes(D, {y: 'foo'})).toBe(false);
|
|
}));
|
|
|
|
it('should return true when the current state is passed with partial matching parameters from state\'s parent', inject(function ($state, $q) {
|
|
$state.transitionTo('about.person.item', {person: 'bob', id: 5}); $q.flush();
|
|
expect($state.includes('about.person', {person: 'bob'})).toBe(true);
|
|
expect($state.includes('about.person', {person: 'steve'})).toBe(false);
|
|
}));
|
|
});
|
|
|
|
describe('.current', function () {
|
|
it('is always defined', inject(function ($state) {
|
|
expect($state.current).toBeDefined();
|
|
}));
|
|
|
|
it('updates asynchronously as the transitionTo() promise is resolved', inject(function ($state, $q) {
|
|
var trans = $state.transitionTo(A, {});
|
|
expect($state.current).not.toBe(A);
|
|
$q.flush();
|
|
expect($state.current).toBe(A);
|
|
}));
|
|
});
|
|
|
|
|
|
describe('$current', function () {
|
|
it('is always defined', inject(function ($state) {
|
|
expect($state.$current).toBeDefined();
|
|
}));
|
|
|
|
it('wraps the raw state object', inject(function ($state) {
|
|
initStateTo(A);
|
|
expect($state.$current.data).toBe(A.data); // 'data' is reserved for app use
|
|
}));
|
|
});
|
|
|
|
|
|
describe('.params', function () {
|
|
it('is always defined', inject(function ($state) {
|
|
expect($state.params).toBeDefined();
|
|
expect(angular.isObject($state.params)).toBe(true);
|
|
}));
|
|
|
|
it('contains the parameter values for the current state', inject(function ($state, $q) {
|
|
initStateTo(D, { x: 'x value', z: 'invalid value' });
|
|
expect($state.params).toEqual({ x: 'x value', y: null });
|
|
}));
|
|
});
|
|
|
|
|
|
describe('.transition', function () {
|
|
it('is null when no transition is taking place', inject(function ($state, $q) {
|
|
expect($state.transition).toBeNull();
|
|
$state.transitionTo(A, {});
|
|
$q.flush();
|
|
expect($state.transition).toBeNull();
|
|
}));
|
|
|
|
it('is the current transition', inject(function ($state, $q) {
|
|
var trans = $state.transitionTo(A, {});
|
|
expect($state.transition).toBe(trans);
|
|
}));
|
|
});
|
|
|
|
|
|
describe('.href()', function () {
|
|
it('aborts on un-navigable states', inject(function ($state) {
|
|
expect($state.href("A")).toBeNull();
|
|
expect($state.href("about.sidebar", null, { lossy: false })).toBeNull();
|
|
}));
|
|
|
|
it('generates a parent state URL when lossy is true', inject(function ($state) {
|
|
expect($state.href("about.sidebar", null, { lossy: true })).toEqual("#/about");
|
|
}));
|
|
|
|
it('generates a URL without parameters', inject(function ($state) {
|
|
expect($state.href("home")).toEqual("#/");
|
|
expect($state.href("about", {})).toEqual("#/about");
|
|
expect($state.href("about", { foo: "bar" })).toEqual("#/about");
|
|
}));
|
|
|
|
it('generates a URL with parameters', inject(function ($state) {
|
|
expect($state.href("about.person", { person: "bob" })).toEqual("#/about/bob");
|
|
expect($state.href("about.person.item", { person: "bob", id: null })).toEqual("#/about/bob/");
|
|
}));
|
|
|
|
it('generates absolute url when absolute is true', inject(function ($state) {
|
|
expect($state.href("about.sidebar", null, { absolute: true })).toEqual("http://server/#/about");
|
|
locationProvider.html5Mode(true);
|
|
expect($state.href("about.sidebar", null, { absolute: true })).toEqual("http://server/about");
|
|
}));
|
|
|
|
it('respects $locationProvider.hashPrefix()', inject(function ($state) {
|
|
locationProvider.hashPrefix("!");
|
|
expect($state.href("home")).toEqual("#!/");
|
|
}));
|
|
});
|
|
|
|
describe('.get()', function () {
|
|
it("should return the state's config", inject(function ($state) {
|
|
expect($state.get('home').url).toBe('/');
|
|
expect($state.get('home.item').url).toBe('front/:id');
|
|
expect($state.get('A')).toBe(A);
|
|
expect($state.get('Z')).toBeNull();
|
|
}));
|
|
|
|
it("should return all of the state's config", inject(function ($state) {
|
|
var list = $state.get().sort(function(a, b) { return (a.name > b.name) - (b.name > a.name); });
|
|
var names = [
|
|
'', // implicit root state
|
|
'A',
|
|
'B',
|
|
'C',
|
|
'D',
|
|
'DD',
|
|
'E',
|
|
'H',
|
|
'HH',
|
|
'HHH',
|
|
'RS',
|
|
'about',
|
|
'about.person',
|
|
'about.person.item',
|
|
'about.sidebar',
|
|
'about.sidebar.item',
|
|
'dynamicController',
|
|
'first',
|
|
'home',
|
|
'home.item',
|
|
'home.redirect',
|
|
'resolveFail',
|
|
'resolveTimeout',
|
|
'second'
|
|
];
|
|
expect(list.map(function(state) { return state.name; })).toEqual(names);
|
|
}));
|
|
});
|
|
|
|
describe('url handling', function () {
|
|
it('should transition to the same state with different parameters', inject(function ($state, $rootScope, $location) {
|
|
$location.path("/about/bob");
|
|
$rootScope.$broadcast("$locationChangeSuccess");
|
|
$rootScope.$apply();
|
|
expect($state.params).toEqual({ person: "bob" });
|
|
expect($state.current.name).toBe('about.person');
|
|
|
|
$location.path("/about/larry");
|
|
$rootScope.$broadcast("$locationChangeSuccess");
|
|
$rootScope.$apply();
|
|
expect($state.params).toEqual({ person: "larry" });
|
|
expect($state.current.name).toBe('about.person');
|
|
}));
|
|
|
|
it('preserve hash', inject(function($state, $rootScope, $location) {
|
|
$location.path("/about/bob");
|
|
$location.hash("frag");
|
|
$rootScope.$broadcast("$locationChangeSuccess");
|
|
$rootScope.$apply();
|
|
expect($state.params).toEqual({ person: "bob" });
|
|
expect($state.current.name).toBe('about.person');
|
|
expect($location.path()).toBe('/about/bob');
|
|
expect($location.hash()).toBe('frag');
|
|
}));
|
|
|
|
it('should correctly handle absolute urls', inject(function ($state, $rootScope, $location) {
|
|
$location.path("/first/subpath");
|
|
$rootScope.$broadcast("$locationChangeSuccess");
|
|
$rootScope.$apply();
|
|
expect($state.current.name).toBe('first');
|
|
|
|
$state.transitionTo('second');
|
|
$rootScope.$apply();
|
|
expect($state.current.name).toBe('second');
|
|
expect($location.path()).toBe('/second');
|
|
}));
|
|
|
|
it('should ignore bad urls', inject(function ($state, $rootScope, $location) {
|
|
$location.path("/first/second");
|
|
$rootScope.$broadcast("$locationChangeSuccess");
|
|
$rootScope.$apply();
|
|
expect($state.current.name).toBe('');
|
|
}));
|
|
|
|
it('should revert to last known working url on state change failure', inject(function ($state, $rootScope, $location, $q) {
|
|
$state.transitionTo("about");
|
|
$q.flush();
|
|
|
|
$location.path("/resolve-fail");
|
|
$rootScope.$broadcast("$locationChangeSuccess");
|
|
$rootScope.$apply();
|
|
|
|
expect($state.current.name).toBe("about");
|
|
}));
|
|
|
|
it('should replace browser history when "replace" enabled', inject(function ($state, $rootScope, $location, $q) {
|
|
var originalReplaceFn = $location.replace, replaceWasCalled = false;
|
|
|
|
// @todo Replace this with a spy
|
|
var decoratedReplaceFn = function() {
|
|
replaceWasCalled = true;
|
|
originalReplaceFn.call($location);
|
|
};
|
|
$location.replace = decoratedReplaceFn;
|
|
|
|
$state.transitionTo('about', {}, { location: 'replace' });
|
|
$q.flush();
|
|
|
|
expect(replaceWasCalled).toEqual(true);
|
|
}));
|
|
|
|
it('should not replace history normally', inject(function ($state, $rootScope, $location, $q) {
|
|
var originalReplaceFn = $location.replace, replaceWasCalled = false;
|
|
|
|
// @todo Replace with spy
|
|
var decoratedReplaceFn = function() {
|
|
replaceWasCalled = true;
|
|
originalReplaceFn.call($location);
|
|
};
|
|
$location.replace = decoratedReplaceFn;
|
|
|
|
$state.transitionTo('about');
|
|
$q.flush();
|
|
|
|
expect(replaceWasCalled).toEqual(false);
|
|
}));
|
|
});
|
|
|
|
describe('default properties', function() {
|
|
it('should always have a name', inject(function ($state, $q) {
|
|
$state.transitionTo(A);
|
|
$q.flush();
|
|
expect($state.$current.name).toBe('A');
|
|
expect($state.$current.toString()).toBe('A');
|
|
}));
|
|
|
|
it('should always have a resolve object', inject(function ($state) {
|
|
expect($state.$current.resolve).toEqual({});
|
|
}));
|
|
});
|
|
|
|
describe('"data" property inheritance/override', function () {
|
|
it('should stay immutable for if state doesn\'t have parent', inject(function ($state) {
|
|
initStateTo(H);
|
|
expect($state.current.name).toEqual('H');
|
|
expect($state.current.data.propA).toEqual(H.data.propA);
|
|
expect($state.current.data.propB).toEqual(H.data.propB);
|
|
}));
|
|
|
|
it('should be inherited from parent if state doesn\'t define it', inject(function ($state) {
|
|
initStateTo(HH);
|
|
expect($state.current.name).toEqual('HH');
|
|
expect($state.current.data.propA).toEqual(H.data.propA);
|
|
expect($state.current.data.propB).toEqual(H.data.propB);
|
|
}));
|
|
|
|
it('should be overridden/extended if state defines it', inject(function ($state) {
|
|
initStateTo(HHH);
|
|
expect($state.current.name).toEqual('HHH');
|
|
expect($state.current.data.propA).toEqual(HHH.data.propA);
|
|
expect($state.current.data.propB).toEqual(H.data.propB);
|
|
expect($state.current.data.propB).toEqual(HH.data.propB);
|
|
expect($state.current.data.propC).toEqual(HHH.data.propC);
|
|
}));
|
|
});
|
|
|
|
describe('html5Mode compatibility', function() {
|
|
|
|
it('should generate non-hashbang URLs in HTML5 mode', inject(function ($state) {
|
|
expect($state.href("about.person", { person: "bob" })).toEqual("#/about/bob");
|
|
locationProvider.html5Mode(true);
|
|
expect($state.href("about.person", { person: "bob" })).toEqual("/about/bob");
|
|
}));
|
|
});
|
|
|
|
describe('default properties', function () {
|
|
it('should always have a name', inject(function ($state, $q) {
|
|
$state.transitionTo(A); $q.flush();
|
|
expect($state.$current.name).toBe('A');
|
|
expect($state.$current.toString()).toBe('A');
|
|
}));
|
|
|
|
it('should always have a resolve object', inject(function ($state) {
|
|
expect($state.$current.resolve).toEqual({});
|
|
}));
|
|
|
|
it('should include itself and parent states', inject(function ($state, $q) {
|
|
$state.transitionTo(DD); $q.flush();
|
|
expect($state.$current.includes).toEqual({ '': true, D: true, DD: true });
|
|
}));
|
|
});
|
|
|
|
describe('template handling', function () {
|
|
it('should inject $stateParams into templateUrl function', inject(function ($state, $q, $httpBackend) {
|
|
$httpBackend.expectGET("/templates/foo.html").respond("200");
|
|
$state.transitionTo('about.sidebar.item', { item: "foo" }); $q.flush();
|
|
expect(templateParams).toEqual({ item: "foo" });
|
|
}));
|
|
});
|
|
|
|
describe('provider decorators', function () {
|
|
|
|
it('should return built-in decorators', function () {
|
|
expect(stateProvider.decorator('parent')({ parent: A }).self.name).toBe("A");
|
|
});
|
|
|
|
it('should allow built-in decorators to be overridden', inject(function ($state, $q) {
|
|
stateProvider.decorator('data', function(state) {
|
|
return angular.extend(state.data || {}, { foo: "bar" });
|
|
});
|
|
stateProvider.state('AA', { parent: A, data: { baz: "true" } });
|
|
|
|
$state.transitionTo('AA');
|
|
$q.flush();
|
|
expect($state.current.data).toEqual({ baz: 'true', foo: 'bar' });
|
|
}));
|
|
|
|
it('should allow new decorators to be added', inject(function ($state, $q) {
|
|
stateProvider.decorator('custom', function(state) {
|
|
return function() { return "Custom functionality for state '" + state + "'" };
|
|
});
|
|
stateProvider.state('decoratorTest', {});
|
|
|
|
$state.transitionTo('decoratorTest');
|
|
$q.flush();
|
|
expect($state.$current.custom()).toBe("Custom functionality for state 'decoratorTest'");
|
|
}));
|
|
|
|
it('should allow built-in decorators to be extended', inject(function ($state, $q, $httpBackend) {
|
|
stateProvider.decorator('views', function(state, parent) {
|
|
var result = {};
|
|
|
|
angular.forEach(parent(state), function(config, name) {
|
|
result[name] = angular.extend(config, { templateProvider: function() {
|
|
return "Template for " + name;
|
|
}});
|
|
});
|
|
return result;
|
|
});
|
|
|
|
stateProvider.state('viewTest', {
|
|
views: {
|
|
viewA: {},
|
|
viewB: {}
|
|
}
|
|
});
|
|
|
|
$state.transitionTo('viewTest');
|
|
$q.flush();
|
|
|
|
expect($state.$current.views['viewA@'].templateProvider()).toBe('Template for viewA@');
|
|
expect($state.$current.views['viewB@'].templateProvider()).toBe('Template for viewB@');
|
|
}));
|
|
|
|
});
|
|
});
|
|
|
|
describe('state queue', function(){
|
|
angular.module('ui.router.queue.test', ['ui.router.queue.test.dependency'])
|
|
.config(function($stateProvider) {
|
|
$stateProvider
|
|
.state('queue-test-a', {})
|
|
.state('queue-test-b-child', { parent: 'queue-test-b' })
|
|
.state('queue-test-b', {});
|
|
});
|
|
angular.module('ui.router.queue.test.dependency', [])
|
|
.config(function($stateProvider) {
|
|
$stateProvider
|
|
.state('queue-test-a.child', {})
|
|
});
|
|
|
|
var expectedStates = ['','queue-test-a', 'queue-test-a.child', 'queue-test-b', 'queue-test-b-child'];
|
|
|
|
it('should work across modules', function() {
|
|
module('ui.router.queue.test', 'ui.router.queue.test.dependency');
|
|
|
|
inject(function ($state) {
|
|
var list = $state.get().sort(function(a, b) { return (a.name > b.name) - (b.name > a.name); });
|
|
expect(list.map(function(state) { return state.name; })).toEqual(expectedStates);
|
|
});
|
|
});
|
|
|
|
it('should work when parent is name string', function() {
|
|
module('ui.router.queue.test', 'ui.router.queue.test.dependency');
|
|
|
|
inject(function ($state) {
|
|
var list = $state.get().sort(function(a, b) { return (a.name > b.name) - (b.name > a.name); });
|
|
expect(list.map(function(state) { return state.name; })).toEqual(expectedStates);
|
|
});
|
|
});
|
|
});
|