fix(ivy): should support components without selector (#27169)
PR Close #27169
This commit is contained in:
parent
d767e0b2c0
commit
c2f30542e7
|
@ -6,7 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {ConstantPool, CssSelector, Expression, R3ComponentMetadata, R3DirectiveMetadata, SelectorMatcher, Statement, TmplAstNode, WrappedNodeExpr, compileComponentFromMetadata, makeBindingParser, parseTemplate} from '@angular/compiler';
|
import {ConstantPool, CssSelector, DomElementSchemaRegistry, ElementSchemaRegistry, Expression, R3ComponentMetadata, R3DirectiveMetadata, SelectorMatcher, Statement, TmplAstNode, WrappedNodeExpr, compileComponentFromMetadata, makeBindingParser, parseTemplate} from '@angular/compiler';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
|
@ -41,6 +41,7 @@ export class ComponentDecoratorHandler implements
|
||||||
private resourceLoader: ResourceLoader, private rootDirs: string[]) {}
|
private resourceLoader: ResourceLoader, private rootDirs: string[]) {}
|
||||||
|
|
||||||
private literalCache = new Map<Decorator, ts.ObjectLiteralExpression>();
|
private literalCache = new Map<Decorator, ts.ObjectLiteralExpression>();
|
||||||
|
private elementSchemaRegistry = new DomElementSchemaRegistry();
|
||||||
|
|
||||||
|
|
||||||
detect(node: ts.Declaration, decorators: Decorator[]|null): Decorator|undefined {
|
detect(node: ts.Declaration, decorators: Decorator[]|null): Decorator|undefined {
|
||||||
|
@ -74,8 +75,9 @@ export class ComponentDecoratorHandler implements
|
||||||
|
|
||||||
// @Component inherits @Directive, so begin by extracting the @Directive metadata and building
|
// @Component inherits @Directive, so begin by extracting the @Directive metadata and building
|
||||||
// on it.
|
// on it.
|
||||||
const directiveResult =
|
const directiveResult = extractDirectiveMetadata(
|
||||||
extractDirectiveMetadata(node, decorator, this.checker, this.reflector, this.isCore);
|
node, decorator, this.checker, this.reflector, this.isCore,
|
||||||
|
this.elementSchemaRegistry.getDefaultComponentElementName());
|
||||||
if (directiveResult === undefined) {
|
if (directiveResult === undefined) {
|
||||||
// `extractDirectiveMetadata` returns undefined when the @Directive has `jit: true`. In this
|
// `extractDirectiveMetadata` returns undefined when the @Directive has `jit: true`. In this
|
||||||
// case, compilation of the decorator is skipped. Returning an empty object signifies
|
// case, compilation of the decorator is skipped. Returning an empty object signifies
|
||||||
|
|
|
@ -93,7 +93,7 @@ export class DirectiveDecoratorHandler implements
|
||||||
*/
|
*/
|
||||||
export function extractDirectiveMetadata(
|
export function extractDirectiveMetadata(
|
||||||
clazz: ts.ClassDeclaration, decorator: Decorator, checker: ts.TypeChecker,
|
clazz: ts.ClassDeclaration, decorator: Decorator, checker: ts.TypeChecker,
|
||||||
reflector: ReflectionHost, isCore: boolean): {
|
reflector: ReflectionHost, isCore: boolean, defaultSelector: string | null = null): {
|
||||||
decorator: Map<string, ts.Expression>,
|
decorator: Map<string, ts.Expression>,
|
||||||
metadata: R3DirectiveMetadata,
|
metadata: R3DirectiveMetadata,
|
||||||
decoratedElements: ClassMember[],
|
decoratedElements: ClassMember[],
|
||||||
|
@ -154,7 +154,7 @@ export function extractDirectiveMetadata(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse the selector.
|
// Parse the selector.
|
||||||
let selector = '';
|
let selector = defaultSelector;
|
||||||
if (directive.has('selector')) {
|
if (directive.has('selector')) {
|
||||||
const expr = directive.get('selector') !;
|
const expr = directive.get('selector') !;
|
||||||
const resolved = staticallyResolve(expr, reflector, checker);
|
const resolved = staticallyResolve(expr, reflector, checker);
|
||||||
|
|
|
@ -561,6 +561,62 @@ describe('compiler compliance', () => {
|
||||||
expectEmit(source, OtherDirectiveDefinition, 'Incorrect OtherDirective.ngDirectiveDef');
|
expectEmit(source, OtherDirectiveDefinition, 'Incorrect OtherDirective.ngDirectiveDef');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should support components without selector', () => {
|
||||||
|
const files = {
|
||||||
|
app: {
|
||||||
|
'spec.ts': `
|
||||||
|
import {Component, Directive, NgModule} from '@angular/core';
|
||||||
|
|
||||||
|
@Directive({})
|
||||||
|
export class EmptyOutletDirective {}
|
||||||
|
|
||||||
|
@Component({template: '<router-outlet></router-outlet>'})
|
||||||
|
export class EmptyOutletComponent {}
|
||||||
|
|
||||||
|
@NgModule({declarations: [EmptyOutletComponent]})
|
||||||
|
export class MyModule{}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// EmptyOutletDirective definition should be:
|
||||||
|
const EmptyOutletDirectiveDefinition = `
|
||||||
|
…
|
||||||
|
EmptyOutletDirective.ngDirectiveDef = $r3$.ɵdefineDirective({
|
||||||
|
type: EmptyOutletDirective,
|
||||||
|
selectors: [],
|
||||||
|
factory: function EmptyOutletDirective_Factory(t) { return new (t || EmptyOutletDirective)(); }
|
||||||
|
});
|
||||||
|
`;
|
||||||
|
|
||||||
|
// EmptyOutletComponent definition should be:
|
||||||
|
const EmptyOutletComponentDefinition = `
|
||||||
|
…
|
||||||
|
EmptyOutletComponent.ngComponentDef = $r3$.ɵdefineComponent({
|
||||||
|
type: EmptyOutletComponent,
|
||||||
|
selectors: [["ng-component"]],
|
||||||
|
factory: function EmptyOutletComponent_Factory(t) { return new (t || EmptyOutletComponent)(); },
|
||||||
|
consts: 1,
|
||||||
|
vars: 0,
|
||||||
|
template: function EmptyOutletComponent_Template(rf, ctx) {
|
||||||
|
if (rf & 1) {
|
||||||
|
$r3$.ɵelement(0, "router-outlet");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
encapsulation: 2
|
||||||
|
});
|
||||||
|
`;
|
||||||
|
|
||||||
|
const result = compile(files, angularFiles);
|
||||||
|
const source = result.source;
|
||||||
|
|
||||||
|
expectEmit(
|
||||||
|
source, EmptyOutletDirectiveDefinition,
|
||||||
|
'Incorrect EmptyOutletDirective.ngDirectiveDefDef');
|
||||||
|
expectEmit(
|
||||||
|
source, EmptyOutletComponentDefinition, 'Incorrect EmptyOutletComponent.ngComponentDef');
|
||||||
|
});
|
||||||
|
|
||||||
it('should not treat ElementRef, ViewContainerRef, or ChangeDetectorRef specially when injecting',
|
it('should not treat ElementRef, ViewContainerRef, or ChangeDetectorRef specially when injecting',
|
||||||
() => {
|
() => {
|
||||||
const files = {
|
const files = {
|
||||||
|
|
|
@ -358,9 +358,8 @@ function parserSelectorToR3Selector(selector: CssSelector): R3CssSelector {
|
||||||
return positive.concat(...negative);
|
return positive.concat(...negative);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseSelectorToR3Selector(selector: string): R3CssSelectorList {
|
export function parseSelectorToR3Selector(selector: string | null): R3CssSelectorList {
|
||||||
const selectors = CssSelector.parse(selector);
|
return selector ? CssSelector.parse(selector).map(parserSelectorToR3Selector) : [];
|
||||||
return selectors.map(parserSelectorToR3Selector);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pasted from render3/interfaces/definition since it cannot be referenced directly
|
// Pasted from render3/interfaces/definition since it cannot be referenced directly
|
||||||
|
|
|
@ -20,9 +20,11 @@ import {R3Reference} from './render3/util';
|
||||||
import {R3DirectiveMetadata, R3QueryMetadata} from './render3/view/api';
|
import {R3DirectiveMetadata, R3QueryMetadata} from './render3/view/api';
|
||||||
import {compileComponentFromMetadata, compileDirectiveFromMetadata, parseHostBindings} from './render3/view/compiler';
|
import {compileComponentFromMetadata, compileDirectiveFromMetadata, parseHostBindings} from './render3/view/compiler';
|
||||||
import {makeBindingParser, parseTemplate} from './render3/view/template';
|
import {makeBindingParser, parseTemplate} from './render3/view/template';
|
||||||
|
import {DomElementSchemaRegistry} from './schema/dom_element_schema_registry';
|
||||||
|
|
||||||
export class CompilerFacadeImpl implements CompilerFacade {
|
export class CompilerFacadeImpl implements CompilerFacade {
|
||||||
R3ResolvedDependencyType = R3ResolvedDependencyType as any;
|
R3ResolvedDependencyType = R3ResolvedDependencyType as any;
|
||||||
|
private elementSchemaRegistry = new DomElementSchemaRegistry();
|
||||||
|
|
||||||
compilePipe(angularCoreEnv: CoreEnvironment, sourceMapUrl: string, facade: R3PipeMetadataFacade):
|
compilePipe(angularCoreEnv: CoreEnvironment, sourceMapUrl: string, facade: R3PipeMetadataFacade):
|
||||||
any {
|
any {
|
||||||
|
@ -118,6 +120,7 @@ export class CompilerFacadeImpl implements CompilerFacade {
|
||||||
{
|
{
|
||||||
...facade as R3ComponentMetadataFacadeNoPropAndWhitespace,
|
...facade as R3ComponentMetadataFacadeNoPropAndWhitespace,
|
||||||
...convertDirectiveFacadeToMetadata(facade),
|
...convertDirectiveFacadeToMetadata(facade),
|
||||||
|
selector: facade.selector || this.elementSchemaRegistry.getDefaultComponentElementName(),
|
||||||
template,
|
template,
|
||||||
viewQueries: facade.viewQueries.map(convertToR3QueryMetadata),
|
viewQueries: facade.viewQueries.map(convertToR3QueryMetadata),
|
||||||
wrapDirectivesAndPipesInClosure: false,
|
wrapDirectivesAndPipesInClosure: false,
|
||||||
|
|
|
@ -46,7 +46,7 @@ function baseDirectiveFields(
|
||||||
definitionMap.set('type', meta.type);
|
definitionMap.set('type', meta.type);
|
||||||
|
|
||||||
// e.g. `selectors: [['', 'someDir', '']]`
|
// e.g. `selectors: [['', 'someDir', '']]`
|
||||||
definitionMap.set('selectors', createDirectiveSelector(meta.selector !));
|
definitionMap.set('selectors', createDirectiveSelector(meta.selector));
|
||||||
|
|
||||||
|
|
||||||
// e.g. `factory: () => new MyApp(directiveInject(ElementRef))`
|
// e.g. `factory: () => new MyApp(directiveInject(ElementRef))`
|
||||||
|
@ -494,7 +494,7 @@ function createQueryDefinition(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Turn a directive selector into an R3-compatible selector for directive def
|
// Turn a directive selector into an R3-compatible selector for directive def
|
||||||
function createDirectiveSelector(selector: string): o.Expression {
|
function createDirectiveSelector(selector: string | null): o.Expression {
|
||||||
return asLiteral(core.parseSelectorToR3Selector(selector));
|
return asLiteral(core.parseSelectorToR3Selector(selector));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -65,7 +65,7 @@ export function isNodeMatchingSelector(tNode: TNode, selector: CssSelector): boo
|
||||||
|
|
||||||
if (mode & SelectorFlags.ELEMENT) {
|
if (mode & SelectorFlags.ELEMENT) {
|
||||||
mode = SelectorFlags.ATTRIBUTE | mode & SelectorFlags.NOT;
|
mode = SelectorFlags.ATTRIBUTE | mode & SelectorFlags.NOT;
|
||||||
if (current !== '' && current !== tNode.tagName) {
|
if (current !== '' && current !== tNode.tagName || current === '' && selector.length === 1) {
|
||||||
if (isPositive(mode)) return false;
|
if (isPositive(mode)) return false;
|
||||||
skipToNextSelector = true;
|
skipToNextSelector = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,6 +47,9 @@ describe('css selector matching', () => {
|
||||||
.toBeFalsy(`Selector 'span' should NOT match <SPAN>'`);
|
.toBeFalsy(`Selector 'span' should NOT match <SPAN>'`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should never match empty string selector', () => {
|
||||||
|
expect(isMatching('span', null, [''])).toBeFalsy(`Selector '' should NOT match <span>`);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('attributes matching', () => {
|
describe('attributes matching', () => {
|
||||||
|
|
|
@ -477,37 +477,36 @@ describe('Integration', () => {
|
||||||
expect(fixture.nativeElement).toHaveText('[simple]');
|
expect(fixture.nativeElement).toHaveText('[simple]');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
fixmeIvy('FW-???: TypeError: Cannot read property \'componentInstance\' of undefined') &&
|
it('should update location when navigating', fakeAsync(() => {
|
||||||
it('should update location when navigating', fakeAsync(() => {
|
@Component({template: `record`})
|
||||||
@Component({template: `record`})
|
class RecordLocationCmp {
|
||||||
class RecordLocationCmp {
|
private storedPath: string;
|
||||||
private storedPath: string;
|
constructor(loc: Location) { this.storedPath = loc.path(); }
|
||||||
constructor(loc: Location) { this.storedPath = loc.path(); }
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@NgModule({declarations: [RecordLocationCmp], entryComponents: [RecordLocationCmp]})
|
@NgModule({declarations: [RecordLocationCmp], entryComponents: [RecordLocationCmp]})
|
||||||
class TestModule {
|
class TestModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
TestBed.configureTestingModule({imports: [TestModule]});
|
TestBed.configureTestingModule({imports: [TestModule]});
|
||||||
|
|
||||||
const router = TestBed.get(Router);
|
const router = TestBed.get(Router);
|
||||||
const location = TestBed.get(Location);
|
const location = TestBed.get(Location);
|
||||||
const fixture = createRoot(router, RootCmp);
|
const fixture = createRoot(router, RootCmp);
|
||||||
|
|
||||||
router.resetConfig([{path: 'record/:id', component: RecordLocationCmp}]);
|
router.resetConfig([{path: 'record/:id', component: RecordLocationCmp}]);
|
||||||
|
|
||||||
router.navigateByUrl('/record/22');
|
router.navigateByUrl('/record/22');
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
|
|
||||||
const c = fixture.debugElement.children[1].componentInstance;
|
const c = fixture.debugElement.children[1].componentInstance;
|
||||||
expect(location.path()).toEqual('/record/22');
|
expect(location.path()).toEqual('/record/22');
|
||||||
expect(c.storedPath).toEqual('/record/22');
|
expect(c.storedPath).toEqual('/record/22');
|
||||||
|
|
||||||
router.navigateByUrl('/record/33');
|
router.navigateByUrl('/record/33');
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
expect(location.path()).toEqual('/record/33');
|
expect(location.path()).toEqual('/record/33');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
fixmeIvy('FW-???: Error: ExpressionChangedAfterItHasBeenCheckedError') &&
|
fixmeIvy('FW-???: Error: ExpressionChangedAfterItHasBeenCheckedError') &&
|
||||||
it('should skip location update when using NavigationExtras.skipLocationChange with navigateByUrl',
|
it('should skip location update when using NavigationExtras.skipLocationChange with navigateByUrl',
|
||||||
|
@ -673,27 +672,26 @@ describe('Integration', () => {
|
||||||
]);
|
]);
|
||||||
})));
|
})));
|
||||||
|
|
||||||
fixmeIvy('FW-???: TypeError: Cannot read property \'componentInstance\' of undefined') &&
|
it('should update the location when the matched route does not change',
|
||||||
it('should update the location when the matched route does not change',
|
fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
|
||||||
fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
|
const fixture = createRoot(router, RootCmp);
|
||||||
const fixture = createRoot(router, RootCmp);
|
|
||||||
|
|
||||||
router.resetConfig([{path: '**', component: CollectParamsCmp}]);
|
router.resetConfig([{path: '**', component: CollectParamsCmp}]);
|
||||||
|
|
||||||
router.navigateByUrl('/one/two');
|
router.navigateByUrl('/one/two');
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
const cmp = fixture.debugElement.children[1].componentInstance;
|
const cmp = fixture.debugElement.children[1].componentInstance;
|
||||||
expect(location.path()).toEqual('/one/two');
|
expect(location.path()).toEqual('/one/two');
|
||||||
expect(fixture.nativeElement).toHaveText('collect-params');
|
expect(fixture.nativeElement).toHaveText('collect-params');
|
||||||
|
|
||||||
expect(cmp.recordedUrls()).toEqual(['one/two']);
|
expect(cmp.recordedUrls()).toEqual(['one/two']);
|
||||||
|
|
||||||
router.navigateByUrl('/three/four');
|
router.navigateByUrl('/three/four');
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
expect(location.path()).toEqual('/three/four');
|
expect(location.path()).toEqual('/three/four');
|
||||||
expect(fixture.nativeElement).toHaveText('collect-params');
|
expect(fixture.nativeElement).toHaveText('collect-params');
|
||||||
expect(cmp.recordedUrls()).toEqual(['one/two', 'three/four']);
|
expect(cmp.recordedUrls()).toEqual(['one/two', 'three/four']);
|
||||||
})));
|
})));
|
||||||
|
|
||||||
describe('should reset location if a navigation by location is successful', () => {
|
describe('should reset location if a navigation by location is successful', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
@ -835,18 +833,17 @@ describe('Integration', () => {
|
||||||
expect(fixture.nativeElement).toHaveText('query: 2 fragment: fragment2');
|
expect(fixture.nativeElement).toHaveText('query: 2 fragment: fragment2');
|
||||||
})));
|
})));
|
||||||
|
|
||||||
fixmeIvy('FW-???: TypeError: Cannot read property \'componentInstance\' of undefined') &&
|
it('should ignore null and undefined query params',
|
||||||
it('should ignore null and undefined query params',
|
fakeAsync(inject([Router], (router: Router) => {
|
||||||
fakeAsync(inject([Router], (router: Router) => {
|
const fixture = createRoot(router, RootCmp);
|
||||||
const fixture = createRoot(router, RootCmp);
|
|
||||||
|
|
||||||
router.resetConfig([{path: 'query', component: EmptyQueryParamsCmp}]);
|
router.resetConfig([{path: 'query', component: EmptyQueryParamsCmp}]);
|
||||||
|
|
||||||
router.navigate(['query'], {queryParams: {name: 1, age: null, page: undefined}});
|
router.navigate(['query'], {queryParams: {name: 1, age: null, page: undefined}});
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
const cmp = fixture.debugElement.children[1].componentInstance;
|
const cmp = fixture.debugElement.children[1].componentInstance;
|
||||||
expect(cmp.recordedParams).toEqual([{name: '1'}]);
|
expect(cmp.recordedParams).toEqual([{name: '1'}]);
|
||||||
})));
|
})));
|
||||||
|
|
||||||
it('should throw an error when one of the commands is null/undefined',
|
it('should throw an error when one of the commands is null/undefined',
|
||||||
fakeAsync(inject([Router], (router: Router) => {
|
fakeAsync(inject([Router], (router: Router) => {
|
||||||
|
@ -859,7 +856,7 @@ describe('Integration', () => {
|
||||||
])).toThrowError(`The requested path contains undefined segment at index 0`);
|
])).toThrowError(`The requested path contains undefined segment at index 0`);
|
||||||
})));
|
})));
|
||||||
|
|
||||||
fixmeIvy('FW-???: TypeError: Cannot read property \'componentInstance\' of undefined') &&
|
fixmeIvy('FW-???: Error: ExpressionChangedAfterItHasBeenCheckedError') &&
|
||||||
it('should push params only when they change',
|
it('should push params only when they change',
|
||||||
fakeAsync(inject([Router], (router: Router) => {
|
fakeAsync(inject([Router], (router: Router) => {
|
||||||
const fixture = createRoot(router, RootCmp);
|
const fixture = createRoot(router, RootCmp);
|
||||||
|
@ -909,7 +906,7 @@ describe('Integration', () => {
|
||||||
expect(fixture.nativeElement).toHaveText('simple');
|
expect(fixture.nativeElement).toHaveText('simple');
|
||||||
})));
|
})));
|
||||||
|
|
||||||
fixmeIvy('FW-???: TypeError: Cannot read property \'componentInstance\' of undefined') &&
|
fixmeIvy('FW-???: Error: ExpressionChangedAfterItHasBeenCheckedError') &&
|
||||||
it('should cancel in-flight navigations', fakeAsync(inject([Router], (router: Router) => {
|
it('should cancel in-flight navigations', fakeAsync(inject([Router], (router: Router) => {
|
||||||
const fixture = createRoot(router, RootCmp);
|
const fixture = createRoot(router, RootCmp);
|
||||||
|
|
||||||
|
@ -1308,22 +1305,21 @@ describe('Integration', () => {
|
||||||
expect(cmp.deactivations[0] instanceof BlankCmp).toBe(true);
|
expect(cmp.deactivations[0] instanceof BlankCmp).toBe(true);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
fixmeIvy('FW-???: TypeError: Cannot read property \'componentInstance\' of undefined') &&
|
it('should update url and router state before activating components',
|
||||||
it('should update url and router state before activating components',
|
fakeAsync(inject([Router], (router: Router) => {
|
||||||
fakeAsync(inject([Router], (router: Router) => {
|
|
||||||
|
|
||||||
const fixture = createRoot(router, RootCmp);
|
const fixture = createRoot(router, RootCmp);
|
||||||
|
|
||||||
router.resetConfig([{path: 'cmp', component: ComponentRecordingRoutePathAndUrl}]);
|
router.resetConfig([{path: 'cmp', component: ComponentRecordingRoutePathAndUrl}]);
|
||||||
|
|
||||||
router.navigateByUrl('/cmp');
|
router.navigateByUrl('/cmp');
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
|
|
||||||
const cmp = fixture.debugElement.children[1].componentInstance;
|
const cmp = fixture.debugElement.children[1].componentInstance;
|
||||||
|
|
||||||
expect(cmp.url).toBe('/cmp');
|
expect(cmp.url).toBe('/cmp');
|
||||||
expect(cmp.path.length).toEqual(2);
|
expect(cmp.path.length).toEqual(2);
|
||||||
})));
|
})));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -1345,47 +1341,45 @@ describe('Integration', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
fixmeIvy('FW-???: TypeError: Cannot read property \'componentInstance\' of undefined') &&
|
it('should provide resolved data', fakeAsync(inject([Router], (router: Router) => {
|
||||||
it('should provide resolved data', fakeAsync(inject([Router], (router: Router) => {
|
const fixture = createRoot(router, RootCmpWithTwoOutlets);
|
||||||
const fixture = createRoot(router, RootCmpWithTwoOutlets);
|
|
||||||
|
|
||||||
router.resetConfig([{
|
router.resetConfig([{
|
||||||
path: 'parent/:id',
|
path: 'parent/:id',
|
||||||
data: {one: 1},
|
data: {one: 1},
|
||||||
resolve: {two: 'resolveTwo'},
|
resolve: {two: 'resolveTwo'},
|
||||||
children: [
|
children: [
|
||||||
{path: '', data: {three: 3}, resolve: {four: 'resolveFour'}, component: RouteCmp},
|
{path: '', data: {three: 3}, resolve: {four: 'resolveFour'}, component: RouteCmp}, {
|
||||||
{
|
path: '',
|
||||||
path: '',
|
data: {five: 5},
|
||||||
data: {five: 5},
|
resolve: {six: 'resolveSix'},
|
||||||
resolve: {six: 'resolveSix'},
|
component: RouteCmp,
|
||||||
component: RouteCmp,
|
outlet: 'right'
|
||||||
outlet: 'right'
|
}
|
||||||
}
|
]
|
||||||
]
|
}]);
|
||||||
}]);
|
|
||||||
|
|
||||||
router.navigateByUrl('/parent/1');
|
router.navigateByUrl('/parent/1');
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
|
|
||||||
const primaryCmp = fixture.debugElement.children[1].componentInstance;
|
const primaryCmp = fixture.debugElement.children[1].componentInstance;
|
||||||
const rightCmp = fixture.debugElement.children[3].componentInstance;
|
const rightCmp = fixture.debugElement.children[3].componentInstance;
|
||||||
|
|
||||||
expect(primaryCmp.route.snapshot.data).toEqual({one: 1, two: 2, three: 3, four: 4});
|
expect(primaryCmp.route.snapshot.data).toEqual({one: 1, two: 2, three: 3, four: 4});
|
||||||
expect(rightCmp.route.snapshot.data).toEqual({one: 1, two: 2, five: 5, six: 6});
|
expect(rightCmp.route.snapshot.data).toEqual({one: 1, two: 2, five: 5, six: 6});
|
||||||
|
|
||||||
const primaryRecorded: any[] = [];
|
const primaryRecorded: any[] = [];
|
||||||
primaryCmp.route.data.forEach((rec: any) => primaryRecorded.push(rec));
|
primaryCmp.route.data.forEach((rec: any) => primaryRecorded.push(rec));
|
||||||
|
|
||||||
const rightRecorded: any[] = [];
|
const rightRecorded: any[] = [];
|
||||||
rightCmp.route.data.forEach((rec: any) => rightRecorded.push(rec));
|
rightCmp.route.data.forEach((rec: any) => rightRecorded.push(rec));
|
||||||
|
|
||||||
router.navigateByUrl('/parent/2');
|
router.navigateByUrl('/parent/2');
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
|
|
||||||
expect(primaryRecorded).toEqual([{one: 1, three: 3, two: 2, four: 4}]);
|
expect(primaryRecorded).toEqual([{one: 1, three: 3, two: 2, four: 4}]);
|
||||||
expect(rightRecorded).toEqual([{one: 1, five: 5, two: 2, six: 6}]);
|
expect(rightRecorded).toEqual([{one: 1, five: 5, two: 2, six: 6}]);
|
||||||
})));
|
})));
|
||||||
|
|
||||||
it('should handle errors', fakeAsync(inject([Router], (router: Router) => {
|
it('should handle errors', fakeAsync(inject([Router], (router: Router) => {
|
||||||
const fixture = createRoot(router, RootCmp);
|
const fixture = createRoot(router, RootCmp);
|
||||||
|
@ -1425,52 +1419,50 @@ describe('Integration', () => {
|
||||||
expect(e).toEqual(null);
|
expect(e).toEqual(null);
|
||||||
})));
|
})));
|
||||||
|
|
||||||
fixmeIvy('FW-???: TypeError: Cannot read property \'componentInstance\' of undefined') &&
|
it('should preserve resolved data', fakeAsync(inject([Router], (router: Router) => {
|
||||||
it('should preserve resolved data', fakeAsync(inject([Router], (router: Router) => {
|
const fixture = createRoot(router, RootCmp);
|
||||||
const fixture = createRoot(router, RootCmp);
|
|
||||||
|
|
||||||
router.resetConfig([{
|
router.resetConfig([{
|
||||||
path: 'parent',
|
path: 'parent',
|
||||||
resolve: {two: 'resolveTwo'},
|
resolve: {two: 'resolveTwo'},
|
||||||
children: [
|
children: [
|
||||||
{path: 'child1', component: CollectParamsCmp},
|
{path: 'child1', component: CollectParamsCmp},
|
||||||
{path: 'child2', component: CollectParamsCmp}
|
{path: 'child2', component: CollectParamsCmp}
|
||||||
]
|
]
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
const e: any = null;
|
const e: any = null;
|
||||||
router.navigateByUrl('/parent/child1');
|
router.navigateByUrl('/parent/child1');
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
|
|
||||||
router.navigateByUrl('/parent/child2');
|
router.navigateByUrl('/parent/child2');
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
|
|
||||||
const cmp = fixture.debugElement.children[1].componentInstance;
|
const cmp = fixture.debugElement.children[1].componentInstance;
|
||||||
expect(cmp.route.snapshot.data).toEqual({two: 2});
|
expect(cmp.route.snapshot.data).toEqual({two: 2});
|
||||||
})));
|
})));
|
||||||
|
|
||||||
fixmeIvy('FW-???: TypeError: Cannot read property \'componentInstance\' of undefined') &&
|
it('should rerun resolvers when the urls segments of a wildcard route change',
|
||||||
it('should rerun resolvers when the urls segments of a wildcard route change',
|
fakeAsync(inject([Router, Location], (router: Router) => {
|
||||||
fakeAsync(inject([Router, Location], (router: Router) => {
|
const fixture = createRoot(router, RootCmp);
|
||||||
const fixture = createRoot(router, RootCmp);
|
|
||||||
|
|
||||||
router.resetConfig([{
|
router.resetConfig([{
|
||||||
path: '**',
|
path: '**',
|
||||||
component: CollectParamsCmp,
|
component: CollectParamsCmp,
|
||||||
resolve: {numberOfUrlSegments: 'numberOfUrlSegments'}
|
resolve: {numberOfUrlSegments: 'numberOfUrlSegments'}
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
router.navigateByUrl('/one/two');
|
router.navigateByUrl('/one/two');
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
const cmp = fixture.debugElement.children[1].componentInstance;
|
const cmp = fixture.debugElement.children[1].componentInstance;
|
||||||
|
|
||||||
expect(cmp.route.snapshot.data).toEqual({numberOfUrlSegments: 2});
|
expect(cmp.route.snapshot.data).toEqual({numberOfUrlSegments: 2});
|
||||||
|
|
||||||
router.navigateByUrl('/one/two/three');
|
router.navigateByUrl('/one/two/three');
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
|
|
||||||
expect(cmp.route.snapshot.data).toEqual({numberOfUrlSegments: 3});
|
expect(cmp.route.snapshot.data).toEqual({numberOfUrlSegments: 3});
|
||||||
})));
|
})));
|
||||||
|
|
||||||
describe('should run resolvers for the same route concurrently', () => {
|
describe('should run resolvers for the same route concurrently', () => {
|
||||||
let log: string[];
|
let log: string[];
|
||||||
|
@ -1525,7 +1517,7 @@ describe('Integration', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('router links', () => {
|
describe('router links', () => {
|
||||||
fixmeIvy('FW-???: ASSERTION ERROR: The provided value must be an instance of an HTMLElement') &&
|
fixmeIvy('FW-???: Error: ExpressionChangedAfterItHasBeenCheckedError') &&
|
||||||
it('should support skipping location update for anchor router links',
|
it('should support skipping location update for anchor router links',
|
||||||
fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
|
fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
|
||||||
const fixture = TestBed.createComponent(RootCmp);
|
const fixture = TestBed.createComponent(RootCmp);
|
||||||
|
@ -1775,31 +1767,30 @@ describe('Integration', () => {
|
||||||
expect(fixture.nativeElement).toHaveText('team 22 [ simple, right: ]');
|
expect(fixture.nativeElement).toHaveText('team 22 [ simple, right: ]');
|
||||||
})));
|
})));
|
||||||
|
|
||||||
fixmeIvy('FW-662: Components without selector are not supported') &&
|
it('should support top-level link', fakeAsync(inject([Router], (router: Router) => {
|
||||||
it('should support top-level link', fakeAsync(inject([Router], (router: Router) => {
|
const fixture = createRoot(router, RelativeLinkInIfCmp);
|
||||||
const fixture = createRoot(router, RelativeLinkInIfCmp);
|
advance(fixture);
|
||||||
advance(fixture);
|
|
||||||
|
|
||||||
router.resetConfig(
|
router.resetConfig(
|
||||||
[{path: 'simple', component: SimpleCmp}, {path: '', component: BlankCmp}]);
|
[{path: 'simple', component: SimpleCmp}, {path: '', component: BlankCmp}]);
|
||||||
|
|
||||||
router.navigateByUrl('/');
|
router.navigateByUrl('/');
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
expect(fixture.nativeElement).toHaveText('');
|
expect(fixture.nativeElement).toHaveText('');
|
||||||
const cmp = fixture.componentInstance;
|
const cmp = fixture.componentInstance;
|
||||||
|
|
||||||
cmp.show = true;
|
cmp.show = true;
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
|
|
||||||
expect(fixture.nativeElement).toHaveText('link');
|
expect(fixture.nativeElement).toHaveText('link');
|
||||||
const native = fixture.nativeElement.querySelector('a');
|
const native = fixture.nativeElement.querySelector('a');
|
||||||
|
|
||||||
expect(native.getAttribute('href')).toEqual('/simple');
|
expect(native.getAttribute('href')).toEqual('/simple');
|
||||||
native.click();
|
native.click();
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
|
|
||||||
expect(fixture.nativeElement).toHaveText('linksimple');
|
expect(fixture.nativeElement).toHaveText('linksimple');
|
||||||
})));
|
})));
|
||||||
|
|
||||||
fixmeIvy('FW-???: assertion failures') &&
|
fixmeIvy('FW-???: assertion failures') &&
|
||||||
it('should support query params and fragments',
|
it('should support query params and fragments',
|
||||||
|
@ -2201,152 +2192,146 @@ describe('Integration', () => {
|
||||||
return fixture;
|
return fixture;
|
||||||
}
|
}
|
||||||
|
|
||||||
fixmeIvy('FW-???: TypeError: Cannot read property \'data\' of undefined') &&
|
|
||||||
it('should rerun guards and resolvers when params change',
|
|
||||||
fakeAsync(inject([Router], (router: Router) => {
|
|
||||||
const fixture = configureRouter(router, 'paramsChange');
|
|
||||||
|
|
||||||
const cmp: RouteCmp = fixture.debugElement.children[1].componentInstance;
|
it('should rerun guards and resolvers when params change',
|
||||||
const recordedData: any[] = [];
|
fakeAsync(inject([Router], (router: Router) => {
|
||||||
cmp.route.data.subscribe((data: any) => recordedData.push(data));
|
const fixture = configureRouter(router, 'paramsChange');
|
||||||
|
|
||||||
expect(guardRunCount).toEqual(1);
|
const cmp: RouteCmp = fixture.debugElement.children[1].componentInstance;
|
||||||
expect(recordedData).toEqual([{data: 0}]);
|
const recordedData: any[] = [];
|
||||||
|
cmp.route.data.subscribe((data: any) => recordedData.push(data));
|
||||||
|
|
||||||
router.navigateByUrl('/a;p=1');
|
expect(guardRunCount).toEqual(1);
|
||||||
advance(fixture);
|
expect(recordedData).toEqual([{data: 0}]);
|
||||||
expect(guardRunCount).toEqual(2);
|
|
||||||
expect(recordedData).toEqual([{data: 0}, {data: 1}]);
|
|
||||||
|
|
||||||
router.navigateByUrl('/a;p=2');
|
router.navigateByUrl('/a;p=1');
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
expect(guardRunCount).toEqual(3);
|
expect(guardRunCount).toEqual(2);
|
||||||
expect(recordedData).toEqual([{data: 0}, {data: 1}, {data: 2}]);
|
expect(recordedData).toEqual([{data: 0}, {data: 1}]);
|
||||||
|
|
||||||
router.navigateByUrl('/a;p=2?q=1');
|
router.navigateByUrl('/a;p=2');
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
expect(guardRunCount).toEqual(3);
|
expect(guardRunCount).toEqual(3);
|
||||||
expect(recordedData).toEqual([{data: 0}, {data: 1}, {data: 2}]);
|
expect(recordedData).toEqual([{data: 0}, {data: 1}, {data: 2}]);
|
||||||
})));
|
|
||||||
|
|
||||||
fixmeIvy('FW-???: TypeError: Cannot read property \'data\' of undefined') &&
|
router.navigateByUrl('/a;p=2?q=1');
|
||||||
it('should rerun guards and resolvers when query params change',
|
advance(fixture);
|
||||||
fakeAsync(inject([Router], (router: Router) => {
|
expect(guardRunCount).toEqual(3);
|
||||||
const fixture = configureRouter(router, 'paramsOrQueryParamsChange');
|
expect(recordedData).toEqual([{data: 0}, {data: 1}, {data: 2}]);
|
||||||
|
})));
|
||||||
|
|
||||||
const cmp: RouteCmp = fixture.debugElement.children[1].componentInstance;
|
it('should rerun guards and resolvers when query params change',
|
||||||
const recordedData: any[] = [];
|
fakeAsync(inject([Router], (router: Router) => {
|
||||||
cmp.route.data.subscribe((data: any) => recordedData.push(data));
|
const fixture = configureRouter(router, 'paramsOrQueryParamsChange');
|
||||||
|
|
||||||
expect(guardRunCount).toEqual(1);
|
const cmp: RouteCmp = fixture.debugElement.children[1].componentInstance;
|
||||||
expect(recordedData).toEqual([{data: 0}]);
|
const recordedData: any[] = [];
|
||||||
|
cmp.route.data.subscribe((data: any) => recordedData.push(data));
|
||||||
|
|
||||||
router.navigateByUrl('/a;p=1');
|
expect(guardRunCount).toEqual(1);
|
||||||
advance(fixture);
|
expect(recordedData).toEqual([{data: 0}]);
|
||||||
expect(guardRunCount).toEqual(2);
|
|
||||||
expect(recordedData).toEqual([{data: 0}, {data: 1}]);
|
|
||||||
|
|
||||||
router.navigateByUrl('/a;p=2');
|
router.navigateByUrl('/a;p=1');
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
expect(guardRunCount).toEqual(3);
|
expect(guardRunCount).toEqual(2);
|
||||||
expect(recordedData).toEqual([{data: 0}, {data: 1}, {data: 2}]);
|
expect(recordedData).toEqual([{data: 0}, {data: 1}]);
|
||||||
|
|
||||||
router.navigateByUrl('/a;p=2?q=1');
|
router.navigateByUrl('/a;p=2');
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
expect(guardRunCount).toEqual(4);
|
expect(guardRunCount).toEqual(3);
|
||||||
expect(recordedData).toEqual([{data: 0}, {data: 1}, {data: 2}, {data: 3}]);
|
expect(recordedData).toEqual([{data: 0}, {data: 1}, {data: 2}]);
|
||||||
|
|
||||||
router.navigateByUrl('/a;p=2(right:b)?q=1');
|
router.navigateByUrl('/a;p=2?q=1');
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
expect(guardRunCount).toEqual(4);
|
expect(guardRunCount).toEqual(4);
|
||||||
expect(recordedData).toEqual([{data: 0}, {data: 1}, {data: 2}, {data: 3}]);
|
expect(recordedData).toEqual([{data: 0}, {data: 1}, {data: 2}, {data: 3}]);
|
||||||
})));
|
|
||||||
|
|
||||||
fixmeIvy('FW-???: TypeError: Cannot read property \'data\' of undefined') &&
|
router.navigateByUrl('/a;p=2(right:b)?q=1');
|
||||||
it('should always rerun guards and resolvers',
|
advance(fixture);
|
||||||
fakeAsync(inject([Router], (router: Router) => {
|
expect(guardRunCount).toEqual(4);
|
||||||
const fixture = configureRouter(router, 'always');
|
expect(recordedData).toEqual([{data: 0}, {data: 1}, {data: 2}, {data: 3}]);
|
||||||
|
})));
|
||||||
|
|
||||||
const cmp: RouteCmp = fixture.debugElement.children[1].componentInstance;
|
it('should always rerun guards and resolvers',
|
||||||
const recordedData: any[] = [];
|
fakeAsync(inject([Router], (router: Router) => {
|
||||||
cmp.route.data.subscribe((data: any) => recordedData.push(data));
|
const fixture = configureRouter(router, 'always');
|
||||||
|
|
||||||
expect(guardRunCount).toEqual(1);
|
const cmp: RouteCmp = fixture.debugElement.children[1].componentInstance;
|
||||||
expect(recordedData).toEqual([{data: 0}]);
|
const recordedData: any[] = [];
|
||||||
|
cmp.route.data.subscribe((data: any) => recordedData.push(data));
|
||||||
|
|
||||||
router.navigateByUrl('/a;p=1');
|
expect(guardRunCount).toEqual(1);
|
||||||
advance(fixture);
|
expect(recordedData).toEqual([{data: 0}]);
|
||||||
expect(guardRunCount).toEqual(2);
|
|
||||||
expect(recordedData).toEqual([{data: 0}, {data: 1}]);
|
|
||||||
|
|
||||||
router.navigateByUrl('/a;p=2');
|
router.navigateByUrl('/a;p=1');
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
expect(guardRunCount).toEqual(3);
|
expect(guardRunCount).toEqual(2);
|
||||||
expect(recordedData).toEqual([{data: 0}, {data: 1}, {data: 2}]);
|
expect(recordedData).toEqual([{data: 0}, {data: 1}]);
|
||||||
|
|
||||||
router.navigateByUrl('/a;p=2?q=1');
|
router.navigateByUrl('/a;p=2');
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
expect(guardRunCount).toEqual(4);
|
expect(guardRunCount).toEqual(3);
|
||||||
expect(recordedData).toEqual([{data: 0}, {data: 1}, {data: 2}, {data: 3}]);
|
expect(recordedData).toEqual([{data: 0}, {data: 1}, {data: 2}]);
|
||||||
|
|
||||||
router.navigateByUrl('/a;p=2(right:b)?q=1');
|
router.navigateByUrl('/a;p=2?q=1');
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
expect(guardRunCount).toEqual(5);
|
expect(guardRunCount).toEqual(4);
|
||||||
expect(recordedData).toEqual([
|
expect(recordedData).toEqual([{data: 0}, {data: 1}, {data: 2}, {data: 3}]);
|
||||||
{data: 0}, {data: 1}, {data: 2}, {data: 3}, {data: 4}
|
|
||||||
]);
|
|
||||||
})));
|
|
||||||
|
|
||||||
fixmeIvy('FW-???: TypeError: Cannot read property \'data\' of undefined') &&
|
router.navigateByUrl('/a;p=2(right:b)?q=1');
|
||||||
it('should not rerun guards and resolvers',
|
advance(fixture);
|
||||||
fakeAsync(inject([Router], (router: Router) => {
|
expect(guardRunCount).toEqual(5);
|
||||||
const fixture = configureRouter(router, 'pathParamsChange');
|
expect(recordedData).toEqual([{data: 0}, {data: 1}, {data: 2}, {data: 3}, {data: 4}]);
|
||||||
|
})));
|
||||||
|
|
||||||
const cmp: RouteCmp = fixture.debugElement.children[1].componentInstance;
|
it('should not rerun guards and resolvers', fakeAsync(inject([Router], (router: Router) => {
|
||||||
const recordedData: any[] = [];
|
const fixture = configureRouter(router, 'pathParamsChange');
|
||||||
cmp.route.data.subscribe((data: any) => recordedData.push(data));
|
|
||||||
|
|
||||||
// First navigation has already run
|
const cmp: RouteCmp = fixture.debugElement.children[1].componentInstance;
|
||||||
expect(guardRunCount).toEqual(1);
|
const recordedData: any[] = [];
|
||||||
expect(recordedData).toEqual([{data: 0}]);
|
cmp.route.data.subscribe((data: any) => recordedData.push(data));
|
||||||
|
|
||||||
// Changing any optional params will not result in running guards or resolvers
|
// First navigation has already run
|
||||||
router.navigateByUrl('/a;p=1');
|
expect(guardRunCount).toEqual(1);
|
||||||
advance(fixture);
|
expect(recordedData).toEqual([{data: 0}]);
|
||||||
expect(guardRunCount).toEqual(1);
|
|
||||||
expect(recordedData).toEqual([{data: 0}]);
|
|
||||||
|
|
||||||
router.navigateByUrl('/a;p=2');
|
// Changing any optional params will not result in running guards or resolvers
|
||||||
advance(fixture);
|
router.navigateByUrl('/a;p=1');
|
||||||
expect(guardRunCount).toEqual(1);
|
advance(fixture);
|
||||||
expect(recordedData).toEqual([{data: 0}]);
|
expect(guardRunCount).toEqual(1);
|
||||||
|
expect(recordedData).toEqual([{data: 0}]);
|
||||||
|
|
||||||
router.navigateByUrl('/a;p=2?q=1');
|
router.navigateByUrl('/a;p=2');
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
expect(guardRunCount).toEqual(1);
|
expect(guardRunCount).toEqual(1);
|
||||||
expect(recordedData).toEqual([{data: 0}]);
|
expect(recordedData).toEqual([{data: 0}]);
|
||||||
|
|
||||||
router.navigateByUrl('/a;p=2(right:b)?q=1');
|
router.navigateByUrl('/a;p=2?q=1');
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
expect(guardRunCount).toEqual(1);
|
expect(guardRunCount).toEqual(1);
|
||||||
expect(recordedData).toEqual([{data: 0}]);
|
expect(recordedData).toEqual([{data: 0}]);
|
||||||
|
|
||||||
// Change to new route with path param should run guards and resolvers
|
router.navigateByUrl('/a;p=2(right:b)?q=1');
|
||||||
router.navigateByUrl('/c/paramValue');
|
advance(fixture);
|
||||||
advance(fixture);
|
expect(guardRunCount).toEqual(1);
|
||||||
|
expect(recordedData).toEqual([{data: 0}]);
|
||||||
|
|
||||||
expect(guardRunCount).toEqual(2);
|
// Change to new route with path param should run guards and resolvers
|
||||||
|
router.navigateByUrl('/c/paramValue');
|
||||||
|
advance(fixture);
|
||||||
|
|
||||||
// Modifying a path param should run guards and resolvers
|
expect(guardRunCount).toEqual(2);
|
||||||
router.navigateByUrl('/c/paramValueChanged');
|
|
||||||
advance(fixture);
|
|
||||||
expect(guardRunCount).toEqual(3);
|
|
||||||
|
|
||||||
// Adding optional params should not cause guards/resolvers to run
|
// Modifying a path param should run guards and resolvers
|
||||||
router.navigateByUrl('/c/paramValueChanged;p=1?q=2');
|
router.navigateByUrl('/c/paramValueChanged');
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
expect(guardRunCount).toEqual(3);
|
expect(guardRunCount).toEqual(3);
|
||||||
})));
|
|
||||||
|
// Adding optional params should not cause guards/resolvers to run
|
||||||
|
router.navigateByUrl('/c/paramValueChanged;p=1?q=2');
|
||||||
|
advance(fixture);
|
||||||
|
expect(guardRunCount).toEqual(3);
|
||||||
|
})));
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('should wait for parent to complete', () => {
|
describe('should wait for parent to complete', () => {
|
||||||
|
@ -2497,46 +2482,45 @@ describe('Integration', () => {
|
||||||
expect(canceledStatus).toEqual(false);
|
expect(canceledStatus).toEqual(false);
|
||||||
})));
|
})));
|
||||||
|
|
||||||
fixmeIvy('FW-???: TypeError: Cannot read property \'componentInstance\' of undefined') &&
|
it('works with componentless routes',
|
||||||
it('works with componentless routes',
|
fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
|
||||||
fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
|
const fixture = createRoot(router, RootCmp);
|
||||||
const fixture = createRoot(router, RootCmp);
|
|
||||||
|
|
||||||
router.resetConfig([
|
router.resetConfig([
|
||||||
{
|
{
|
||||||
path: 'grandparent',
|
path: 'grandparent',
|
||||||
|
canDeactivate: ['RecordingDeactivate'],
|
||||||
|
children: [{
|
||||||
|
path: 'parent',
|
||||||
|
canDeactivate: ['RecordingDeactivate'],
|
||||||
|
children: [{
|
||||||
|
path: 'child',
|
||||||
canDeactivate: ['RecordingDeactivate'],
|
canDeactivate: ['RecordingDeactivate'],
|
||||||
children: [{
|
children: [{
|
||||||
path: 'parent',
|
path: 'simple',
|
||||||
canDeactivate: ['RecordingDeactivate'],
|
component: SimpleCmp,
|
||||||
children: [{
|
canDeactivate: ['RecordingDeactivate']
|
||||||
path: 'child',
|
|
||||||
canDeactivate: ['RecordingDeactivate'],
|
|
||||||
children: [{
|
|
||||||
path: 'simple',
|
|
||||||
component: SimpleCmp,
|
|
||||||
canDeactivate: ['RecordingDeactivate']
|
|
||||||
}]
|
|
||||||
}]
|
|
||||||
}]
|
}]
|
||||||
},
|
}]
|
||||||
{path: 'simple', component: SimpleCmp}
|
}]
|
||||||
]);
|
},
|
||||||
|
{path: 'simple', component: SimpleCmp}
|
||||||
|
]);
|
||||||
|
|
||||||
router.navigateByUrl('/grandparent/parent/child/simple');
|
router.navigateByUrl('/grandparent/parent/child/simple');
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
expect(location.path()).toEqual('/grandparent/parent/child/simple');
|
expect(location.path()).toEqual('/grandparent/parent/child/simple');
|
||||||
|
|
||||||
router.navigateByUrl('/simple');
|
router.navigateByUrl('/simple');
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
|
|
||||||
const child = fixture.debugElement.children[1].componentInstance;
|
const child = fixture.debugElement.children[1].componentInstance;
|
||||||
|
|
||||||
expect(log.map((a: any) => a.path)).toEqual([
|
expect(log.map((a: any) => a.path)).toEqual([
|
||||||
'simple', 'child', 'parent', 'grandparent'
|
'simple', 'child', 'parent', 'grandparent'
|
||||||
]);
|
]);
|
||||||
expect(log.map((a: any) => a.component)).toEqual([child, null, null, null]);
|
expect(log.map((a: any) => a.component)).toEqual([child, null, null, null]);
|
||||||
})));
|
})));
|
||||||
|
|
||||||
it('works with aux routes',
|
it('works with aux routes',
|
||||||
fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
|
fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
|
||||||
|
@ -2601,30 +2585,29 @@ describe('Integration', () => {
|
||||||
})));
|
})));
|
||||||
});
|
});
|
||||||
|
|
||||||
fixmeIvy('FW-???: TypeError: Cannot read property \'componentInstance\' of undefined') &&
|
it('should not create a route state if navigation is canceled',
|
||||||
it('should not create a route state if navigation is canceled',
|
fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
|
||||||
fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
|
const fixture = createRoot(router, RootCmp);
|
||||||
const fixture = createRoot(router, RootCmp);
|
|
||||||
|
|
||||||
router.resetConfig([{
|
router.resetConfig([{
|
||||||
path: 'main',
|
path: 'main',
|
||||||
component: TeamCmp,
|
component: TeamCmp,
|
||||||
children: [
|
children: [
|
||||||
{path: 'component1', component: SimpleCmp, canDeactivate: ['alwaysFalse']},
|
{path: 'component1', component: SimpleCmp, canDeactivate: ['alwaysFalse']},
|
||||||
{path: 'component2', component: SimpleCmp}
|
{path: 'component2', component: SimpleCmp}
|
||||||
]
|
]
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
router.navigateByUrl('/main/component1');
|
router.navigateByUrl('/main/component1');
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
|
|
||||||
router.navigateByUrl('/main/component2');
|
router.navigateByUrl('/main/component2');
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
|
|
||||||
const teamCmp = fixture.debugElement.children[1].componentInstance;
|
const teamCmp = fixture.debugElement.children[1].componentInstance;
|
||||||
expect(teamCmp.route.firstChild.url.value[0].path).toEqual('component1');
|
expect(teamCmp.route.firstChild.url.value[0].path).toEqual('component1');
|
||||||
expect(location.path()).toEqual('/main/component1');
|
expect(location.path()).toEqual('/main/component1');
|
||||||
})));
|
})));
|
||||||
|
|
||||||
it('should not run CanActivate when CanDeactivate returns false',
|
it('should not run CanActivate when CanDeactivate returns false',
|
||||||
fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
|
fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
|
||||||
|
@ -3365,46 +3348,44 @@ describe('Integration', () => {
|
||||||
expect(native.className).toEqual('active');
|
expect(native.className).toEqual('active');
|
||||||
})));
|
})));
|
||||||
|
|
||||||
|
it('should expose an isActive property', fakeAsync(() => {
|
||||||
fixmeIvy('FW-662: Components without selector are not supported') &&
|
@Component({
|
||||||
it('should expose an isActive property', fakeAsync(() => {
|
template: `<a routerLink="/team" routerLinkActive #rla="routerLinkActive"></a>
|
||||||
@Component({
|
|
||||||
template: `<a routerLink="/team" routerLinkActive #rla="routerLinkActive"></a>
|
|
||||||
<p>{{rla.isActive}}</p>
|
<p>{{rla.isActive}}</p>
|
||||||
<span *ngIf="rla.isActive"></span>
|
<span *ngIf="rla.isActive"></span>
|
||||||
<span [ngClass]="{'highlight': rla.isActive}"></span>
|
<span [ngClass]="{'highlight': rla.isActive}"></span>
|
||||||
<router-outlet></router-outlet>`
|
<router-outlet></router-outlet>`
|
||||||
})
|
})
|
||||||
class ComponentWithRouterLink {
|
class ComponentWithRouterLink {
|
||||||
}
|
}
|
||||||
|
|
||||||
TestBed.configureTestingModule({declarations: [ComponentWithRouterLink]});
|
TestBed.configureTestingModule({declarations: [ComponentWithRouterLink]});
|
||||||
const router: Router = TestBed.get(Router);
|
const router: Router = TestBed.get(Router);
|
||||||
|
|
||||||
router.resetConfig([
|
router.resetConfig([
|
||||||
{
|
{
|
||||||
path: 'team',
|
path: 'team',
|
||||||
component: TeamCmp,
|
component: TeamCmp,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'otherteam',
|
path: 'otherteam',
|
||||||
component: TeamCmp,
|
component: TeamCmp,
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const fixture = TestBed.createComponent(ComponentWithRouterLink);
|
const fixture = TestBed.createComponent(ComponentWithRouterLink);
|
||||||
router.navigateByUrl('/team');
|
router.navigateByUrl('/team');
|
||||||
expect(() => advance(fixture)).not.toThrow();
|
expect(() => advance(fixture)).not.toThrow();
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
|
|
||||||
const paragraph = fixture.nativeElement.querySelector('p');
|
const paragraph = fixture.nativeElement.querySelector('p');
|
||||||
expect(paragraph.textContent).toEqual('true');
|
expect(paragraph.textContent).toEqual('true');
|
||||||
|
|
||||||
router.navigateByUrl('/otherteam');
|
router.navigateByUrl('/otherteam');
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
expect(paragraph.textContent).toEqual('false');
|
expect(paragraph.textContent).toEqual('false');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -16,15 +16,14 @@ import {RouterTestingModule} from '@angular/router/testing';
|
||||||
describe('Integration', () => {
|
describe('Integration', () => {
|
||||||
|
|
||||||
describe('routerLinkActive', () => {
|
describe('routerLinkActive', () => {
|
||||||
fixmeIvy('FW-662: Components without selector are not supported') &&
|
it('should not cause infinite loops in the change detection - #15825', fakeAsync(() => {
|
||||||
it('should not cause infinite loops in the change detection - #15825', fakeAsync(() => {
|
@Component({selector: 'simple', template: 'simple'})
|
||||||
@Component({selector: 'simple', template: 'simple'})
|
class SimpleCmp {
|
||||||
class SimpleCmp {
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'some-root',
|
selector: 'some-root',
|
||||||
template: `
|
template: `
|
||||||
<div *ngIf="show">
|
<div *ngIf="show">
|
||||||
<ng-container *ngTemplateOutlet="tpl"></ng-container>
|
<ng-container *ngTemplateOutlet="tpl"></ng-container>
|
||||||
</div>
|
</div>
|
||||||
|
@ -32,37 +31,36 @@ describe('Integration', () => {
|
||||||
<ng-template #tpl>
|
<ng-template #tpl>
|
||||||
<a routerLink="/simple" routerLinkActive="active"></a>
|
<a routerLink="/simple" routerLinkActive="active"></a>
|
||||||
</ng-template>`
|
</ng-template>`
|
||||||
})
|
})
|
||||||
class MyCmp {
|
class MyCmp {
|
||||||
show: boolean = false;
|
show: boolean = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [CommonModule, RouterTestingModule],
|
imports: [CommonModule, RouterTestingModule],
|
||||||
declarations: [MyCmp, SimpleCmp],
|
declarations: [MyCmp, SimpleCmp],
|
||||||
entryComponents: [SimpleCmp],
|
entryComponents: [SimpleCmp],
|
||||||
})
|
})
|
||||||
class MyModule {
|
class MyModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
TestBed.configureTestingModule({imports: [MyModule]});
|
TestBed.configureTestingModule({imports: [MyModule]});
|
||||||
|
|
||||||
const router: Router = TestBed.get(Router);
|
const router: Router = TestBed.get(Router);
|
||||||
const fixture = createRoot(router, MyCmp);
|
const fixture = createRoot(router, MyCmp);
|
||||||
router.resetConfig([{path: 'simple', component: SimpleCmp}]);
|
router.resetConfig([{path: 'simple', component: SimpleCmp}]);
|
||||||
|
|
||||||
router.navigateByUrl('/simple');
|
router.navigateByUrl('/simple');
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
|
|
||||||
const instance = fixture.componentInstance;
|
const instance = fixture.componentInstance;
|
||||||
instance.show = true;
|
instance.show = true;
|
||||||
expect(() => advance(fixture)).not.toThrow();
|
expect(() => advance(fixture)).not.toThrow();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
fixmeIvy('FW-662: Components without selector are not supported') &&
|
it('should set isActive right after looking at its children -- #18983', fakeAsync(() => {
|
||||||
it('should set isActive right after looking at its children -- #18983', fakeAsync(() => {
|
@Component({
|
||||||
@Component({
|
template: `
|
||||||
template: `
|
|
||||||
<div #rla="routerLinkActive" routerLinkActive>
|
<div #rla="routerLinkActive" routerLinkActive>
|
||||||
isActive: {{rla.isActive}}
|
isActive: {{rla.isActive}}
|
||||||
|
|
||||||
|
@ -73,43 +71,43 @@ describe('Integration', () => {
|
||||||
<ng-container #container></ng-container>
|
<ng-container #container></ng-container>
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
})
|
})
|
||||||
class ComponentWithRouterLink {
|
class ComponentWithRouterLink {
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
@ViewChild(TemplateRef) templateRef !: TemplateRef<any>;
|
@ViewChild(TemplateRef) templateRef !: TemplateRef<any>;
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
@ViewChild('container', {read: ViewContainerRef}) container !: ViewContainerRef;
|
@ViewChild('container', {read: ViewContainerRef}) container !: ViewContainerRef;
|
||||||
|
|
||||||
addLink() {
|
addLink() {
|
||||||
this.container.createEmbeddedView(this.templateRef, {$implicit: '/simple'});
|
this.container.createEmbeddedView(this.templateRef, {$implicit: '/simple'});
|
||||||
}
|
}
|
||||||
|
|
||||||
removeLink() { this.container.clear(); }
|
removeLink() { this.container.clear(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({template: 'simple'})
|
@Component({template: 'simple'})
|
||||||
class SimpleCmp {
|
class SimpleCmp {
|
||||||
}
|
}
|
||||||
|
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [RouterTestingModule.withRoutes([{path: 'simple', component: SimpleCmp}])],
|
imports: [RouterTestingModule.withRoutes([{path: 'simple', component: SimpleCmp}])],
|
||||||
declarations: [ComponentWithRouterLink, SimpleCmp]
|
declarations: [ComponentWithRouterLink, SimpleCmp]
|
||||||
});
|
});
|
||||||
|
|
||||||
const router: Router = TestBed.get(Router);
|
const router: Router = TestBed.get(Router);
|
||||||
const fixture = createRoot(router, ComponentWithRouterLink);
|
const fixture = createRoot(router, ComponentWithRouterLink);
|
||||||
router.navigateByUrl('/simple');
|
router.navigateByUrl('/simple');
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
|
|
||||||
fixture.componentInstance.addLink();
|
fixture.componentInstance.addLink();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
fixture.componentInstance.removeLink();
|
fixture.componentInstance.removeLink();
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
|
|
||||||
expect(fixture.nativeElement.innerHTML).toContain('isActive: false');
|
expect(fixture.nativeElement.innerHTML).toContain('isActive: false');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue