2016-04-12 12:40:37 -04:00
|
|
|
import {
|
|
|
|
ComponentFixture,
|
|
|
|
AsyncTestCompleter,
|
|
|
|
beforeEach,
|
|
|
|
ddescribe,
|
|
|
|
xdescribe,
|
|
|
|
describe,
|
|
|
|
dispatchEvent,
|
|
|
|
expect,
|
|
|
|
iit,
|
|
|
|
inject,
|
|
|
|
beforeEachProviders,
|
|
|
|
it,
|
|
|
|
xit,
|
|
|
|
TestComponentBuilder,
|
|
|
|
SpyObject
|
|
|
|
} from 'angular2/testing_internal';
|
2015-08-24 14:24:53 -04:00
|
|
|
|
2016-01-14 00:35:21 -05:00
|
|
|
import {By} from 'angular2/platform/common_dom';
|
refactor(Location): out of router and into platform/common
closes https://github.com/angular/angular/issues/4943
BREAKING CHANGE:
`Location` and other related providers have been moved out of `router` and into `platform/common`. `BrowserPlatformLocation` is not meant to be used directly however advanced configurations may use it via the following import change.
Before:
```
import {
PlatformLocation,
Location,
LocationStrategy,
HashLocationStrategy,
PathLocationStrategy,
APP_BASE_HREF}
from 'angular2/router';
import {BrowserPlatformLocation} from 'angular2/src/router/location/browser_platform_location';
```
After:
```
import {
PlatformLocation,
Location,
LocationStrategy,
HashLocationStrategy,
PathLocationStrategy,
APP_BASE_HREF}
from 'angular2/platform/common';
import {BrowserPlatformLocation} from 'angular2/src/platform/browser/location/browser_platform_location';
```
Closes #7962
2016-04-08 03:31:20 -04:00
|
|
|
import {Location} from 'angular2/platform/common';
|
2015-11-06 20:34:07 -05:00
|
|
|
import {NumberWrapper} from 'angular2/src/facade/lang';
|
|
|
|
import {PromiseWrapper} from 'angular2/src/facade/async';
|
|
|
|
import {ListWrapper} from 'angular2/src/facade/collection';
|
2015-08-24 14:24:53 -04:00
|
|
|
|
2016-01-06 17:13:44 -05:00
|
|
|
import {provide, Component} from 'angular2/core';
|
2015-08-24 14:24:53 -04:00
|
|
|
|
|
|
|
import {SpyLocation} from 'angular2/src/mock/location_mock';
|
2016-04-12 12:40:37 -04:00
|
|
|
import {
|
|
|
|
Router,
|
|
|
|
RouteRegistry,
|
|
|
|
RouterLink,
|
|
|
|
RouterOutlet,
|
|
|
|
AsyncRoute,
|
|
|
|
AuxRoute,
|
|
|
|
Route,
|
|
|
|
RouteParams,
|
|
|
|
RouteConfig,
|
|
|
|
ROUTER_DIRECTIVES,
|
|
|
|
ROUTER_PRIMARY_COMPONENT
|
|
|
|
} from 'angular2/router';
|
2015-09-21 22:58:45 -04:00
|
|
|
import {RootRouter} from 'angular2/src/router/router';
|
2015-08-24 14:24:53 -04:00
|
|
|
|
2015-11-19 18:09:34 -05:00
|
|
|
import {DOM} from 'angular2/src/platform/dom/dom_adapter';
|
2015-12-03 19:14:34 -05:00
|
|
|
import {TEMPLATE_TRANSFORMS} from 'angular2/compiler';
|
2016-02-09 14:12:41 -05:00
|
|
|
import {RouterLinkTransform} from 'angular2/src/router/directives/router_link_transform';
|
2015-08-24 14:24:53 -04:00
|
|
|
|
|
|
|
export function main() {
|
2015-11-23 19:02:19 -05:00
|
|
|
describe('routerLink directive', function() {
|
2015-08-24 14:24:53 -04:00
|
|
|
var tcb: TestComponentBuilder;
|
2015-10-31 12:50:19 -04:00
|
|
|
var fixture: ComponentFixture;
|
2016-02-19 14:49:31 -05:00
|
|
|
var router: Router;
|
|
|
|
var location: Location;
|
2015-08-24 14:24:53 -04:00
|
|
|
|
2016-04-12 12:40:37 -04:00
|
|
|
beforeEachProviders(() => [
|
|
|
|
RouteRegistry,
|
|
|
|
provide(Location, {useClass: SpyLocation}),
|
|
|
|
provide(ROUTER_PRIMARY_COMPONENT, {useValue: MyComp}),
|
|
|
|
provide(Router, {useClass: RootRouter}),
|
|
|
|
provide(TEMPLATE_TRANSFORMS, {useClass: RouterLinkTransform, multi: true})
|
|
|
|
]);
|
|
|
|
|
|
|
|
beforeEach(inject([TestComponentBuilder, Router, Location],
|
|
|
|
(tcBuilder, rtr: Router, loc: Location) => {
|
|
|
|
tcb = tcBuilder;
|
|
|
|
router = rtr;
|
|
|
|
location = loc;
|
|
|
|
}));
|
|
|
|
|
|
|
|
function compile(template: string = "<router-outlet></router-outlet>") {
|
2016-03-08 16:36:48 -05:00
|
|
|
return tcb.overrideTemplate(MyComp, ('<div>' + template + '</div>'))
|
2015-08-24 14:24:53 -04:00
|
|
|
.createAsync(MyComp)
|
2015-10-31 12:50:19 -04:00
|
|
|
.then((tc) => { fixture = tc; });
|
2015-08-24 14:24:53 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
it('should generate absolute hrefs that include the base href',
|
|
|
|
inject([AsyncTestCompleter], (async) => {
|
2016-02-19 14:49:31 -05:00
|
|
|
(<SpyLocation>location).setBaseHref('/my/base');
|
2015-11-23 19:02:19 -05:00
|
|
|
compile('<a href="hello" [routerLink]="[\'./User\']"></a>')
|
2016-04-12 12:40:37 -04:00
|
|
|
.then((_) => router.config(
|
|
|
|
[new Route({path: '/user', component: UserCmp, name: 'User'})]))
|
2015-09-09 00:41:56 -04:00
|
|
|
.then((_) => router.navigateByUrl('/a/b'))
|
2015-08-24 14:24:53 -04:00
|
|
|
.then((_) => {
|
2015-10-31 12:50:19 -04:00
|
|
|
fixture.detectChanges();
|
|
|
|
expect(getHref(fixture)).toEqual('/my/base/user');
|
2015-08-24 14:24:53 -04:00
|
|
|
async.done();
|
|
|
|
});
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
it('should generate link hrefs without params', inject([AsyncTestCompleter], (async) => {
|
2015-11-23 19:02:19 -05:00
|
|
|
compile('<a href="hello" [routerLink]="[\'./User\']"></a>')
|
2016-04-12 12:40:37 -04:00
|
|
|
.then((_) => router.config(
|
|
|
|
[new Route({path: '/user', component: UserCmp, name: 'User'})]))
|
2015-09-09 00:41:56 -04:00
|
|
|
.then((_) => router.navigateByUrl('/a/b'))
|
2015-08-24 14:24:53 -04:00
|
|
|
.then((_) => {
|
2015-10-31 12:50:19 -04:00
|
|
|
fixture.detectChanges();
|
|
|
|
expect(getHref(fixture)).toEqual('/user');
|
2015-08-24 14:24:53 -04:00
|
|
|
async.done();
|
|
|
|
});
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
it('should generate link hrefs with params', inject([AsyncTestCompleter], (async) => {
|
2015-11-23 19:02:19 -05:00
|
|
|
compile('<a href="hello" [routerLink]="[\'./User\', {name: name}]">{{name}}</a>')
|
2016-04-12 12:40:37 -04:00
|
|
|
.then((_) => router.config(
|
|
|
|
[new Route({path: '/user/:name', component: UserCmp, name: 'User'})]))
|
2015-09-09 00:41:56 -04:00
|
|
|
.then((_) => router.navigateByUrl('/a/b'))
|
2015-08-24 14:24:53 -04:00
|
|
|
.then((_) => {
|
2015-10-31 12:50:19 -04:00
|
|
|
fixture.debugElement.componentInstance.name = 'brian';
|
|
|
|
fixture.detectChanges();
|
|
|
|
expect(fixture.debugElement.nativeElement).toHaveText('brian');
|
2016-01-14 00:35:21 -05:00
|
|
|
expect(getHref(fixture)).toEqual('/user/brian');
|
2015-08-24 14:24:53 -04:00
|
|
|
async.done();
|
|
|
|
});
|
|
|
|
}));
|
|
|
|
|
|
|
|
it('should generate link hrefs from a child to its sibling',
|
|
|
|
inject([AsyncTestCompleter], (async) => {
|
|
|
|
compile()
|
2016-04-12 12:40:37 -04:00
|
|
|
.then(
|
|
|
|
(_) => router.config(
|
|
|
|
[new Route({path: '/page/:number', component: SiblingPageCmp, name: 'Page'})]))
|
2015-09-09 00:41:56 -04:00
|
|
|
.then((_) => router.navigateByUrl('/page/1'))
|
2015-08-24 14:24:53 -04:00
|
|
|
.then((_) => {
|
2015-10-31 12:50:19 -04:00
|
|
|
fixture.detectChanges();
|
2016-01-14 00:35:21 -05:00
|
|
|
expect(getHref(fixture)).toEqual('/page/2');
|
2015-08-24 14:24:53 -04:00
|
|
|
async.done();
|
|
|
|
});
|
|
|
|
}));
|
|
|
|
|
2015-10-26 13:18:08 -04:00
|
|
|
it('should generate link hrefs from a child to its sibling with no leading slash',
|
|
|
|
inject([AsyncTestCompleter], (async) => {
|
|
|
|
compile()
|
2016-04-12 12:40:37 -04:00
|
|
|
.then((_) => router.config([
|
|
|
|
new Route(
|
|
|
|
{path: '/page/:number', component: NoPrefixSiblingPageCmp, name: 'Page'})
|
|
|
|
]))
|
2015-10-26 13:18:08 -04:00
|
|
|
.then((_) => router.navigateByUrl('/page/1'))
|
|
|
|
.then((_) => {
|
2015-10-31 12:50:19 -04:00
|
|
|
fixture.detectChanges();
|
2016-01-14 00:35:21 -05:00
|
|
|
expect(getHref(fixture)).toEqual('/page/2');
|
2015-10-26 13:18:08 -04:00
|
|
|
async.done();
|
|
|
|
});
|
|
|
|
}));
|
|
|
|
|
|
|
|
it('should generate link hrefs to a child with no leading slash',
|
|
|
|
inject([AsyncTestCompleter], (async) => {
|
|
|
|
compile()
|
2016-04-12 12:40:37 -04:00
|
|
|
.then((_) => router.config([
|
|
|
|
new Route({path: '/book/:title/...', component: NoPrefixBookCmp, name: 'Book'})
|
|
|
|
]))
|
2015-10-26 13:18:08 -04:00
|
|
|
.then((_) => router.navigateByUrl('/book/1984/page/1'))
|
|
|
|
.then((_) => {
|
2015-10-31 12:50:19 -04:00
|
|
|
fixture.detectChanges();
|
2016-01-14 00:35:21 -05:00
|
|
|
expect(getHref(fixture)).toEqual('/book/1984/page/100');
|
2015-10-26 13:18:08 -04:00
|
|
|
async.done();
|
|
|
|
});
|
|
|
|
}));
|
|
|
|
|
|
|
|
it('should throw when links without a leading slash are ambiguous',
|
|
|
|
inject([AsyncTestCompleter], (async) => {
|
|
|
|
compile()
|
2016-04-12 12:40:37 -04:00
|
|
|
.then((_) => router.config([
|
|
|
|
new Route({path: '/book/:title/...', component: AmbiguousBookCmp, name: 'Book'})
|
|
|
|
]))
|
2015-10-26 13:18:08 -04:00
|
|
|
.then((_) => router.navigateByUrl('/book/1984/page/1'))
|
|
|
|
.then((_) => {
|
|
|
|
var link = ListWrapper.toJSON(['Book', {number: 100}]);
|
2015-10-31 12:50:19 -04:00
|
|
|
expect(() => fixture.detectChanges())
|
2015-10-26 13:18:08 -04:00
|
|
|
.toThrowErrorWith(
|
|
|
|
`Link "${link}" is ambiguous, use "./" or "../" to disambiguate.`);
|
|
|
|
async.done();
|
|
|
|
});
|
|
|
|
}));
|
|
|
|
|
2015-09-11 17:50:41 -04:00
|
|
|
it('should generate link hrefs when asynchronously loaded',
|
|
|
|
inject([AsyncTestCompleter], (async) => {
|
|
|
|
compile()
|
2016-04-12 12:40:37 -04:00
|
|
|
.then((_) => router.config([
|
|
|
|
new AsyncRoute({
|
|
|
|
path: '/child-with-grandchild/...',
|
|
|
|
loader: parentCmpLoader,
|
|
|
|
name: 'ChildWithGrandchild'
|
|
|
|
})
|
|
|
|
]))
|
2015-11-23 21:07:10 -05:00
|
|
|
.then((_) => router.navigateByUrl('/child-with-grandchild/grandchild'))
|
2015-09-11 17:50:41 -04:00
|
|
|
.then((_) => {
|
2015-10-31 12:50:19 -04:00
|
|
|
fixture.detectChanges();
|
2016-01-14 00:35:21 -05:00
|
|
|
expect(getHref(fixture)).toEqual('/child-with-grandchild/grandchild');
|
2015-09-11 17:50:41 -04:00
|
|
|
async.done();
|
|
|
|
});
|
|
|
|
}));
|
|
|
|
|
2015-08-24 14:24:53 -04:00
|
|
|
it('should generate relative links preserving the existing parent route',
|
|
|
|
inject([AsyncTestCompleter], (async) => {
|
|
|
|
compile()
|
2016-04-12 12:40:37 -04:00
|
|
|
.then((_) => router.config(
|
|
|
|
[new Route({path: '/book/:title/...', component: BookCmp, name: 'Book'})]))
|
2015-09-09 00:41:56 -04:00
|
|
|
.then((_) => router.navigateByUrl('/book/1984/page/1'))
|
2015-08-24 14:24:53 -04:00
|
|
|
.then((_) => {
|
2015-10-31 12:50:19 -04:00
|
|
|
fixture.detectChanges();
|
2016-01-14 00:35:21 -05:00
|
|
|
// TODO(juliemr): This should be one By.css('book-cmp a') query, but the parse5
|
|
|
|
// adapter
|
|
|
|
// can't handle css child selectors.
|
2016-04-12 12:40:37 -04:00
|
|
|
expect(DOM.getAttribute(fixture.debugElement.query(By.css('book-cmp'))
|
|
|
|
.query(By.css('a'))
|
|
|
|
.nativeElement,
|
|
|
|
'href'))
|
2015-08-24 14:24:53 -04:00
|
|
|
.toEqual('/book/1984/page/100');
|
|
|
|
|
2016-04-12 12:40:37 -04:00
|
|
|
expect(DOM.getAttribute(fixture.debugElement.query(By.css('page-cmp'))
|
|
|
|
.query(By.css('a'))
|
|
|
|
.nativeElement,
|
|
|
|
'href'))
|
2015-08-24 14:24:53 -04:00
|
|
|
.toEqual('/book/1984/page/2');
|
|
|
|
async.done();
|
|
|
|
});
|
|
|
|
}));
|
|
|
|
|
2015-11-23 21:07:10 -05:00
|
|
|
it('should generate links to auxiliary routes', inject([AsyncTestCompleter], (async) => {
|
|
|
|
compile()
|
|
|
|
.then((_) => router.config([new Route({path: '/...', component: AuxLinkCmp})]))
|
|
|
|
.then((_) => router.navigateByUrl('/'))
|
|
|
|
.then((_) => {
|
refactor(router): improve recognition and generation pipeline
This is a big change. @matsko also deserves much of the credit for the implementation.
Previously, `ComponentInstruction`s held all the state for async components.
Now, we introduce several subclasses for `Instruction` to describe each type of navigation.
BREAKING CHANGE:
Redirects now use the Link DSL syntax. Before:
```
@RouteConfig([
{ path: '/foo', redirectTo: '/bar' },
{ path: '/bar', component: BarCmp }
])
```
After:
```
@RouteConfig([
{ path: '/foo', redirectTo: ['Bar'] },
{ path: '/bar', component: BarCmp, name: 'Bar' }
])
```
BREAKING CHANGE:
This also introduces `useAsDefault` in the RouteConfig, which makes cases like lazy-loading
and encapsulating large routes with sub-routes easier.
Previously, you could use `redirectTo` like this to expand a URL like `/tab` to `/tab/posts`:
@RouteConfig([
{ path: '/tab', redirectTo: '/tab/users' }
{ path: '/tab', component: TabsCmp, name: 'Tab' }
])
AppCmp { ... }
Now the recommended way to handle this is case is to use `useAsDefault` like so:
```
@RouteConfig([
{ path: '/tab', component: TabsCmp, name: 'Tab' }
])
AppCmp { ... }
@RouteConfig([
{ path: '/posts', component: PostsCmp, useAsDefault: true, name: 'Posts' },
{ path: '/users', component: UsersCmp, name: 'Users' }
])
TabsCmp { ... }
```
In the above example, you can write just `['/Tab']` and the route `Users` is automatically selected as a child route.
Closes #4728
Closes #4228
Closes #4170
Closes #4490
Closes #4694
Closes #5200
Closes #5475
2015-11-23 21:07:37 -05:00
|
|
|
fixture.detectChanges();
|
2016-01-14 00:35:21 -05:00
|
|
|
expect(getHref(fixture)).toEqual('/(aside)');
|
2015-11-23 21:07:10 -05:00
|
|
|
async.done();
|
|
|
|
});
|
|
|
|
}));
|
|
|
|
|
2015-08-24 14:24:53 -04:00
|
|
|
|
2015-08-31 00:27:11 -04:00
|
|
|
describe('router-link-active CSS class', () => {
|
|
|
|
it('should be added to the associated element', inject([AsyncTestCompleter], (async) => {
|
2016-04-12 12:40:37 -04:00
|
|
|
router.config([
|
|
|
|
new Route({path: '/child', component: HelloCmp, name: 'Child'}),
|
|
|
|
new Route({path: '/better-child', component: Hello2Cmp, name: 'BetterChild'})
|
|
|
|
])
|
2015-11-23 19:02:19 -05:00
|
|
|
.then((_) => compile(`<a [routerLink]="['./Child']" class="child-link">Child</a>
|
|
|
|
<a [routerLink]="['./BetterChild']" class="better-child-link">Better Child</a>
|
2015-08-31 00:27:11 -04:00
|
|
|
<router-outlet></router-outlet>`))
|
|
|
|
.then((_) => {
|
2015-10-31 12:50:19 -04:00
|
|
|
var element = fixture.debugElement.nativeElement;
|
2015-08-31 00:27:11 -04:00
|
|
|
|
2015-10-31 12:50:19 -04:00
|
|
|
fixture.detectChanges();
|
2015-08-31 00:27:11 -04:00
|
|
|
|
|
|
|
var link1 = DOM.querySelector(element, '.child-link');
|
|
|
|
var link2 = DOM.querySelector(element, '.better-child-link');
|
|
|
|
|
|
|
|
expect(link1).not.toHaveCssClass('router-link-active');
|
|
|
|
expect(link2).not.toHaveCssClass('router-link-active');
|
|
|
|
|
|
|
|
router.subscribe((_) => {
|
2015-10-31 12:50:19 -04:00
|
|
|
fixture.detectChanges();
|
2015-08-31 00:27:11 -04:00
|
|
|
|
|
|
|
expect(link1).not.toHaveCssClass('router-link-active');
|
|
|
|
expect(link2).toHaveCssClass('router-link-active');
|
|
|
|
|
|
|
|
async.done();
|
|
|
|
});
|
2016-03-30 14:26:31 -04:00
|
|
|
router.navigateByUrl('/better-child?extra=0');
|
2015-08-31 00:27:11 -04:00
|
|
|
});
|
|
|
|
}));
|
|
|
|
|
|
|
|
it('should be added to links in child routes', inject([AsyncTestCompleter], (async) => {
|
2016-04-12 12:40:37 -04:00
|
|
|
router.config([
|
|
|
|
new Route({path: '/child', component: HelloCmp, name: 'Child'}),
|
|
|
|
new Route({
|
|
|
|
path: '/child-with-grandchild/...',
|
|
|
|
component: ParentCmp,
|
|
|
|
name: 'ChildWithGrandchild'
|
|
|
|
})
|
|
|
|
])
|
2015-11-23 19:02:19 -05:00
|
|
|
.then((_) => compile(`<a [routerLink]="['./Child']" class="child-link">Child</a>
|
|
|
|
<a [routerLink]="['./ChildWithGrandchild/Grandchild']" class="child-with-grandchild-link">Better Child</a>
|
2015-08-31 00:27:11 -04:00
|
|
|
<router-outlet></router-outlet>`))
|
|
|
|
.then((_) => {
|
2015-10-31 12:50:19 -04:00
|
|
|
var element = fixture.debugElement.nativeElement;
|
2015-08-31 00:27:11 -04:00
|
|
|
|
2015-10-31 12:50:19 -04:00
|
|
|
fixture.detectChanges();
|
2015-08-31 00:27:11 -04:00
|
|
|
|
|
|
|
var link1 = DOM.querySelector(element, '.child-link');
|
|
|
|
var link2 = DOM.querySelector(element, '.child-with-grandchild-link');
|
|
|
|
|
|
|
|
expect(link1).not.toHaveCssClass('router-link-active');
|
|
|
|
expect(link2).not.toHaveCssClass('router-link-active');
|
|
|
|
|
|
|
|
router.subscribe((_) => {
|
2015-10-31 12:50:19 -04:00
|
|
|
fixture.detectChanges();
|
2015-08-31 00:27:11 -04:00
|
|
|
|
|
|
|
expect(link1).not.toHaveCssClass('router-link-active');
|
|
|
|
expect(link2).toHaveCssClass('router-link-active');
|
|
|
|
|
|
|
|
var link3 = DOM.querySelector(element, '.grandchild-link');
|
|
|
|
var link4 = DOM.querySelector(element, '.better-grandchild-link');
|
|
|
|
|
|
|
|
expect(link3).toHaveCssClass('router-link-active');
|
|
|
|
expect(link4).not.toHaveCssClass('router-link-active');
|
|
|
|
|
|
|
|
async.done();
|
|
|
|
});
|
2016-03-30 14:26:31 -04:00
|
|
|
router.navigateByUrl('/child-with-grandchild/grandchild?extra=0');
|
2015-08-31 00:27:11 -04:00
|
|
|
});
|
|
|
|
}));
|
2015-12-03 19:14:34 -05:00
|
|
|
|
|
|
|
|
2015-12-07 13:51:01 -05:00
|
|
|
describe('router link dsl', () => {
|
2015-12-03 19:14:34 -05:00
|
|
|
it('should generate link hrefs with params', inject([AsyncTestCompleter], (async) => {
|
2015-11-23 19:02:19 -05:00
|
|
|
compile('<a href="hello" [routerLink]="route:./User(name: name)">{{name}}</a>')
|
2016-04-12 12:40:37 -04:00
|
|
|
.then((_) => router.config(
|
|
|
|
[new Route({path: '/user/:name', component: UserCmp, name: 'User'})]))
|
2015-12-03 19:14:34 -05:00
|
|
|
.then((_) => router.navigateByUrl('/a/b'))
|
|
|
|
.then((_) => {
|
|
|
|
fixture.debugElement.componentInstance.name = 'brian';
|
|
|
|
fixture.detectChanges();
|
|
|
|
expect(fixture.debugElement.nativeElement).toHaveText('brian');
|
2016-01-14 00:35:21 -05:00
|
|
|
expect(getHref(fixture)).toEqual('/user/brian');
|
2015-12-03 19:14:34 -05:00
|
|
|
async.done();
|
|
|
|
});
|
|
|
|
}));
|
|
|
|
});
|
2015-08-31 00:27:11 -04:00
|
|
|
});
|
|
|
|
|
2015-08-24 14:24:53 -04:00
|
|
|
describe('when clicked', () => {
|
|
|
|
|
|
|
|
var clickOnElement = function(view) {
|
2016-01-14 00:35:21 -05:00
|
|
|
var anchorEl = fixture.debugElement.query(By.css('a')).nativeElement;
|
2015-08-24 14:24:53 -04:00
|
|
|
var dispatchedEvent = DOM.createMouseEvent('click');
|
|
|
|
DOM.dispatchEvent(anchorEl, dispatchedEvent);
|
|
|
|
return dispatchedEvent;
|
|
|
|
};
|
|
|
|
|
|
|
|
it('should navigate to link hrefs without params', inject([AsyncTestCompleter], (async) => {
|
2015-11-23 19:02:19 -05:00
|
|
|
compile('<a href="hello" [routerLink]="[\'./User\']"></a>')
|
2016-04-12 12:40:37 -04:00
|
|
|
.then((_) => router.config(
|
|
|
|
[new Route({path: '/user', component: UserCmp, name: 'User'})]))
|
2015-09-09 00:41:56 -04:00
|
|
|
.then((_) => router.navigateByUrl('/a/b'))
|
2015-08-24 14:24:53 -04:00
|
|
|
.then((_) => {
|
2015-10-31 12:50:19 -04:00
|
|
|
fixture.detectChanges();
|
2015-08-24 14:24:53 -04:00
|
|
|
|
2015-10-31 12:50:19 -04:00
|
|
|
var dispatchedEvent = clickOnElement(fixture);
|
2015-08-24 14:24:53 -04:00
|
|
|
expect(DOM.isPrevented(dispatchedEvent)).toBe(true);
|
|
|
|
|
|
|
|
// router navigation is async.
|
|
|
|
router.subscribe((_) => {
|
2016-02-19 14:49:31 -05:00
|
|
|
expect((<SpyLocation>location).urlChanges).toEqual(['/user']);
|
2015-08-24 14:24:53 -04:00
|
|
|
async.done();
|
|
|
|
});
|
|
|
|
});
|
2015-09-09 10:41:11 -04:00
|
|
|
}));
|
2015-08-24 14:24:53 -04:00
|
|
|
|
|
|
|
it('should navigate to link hrefs in presence of base href',
|
|
|
|
inject([AsyncTestCompleter], (async) => {
|
2016-02-19 14:49:31 -05:00
|
|
|
(<SpyLocation>location).setBaseHref('/base');
|
2015-11-23 19:02:19 -05:00
|
|
|
compile('<a href="hello" [routerLink]="[\'./User\']"></a>')
|
2016-04-12 12:40:37 -04:00
|
|
|
.then((_) => router.config(
|
|
|
|
[new Route({path: '/user', component: UserCmp, name: 'User'})]))
|
2015-09-09 00:41:56 -04:00
|
|
|
.then((_) => router.navigateByUrl('/a/b'))
|
2015-08-24 14:24:53 -04:00
|
|
|
.then((_) => {
|
2015-10-31 12:50:19 -04:00
|
|
|
fixture.detectChanges();
|
2015-08-24 14:24:53 -04:00
|
|
|
|
2015-12-03 19:14:34 -05:00
|
|
|
|
2015-10-31 12:50:19 -04:00
|
|
|
var dispatchedEvent = clickOnElement(fixture);
|
2015-08-24 14:24:53 -04:00
|
|
|
expect(DOM.isPrevented(dispatchedEvent)).toBe(true);
|
|
|
|
|
|
|
|
// router navigation is async.
|
|
|
|
router.subscribe((_) => {
|
2016-02-19 14:49:31 -05:00
|
|
|
expect((<SpyLocation>location).urlChanges).toEqual(['/base/user']);
|
2015-08-24 14:24:53 -04:00
|
|
|
async.done();
|
|
|
|
});
|
|
|
|
});
|
2015-09-09 10:41:11 -04:00
|
|
|
}));
|
2015-08-24 14:24:53 -04:00
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2015-10-31 12:50:19 -04:00
|
|
|
function getHref(tc: ComponentFixture) {
|
2016-01-14 00:35:21 -05:00
|
|
|
return DOM.getAttribute(tc.debugElement.query(By.css('a')).nativeElement, 'href');
|
2015-08-24 14:24:53 -04:00
|
|
|
}
|
|
|
|
|
2016-03-08 16:36:48 -05:00
|
|
|
@Component({selector: 'my-comp', template: '', directives: [ROUTER_DIRECTIVES]})
|
2015-08-24 14:24:53 -04:00
|
|
|
class MyComp {
|
|
|
|
name;
|
|
|
|
}
|
|
|
|
|
2016-04-12 12:40:37 -04:00
|
|
|
@Component({selector: 'user-cmp', template: "hello {{user}}"})
|
2015-08-24 14:24:53 -04:00
|
|
|
class UserCmp {
|
|
|
|
user: string;
|
|
|
|
constructor(params: RouteParams) { this.user = params.get('name'); }
|
|
|
|
}
|
|
|
|
|
2015-11-23 21:07:24 -05:00
|
|
|
@Component({
|
|
|
|
selector: 'page-cmp',
|
2015-08-24 14:24:53 -04:00
|
|
|
template:
|
2015-11-23 19:02:19 -05:00
|
|
|
`page #{{pageNumber}} | <a href="hello" [routerLink]="[\'../Page\', {number: nextPage}]">next</a>`,
|
2015-08-24 14:24:53 -04:00
|
|
|
directives: [RouterLink]
|
|
|
|
})
|
|
|
|
class SiblingPageCmp {
|
|
|
|
pageNumber: number;
|
|
|
|
nextPage: number;
|
|
|
|
constructor(params: RouteParams) {
|
|
|
|
this.pageNumber = NumberWrapper.parseInt(params.get('number'), 10);
|
|
|
|
this.nextPage = this.pageNumber + 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-11-23 21:07:24 -05:00
|
|
|
@Component({
|
|
|
|
selector: 'page-cmp',
|
2015-10-26 13:18:08 -04:00
|
|
|
template:
|
2015-11-23 19:02:19 -05:00
|
|
|
`page #{{pageNumber}} | <a href="hello" [routerLink]="[\'Page\', {number: nextPage}]">next</a>`,
|
2015-10-26 13:18:08 -04:00
|
|
|
directives: [RouterLink]
|
|
|
|
})
|
|
|
|
class NoPrefixSiblingPageCmp {
|
|
|
|
pageNumber: number;
|
|
|
|
nextPage: number;
|
|
|
|
constructor(params: RouteParams) {
|
|
|
|
this.pageNumber = NumberWrapper.parseInt(params.get('number'), 10);
|
|
|
|
this.nextPage = this.pageNumber + 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
refactor(router): improve recognition and generation pipeline
This is a big change. @matsko also deserves much of the credit for the implementation.
Previously, `ComponentInstruction`s held all the state for async components.
Now, we introduce several subclasses for `Instruction` to describe each type of navigation.
BREAKING CHANGE:
Redirects now use the Link DSL syntax. Before:
```
@RouteConfig([
{ path: '/foo', redirectTo: '/bar' },
{ path: '/bar', component: BarCmp }
])
```
After:
```
@RouteConfig([
{ path: '/foo', redirectTo: ['Bar'] },
{ path: '/bar', component: BarCmp, name: 'Bar' }
])
```
BREAKING CHANGE:
This also introduces `useAsDefault` in the RouteConfig, which makes cases like lazy-loading
and encapsulating large routes with sub-routes easier.
Previously, you could use `redirectTo` like this to expand a URL like `/tab` to `/tab/posts`:
@RouteConfig([
{ path: '/tab', redirectTo: '/tab/users' }
{ path: '/tab', component: TabsCmp, name: 'Tab' }
])
AppCmp { ... }
Now the recommended way to handle this is case is to use `useAsDefault` like so:
```
@RouteConfig([
{ path: '/tab', component: TabsCmp, name: 'Tab' }
])
AppCmp { ... }
@RouteConfig([
{ path: '/posts', component: PostsCmp, useAsDefault: true, name: 'Posts' },
{ path: '/users', component: UsersCmp, name: 'Users' }
])
TabsCmp { ... }
```
In the above example, you can write just `['/Tab']` and the route `Users` is automatically selected as a child route.
Closes #4728
Closes #4228
Closes #4170
Closes #4490
Closes #4694
Closes #5200
Closes #5475
2015-11-23 21:07:37 -05:00
|
|
|
@Component({selector: 'hello-cmp', template: 'hello'})
|
2015-08-31 00:27:11 -04:00
|
|
|
class HelloCmp {
|
|
|
|
}
|
|
|
|
|
refactor(router): improve recognition and generation pipeline
This is a big change. @matsko also deserves much of the credit for the implementation.
Previously, `ComponentInstruction`s held all the state for async components.
Now, we introduce several subclasses for `Instruction` to describe each type of navigation.
BREAKING CHANGE:
Redirects now use the Link DSL syntax. Before:
```
@RouteConfig([
{ path: '/foo', redirectTo: '/bar' },
{ path: '/bar', component: BarCmp }
])
```
After:
```
@RouteConfig([
{ path: '/foo', redirectTo: ['Bar'] },
{ path: '/bar', component: BarCmp, name: 'Bar' }
])
```
BREAKING CHANGE:
This also introduces `useAsDefault` in the RouteConfig, which makes cases like lazy-loading
and encapsulating large routes with sub-routes easier.
Previously, you could use `redirectTo` like this to expand a URL like `/tab` to `/tab/posts`:
@RouteConfig([
{ path: '/tab', redirectTo: '/tab/users' }
{ path: '/tab', component: TabsCmp, name: 'Tab' }
])
AppCmp { ... }
Now the recommended way to handle this is case is to use `useAsDefault` like so:
```
@RouteConfig([
{ path: '/tab', component: TabsCmp, name: 'Tab' }
])
AppCmp { ... }
@RouteConfig([
{ path: '/posts', component: PostsCmp, useAsDefault: true, name: 'Posts' },
{ path: '/users', component: UsersCmp, name: 'Users' }
])
TabsCmp { ... }
```
In the above example, you can write just `['/Tab']` and the route `Users` is automatically selected as a child route.
Closes #4728
Closes #4228
Closes #4170
Closes #4490
Closes #4694
Closes #5200
Closes #5475
2015-11-23 21:07:37 -05:00
|
|
|
@Component({selector: 'hello2-cmp', template: 'hello2'})
|
2015-08-31 00:27:11 -04:00
|
|
|
class Hello2Cmp {
|
|
|
|
}
|
|
|
|
|
2015-09-11 17:50:41 -04:00
|
|
|
function parentCmpLoader() {
|
|
|
|
return PromiseWrapper.resolve(ParentCmp);
|
|
|
|
}
|
|
|
|
|
2015-11-23 21:07:24 -05:00
|
|
|
@Component({
|
|
|
|
selector: 'parent-cmp',
|
2015-11-23 19:02:19 -05:00
|
|
|
template: `{ <a [routerLink]="['./Grandchild']" class="grandchild-link">Grandchild</a>
|
|
|
|
<a [routerLink]="['./BetterGrandchild']" class="better-grandchild-link">Better Grandchild</a>
|
2015-08-31 00:27:11 -04:00
|
|
|
<router-outlet></router-outlet> }`,
|
|
|
|
directives: ROUTER_DIRECTIVES
|
|
|
|
})
|
|
|
|
@RouteConfig([
|
2015-10-25 05:30:27 -04:00
|
|
|
new Route({path: '/grandchild', component: HelloCmp, name: 'Grandchild'}),
|
|
|
|
new Route({path: '/better-grandchild', component: Hello2Cmp, name: 'BetterGrandchild'})
|
2015-08-31 00:27:11 -04:00
|
|
|
])
|
|
|
|
class ParentCmp {
|
|
|
|
}
|
|
|
|
|
2015-11-23 21:07:24 -05:00
|
|
|
@Component({
|
|
|
|
selector: 'book-cmp',
|
2015-11-23 19:02:19 -05:00
|
|
|
template: `<a href="hello" [routerLink]="[\'./Page\', {number: 100}]">{{title}}</a> |
|
2015-08-24 14:24:53 -04:00
|
|
|
<router-outlet></router-outlet>`,
|
2015-08-31 00:27:11 -04:00
|
|
|
directives: ROUTER_DIRECTIVES
|
2015-08-24 14:24:53 -04:00
|
|
|
})
|
2015-10-25 05:30:27 -04:00
|
|
|
@RouteConfig([new Route({path: '/page/:number', component: SiblingPageCmp, name: 'Page'})])
|
2015-08-24 14:24:53 -04:00
|
|
|
class BookCmp {
|
|
|
|
title: string;
|
|
|
|
constructor(params: RouteParams) { this.title = params.get('title'); }
|
|
|
|
}
|
2015-10-26 13:18:08 -04:00
|
|
|
|
2015-11-23 21:07:24 -05:00
|
|
|
@Component({
|
|
|
|
selector: 'book-cmp',
|
2015-11-23 19:02:19 -05:00
|
|
|
template: `<a href="hello" [routerLink]="[\'Page\', {number: 100}]">{{title}}</a> |
|
2015-10-26 13:18:08 -04:00
|
|
|
<router-outlet></router-outlet>`,
|
|
|
|
directives: ROUTER_DIRECTIVES
|
|
|
|
})
|
2015-10-25 05:30:27 -04:00
|
|
|
@RouteConfig([new Route({path: '/page/:number', component: SiblingPageCmp, name: 'Page'})])
|
2015-10-26 13:18:08 -04:00
|
|
|
class NoPrefixBookCmp {
|
|
|
|
title: string;
|
|
|
|
constructor(params: RouteParams) { this.title = params.get('title'); }
|
|
|
|
}
|
|
|
|
|
2015-11-23 21:07:24 -05:00
|
|
|
@Component({
|
|
|
|
selector: 'book-cmp',
|
2015-11-23 19:02:19 -05:00
|
|
|
template: `<a href="hello" [routerLink]="[\'Book\', {number: 100}]">{{title}}</a> |
|
2015-10-26 13:18:08 -04:00
|
|
|
<router-outlet></router-outlet>`,
|
|
|
|
directives: ROUTER_DIRECTIVES
|
|
|
|
})
|
2015-10-25 05:30:27 -04:00
|
|
|
@RouteConfig([new Route({path: '/page/:number', component: SiblingPageCmp, name: 'Book'})])
|
2015-10-26 13:18:08 -04:00
|
|
|
class AmbiguousBookCmp {
|
|
|
|
title: string;
|
|
|
|
constructor(params: RouteParams) { this.title = params.get('title'); }
|
|
|
|
}
|
2015-11-23 21:07:10 -05:00
|
|
|
|
2015-11-23 21:07:24 -05:00
|
|
|
@Component({
|
|
|
|
selector: 'aux-cmp',
|
2015-11-23 21:07:10 -05:00
|
|
|
template:
|
2015-11-23 19:02:19 -05:00
|
|
|
`<a [routerLink]="[\'./Hello\', [ \'Aside\' ] ]">aside</a> |
|
2015-11-23 21:07:10 -05:00
|
|
|
<router-outlet></router-outlet> | aside <router-outlet name="aside"></router-outlet>`,
|
|
|
|
directives: ROUTER_DIRECTIVES
|
|
|
|
})
|
|
|
|
@RouteConfig([
|
|
|
|
new Route({path: '/', component: HelloCmp, name: 'Hello'}),
|
|
|
|
new AuxRoute({path: '/aside', component: Hello2Cmp, name: 'Aside'})
|
|
|
|
])
|
|
|
|
class AuxLinkCmp {
|
|
|
|
}
|