From e162fa3735f5f212cde90cb1e23a980cdcbb7d92 Mon Sep 17 00:00:00 2001 From: Pawel Kozlowski Date: Thu, 14 Apr 2022 10:19:54 -0700 Subject: [PATCH] test(core): add more tests for the standalone injector (#45687) This commit reorganizes the tests around the EnvironmentInjector and its use for standalone injectors, and adds a number of new test cases. PR Close #45687 --- .../env_injector_standalone_spec.ts | 73 ++++++++++++++ .../acceptance/standalone_injector_spec.ts | 96 +++++++++++++++++++ packages/core/test/render3/standalone_spec.ts | 70 -------------- 3 files changed, 169 insertions(+), 70 deletions(-) create mode 100644 packages/core/test/acceptance/env_injector_standalone_spec.ts create mode 100644 packages/core/test/acceptance/standalone_injector_spec.ts delete mode 100644 packages/core/test/render3/standalone_spec.ts diff --git a/packages/core/test/acceptance/env_injector_standalone_spec.ts b/packages/core/test/acceptance/env_injector_standalone_spec.ts new file mode 100644 index 00000000000..f98d857da30 --- /dev/null +++ b/packages/core/test/acceptance/env_injector_standalone_spec.ts @@ -0,0 +1,73 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {Component, createEnvironmentInjector, importProvidersFrom, NgModule} from '@angular/core'; + +describe('environement injector and standalone components', () => { + it('should see providers from modules imported by standalone components', () => { + class ModuleService {} + + @NgModule({providers: [ModuleService]}) + class Module { + } + + @Component({standalone: true, imports: [Module]}) + class StandaloneComponent { + } + + const envInjector = createEnvironmentInjector(importProvidersFrom(StandaloneComponent)); + expect(envInjector.get(ModuleService)).toBeInstanceOf(ModuleService); + }); + + it('should see providers when exporting a standalone components', () => { + class ModuleService {} + + @NgModule({providers: [ModuleService]}) + class Module { + } + + @Component({standalone: true, imports: [Module]}) + class StandaloneComponent { + } + + @NgModule({imports: [StandaloneComponent], exports: [StandaloneComponent]}) + class AppModule { + } + + const envInjector = createEnvironmentInjector(importProvidersFrom(AppModule)); + expect(envInjector.get(ModuleService)).toBeInstanceOf(ModuleService); + }); + + it('should not collect duplicate providers', () => { + class ModuleService {} + + @NgModule({providers: [{provide: ModuleService, useClass: ModuleService, multi: true}]}) + class Module { + } + + @Component({standalone: true, imports: [Module]}) + class StandaloneComponent1 { + } + + @Component({standalone: true, imports: [Module]}) + class StandaloneComponent2 { + } + + @NgModule({ + imports: [StandaloneComponent1, StandaloneComponent2], + exports: [StandaloneComponent1, StandaloneComponent2] + }) + class AppModule { + } + + const envInjector = createEnvironmentInjector(importProvidersFrom(AppModule)); + const services = envInjector.get(ModuleService) as ModuleService[]; + + expect(services.length).toBe(1); + }); +}); diff --git a/packages/core/test/acceptance/standalone_injector_spec.ts b/packages/core/test/acceptance/standalone_injector_spec.ts new file mode 100644 index 00000000000..6c755e5066c --- /dev/null +++ b/packages/core/test/acceptance/standalone_injector_spec.ts @@ -0,0 +1,96 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {Component, ComponentFactoryResolver, createEnvironmentInjector, EnvironmentInjector, Injector, NgModule, ViewChild, ViewContainerRef} from '@angular/core'; +import {TestBed} from '@angular/core/testing'; + +describe('standalone injector', () => { + it('should create one standalone injector for each parent EnvInjector', () => { + let counter = 0; + + class Service { + value = counter++; + } + + @NgModule({providers: [Service]}) + class ModuleWithAService { + } + + @Component({ + selector: 'standalone', + standalone: true, + imports: [ModuleWithAService], + template: `({{service.value}})` + }) + class TestComponent { + constructor(readonly service: Service) {} + } + + @Component({selector: 'app', template: ``}) + class AppComponent { + @ViewChild('insert', {static: true, read: ViewContainerRef}) vcRef!: ViewContainerRef; + + createComponent(envInjector?: EnvironmentInjector): void { + this.vcRef.createComponent(TestComponent, {environmentInjector: envInjector}); + } + } + + const fixture = TestBed.createComponent(AppComponent); + const currEnvInjector = TestBed.inject(EnvironmentInjector); + + fixture.componentInstance.createComponent(currEnvInjector); + fixture.detectChanges(); + expect(fixture.nativeElement.textContent).toBe('(0)'); + + // inserting the same standalone component second time and asserting that no new injector / + // service instance gets created + fixture.componentInstance.createComponent(currEnvInjector); + fixture.detectChanges(); + expect(fixture.nativeElement.textContent).toBe('(0)(0)'); + + // inserting with a different EnvInjector as a parent should trigger a new service instance + // creation + fixture.componentInstance.createComponent(createEnvironmentInjector([], currEnvInjector)); + fixture.detectChanges(); + expect(fixture.nativeElement.textContent).toBe('(0)(0)(1)'); + }); + + it('should create a standalone Injector for ComponentRefs that are not inserted anywhere', () => { + class Service { + value = 'Service value'; + } + + @NgModule({providers: [Service]}) + class ModuleWithAService { + } + + @Component({ + selector: 'standalone', + standalone: true, + imports: [ModuleWithAService], + template: `{{service.value}}` + }) + class DynamicComponent { + constructor(readonly service: Service) {} + } + + @Component({}) + class AppComponent { + } + + const fixture = TestBed.createComponent(AppComponent); + + const cfr = TestBed.inject(ComponentFactoryResolver); + const cf = cfr.resolveComponentFactory(DynamicComponent); + + const componentRef = cf.create(Injector.NULL); + componentRef.changeDetectorRef.detectChanges(); + + expect(componentRef.location.nativeElement.textContent).toBe('Service value'); + }); +}); diff --git a/packages/core/test/render3/standalone_spec.ts b/packages/core/test/render3/standalone_spec.ts deleted file mode 100644 index e74cbb23adf..00000000000 --- a/packages/core/test/render3/standalone_spec.ts +++ /dev/null @@ -1,70 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import {InjectionToken, Injector, ɵRenderFlags, ɵɵdefineComponent, ɵɵdefineInjector, ɵɵelement, ɵɵStandaloneFeature} from '../../src/core'; -import {getComponentDef} from '../../src/render3/definition'; - -describe('standalone components, directives and pipes', () => { - it('should filter out imported NgModules from a list of directives to match', () => { - const token = new InjectionToken('TestToken'); - - let count = 0; - class TestModule { - static ɵinj = ɵɵdefineInjector({ - providers: [{ - provide: token, - useFactory: - () => { - return `From module: ${count++}`; - } - }] - }); - } - - class TestComponent { - static ɵfac = () => new TestComponent(); - static ɵcmp = ɵɵdefineComponent({ - type: TestComponent, - selectors: [['test-cmp']], - decls: 1, - vars: 0, - template: - (rf: ɵRenderFlags, ctx: TestComponent) => { - if (rf & ɵRenderFlags.Create) { - ɵɵelement(0, 'div'); - } - }, - dependencies: [TestModule], - features: [ɵɵStandaloneFeature], - standalone: true - }); - } - - // simulate logic executed by ComponentFactory::create - const def = getComponentDef(TestComponent); - - if (def !== null && def.getStandaloneInjector !== null) { - const parentInjector = Injector.create({providers: []}); - expect(def.getStandaloneInjector(parentInjector)!.get(token)).toEqual('From module: 0'); - - // subsequent calls with the same same parent injector should not create new provider - // instances - expect(def.getStandaloneInjector(parentInjector)!.get(token)).toEqual('From module: 0'); - - // but specifying a different injector _should_ create a new provider instance - const otherParentInjector = Injector.create({providers: []}); - expect(def.getStandaloneInjector(otherParentInjector)!.get(token)).toEqual('From module: 1'); - - // again, same calls should not create new instances - expect(def.getStandaloneInjector(parentInjector)!.get(token)).toEqual('From module: 0'); - expect(def.getStandaloneInjector(otherParentInjector)!.get(token)).toEqual('From module: 1'); - } else { - fail('Failed to get component def or standalone feature function'); - } - }); -});