Skip to content

Commit

Permalink
fix(urlMatcherFactory): fix configuring a parameter type by name in a…
Browse files Browse the repository at this point in the history
… params block

closes #2294
  • Loading branch information
christopherthielen committed Oct 6, 2015
1 parent 1c763c8 commit 9803883
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 3 deletions.
15 changes: 12 additions & 3 deletions src/urlMatcherFactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,11 @@ function UrlMatcher(pattern, config, parentMatcher) {
cfg = config.params[id];
segment = pattern.substring(last, m.index);
regexp = isSearch ? m[4] : m[4] || (m[1] == '*' ? '.*' : null);
type = $$UMFP.type(regexp || "string") || inherit($$UMFP.type("string"), { pattern: new RegExp(regexp, config.caseInsensitive ? 'i' : undefined) });

if (regexp) {
type = $$UMFP.type(regexp) || inherit($$UMFP.type("string"), { pattern: new RegExp(regexp, config.caseInsensitive ? 'i' : undefined) });
}

return {
id: id, regexp: regexp, segment: segment, type: type, cfg: cfg
};
Expand Down Expand Up @@ -917,10 +921,15 @@ function $UrlMatcherFactory() {
}

function getType(config, urlType, location) {
if (config.type && urlType) throw new Error("Param '"+id+"' has two type configurations.");
if (config.type && urlType && config.type !== urlType) throw new Error("Param '"+id+"' has two type configurations.");
if (urlType) return urlType;
if (!config.type) return (location === "config" ? $types.any : $types.string);
return config.type instanceof Type ? config.type : new Type(config.type);

if (angular.isString(config.type))
return $types[config.type];
if (config.type instanceof Type)
return config.type;
return new Type(config.type);
}

// array config: param name (param[]) overrides default settings. explicit config overrides param name.
Expand Down
32 changes: 32 additions & 0 deletions test/stateSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ describe('state', function () {
RSP = { url: '^/:doReload/search?term', reloadOnSearch: false },
OPT = { url: '/opt/:param', params: { param: "100" } },
OPT2 = { url: '/opt2/:param2/:param3', params: { param3: "300", param4: "400" } },
URLLESS = { url: '/urllessparams', params: { myparam: { type: 'int' } } },
ISS2101 = { params: { bar: { squash: false, value: 'qux'}}, url: '/2101/{bar:string}' };
AppInjectable = {};

Expand All @@ -56,6 +57,7 @@ describe('state', function () {
.state('HHH', HHH)
.state('OPT', OPT)
.state('OPT.OPT2', OPT2)
.state('URLLESS', URLLESS)
.state('ISS2101', ISS2101)
.state('RS', RS)
.state('RSP', RSP)
Expand Down Expand Up @@ -989,6 +991,7 @@ describe('state', function () {
'OPT.OPT2',
'RS',
'RSP',
'URLLESS',
'about',
'about.person',
'about.person.item',
Expand Down Expand Up @@ -1156,6 +1159,7 @@ describe('state', function () {

describe("typed parameter handling", function() {
beforeEach(function () {
var $state = $get('$state')
stateProvider.state({
name: "types",
url: "/types/{p1:string}/{p2:date}",
Expand Down Expand Up @@ -1255,6 +1259,34 @@ describe('state', function () {
extend(params, { p5: true });
check('types.substate', "/types/foo/2014-11-15/sub/10/%7B%22baz%22:%22qux%22%7D?p5=1", params, defaults, nonurl);
}));

it('should support non-url parameters', inject(function($state, $q, $stateParams) {
$state.transitionTo(A); $q.flush();
expect($state.is(A)).toBe(true);

$state.go('URLLESS', { myparam: "0"}); $q.flush(); // string "0" decodes to 0
expect($state.current.name).toBe("URLLESS");
expect($stateParams.myparam).toBe(0);

$state.go('URLLESS', { myparam: "1"}); $q.flush(); // string "1" decodes to 1
expect($stateParams.myparam).toBe(1);
}));

it('should not transition if a required non-url parameter is missing', inject(function($state, $q, $stateParams) {
$state.transitionTo(A); $q.flush();
expect($state.current.name).toBe("A");

$state.go('URLLESS'); $q.flush(); // Missing required parameter; transition fails
expect($state.current.name).toBe("A");
}));

it('should not transition if a required non-url parameter is invalid', inject(function($state, $q, $stateParams) {
$state.transitionTo(A); $q.flush();
expect($state.current.name).toBe("A");

$state.go('URLLESS', { myparam: "somestring"}); $q.flush(); // string "somestring" is not an int
expect($state.current.name).toBe("A");
}));
});

it('should revert to last known working url on state change failure', inject(function ($state, $rootScope, $location, $q) {
Expand Down
24 changes: 24 additions & 0 deletions test/urlMatcherFactorySpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,30 @@ describe("urlMatcherFactory", function () {
expect(m.format({ foo: 5, flag: true })).toBe("/5/1");
});

it("should match types named only in params", function () {
var m = new UrlMatcher("/{foo}/{flag}", {
params: {
foo: { type: 'int'},
flag: { type: 'bool'}
}
});
expect(m.exec("/1138/1")).toEqual({foo: 1138, flag: true});
expect(m.format({foo: 5, flag: true})).toBe("/5/1");
});

it("should throw an error if a param type is declared twice", function () {
try {
new UrlMatcher("/{foo:int}", {
params: {
foo: {type: 'int'}
}
});
expect("This should not be reached").toBeNull();
} catch (error) {
expect(error.message).toBe("Param 'foo' has two type configurations.")
}
});

it("should encode/decode dates", function () {
var m = new UrlMatcher("/calendar/{date:date}"),
result = m.exec("/calendar/2014-03-26");
Expand Down

0 comments on commit 9803883

Please sign in to comment.