From 849f84f7c35530b01d3ebae1a2fd500c2eebda21 Mon Sep 17 00:00:00 2001 From: Chris Thielen Date: Sat, 7 Jan 2017 14:30:59 -0600 Subject: [PATCH] feat(Params): Allow `inherit: false` specified per parameter or type - During a transition (which has `inherit: true`) (`StateService.go`, `uiSref`), do not inherit parameters which are declared as `{ inherit: false }` or whose type is declared as `{ inherit: false }` feat(hash): Change the hash parameter type (`'#'`) to `inherit: false` so it is cleared out when another transition occurs. Closes #3245 Closes #3218 Closes #3017 --- src/params/param.ts | 10 +++-- src/params/paramType.ts | 7 ++++ src/params/paramTypes.ts | 12 +++--- src/path/pathFactory.ts | 7 +++- test/transitionSpec.ts | 81 ++++++++++++++++++++++++++++++++++++++-- 5 files changed, 104 insertions(+), 13 deletions(-) diff --git a/src/params/param.ts b/src/params/param.ts index 330e9f8c..21175742 100644 --- a/src/params/param.ts +++ b/src/params/param.ts @@ -67,12 +67,13 @@ export class Param { id: string; type: ParamType; location: DefType; - array: boolean; - squash: (boolean|string); - replace: any; isOptional: boolean; dynamic: boolean; raw: boolean; + squash: (boolean|string); + replace: any; + inherit: boolean; + array: boolean; config: any; constructor(id: string, type: ParamType, config: ParamDeclaration, location: DefType, urlMatcherFactory: UrlMatcherFactory) { @@ -85,6 +86,7 @@ export class Param { let raw = isDefined(config.raw) ? !!config.raw : !!type.raw; let squash = getSquashPolicy(config, isOptional, urlMatcherFactory.defaultSquashPolicy()); let replace = getReplace(config, arrayMode, isOptional, squash); + let inherit = isDefined(config.inherit) ? !!config.inherit : !!type.inherit; // array config: param name (param[]) overrides default settings. explicit config overrides param name. function getArrayMode() { @@ -93,7 +95,7 @@ export class Param { return extend(arrayDefaults, arrayParamNomenclature, config).array; } - extend(this, {id, type, location, squash, replace, isOptional, dynamic, raw, config, array: arrayMode}); + extend(this, {id, type, location, isOptional, dynamic, raw, squash, replace, inherit, array: arrayMode, config, }); } isDefaultValue(value: any): boolean { diff --git a/src/params/paramType.ts b/src/params/paramType.ts index 6a2b92a4..928d6ad9 100644 --- a/src/params/paramType.ts +++ b/src/params/paramType.ts @@ -28,10 +28,16 @@ import {ParamTypeDefinition} from "./interface"; * @internalapi */ export class ParamType implements ParamTypeDefinition { + /** @inheritdoc */ pattern: RegExp = /.*/; + /** The name/id of the parameter type */ name: string; + /** @inheritdoc */ raw: boolean; + /** @inheritdoc */ dynamic: boolean; + /** @inheritdoc */ + inherit = true; /** * @param def A configuration object which contains the custom type definition. The object's @@ -136,6 +142,7 @@ function ArrayType(type: ParamType, mode: (boolean|"auto")) { dynamic: type.dynamic, name: type.name, pattern: type.pattern, + inherit: type.inherit, is: arrayHandler(type.is.bind(type), true), $arrayMode: mode }); diff --git a/src/params/paramTypes.ts b/src/params/paramTypes.ts index a72132fd..22c4fc42 100644 --- a/src/params/paramTypes.ts +++ b/src/params/paramTypes.ts @@ -261,14 +261,16 @@ function initDefaultTypes() { // Default Parameter Type Definitions extend(ParamTypes.prototype, { - hash: makeDefaultType({}), - - query: makeDefaultType({}), - string: makeDefaultType({}), path: makeDefaultType({ - pattern: /[^/]*/ + pattern: /[^/]*/, + }), + + query: makeDefaultType({}), + + hash: makeDefaultType({ + inherit: false, }), int: makeDefaultType({ diff --git a/src/path/pathFactory.ts b/src/path/pathFactory.ts index 59608ced..1001c66f 100644 --- a/src/path/pathFactory.ts +++ b/src/path/pathFactory.ts @@ -72,6 +72,11 @@ export class PathFactory { return extend({}, node && node.paramValues); } + let noInherit = fromPath.map(node => node.paramSchema) + .reduce(unnestR, []) + .filter(param => !param.inherit) + .map(prop('id')); + /** * Given an [[PathNode]] "toNode", return a new [[PathNode]] with param values inherited from the * matching node in fromPath. Only inherit keys that aren't found in "toKeys" from the node in "fromPath"" @@ -82,7 +87,7 @@ export class PathFactory { // limited to only those keys found in toParams let incomingParamVals = pick(toParamVals, toKeys); toParamVals = omit(toParamVals, toKeys); - let fromParamVals = nodeParamVals(fromPath, toNode.state) || {}; + let fromParamVals = omit(nodeParamVals(fromPath, toNode.state) || {}, noInherit); // extend toParamVals with any fromParamVals, then override any of those those with incomingParamVals let ownParamVals: RawParams = extend(toParamVals, fromParamVals, incomingParamVals); return new PathNode(toNode.state).applyRawParams(ownParamVals); diff --git a/test/transitionSpec.ts b/test/transitionSpec.ts index 72551a10..bdff0109 100644 --- a/test/transitionSpec.ts +++ b/test/transitionSpec.ts @@ -4,6 +4,7 @@ import { } from "../src/index"; import { tree2Array, PromiseResult } from "./_testUtils"; import { TestingPlugin } from "./_testingPlugin"; +import { equals } from "../src/common/common"; describe('transition', function () { @@ -783,8 +784,82 @@ describe('transition', function () { })); }); }); -}); -describe("initial url redirect", () => { + describe('inherited params', () => { + it('should inherit params when trans options `inherit: true`', async(done) => { + router.stateRegistry.register({ name: 'foo', url: '/:path?query1&query2' }); + + await $state.go('foo', { path: 'abc', query1: 'def', query2: 'ghi' }); + expect(router.globals.params).toEqualValues({ path: 'abc', query1: 'def', query2: 'ghi' }); + + await $state.go('foo', { query2: 'jkl' }); + expect(router.globals.params).toEqualValues({ path: 'abc', query1: 'def', query2: 'jkl' }); + + done(); + }); + + it('should not inherit params when param declaration has inherit: false', async(done) => { + router.stateRegistry.register({ + name: 'foo', + url: '/:path?query1&query2', + params: { + query1: { inherit: false, value: null } + } + }); + + await $state.go('foo', { path: 'abc', query1: 'def', query2: 'ghi' }); + expect(router.globals.params).toEqualValues({ path: 'abc', query1: 'def', query2: 'ghi' }); + + await $state.go('foo', { query2: 'jkl' }); + expect(router.globals.params).toEqualValues({ path: 'abc', query1: null, query2: 'jkl' }); + + done(); + }); + + it('should not inherit params whose type has inherit: false', async(done) => { + router.urlService.config.type('inherit', { + inherit: true, encode: x=>x, decode: x=>x, is: () => true, equals: equals, pattern: /.*/, raw: false, + }); + + router.urlService.config.type('noinherit', { + inherit: false, encode: x=>x, decode: x=>x, is: () => true, equals: equals, pattern: /.*/, raw: false, + }); + + router.stateRegistry.register({ + name: 'foo', + url: '/?{query1:inherit}&{query2:noinherit}', + }); + + await $state.go('foo', { query1: 'abc', query2: 'def' }); + expect(router.globals.params).toEqualValues({ query1: 'abc', query2: 'def' }); + + await $state.go('foo'); + expect(router.globals.params).toEqualValues({ query1: 'abc', query2: undefined }); + + done(); + }); + + it('should not inherit the "hash" param value', async(done) => { + router.stateRegistry.register({ name: 'hash', url: '/hash' }); + router.stateRegistry.register({ name: 'other', url: '/other' }); -}); \ No newline at end of file + await $state.go('hash', { "#": "abc" }); + expect(router.globals.params).toEqualValues({ "#": "abc" }); + expect(router.urlService.hash()).toBe('abc'); + + await $state.go('hash'); + expect(router.globals.params).toEqualValues({ "#": null }); + expect(router.urlService.hash()).toBe(''); + + await $state.go('other', { "#": "def" }); + expect(router.globals.params).toEqualValues({ "#": "def" }); + expect(router.urlService.hash()).toBe('def'); + + await $state.go('hash'); + expect(router.globals.params).toEqualValues({ "#": null }); + expect(router.urlService.hash()).toBe(''); + + done(); + }); + }); +});