Skip to content

Commit

Permalink
feat(urlRuleFactory): Add support for StateDeclarations in UrlRuleFac…
Browse files Browse the repository at this point in the history
…tory.fromState()
  • Loading branch information
christopherthielen authored and mergify[bot] committed Apr 19, 2020
1 parent e657cfe commit 539d33a
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 10 deletions.
5 changes: 4 additions & 1 deletion src/state/stateObject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,9 @@ export class StateObject {
static isStateClass = (stateDecl: _StateDeclaration): stateDecl is { new (): StateDeclaration } =>
isFunction(stateDecl) && stateDecl['__uiRouterState'] === true;

/** Predicate which returns true if the object is a [[StateDeclaration]] object */
static isStateDeclaration = (obj: any): obj is StateDeclaration => isFunction(obj['$$state']);

/** Predicate which returns true if the object is an internal [[StateObject]] object */
static isState = (obj: any): obj is StateObject => isObject(obj['__stateObjectCache']);

Expand Down Expand Up @@ -181,7 +184,7 @@ export class StateObject {
const inherited = (opts.inherit && this.parent && this.parent.parameters()) || [];
return inherited
.concat(values(this.params))
.filter(param => !opts.matchingKeys || opts.matchingKeys.hasOwnProperty(param.id));
.filter((param) => !opts.matchingKeys || opts.matchingKeys.hasOwnProperty(param.id));
}

/**
Expand Down
21 changes: 12 additions & 9 deletions src/url/urlRule.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
/** @packageDocumentation @publicapi @module url */
import { StateDeclaration } from '../state';
import { UrlMatcher } from './urlMatcher';
import { isString, isDefined, isFunction } from '../common/predicates';
import { UIRouter } from '../router';
import { identity, extend } from '../common/common';
import { is, pattern } from '../common/hof';
import { is, or, pattern } from '../common/hof';
import { StateObject } from '../state/stateObject';
import { RawParams } from '../params/interface';
import {
Expand All @@ -29,7 +30,7 @@ import {
* @internalapi
*/
export class UrlRuleFactory {
static isUrlRule = obj => obj && ['type', 'match', 'handler'].every(key => isDefined(obj[key]));
static isUrlRule = (obj) => obj && ['type', 'match', 'handler'].every((key) => isDefined(obj[key]));

constructor(public router: UIRouter) {}

Expand All @@ -38,14 +39,14 @@ export class UrlRuleFactory {
}

create(
what: string | UrlMatcher | StateObject | RegExp | UrlRuleMatchFn,
what: string | UrlMatcher | StateObject | StateDeclaration | RegExp | UrlRuleMatchFn,
handler?: string | UrlRuleHandlerFn
): UrlRule {
const isState = StateObject.isState;
const { isState, isStateDeclaration } = StateObject;
const makeRule = pattern([
[isString, (_what: string) => makeRule(this.compile(_what))],
[is(UrlMatcher), (_what: UrlMatcher) => this.fromUrlMatcher(_what, handler)],
[isState, (_what: StateObject) => this.fromState(_what, this.router)],
[or(isState, isStateDeclaration), (_what: StateObject | StateDeclaration) => this.fromState(_what, this.router)],
[is(RegExp), (_what: RegExp) => this.fromRegExp(_what, handler)],
[isFunction, (_what: UrlRuleMatchFn) => new BaseUrlRule(_what, handler as UrlRuleHandlerFn)],
]);
Expand Down Expand Up @@ -107,9 +108,9 @@ export class UrlRuleFactory {
// - Some optional parameters, some matched
// - Some optional parameters, all matched
function matchPriority(params: RawParams): number {
const optional = urlMatcher.parameters().filter(param => param.isOptional);
const optional = urlMatcher.parameters().filter((param) => param.isOptional);
if (!optional.length) return 0.000001;
const matched = optional.filter(param => params[param.id]);
const matched = optional.filter((param) => params[param.id]);
return matched.length / optional.length;
}

Expand All @@ -128,7 +129,9 @@ export class UrlRuleFactory {
* // Starts a transition to 'foo' with params: { fooId: '123', barId: '456' }
* ```
*/
fromState(state: StateObject, router: UIRouter): StateRule {
fromState(stateOrDecl: StateObject | StateDeclaration, router: UIRouter): StateRule {
const state = StateObject.isStateDeclaration(stateOrDecl) ? stateOrDecl.$$state() : stateOrDecl;

/**
* Handles match by transitioning to matched state
*
Expand Down Expand Up @@ -213,7 +216,7 @@ export class BaseUrlRule implements UrlRule {
_group: number;
type: UrlRuleType = 'RAW';
handler: UrlRuleHandlerFn;
matchPriority = match => 0 - this.$id;
matchPriority = (match) => 0 - this.$id;

constructor(public match: UrlRuleMatchFn, handler?: UrlRuleHandlerFn) {
this.handler = handler || identity;
Expand Down
79 changes: 79 additions & 0 deletions test/urlRuleSpec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { StateDeclaration, UIRouter, UrlMatcher, UrlRule } from '../src';
import { UrlRuleFactory } from '../src/url';
import { TestingPlugin } from './_testingPlugin';

const setup = () => {
const router = new UIRouter();
router.plugin(TestingPlugin);
return new UrlRuleFactory(router);
};

describe('UrlRuleFactory', () => {
it('.compile() should create a UrlMatcher from a string', () => {
const factory = setup();
const rule: UrlMatcher = factory.compile('/foo/bar/baz');
expect(rule instanceof UrlMatcher).toBeTruthy();
expect(rule.exec('/foo/bar/baz')).toBeTruthy();
});

describe('.create()', () => {
it('should create a UrlRule from a string', () => {
const factory = setup();
const rule: UrlRule = factory.create('/foo/bar/baz');
expect(rule.type).toBe('URLMATCHER');
expect(rule.match({ path: '/foo/bar/baz' })).toBeTruthy();
expect(rule.match({ path: '/nope/bar/baz' })).toBeFalsy();
});

it('should create a UrlRule from a UrlMatcher', () => {
const factory = setup();
const matcher: UrlMatcher = factory.compile('/foo/bar/baz');
const rule = factory.create(matcher);
expect(rule.type).toBe('URLMATCHER');
expect(rule.match({ path: '/foo/bar/baz' })).toBeTruthy();
expect(rule.match({ path: '/nope/bar/baz' })).toBeFalsy();
});

it('should create a UrlRule from a StateObject', () => {
const factory = setup();
const { stateRegistry } = factory.router;

const stateDecl: StateDeclaration = { name: 'state', url: '/foo/bar/baz' };
stateRegistry.register(stateDecl);

const rule = factory.create(stateRegistry.get('state').$$state());
expect(rule.type).toBe('STATE');
expect(rule.match({ path: '/foo/bar/baz' })).toBeTruthy();
expect(rule.match({ path: '/nope/bar/baz' })).toBeFalsy();
});

it('should create a UrlRule from a StateDeclaration', () => {
const factory = setup();
const { stateRegistry } = factory.router;

const stateDecl: StateDeclaration = { name: 'state', url: '/foo/bar/baz' };
stateRegistry.register(stateDecl);

const rule = factory.create(stateRegistry.get('state'));
expect(rule.type).toBe('STATE');
expect(rule.match({ path: '/foo/bar/baz' })).toBeTruthy();
expect(rule.match({ path: '/nope/bar/baz' })).toBeFalsy();
});

it('should create a UrlRule from a RegExp', () => {
const factory = setup();
const rule = factory.create(new RegExp('/foo/bar/baz'));
expect(rule.type).toBe('REGEXP');
expect(rule.match({ path: '/foo/bar/baz' })).toBeTruthy();
expect(rule.match({ path: '/nope/bar/baz' })).toBeFalsy();
});

it('should create a UrlRule from a UrlRuleMatchFn', () => {
const factory = setup();
const rule = factory.create((url) => url.path === '/foo/bar/baz');
expect(rule.type).toBe('RAW');
expect(rule.match({ path: '/foo/bar/baz' })).toBeTruthy();
expect(rule.match({ path: '/nope/bar/baz' })).toBeFalsy();
});
});
});

0 comments on commit 539d33a

Please sign in to comment.