Skip to content

feat($state): is/includes/get work on relative stateOrName #1371

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 15, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 26 additions & 11 deletions src/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -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++) {
Expand Down Expand Up @@ -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
Expand All @@ -990,13 +992,19 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
* <div ng-class="{highlighted: $state.is('.item')}">Item</div>
* </pre>
*
* @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;
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
};

Expand Down
40 changes: 39 additions & 1 deletion test/stateSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 () {
Expand Down Expand Up @@ -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 () {
Expand All @@ -595,7 +622,6 @@ describe('state', function () {
}));
});


describe('$current', function () {
it('is always defined', inject(function ($state) {
expect($state.$current).toBeDefined();
Expand Down Expand Up @@ -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();
Expand Down