From 539d33a4c14269df614210f447d11b53ae3dbb0a Mon Sep 17 00:00:00 2001 From: Chris Thielen Date: Sun, 19 Apr 2020 14:28:31 -0700 Subject: [PATCH] feat(urlRuleFactory): Add support for StateDeclarations in UrlRuleFactory.fromState() --- src/state/stateObject.ts | 5 ++- src/url/urlRule.ts | 21 ++++++----- test/urlRuleSpec.ts | 79 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+), 10 deletions(-) create mode 100644 test/urlRuleSpec.ts diff --git a/src/state/stateObject.ts b/src/state/stateObject.ts index 36b69ac4..ee84e0bb 100644 --- a/src/state/stateObject.ts +++ b/src/state/stateObject.ts @@ -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']); @@ -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)); } /** diff --git a/src/url/urlRule.ts b/src/url/urlRule.ts index 0cce3635..56708347 100644 --- a/src/url/urlRule.ts +++ b/src/url/urlRule.ts @@ -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 { @@ -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) {} @@ -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)], ]); @@ -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; } @@ -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 * @@ -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; diff --git a/test/urlRuleSpec.ts b/test/urlRuleSpec.ts new file mode 100644 index 00000000..f47603c5 --- /dev/null +++ b/test/urlRuleSpec.ts @@ -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(); + }); + }); +});