fix(ivy): check semantics of NgModule for consistency (#27604)

`NgModule` requires that `Component`s/`Directive`s/`Pipe`s are listed in
declarations, and that each `Component`s/`Directive`s/`Pipe` is declared
in exactly one `NgModule`. This change adds runtime checks to ensure
that these sementics are true at runtime.

There will need to be seperate set of checks for the AoT path of the
codebase to verify that same set of semantics hold. Due to current
design there does not seem to be an easy way to share the two checks
because JIT deal with references where as AoT deals with AST nodes.

PR Close #27604
This commit is contained in:
Miško Hevery
2018-12-11 10:43:02 -08:00
parent d132baede3
commit e94975d109
23 changed files with 627 additions and 366 deletions

View File

@ -3991,26 +3991,27 @@ describe('Integration', () => {
});
});
it('should use the injector of the lazily-loaded configuration',
fakeAsync(inject(
[Router, Location, NgModuleFactoryLoader],
(router: Router, location: Location, loader: SpyNgModuleFactoryLoader) => {
loader.stubbedModules = {expected: LoadedModule};
fixmeIvy('unknown').it(
'should use the injector of the lazily-loaded configuration',
fakeAsync(inject(
[Router, Location, NgModuleFactoryLoader],
(router: Router, location: Location, loader: SpyNgModuleFactoryLoader) => {
loader.stubbedModules = {expected: LoadedModule};
const fixture = createRoot(router, RootCmp);
const fixture = createRoot(router, RootCmp);
router.resetConfig([{
path: 'eager-parent',
component: EagerParentComponent,
children: [{path: 'lazy', loadChildren: 'expected'}]
}]);
router.resetConfig([{
path: 'eager-parent',
component: EagerParentComponent,
children: [{path: 'lazy', loadChildren: 'expected'}]
}]);
router.navigateByUrl('/eager-parent/lazy/lazy-parent/lazy-child');
advance(fixture);
router.navigateByUrl('/eager-parent/lazy/lazy-parent/lazy-child');
advance(fixture);
expect(location.path()).toEqual('/eager-parent/lazy/lazy-parent/lazy-child');
expect(fixture.nativeElement).toHaveText('eager-parent lazy-parent lazy-child');
})));
expect(location.path()).toEqual('/eager-parent/lazy/lazy-parent/lazy-child');
expect(fixture.nativeElement).toHaveText('eager-parent lazy-parent lazy-child');
})));
});
it('works when given a callback',
@ -4333,41 +4334,43 @@ describe('Integration', () => {
class LazyLoadedModule {
}
it('should not ignore empty path when in legacy mode',
fakeAsync(inject(
[Router, NgModuleFactoryLoader],
(router: Router, loader: SpyNgModuleFactoryLoader) => {
router.relativeLinkResolution = 'legacy';
loader.stubbedModules = {expected: LazyLoadedModule};
fixmeIvy('unknown').it(
'should not ignore empty path when in legacy mode',
fakeAsync(inject(
[Router, NgModuleFactoryLoader],
(router: Router, loader: SpyNgModuleFactoryLoader) => {
router.relativeLinkResolution = 'legacy';
loader.stubbedModules = {expected: LazyLoadedModule};
const fixture = createRoot(router, RootCmp);
const fixture = createRoot(router, RootCmp);
router.resetConfig([{path: 'lazy', loadChildren: 'expected'}]);
router.resetConfig([{path: 'lazy', loadChildren: 'expected'}]);
router.navigateByUrl('/lazy/foo/bar');
advance(fixture);
router.navigateByUrl('/lazy/foo/bar');
advance(fixture);
const link = fixture.nativeElement.querySelector('a');
expect(link.getAttribute('href')).toEqual('/lazy/foo/bar/simple');
})));
const link = fixture.nativeElement.querySelector('a');
expect(link.getAttribute('href')).toEqual('/lazy/foo/bar/simple');
})));
it('should ignore empty path when in corrected mode',
fakeAsync(inject(
[Router, NgModuleFactoryLoader],
(router: Router, loader: SpyNgModuleFactoryLoader) => {
router.relativeLinkResolution = 'corrected';
loader.stubbedModules = {expected: LazyLoadedModule};
fixmeIvy('unknown').it(
'should ignore empty path when in corrected mode',
fakeAsync(inject(
[Router, NgModuleFactoryLoader],
(router: Router, loader: SpyNgModuleFactoryLoader) => {
router.relativeLinkResolution = 'corrected';
loader.stubbedModules = {expected: LazyLoadedModule};
const fixture = createRoot(router, RootCmp);
const fixture = createRoot(router, RootCmp);
router.resetConfig([{path: 'lazy', loadChildren: 'expected'}]);
router.resetConfig([{path: 'lazy', loadChildren: 'expected'}]);
router.navigateByUrl('/lazy/foo/bar');
advance(fixture);
router.navigateByUrl('/lazy/foo/bar');
advance(fixture);
const link = fixture.nativeElement.querySelector('a');
expect(link.getAttribute('href')).toEqual('/lazy/foo/simple');
})));
const link = fixture.nativeElement.querySelector('a');
expect(link.getAttribute('href')).toEqual('/lazy/foo/simple');
})));
});
});