X Tutup
Skip to content

Commit 8bdca5c

Browse files
committed
fix(router): improve error messages for routes with no config
Closes #2323
1 parent ccb4163 commit 8bdca5c

File tree

2 files changed

+86
-75
lines changed

2 files changed

+86
-75
lines changed

modules/angular2/src/router/route_registry.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ import {
1717
isStringMap,
1818
isFunction,
1919
StringWrapper,
20-
BaseException
20+
BaseException,
21+
getTypeNameForDebugging
2122
} from 'angular2/src/facade/lang';
2223
import {RouteConfig} from './route_config_impl';
2324
import {reflector} from 'angular2/src/reflection/reflection';
@@ -154,6 +155,9 @@ export class RouteRegistry {
154155
let componentCursor = parentComponent;
155156
for (let i = 0; i < linkParams.length; i += 1) {
156157
let segment = linkParams[i];
158+
if (isBlank(componentCursor)) {
159+
throw new BaseException(`Could not find route named "${segment}".`);
160+
}
157161
if (!isString(segment)) {
158162
throw new BaseException(`Unexpected segment "${segment}" in link DSL. Expected a string.`);
159163
} else if (segment == '' || segment == '.' || segment == '..') {
@@ -170,9 +174,13 @@ export class RouteRegistry {
170174

171175
var componentRecognizer = this._rules.get(componentCursor);
172176
if (isBlank(componentRecognizer)) {
173-
throw new BaseException(`Could not find route config for "${segment}".`);
177+
throw new BaseException(`Component "${getTypeNameForDebugging(componentCursor)}" has no route config.`);
174178
}
175179
var response = componentRecognizer.generate(segment, params);
180+
if (isBlank(response)) {
181+
throw new BaseException(
182+
`Component "${getTypeNameForDebugging(componentCursor)}" has no route named "${segment}".`);
183+
}
176184
url += response['url'];
177185
componentCursor = response['nextComponent'];
178186
}

modules/angular2/test/router/route_registry_spec.ts

Lines changed: 76 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -11,211 +11,214 @@ import {
1111
} from 'angular2/test_lib';
1212

1313
import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
14-
import {ListWrapper} from 'angular2/src/facade/collection';
1514

1615
import {RouteRegistry} from 'angular2/src/router/route_registry';
1716
import {RouteConfig} from 'angular2/src/router/route_config_decorator';
1817

1918
export function main() {
2019
describe('RouteRegistry', () => {
21-
var registry, rootHostComponent = new Object();
20+
var registry;
2221

2322
beforeEach(() => { registry = new RouteRegistry(); });
2423

2524
it('should match the full URL', inject([AsyncTestCompleter], (async) => {
26-
registry.config(rootHostComponent, {'path': '/', 'component': DummyCompA});
27-
registry.config(rootHostComponent, {'path': '/test', 'component': DummyCompB});
25+
registry.config(RootHostCmp, {'path': '/', 'component': DummyCmpA});
26+
registry.config(RootHostCmp, {'path': '/test', 'component': DummyCmpB});
2827

29-
registry.recognize('/test', rootHostComponent)
28+
registry.recognize('/test', RootHostCmp)
3029
.then((instruction) => {
31-
expect(instruction.component).toBe(DummyCompB);
30+
expect(instruction.component).toBe(DummyCmpB);
3231
async.done();
3332
});
3433
}));
3534

3635
it('should generate URLs starting at the given component', () => {
37-
registry.config(rootHostComponent,
38-
{'path': '/first/...', 'component': DummyParentComp, 'as': 'firstCmp'});
36+
registry.config(RootHostCmp,
37+
{'path': '/first/...', 'component': DummyParentCmp, 'as': 'firstCmp'});
3938

40-
expect(registry.generate(['firstCmp', 'secondCmp'], rootHostComponent))
41-
.toEqual('first/second');
42-
expect(registry.generate(['secondCmp'], DummyParentComp)).toEqual('second');
39+
expect(registry.generate(['firstCmp', 'secondCmp'], RootHostCmp)).toEqual('first/second');
40+
expect(registry.generate(['secondCmp'], DummyParentCmp)).toEqual('second');
4341
});
4442

4543
it('should generate URLs with params', () => {
4644
registry.config(
47-
rootHostComponent,
48-
{'path': '/first/:param/...', 'component': DummyParentParamComp, 'as': 'firstCmp'});
45+
RootHostCmp,
46+
{'path': '/first/:param/...', 'component': DummyParentParamCmp, 'as': 'firstCmp'});
4947

50-
var url = registry.generate(['firstCmp', {param: 'one'}, 'secondCmp', {param: 'two'}],
51-
rootHostComponent);
48+
var url =
49+
registry.generate(['firstCmp', {param: 'one'}, 'secondCmp', {param: 'two'}], RootHostCmp);
5250
expect(url).toEqual('first/one/second/two');
5351
});
5452

5553
it('should generate URLs of loaded components after they are loaded',
5654
inject([AsyncTestCompleter], (async) => {
57-
registry.config(rootHostComponent, {
55+
registry.config(RootHostCmp, {
5856
'path': '/first/...',
5957
'component': {'type': 'loader', 'loader': AsyncParentLoader},
6058
'as': 'firstCmp'
6159
});
6260

63-
expect(() => registry.generate(['firstCmp', 'secondCmp'], rootHostComponent))
64-
.toThrowError('Could not find route config for "secondCmp".');
61+
expect(() => registry.generate(['firstCmp', 'secondCmp'], RootHostCmp))
62+
.toThrowError('Could not find route named "secondCmp".');
6563

66-
registry.recognize('/first/second', rootHostComponent)
64+
registry.recognize('/first/second', RootHostCmp)
6765
.then((_) => {
68-
expect(registry.generate(['firstCmp', 'secondCmp'], rootHostComponent))
66+
expect(registry.generate(['firstCmp', 'secondCmp'], RootHostCmp))
6967
.toEqual('first/second');
7068
async.done();
7169
});
7270
}));
7371

72+
73+
it('should throw when generating a url and a parent has no config', () => {
74+
expect(() => registry.generate(['firstCmp', 'secondCmp'], RootHostCmp))
75+
.toThrowError('Component "RootHostCmp" has no route config.');
76+
});
77+
78+
7479
it('should prefer static segments to dynamic', inject([AsyncTestCompleter], (async) => {
75-
registry.config(rootHostComponent, {'path': '/:site', 'component': DummyCompB});
76-
registry.config(rootHostComponent, {'path': '/home', 'component': DummyCompA});
80+
registry.config(RootHostCmp, {'path': '/:site', 'component': DummyCmpB});
81+
registry.config(RootHostCmp, {'path': '/home', 'component': DummyCmpA});
7782

78-
registry.recognize('/home', rootHostComponent)
83+
registry.recognize('/home', RootHostCmp)
7984
.then((instruction) => {
80-
expect(instruction.component).toBe(DummyCompA);
85+
expect(instruction.component).toBe(DummyCmpA);
8186
async.done();
8287
});
8388
}));
8489

8590
it('should prefer dynamic segments to star', inject([AsyncTestCompleter], (async) => {
86-
registry.config(rootHostComponent, {'path': '/:site', 'component': DummyCompA});
87-
registry.config(rootHostComponent, {'path': '/*site', 'component': DummyCompB});
91+
registry.config(RootHostCmp, {'path': '/:site', 'component': DummyCmpA});
92+
registry.config(RootHostCmp, {'path': '/*site', 'component': DummyCmpB});
8893

89-
registry.recognize('/home', rootHostComponent)
94+
registry.recognize('/home', RootHostCmp)
9095
.then((instruction) => {
91-
expect(instruction.component).toBe(DummyCompA);
96+
expect(instruction.component).toBe(DummyCmpA);
9297
async.done();
9398
});
9499
}));
95100

96101
it('should prefer routes with more dynamic segments', inject([AsyncTestCompleter], (async) => {
97-
registry.config(rootHostComponent, {'path': '/:first/*rest', 'component': DummyCompA});
98-
registry.config(rootHostComponent, {'path': '/*all', 'component': DummyCompB});
102+
registry.config(RootHostCmp, {'path': '/:first/*rest', 'component': DummyCmpA});
103+
registry.config(RootHostCmp, {'path': '/*all', 'component': DummyCmpB});
99104

100-
registry.recognize('/some/path', rootHostComponent)
105+
registry.recognize('/some/path', RootHostCmp)
101106
.then((instruction) => {
102-
expect(instruction.component).toBe(DummyCompA);
107+
expect(instruction.component).toBe(DummyCmpA);
103108
async.done();
104109
});
105110
}));
106111

107112
it('should prefer routes with more static segments', inject([AsyncTestCompleter], (async) => {
108-
registry.config(rootHostComponent, {'path': '/first/:second', 'component': DummyCompA});
109-
registry.config(rootHostComponent, {'path': '/:first/:second', 'component': DummyCompB});
113+
registry.config(RootHostCmp, {'path': '/first/:second', 'component': DummyCmpA});
114+
registry.config(RootHostCmp, {'path': '/:first/:second', 'component': DummyCmpB});
110115

111-
registry.recognize('/first/second', rootHostComponent)
116+
registry.recognize('/first/second', RootHostCmp)
112117
.then((instruction) => {
113-
expect(instruction.component).toBe(DummyCompA);
118+
expect(instruction.component).toBe(DummyCmpA);
114119
async.done();
115120
});
116121
}));
117122

118123
it('should prefer routes with static segments before dynamic segments',
119124
inject([AsyncTestCompleter], (async) => {
120-
registry.config(rootHostComponent,
121-
{'path': '/first/second/:third', 'component': DummyCompB});
122-
registry.config(rootHostComponent,
123-
{'path': '/first/:second/third', 'component': DummyCompA});
125+
registry.config(RootHostCmp, {'path': '/first/second/:third', 'component': DummyCmpB});
126+
registry.config(RootHostCmp, {'path': '/first/:second/third', 'component': DummyCmpA});
124127

125-
registry.recognize('/first/second/third', rootHostComponent)
128+
registry.recognize('/first/second/third', RootHostCmp)
126129
.then((instruction) => {
127-
expect(instruction.component).toBe(DummyCompB);
130+
expect(instruction.component).toBe(DummyCmpB);
128131
async.done();
129132
});
130133
}));
131134

132135
it('should match the full URL using child components', inject([AsyncTestCompleter], (async) => {
133-
registry.config(rootHostComponent, {'path': '/first/...', 'component': DummyParentComp});
136+
registry.config(RootHostCmp, {'path': '/first/...', 'component': DummyParentCmp});
134137

135-
registry.recognize('/first/second', rootHostComponent)
138+
registry.recognize('/first/second', RootHostCmp)
136139
.then((instruction) => {
137-
expect(instruction.component).toBe(DummyParentComp);
138-
expect(instruction.child.component).toBe(DummyCompB);
140+
expect(instruction.component).toBe(DummyParentCmp);
141+
expect(instruction.child.component).toBe(DummyCmpB);
139142
async.done();
140143
});
141144
}));
142145

143146
it('should match the URL using async child components',
144147
inject([AsyncTestCompleter], (async) => {
145-
registry.config(rootHostComponent, {'path': '/first/...', 'component': DummyAsyncComp});
148+
registry.config(RootHostCmp, {'path': '/first/...', 'component': DummyAsyncCmp});
146149

147-
registry.recognize('/first/second', rootHostComponent)
150+
registry.recognize('/first/second', RootHostCmp)
148151
.then((instruction) => {
149-
expect(instruction.component).toBe(DummyAsyncComp);
150-
expect(instruction.child.component).toBe(DummyCompB);
152+
expect(instruction.component).toBe(DummyAsyncCmp);
153+
expect(instruction.child.component).toBe(DummyCmpB);
151154
async.done();
152155
});
153156
}));
154157

155158
it('should match the URL using an async parent component',
156159
inject([AsyncTestCompleter], (async) => {
157160
registry.config(
158-
rootHostComponent,
161+
RootHostCmp,
159162
{'path': '/first/...', 'component': {'loader': AsyncParentLoader, 'type': 'loader'}});
160163

161-
registry.recognize('/first/second', rootHostComponent)
164+
registry.recognize('/first/second', RootHostCmp)
162165
.then((instruction) => {
163-
expect(instruction.component).toBe(DummyParentComp);
164-
expect(instruction.child.component).toBe(DummyCompB);
166+
expect(instruction.component).toBe(DummyParentCmp);
167+
expect(instruction.child.component).toBe(DummyCmpB);
165168
async.done();
166169
});
167170
}));
168171

169172
it('should throw when a config does not have a component or redirectTo property', () => {
170-
expect(() => registry.config(rootHostComponent, {'path': '/some/path'}))
173+
expect(() => registry.config(RootHostCmp, {'path': '/some/path'}))
171174
.toThrowError(
172175
'Route config should contain exactly one \'component\', or \'redirectTo\' property');
173176
});
174177

175178
it('should throw when a config has an invalid component type', () => {
176179
expect(() => registry.config(
177-
rootHostComponent,
180+
RootHostCmp,
178181
{'path': '/some/path', 'component': {'type': 'intentionallyWrongComponentType'}}))
179182
.toThrowError('Invalid component type \'intentionallyWrongComponentType\'');
180183
});
181184

182185
it('should throw when a parent config is missing the `...` suffix any of its children add routes',
183186
() => {
184-
expect(() =>
185-
registry.config(rootHostComponent, {'path': '/', 'component': DummyParentComp}))
187+
expect(() => registry.config(RootHostCmp, {'path': '/', 'component': DummyParentCmp}))
186188
.toThrowError(
187189
'Child routes are not allowed for "/". Use "..." on the parent\'s route path.');
188190
});
189191

190-
it('should throw when a parent config is missing the `...` suffix any of its children add routes',
191-
() => {
192-
expect(() => registry.config(rootHostComponent,
193-
{'path': '/home/.../fun/', 'component': DummyParentComp}))
194-
.toThrowError('Unexpected "..." before the end of the path for "home/.../fun/".');
195-
});
192+
it('should throw when a parent config uses `...` suffix before the end of the route', () => {
193+
expect(() => registry.config(RootHostCmp,
194+
{'path': '/home/.../fun/', 'component': DummyParentCmp}))
195+
.toThrowError('Unexpected "..." before the end of the path for "home/.../fun/".');
196+
});
196197
});
197198
}
198199

199200
function AsyncParentLoader() {
200-
return PromiseWrapper.resolve(DummyParentComp);
201+
return PromiseWrapper.resolve(DummyParentCmp);
201202
}
202203

203204
function AsyncChildLoader() {
204-
return PromiseWrapper.resolve(DummyCompB);
205+
return PromiseWrapper.resolve(DummyCmpB);
205206
}
206207

208+
class RootHostCmp {}
209+
207210
@RouteConfig([{'path': '/second', 'component': {'loader': AsyncChildLoader, 'type': 'loader'}}])
208-
class DummyAsyncComp {
211+
class DummyAsyncCmp {
209212
}
210213

211-
class DummyCompA {}
212-
class DummyCompB {}
214+
class DummyCmpA {}
215+
class DummyCmpB {}
213216

214-
@RouteConfig([{'path': '/second', 'component': DummyCompB, 'as': 'secondCmp'}])
215-
class DummyParentComp {
217+
@RouteConfig([{'path': '/second', 'component': DummyCmpB, 'as': 'secondCmp'}])
218+
class DummyParentCmp {
216219
}
217220

218221

219-
@RouteConfig([{'path': '/second/:param', 'component': DummyCompB, 'as': 'secondCmp'}])
220-
class DummyParentParamComp {
222+
@RouteConfig([{'path': '/second/:param', 'component': DummyCmpB, 'as': 'secondCmp'}])
223+
class DummyParentParamCmp {
221224
}

0 commit comments

Comments
 (0)
X Tutup