From 232e94b3c2ca2c764bb9510046e4b61690c87852 Mon Sep 17 00:00:00 2001 From: Popescu Dan Date: Thu, 5 Dec 2013 10:46:08 +0200 Subject: [PATCH] feat($state): is/includes/get work on relative stateOrName Allow $state.is, .get, and .includes to work on relative stateOrName with a passed-in context for relative lookups. --- src/state.js | 37 ++++++++++++++++++++++++++----------- test/stateSpec.js | 40 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 65 insertions(+), 12 deletions(-) diff --git a/src/state.js b/src/state.js index 6a33feb58..b5b56da9c 100644 --- a/src/state.js +++ b/src/state.js @@ -137,6 +137,8 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) { if (path) { if (!base) throw new Error("No reference point given for path '" + name + "'"); + base = findState(base); + var rel = name.split("."), i = 0, pathLength = rel.length, current = base; for (; i < pathLength; i++) { @@ -973,8 +975,8 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) { * * @description * Similar to {@link ui.router.state.$state#methods_includes $state.includes}, - * but only checks for the full state name. If params is supplied then it will be - * tested for strict equality against the current active params object, so all params + * but only checks for the full state name. If params is supplied then it will be + * tested for strict equality against the current active params object, so all params * must match with none missing and no extras. * * @example @@ -990,13 +992,19 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) { *
Item
* * - * @param {string|object} stateName The state name (absolute or relative) or state object you'd like to check. - * @param {object=} params A param object, e.g. `{sectionId: section.id}`, that you'd like + * @param {string|object} stateOrName The state name (absolute or relative) or state object you'd like to check. + * @param {object=} params A param object, e.g. `{sectionId: section.id}`, that you'd like * to test against the current active state. + * @param {object=} options An options object. The options are: + * + * - **`relative`** - {string|object} - If `stateOrName` is a relative state name and `options.relative` is set, .is will + * test relative to `options.relative` state (or name). + * * @returns {boolean} Returns true if it is the state. */ - $state.is = function is(stateOrName, params) { - var state = findState(stateOrName); + $state.is = function is(stateOrName, params, options) { + options = extend({ relative: $state.$current }, options || {}); + var state = findState(stateOrName, options.relative); if (!isDefined(state)) { return undefined; @@ -1051,19 +1059,25 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) { * * @param {string} stateOrName A partial name, relative name, or glob pattern * to be searched for within the current state name. - * @param {object} params A param object, e.g. `{sectionId: section.id}`, + * @param {object=} params A param object, e.g. `{sectionId: section.id}`, * that you'd like to test against the current active state. + * @param {object=} options An options object. The options are: + * + * - **`relative`** - {string|object=} - If `stateOrName` is a relative state reference and `options.relative` is set, + * .includes will test relative to `options.relative` state (or name). + * * @returns {boolean} Returns true if it does include the state */ - $state.includes = function includes(stateOrName, params) { + $state.includes = function includes(stateOrName, params, options) { + options = extend({ relative: $state.$current }, options || {}); if (isString(stateOrName) && isGlob(stateOrName)) { if (!doesStateMatchGlob(stateOrName)) { return false; } stateOrName = $state.$current.name; } - var state = findState(stateOrName); + var state = findState(stateOrName, options.relative); if (!isDefined(state)) { return undefined; } @@ -1132,13 +1146,14 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) { * @description * Returns the state configuration object for any specific state or all states. * - * @param {string|Sbject=} stateOrName (absolute or relative) If provided, will only get the config for + * @param {string|object=} stateOrName (absolute or relative) If provided, will only get the config for * the requested state. If not provided, returns an array of ALL state configs. + * @param {string|object=} context When stateOrName is a relative state reference, the state will be retrieved relative to context. * @returns {Object|Array} State configuration object or array of all objects. */ $state.get = function (stateOrName, context) { if (arguments.length === 0) return objectKeys(states).map(function(name) { return states[name].self; }); - var state = findState(stateOrName, context); + var state = findState(stateOrName, context || $state.$current); return (state && state.self) ? state.self : null; }; diff --git a/test/stateSpec.js b/test/stateSpec.js index 65a8c04b3..7dbbfff5a 100644 --- a/test/stateSpec.js +++ b/test/stateSpec.js @@ -527,10 +527,25 @@ describe('state', function () { 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)).toBe(true); 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); })); + + it('should work for relative states', inject(function ($state, $q) { + var options = { relative: $state.get('about') }; + + $state.transitionTo('about.person'); $q.flush(); + expect($state.is('.person', undefined, options)).toBe(true); + + $state.transitionTo('about.person', { person: 'bob' }); $q.flush(); + expect($state.is('.person', { person: 'bob' }, options)).toBe(true); + expect($state.is('.person', { person: 'john' }, options)).toBe(false); + + options.relative = $state.get('about.person.item'); + expect($state.is('^', undefined, options)).toBe(true); + })); }); describe('.includes()', function () { @@ -580,6 +595,18 @@ describe('state', function () { expect($state.includes('about.*.*', {person: 'bob'})).toBe(true); expect($state.includes('about.*.*', {person: 'shawn'})).toBe(false); })); + + it('should work for relative states', inject(function ($state, $q) { + $state.transitionTo('about.person.item', { person: 'bob', id: 5 }); $q.flush(); + + expect($state.includes('.person', undefined, { relative: 'about' } )).toBe(true); + expect($state.includes('.person', null, { relative: 'about' } )).toBe(true); + + expect($state.includes('^', undefined, { relative: $state.get('about.person.item') })).toBe(true); + + expect($state.includes('.person', { person: 'bob' }, { relative: $state.get('about') } )).toBe(true); + expect($state.includes('.person', { person: 'steve' }, { relative: $state.get('about') } )).toBe(false); + })); }); describe('.current', function () { @@ -595,7 +622,6 @@ describe('state', function () { })); }); - describe('$current', function () { it('is always defined', inject(function ($state) { expect($state.$current).toBeDefined(); @@ -741,6 +767,18 @@ describe('state', function () { expect(list.map(function(state) { return state.name; })).toEqual(names); })); + it('should work for relative states', inject(function ($state) { + var about = $state.get('about'); + + var person = $state.get('.person', about); + expect(person.url).toBe('/:person'); + expect($state.get('^', 'about.person').url).toBe('/about'); + + var item = $state.get('.person.item', about); + expect(item.url).toBe('/:id'); + expect($state.get('^.^', item).url).toBe('/about'); + })); + it("should return undefined on invalid state query", inject(function ($state) { expect($state.get(null)).toBeNull(); expect($state.get(false)).toBeNull();