Make sure the same path is not added multiple times to the history. It is replacing the state, instead of skipping it completely, because the current path in the browser might not be normalized, while the given one is normalized. Closes #7829 Closes #7897
313 lines
11 KiB
TypeScript
313 lines
11 KiB
TypeScript
import {
|
|
beforeEach,
|
|
ddescribe,
|
|
xdescribe,
|
|
describe,
|
|
expect,
|
|
iit,
|
|
inject,
|
|
beforeEachProviders,
|
|
it,
|
|
xit
|
|
} from '@angular/core/testing/testing_internal';
|
|
import {TestComponentBuilder, ComponentFixture} from '@angular/compiler/testing';
|
|
import {AsyncTestCompleter} from '@angular/core/testing/testing_internal';
|
|
|
|
import {provide, Component, Injector, Inject} from '@angular/core';
|
|
import {Location} from '@angular/common';
|
|
import {PromiseWrapper, TimerWrapper} from '../../src/facade/async';
|
|
|
|
import {Router, RouterOutlet, RouterLink, RouteParams, RouteData} from '@angular/router-deprecated';
|
|
import {
|
|
RouteConfig,
|
|
Route,
|
|
AuxRoute,
|
|
AsyncRoute,
|
|
Redirect
|
|
} from '../../src/route_config/route_config_decorator';
|
|
|
|
import {TEST_ROUTER_PROVIDERS, RootCmp, compile} from './util';
|
|
|
|
var cmpInstanceCount;
|
|
var childCmpInstanceCount;
|
|
|
|
export function main() {
|
|
describe('navigation', () => {
|
|
|
|
var tcb: TestComponentBuilder;
|
|
var fixture: ComponentFixture<any>;
|
|
var rtr;
|
|
|
|
beforeEachProviders(() => TEST_ROUTER_PROVIDERS);
|
|
|
|
beforeEach(inject([TestComponentBuilder, Router], (tcBuilder, router) => {
|
|
tcb = tcBuilder;
|
|
rtr = router;
|
|
childCmpInstanceCount = 0;
|
|
cmpInstanceCount = 0;
|
|
}));
|
|
|
|
it('should work in a simple case', inject([AsyncTestCompleter], (async) => {
|
|
compile(tcb)
|
|
.then((rtc) => {fixture = rtc})
|
|
.then((_) => rtr.config([new Route({path: '/test', component: HelloCmp})]))
|
|
.then((_) => rtr.navigateByUrl('/test'))
|
|
.then((_) => {
|
|
fixture.detectChanges();
|
|
expect(fixture.debugElement.nativeElement).toHaveText('hello');
|
|
async.done();
|
|
});
|
|
}));
|
|
|
|
|
|
it('should navigate between components with different parameters',
|
|
inject([AsyncTestCompleter], (async) => {
|
|
compile(tcb)
|
|
.then((rtc) => {fixture = rtc})
|
|
.then((_) => rtr.config([new Route({path: '/user/:name', component: UserCmp})]))
|
|
.then((_) => rtr.navigateByUrl('/user/brian'))
|
|
.then((_) => {
|
|
fixture.detectChanges();
|
|
expect(fixture.debugElement.nativeElement).toHaveText('hello brian');
|
|
})
|
|
.then((_) => rtr.navigateByUrl('/user/igor'))
|
|
.then((_) => {
|
|
fixture.detectChanges();
|
|
expect(fixture.debugElement.nativeElement).toHaveText('hello igor');
|
|
async.done();
|
|
});
|
|
}));
|
|
|
|
it('should navigate to child routes', inject([AsyncTestCompleter], (async) => {
|
|
compile(tcb, 'outer { <router-outlet></router-outlet> }')
|
|
.then((rtc) => {fixture = rtc})
|
|
.then((_) => rtr.config([new Route({path: '/a/...', component: ParentCmp})]))
|
|
.then((_) => rtr.navigateByUrl('/a/b'))
|
|
.then((_) => {
|
|
fixture.detectChanges();
|
|
expect(fixture.debugElement.nativeElement).toHaveText('outer { inner { hello } }');
|
|
async.done();
|
|
});
|
|
}));
|
|
|
|
it('should navigate to child routes that capture an empty path',
|
|
inject([AsyncTestCompleter], (async) => {
|
|
|
|
compile(tcb, 'outer { <router-outlet></router-outlet> }')
|
|
.then((rtc) => {fixture = rtc})
|
|
.then((_) => rtr.config([new Route({path: '/a/...', component: ParentCmp})]))
|
|
.then((_) => rtr.navigateByUrl('/a'))
|
|
.then((_) => {
|
|
fixture.detectChanges();
|
|
expect(fixture.debugElement.nativeElement).toHaveText('outer { inner { hello } }');
|
|
async.done();
|
|
});
|
|
}));
|
|
|
|
it('should navigate to child routes when the root component has an empty path',
|
|
inject([AsyncTestCompleter, Location], (async, location) => {
|
|
compile(tcb, 'outer { <router-outlet></router-outlet> }')
|
|
.then((rtc) => {fixture = rtc})
|
|
.then((_) => rtr.config([new Route({path: '/...', component: ParentCmp})]))
|
|
.then((_) => rtr.navigateByUrl('/b'))
|
|
.then((_) => {
|
|
fixture.detectChanges();
|
|
expect(fixture.debugElement.nativeElement).toHaveText('outer { inner { hello } }');
|
|
expect(location.urlChanges).toEqual(['/b']);
|
|
async.done();
|
|
});
|
|
}));
|
|
|
|
it('should navigate to child routes of async routes', inject([AsyncTestCompleter], (async) => {
|
|
compile(tcb, 'outer { <router-outlet></router-outlet> }')
|
|
.then((rtc) => {fixture = rtc})
|
|
.then((_) => rtr.config([new AsyncRoute({path: '/a/...', loader: parentLoader})]))
|
|
.then((_) => rtr.navigateByUrl('/a/b'))
|
|
.then((_) => {
|
|
fixture.detectChanges();
|
|
expect(fixture.debugElement.nativeElement).toHaveText('outer { inner { hello } }');
|
|
async.done();
|
|
});
|
|
}));
|
|
|
|
|
|
it('should replace state when normalized paths are equal',
|
|
inject([AsyncTestCompleter, Location], (async, location) => {
|
|
compile(tcb)
|
|
.then((rtc) => {fixture = rtc})
|
|
.then((_) => location.setInitialPath("/test/"))
|
|
.then((_) => rtr.config([new Route({path: '/test', component: HelloCmp})]))
|
|
.then((_) => rtr.navigateByUrl('/test'))
|
|
.then((_) => {
|
|
fixture.detectChanges();
|
|
expect(fixture.debugElement.nativeElement).toHaveText('hello');
|
|
expect(location.urlChanges).toEqual(['replace: /test']);
|
|
async.done();
|
|
});
|
|
}));
|
|
|
|
it('should reuse common parent components', inject([AsyncTestCompleter], (async) => {
|
|
compile(tcb)
|
|
.then((rtc) => {fixture = rtc})
|
|
.then((_) => rtr.config([new Route({path: '/team/:id/...', component: TeamCmp})]))
|
|
.then((_) => rtr.navigateByUrl('/team/angular/user/rado'))
|
|
.then((_) => {
|
|
fixture.detectChanges();
|
|
expect(cmpInstanceCount).toBe(1);
|
|
expect(fixture.debugElement.nativeElement).toHaveText('team angular { hello rado }');
|
|
})
|
|
.then((_) => rtr.navigateByUrl('/team/angular/user/victor'))
|
|
.then((_) => {
|
|
fixture.detectChanges();
|
|
expect(cmpInstanceCount).toBe(1);
|
|
expect(fixture.debugElement.nativeElement)
|
|
.toHaveText('team angular { hello victor }');
|
|
async.done();
|
|
});
|
|
}));
|
|
|
|
it('should not reuse children when parent components change',
|
|
inject([AsyncTestCompleter], (async) => {
|
|
compile(tcb)
|
|
.then((rtc) => {fixture = rtc})
|
|
.then((_) => rtr.config([new Route({path: '/team/:id/...', component: TeamCmp})]))
|
|
.then((_) => rtr.navigateByUrl('/team/angular/user/rado'))
|
|
.then((_) => {
|
|
fixture.detectChanges();
|
|
expect(cmpInstanceCount).toBe(1);
|
|
expect(childCmpInstanceCount).toBe(1);
|
|
expect(fixture.debugElement.nativeElement).toHaveText('team angular { hello rado }');
|
|
})
|
|
.then((_) => rtr.navigateByUrl('/team/dart/user/rado'))
|
|
.then((_) => {
|
|
fixture.detectChanges();
|
|
expect(cmpInstanceCount).toBe(2);
|
|
expect(childCmpInstanceCount).toBe(2);
|
|
expect(fixture.debugElement.nativeElement).toHaveText('team dart { hello rado }');
|
|
async.done();
|
|
});
|
|
}));
|
|
|
|
it('should inject route data into component', inject([AsyncTestCompleter], (async) => {
|
|
compile(tcb)
|
|
.then((rtc) => {fixture = rtc})
|
|
.then((_) => rtr.config([
|
|
new Route({path: '/route-data', component: RouteDataCmp, data: {isAdmin: true}})
|
|
]))
|
|
.then((_) => rtr.navigateByUrl('/route-data'))
|
|
.then((_) => {
|
|
fixture.detectChanges();
|
|
expect(fixture.debugElement.nativeElement).toHaveText('true');
|
|
async.done();
|
|
});
|
|
}));
|
|
|
|
it('should inject route data into component with AsyncRoute',
|
|
inject([AsyncTestCompleter], (async) => {
|
|
compile(tcb)
|
|
.then((rtc) => {fixture = rtc})
|
|
.then((_) => rtr.config([
|
|
new AsyncRoute(
|
|
{path: '/route-data', loader: asyncRouteDataCmp, data: {isAdmin: true}})
|
|
]))
|
|
.then((_) => rtr.navigateByUrl('/route-data'))
|
|
.then((_) => {
|
|
fixture.detectChanges();
|
|
expect(fixture.debugElement.nativeElement).toHaveText('true');
|
|
async.done();
|
|
});
|
|
}));
|
|
|
|
it('should inject empty object if the route has no data property',
|
|
inject([AsyncTestCompleter], (async) => {
|
|
compile(tcb)
|
|
.then((rtc) => {fixture = rtc})
|
|
.then((_) => rtr.config(
|
|
[new Route({path: '/route-data-default', component: RouteDataCmp})]))
|
|
.then((_) => rtr.navigateByUrl('/route-data-default'))
|
|
.then((_) => {
|
|
fixture.detectChanges();
|
|
expect(fixture.debugElement.nativeElement).toHaveText('');
|
|
async.done();
|
|
});
|
|
}));
|
|
|
|
it('should fire an event for each activated component',
|
|
inject([AsyncTestCompleter], (async) => {
|
|
compile(tcb, '<router-outlet (activate)="activatedCmp = $event"></router-outlet>')
|
|
.then((rtc) => {fixture = rtc})
|
|
.then((_) => rtr.config([new Route({path: '/test', component: HelloCmp})]))
|
|
.then((_) => rtr.navigateByUrl('/test'))
|
|
.then((_) => {
|
|
// Note: need a timeout so that all promises are flushed
|
|
var completer = PromiseWrapper.completer();
|
|
TimerWrapper.setTimeout(() => { completer.resolve(null); }, 0);
|
|
return completer.promise;
|
|
})
|
|
.then((_) => {
|
|
expect(fixture.componentInstance.activatedCmp).toBeAnInstanceOf(HelloCmp);
|
|
async.done();
|
|
});
|
|
}));
|
|
});
|
|
}
|
|
|
|
|
|
@Component({selector: 'hello-cmp', template: `{{greeting}}`})
|
|
class HelloCmp {
|
|
greeting: string;
|
|
constructor() { this.greeting = 'hello'; }
|
|
}
|
|
|
|
|
|
function asyncRouteDataCmp() {
|
|
return PromiseWrapper.resolve(RouteDataCmp);
|
|
}
|
|
|
|
@Component({selector: 'data-cmp', template: `{{myData}}`})
|
|
class RouteDataCmp {
|
|
myData: boolean;
|
|
constructor(data: RouteData) { this.myData = data.get('isAdmin'); }
|
|
}
|
|
|
|
@Component({selector: 'user-cmp', template: `hello {{user}}`})
|
|
class UserCmp {
|
|
user: string;
|
|
constructor(params: RouteParams) {
|
|
childCmpInstanceCount += 1;
|
|
this.user = params.get('name');
|
|
}
|
|
}
|
|
|
|
|
|
function parentLoader() {
|
|
return PromiseWrapper.resolve(ParentCmp);
|
|
}
|
|
|
|
@Component({
|
|
selector: 'parent-cmp',
|
|
template: `inner { <router-outlet></router-outlet> }`,
|
|
directives: [RouterOutlet],
|
|
})
|
|
@RouteConfig([
|
|
new Route({path: '/b', component: HelloCmp}),
|
|
new Route({path: '/', component: HelloCmp}),
|
|
])
|
|
class ParentCmp {
|
|
}
|
|
|
|
|
|
@Component({
|
|
selector: 'team-cmp',
|
|
template: `team {{id}} { <router-outlet></router-outlet> }`,
|
|
directives: [RouterOutlet],
|
|
})
|
|
@RouteConfig([new Route({path: '/user/:name', component: UserCmp})])
|
|
class TeamCmp {
|
|
id: string;
|
|
constructor(params: RouteParams) {
|
|
this.id = params.get('id');
|
|
cmpInstanceCount += 1;
|
|
}
|
|
}
|