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
		
	
			
		
			
				
	
	
		
			149 lines
		
	
	
		
			4.3 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			149 lines
		
	
	
		
			4.3 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
import {
 | 
						|
  AsyncTestCompleter,
 | 
						|
  beforeEach,
 | 
						|
  ddescribe,
 | 
						|
  xdescribe,
 | 
						|
  describe,
 | 
						|
  dispatchEvent,
 | 
						|
  expect,
 | 
						|
  iit,
 | 
						|
  inject,
 | 
						|
  beforeEachProviders,
 | 
						|
  it,
 | 
						|
  xit,
 | 
						|
  TestComponentBuilder
 | 
						|
} from 'angular2/testing_internal';
 | 
						|
 | 
						|
import {SpyRouter, SpyLocation} from './spies';
 | 
						|
 | 
						|
import {provide, Component, View} from 'angular2/core';
 | 
						|
import {By} from 'angular2/platform/common_dom';
 | 
						|
 | 
						|
import {
 | 
						|
  Location,
 | 
						|
  Router,
 | 
						|
  RouteRegistry,
 | 
						|
  RouterLink,
 | 
						|
  RouterOutlet,
 | 
						|
  Route,
 | 
						|
  RouteParams,
 | 
						|
  ComponentInstruction
 | 
						|
} from 'angular2/router';
 | 
						|
 | 
						|
import {DOM} from 'angular2/src/platform/dom/dom_adapter';
 | 
						|
import {ResolvedInstruction} from 'angular2/src/router/instruction';
 | 
						|
 | 
						|
let dummyInstruction =
 | 
						|
    new ResolvedInstruction(new ComponentInstruction('detail', [], null, null, true, 0), null, {});
 | 
						|
 | 
						|
export function main() {
 | 
						|
  describe('router-link directive', function() {
 | 
						|
    var tcb: TestComponentBuilder;
 | 
						|
 | 
						|
    beforeEachProviders(() => [
 | 
						|
      provide(Location, {useValue: makeDummyLocation()}),
 | 
						|
      provide(Router, {useValue: makeDummyRouter()})
 | 
						|
    ]);
 | 
						|
 | 
						|
    beforeEach(inject([TestComponentBuilder], (tcBuilder) => { tcb = tcBuilder; }));
 | 
						|
 | 
						|
    it('should update a[href] attribute', inject([AsyncTestCompleter], (async) => {
 | 
						|
 | 
						|
         tcb.createAsync(TestComponent)
 | 
						|
             .then((testComponent) => {
 | 
						|
               testComponent.detectChanges();
 | 
						|
               let anchorElement =
 | 
						|
                   testComponent.debugElement.query(By.css('a.detail-view')).nativeElement;
 | 
						|
               expect(DOM.getAttribute(anchorElement, 'href')).toEqual('detail');
 | 
						|
               async.done();
 | 
						|
             });
 | 
						|
       }));
 | 
						|
 | 
						|
 | 
						|
    it('should call router.navigate when a link is clicked',
 | 
						|
       inject([AsyncTestCompleter, Router], (async, router) => {
 | 
						|
 | 
						|
         tcb.createAsync(TestComponent)
 | 
						|
             .then((testComponent) => {
 | 
						|
               testComponent.detectChanges();
 | 
						|
               // TODO: shouldn't this be just 'click' rather than '^click'?
 | 
						|
               testComponent.debugElement.query(By.css('a.detail-view'))
 | 
						|
                   .triggerEventHandler('click', null);
 | 
						|
               expect(router.spy('navigateByInstruction')).toHaveBeenCalledWith(dummyInstruction);
 | 
						|
               async.done();
 | 
						|
             });
 | 
						|
       }));
 | 
						|
 | 
						|
    it('should call router.navigate when a link is clicked if target is _self',
 | 
						|
       inject([AsyncTestCompleter, Router], (async, router) => {
 | 
						|
 | 
						|
         tcb.createAsync(TestComponent)
 | 
						|
             .then((testComponent) => {
 | 
						|
               testComponent.detectChanges();
 | 
						|
               testComponent.debugElement.query(By.css('a.detail-view-self'))
 | 
						|
                   .triggerEventHandler('click', null);
 | 
						|
               expect(router.spy('navigateByInstruction')).toHaveBeenCalledWith(dummyInstruction);
 | 
						|
               async.done();
 | 
						|
             });
 | 
						|
       }));
 | 
						|
 | 
						|
    it('should NOT call router.navigate when a link is clicked if target is set to other than _self',
 | 
						|
       inject([AsyncTestCompleter, Router], (async, router) => {
 | 
						|
 | 
						|
         tcb.createAsync(TestComponent)
 | 
						|
             .then((testComponent) => {
 | 
						|
               testComponent.detectChanges();
 | 
						|
               testComponent.debugElement.query(By.css('a.detail-view-blank'))
 | 
						|
                   .triggerEventHandler('click', null);
 | 
						|
               expect(router.spy('navigateByInstruction')).not.toHaveBeenCalled();
 | 
						|
               async.done();
 | 
						|
             });
 | 
						|
       }));
 | 
						|
  });
 | 
						|
}
 | 
						|
 | 
						|
@Component({selector: 'user-cmp'})
 | 
						|
@View({template: "hello {{user}}"})
 | 
						|
class UserCmp {
 | 
						|
  user: string;
 | 
						|
  constructor(params: RouteParams) { this.user = params.get('name'); }
 | 
						|
}
 | 
						|
 | 
						|
@Component({selector: 'test-component'})
 | 
						|
@View({
 | 
						|
  template: `
 | 
						|
    <div>
 | 
						|
      <a [router-link]="['/Detail']"
 | 
						|
         class="detail-view">
 | 
						|
           detail view
 | 
						|
      </a>
 | 
						|
      <a [router-link]="['/Detail']"
 | 
						|
         class="detail-view-self"
 | 
						|
         target="_self">
 | 
						|
           detail view with _self target
 | 
						|
      </a>
 | 
						|
      <a [router-link]="['/Detail']"
 | 
						|
         class="detail-view-blank"
 | 
						|
         target="_blank">
 | 
						|
           detail view with _blank target
 | 
						|
      </a>
 | 
						|
    </div>`,
 | 
						|
  directives: [RouterLink]
 | 
						|
})
 | 
						|
class TestComponent {
 | 
						|
}
 | 
						|
 | 
						|
function makeDummyLocation() {
 | 
						|
  var dl = new SpyLocation();
 | 
						|
  dl.spy('prepareExternalUrl').andCallFake((url) => url);
 | 
						|
  return dl;
 | 
						|
}
 | 
						|
 | 
						|
function makeDummyRouter() {
 | 
						|
  var dr = new SpyRouter();
 | 
						|
  dr.spy('generate').andCallFake((routeParams) => dummyInstruction);
 | 
						|
  dr.spy('isRouteActive').andCallFake((_) => false);
 | 
						|
  dr.spy('navigateInstruction');
 | 
						|
  return dr;
 | 
						|
}
 |