Skip to content
This repository was archived by the owner on Jan 19, 2023. It is now read-only.

Commit 120555a

Browse files
AndrewKushniralxhub
authored andcommitted
feat(core): support object-based DI flags in TestBed.inject() (angular#46761)
This commit applies the changes similar to the ones performed for the `inject()` function in angular@df246bb. The `TestBed.inject` function is updated to use previously added object-based API for options: now the flags argument supports passing an object which configures injection flags. DEPRECATED: The bit field signature of `TestBed.inject()` has been deprecated, in favor of the new options object. PR Close angular#46761
1 parent 841c8e5 commit 120555a

File tree

18 files changed

+191
-12
lines changed

18 files changed

+191
-12
lines changed

aio/tests/e2e/src/api-pages.e2e-spec.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,11 @@ describe('Api pages', () => {
7676

7777
it('should show all overloads of interface methods', async () => {
7878
await page.navigateTo('api/core/testing/TestBed');
79-
expect(await (await page.getInstanceMethodOverloads('inject')).length).toEqual(2);
79+
expect(await (await page.getInstanceMethodOverloads('inject')).length).toEqual(4);
8080
});
8181

8282
it('should show all overloads of pseudo-class methods', async () => {
8383
await page.navigateTo('api/core/testing/TestBed');
84-
expect(await (await page.getInstanceMethodOverloads('inject')).length).toEqual(2);
84+
expect(await (await page.getInstanceMethodOverloads('inject')).length).toEqual(4);
8585
});
8686
});

goldens/public-api/core/index.md

+8
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,10 @@ export const ENVIRONMENT_INITIALIZER: InjectionToken<() => void>;
492492
export abstract class EnvironmentInjector implements Injector {
493493
// (undocumented)
494494
abstract destroy(): void;
495+
abstract get<T>(token: ProviderToken<T>, notFoundValue: undefined, options: InjectOptions & {
496+
optional?: false;
497+
}): T;
498+
abstract get<T>(token: ProviderToken<T>, notFoundValue: null | undefined, options: InjectOptions): T | null;
495499
abstract get<T>(token: ProviderToken<T>, notFoundValue?: T, options?: InjectOptions): T;
496500
// @deprecated
497501
abstract get<T>(token: ProviderToken<T>, notFoundValue?: T, flags?: InjectFlags): T;
@@ -733,6 +737,10 @@ export abstract class Injector {
733737
parent?: Injector;
734738
name?: string;
735739
}): Injector;
740+
abstract get<T>(token: ProviderToken<T>, notFoundValue: undefined, options: InjectOptions & {
741+
optional?: false;
742+
}): T;
743+
abstract get<T>(token: ProviderToken<T>, notFoundValue: null | undefined, options: InjectOptions): T | null;
736744
abstract get<T>(token: ProviderToken<T>, notFoundValue?: T, options?: InjectOptions | InjectFlags): T;
737745
// @deprecated
738746
abstract get<T>(token: ProviderToken<T>, notFoundValue?: T, flags?: InjectFlags): T;

goldens/public-api/core/testing/index.md

+10-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { Directive } from '@angular/core';
1212
import { ElementRef } from '@angular/core';
1313
import { InjectFlags } from '@angular/core';
1414
import { InjectionToken } from '@angular/core';
15+
import { InjectOptions } from '@angular/core';
1516
import { NgModule } from '@angular/core';
1617
import { NgZone } from '@angular/core';
1718
import { Pipe } from '@angular/core';
@@ -115,8 +116,16 @@ export interface TestBed {
115116
get(token: any, notFoundValue?: any): any;
116117
initTestEnvironment(ngModule: Type<any> | Type<any>[], platform: PlatformRef, options?: TestEnvironmentOptions): void;
117118
// (undocumented)
118-
inject<T>(token: ProviderToken<T>, notFoundValue?: T, flags?: InjectFlags): T;
119+
inject<T>(token: ProviderToken<T>, notFoundValue: undefined, options: InjectOptions & {
120+
optional?: false;
121+
}): T;
122+
// (undocumented)
123+
inject<T>(token: ProviderToken<T>, notFoundValue: null | undefined, options: InjectOptions): T | null;
119124
// (undocumented)
125+
inject<T>(token: ProviderToken<T>, notFoundValue?: T, options?: InjectOptions): T;
126+
// @deprecated (undocumented)
127+
inject<T>(token: ProviderToken<T>, notFoundValue?: T, flags?: InjectFlags): T;
128+
// @deprecated (undocumented)
120129
inject<T>(token: ProviderToken<T>, notFoundValue: null, flags?: InjectFlags): T | null;
121130
// (undocumented)
122131
get ngModule(): Type<any> | Type<any>[];

packages/core/src/core_private_export.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export {defaultIterableDiffers as ɵdefaultIterableDiffers, defaultKeyValueDiffe
1212
export {ChangeDetectorStatus as ɵChangeDetectorStatus, isDefaultChangeDetectionStrategy as ɵisDefaultChangeDetectionStrategy} from './change_detection/constants';
1313
export {Console as ɵConsole} from './console';
1414
export {getDebugNodeR2 as ɵgetDebugNodeR2} from './debug/debug_node';
15-
export {setCurrentInjector as ɵsetCurrentInjector} from './di/injector_compatibility';
15+
export {convertToBitFlags as ɵconvertToBitFlags, setCurrentInjector as ɵsetCurrentInjector} from './di/injector_compatibility';
1616
export {getInjectableDef as ɵgetInjectableDef, ɵɵInjectableDeclaration, ɵɵInjectorDef} from './di/interface/defs';
1717
export {INJECTOR_SCOPE as ɵINJECTOR_SCOPE} from './di/scope';
1818
export {formatRuntimeError as ɵformatRuntimeError, RuntimeError as ɵRuntimeError} from './errors';

packages/core/src/di/injector.ts

+22
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,28 @@ export abstract class Injector {
4545
static THROW_IF_NOT_FOUND = THROW_IF_NOT_FOUND;
4646
static NULL: Injector = (/* @__PURE__ */ new NullInjector());
4747

48+
/**
49+
* Internal note on the `options?: InjectOptions|InjectFlags` override of the `get`
50+
* method: consider dropping the `InjectFlags` part in one of the major versions.
51+
* It can **not** be done in minor/patch, since it's breaking for custom injectors
52+
* that only implement the old `InjectorFlags` interface.
53+
*/
54+
55+
/**
56+
* Retrieves an instance from the injector based on the provided token.
57+
* @returns The instance from the injector if defined, otherwise the `notFoundValue`.
58+
* @throws When the `notFoundValue` is `undefined` or `Injector.THROW_IF_NOT_FOUND`.
59+
*/
60+
abstract get<T>(token: ProviderToken<T>, notFoundValue: undefined, options: InjectOptions&{
61+
optional?: false;
62+
}): T;
63+
/**
64+
* Retrieves an instance from the injector based on the provided token.
65+
* @returns The instance from the injector if defined, otherwise the `notFoundValue`.
66+
* @throws When the `notFoundValue` is `undefined` or `Injector.THROW_IF_NOT_FOUND`.
67+
*/
68+
abstract get<T>(token: ProviderToken<T>, notFoundValue: null|undefined, options: InjectOptions): T
69+
|null;
4870
/**
4971
* Retrieves an instance from the injector based on the provided token.
5072
* @returns The instance from the injector if defined, otherwise the `notFoundValue`.

packages/core/src/di/r3_injector.ts

+15
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,21 @@ interface Record<T> {
7777
* @developerPreview
7878
*/
7979
export abstract class EnvironmentInjector implements Injector {
80+
/**
81+
* Retrieves an instance from the injector based on the provided token.
82+
* @returns The instance from the injector if defined, otherwise the `notFoundValue`.
83+
* @throws When the `notFoundValue` is `undefined` or `Injector.THROW_IF_NOT_FOUND`.
84+
*/
85+
abstract get<T>(token: ProviderToken<T>, notFoundValue: undefined, options: InjectOptions&{
86+
optional?: false;
87+
}): T;
88+
/**
89+
* Retrieves an instance from the injector based on the provided token.
90+
* @returns The instance from the injector if defined, otherwise the `notFoundValue`.
91+
* @throws When the `notFoundValue` is `undefined` or `Injector.THROW_IF_NOT_FOUND`.
92+
*/
93+
abstract get<T>(token: ProviderToken<T>, notFoundValue: null|undefined, options: InjectOptions): T
94+
|null;
8095
/**
8196
* Retrieves an instance from the injector based on the provided token.
8297
* @returns The instance from the injector if defined, otherwise the `notFoundValue`.

packages/core/test/acceptance/di_spec.ts

+34-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
*/
88

99
import {CommonModule} from '@angular/common';
10-
import {Attribute, ChangeDetectorRef, Component, ComponentFactoryResolver, ComponentRef, createEnvironmentInjector, Directive, ElementRef, ENVIRONMENT_INITIALIZER, EnvironmentInjector, EventEmitter, forwardRef, Host, HostBinding, ImportedNgModuleProviders, importProvidersFrom, ImportProvidersSource, inject, Inject, Injectable, InjectFlags, InjectionToken, INJECTOR, Injector, Input, LOCALE_ID, ModuleWithProviders, NgModule, NgZone, Optional, Output, Pipe, PipeTransform, Provider, Self, SkipSelf, TemplateRef, Type, ViewChild, ViewContainerRef, ViewEncapsulation, ViewRef, ɵcreateInjector as createInjector, ɵDEFAULT_LOCALE_ID as DEFAULT_LOCALE_ID, ɵINJECTOR_SCOPE} from '@angular/core';
10+
import {Attribute, ChangeDetectorRef, Component, ComponentFactoryResolver, ComponentRef, createEnvironmentInjector, Directive, ElementRef, ENVIRONMENT_INITIALIZER, EnvironmentInjector, EventEmitter, forwardRef, Host, HostBinding, ImportedNgModuleProviders, importProvidersFrom, ImportProvidersSource, inject, Inject, Injectable, InjectFlags, InjectionToken, InjectOptions, INJECTOR, Injector, Input, LOCALE_ID, ModuleWithProviders, NgModule, NgZone, Optional, Output, Pipe, PipeTransform, Provider, Self, SkipSelf, TemplateRef, Type, ViewChild, ViewContainerRef, ViewEncapsulation, ViewRef, ɵcreateInjector as createInjector, ɵDEFAULT_LOCALE_ID as DEFAULT_LOCALE_ID, ɵINJECTOR_SCOPE} from '@angular/core';
1111
import {ViewRef as ViewRefInternal} from '@angular/core/src/render3/view_ref';
1212
import {TestBed} from '@angular/core/testing';
1313
import {By} from '@angular/platform-browser';
@@ -3467,6 +3467,39 @@ describe('di', () => {
34673467
expect(envInjector.get(TOKEN, undefined, InjectFlags.Optional)).toBeNull();
34683468
});
34693469

3470+
it('should include `null` into the result type when the optional flag is used', () => {
3471+
const TOKEN = new InjectionToken<string>('TOKEN');
3472+
3473+
@Component({
3474+
standalone: true,
3475+
template: '',
3476+
})
3477+
class TestCmp {
3478+
nodeInjector = inject(Injector);
3479+
envInjector = inject(EnvironmentInjector);
3480+
}
3481+
3482+
const {nodeInjector, envInjector} = TestBed.createComponent(TestCmp).componentInstance;
3483+
3484+
const flags: InjectOptions = {optional: true};
3485+
3486+
let nodeInjectorResult = nodeInjector.get(TOKEN, undefined, flags);
3487+
expect(nodeInjectorResult).toBe(null);
3488+
3489+
// Verify that `null` can be a valid value (from typing standpoint),
3490+
// the line below would fail a type check in case the result doesn't
3491+
// have `null` in the type.
3492+
nodeInjectorResult = null;
3493+
3494+
let envInjectorResult = envInjector.get(TOKEN, undefined, flags);
3495+
expect(envInjectorResult).toBe(null);
3496+
3497+
// Verify that `null` can be a valid value (from typing standpoint),
3498+
// the line below would fail a type check in case the result doesn't
3499+
// have `null` in the type.
3500+
envInjectorResult = null;
3501+
});
3502+
34703503
it('should be able to use skipSelf injection in NodeInjector', () => {
34713504
const TOKEN = new InjectionToken<string>('TOKEN', {
34723505
providedIn: 'root',

packages/core/test/bundling/animations/bundle.golden_symbols.json

+3
Original file line numberDiff line numberDiff line change
@@ -650,6 +650,9 @@
650650
{
651651
"name": "containsElement"
652652
},
653+
{
654+
"name": "convertToBitFlags"
655+
},
653656
{
654657
"name": "convertToMap"
655658
},

packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json

+3
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,9 @@
458458
{
459459
"name": "connectableObservableDescriptor"
460460
},
461+
{
462+
"name": "convertToBitFlags"
463+
},
461464
{
462465
"name": "createElementNode"
463466
},

packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json

+3
Original file line numberDiff line numberDiff line change
@@ -677,6 +677,9 @@
677677
{
678678
"name": "controlPath"
679679
},
680+
{
681+
"name": "convertToBitFlags"
682+
},
680683
{
681684
"name": "createDirectivesInstances"
682685
},

packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json

+3
Original file line numberDiff line numberDiff line change
@@ -647,6 +647,9 @@
647647
{
648648
"name": "controlPath"
649649
},
650+
{
651+
"name": "convertToBitFlags"
652+
},
650653
{
651654
"name": "createDirectivesInstances"
652655
},

packages/core/test/bundling/hello_world/bundle.golden_symbols.json

+3
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,9 @@
323323
{
324324
"name": "connectableObservableDescriptor"
325325
},
326+
{
327+
"name": "convertToBitFlags"
328+
},
326329
{
327330
"name": "createElementRef"
328331
},

packages/core/test/bundling/router/bundle.golden_symbols.json

+3
Original file line numberDiff line numberDiff line change
@@ -809,6 +809,9 @@
809809
{
810810
"name": "containsTree"
811811
},
812+
{
813+
"name": "convertToBitFlags"
814+
},
812815
{
813816
"name": "convertToParamMap"
814817
},

packages/core/test/bundling/standalone_bootstrap/bundle.golden_symbols.json

+3
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,9 @@
392392
{
393393
"name": "connectableObservableDescriptor"
394394
},
395+
{
396+
"name": "convertToBitFlags"
397+
},
395398
{
396399
"name": "createElementRef"
397400
},

packages/core/test/bundling/todo/bundle.golden_symbols.json

+3
Original file line numberDiff line numberDiff line change
@@ -557,6 +557,9 @@
557557
{
558558
"name": "connectableObservableDescriptor"
559559
},
560+
{
561+
"name": "convertToBitFlags"
562+
},
560563
{
561564
"name": "createDirectivesInstances"
562565
},

packages/core/test/test_bed_spec.ts

+41-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {APP_INITIALIZER, ChangeDetectorRef, Compiler, Component, Directive, ElementRef, ErrorHandler, getNgModuleById, Inject, Injectable, InjectionToken, Injector, Input, LOCALE_ID, ModuleWithProviders, NgModule, Optional, Pipe, Type, ViewChild, ɵsetClassMetadata as setClassMetadata, ɵɵdefineComponent as defineComponent, ɵɵdefineInjector as defineInjector, ɵɵdefineNgModule as defineNgModule, ɵɵelementEnd as elementEnd, ɵɵelementStart as elementStart, ɵɵsetNgModuleScope as setNgModuleScope, ɵɵtext as text} from '@angular/core';
9+
import {APP_INITIALIZER, ChangeDetectorRef, Compiler, Component, Directive, ElementRef, ErrorHandler, getNgModuleById, Inject, Injectable, InjectFlags, InjectionToken, InjectOptions, Injector, Input, LOCALE_ID, ModuleWithProviders, NgModule, Optional, Pipe, Type, ViewChild, ɵsetClassMetadata as setClassMetadata, ɵɵdefineComponent as defineComponent, ɵɵdefineInjector as defineInjector, ɵɵdefineNgModule as defineNgModule, ɵɵelementEnd as elementEnd, ɵɵelementStart as elementStart, ɵɵsetNgModuleScope as setNgModuleScope, ɵɵtext as text} from '@angular/core';
1010
import {TestBed, TestBedImpl} from '@angular/core/testing/src/test_bed';
1111
import {By} from '@angular/platform-browser';
1212
import {expect} from '@angular/platform-browser/testing/src/matchers';
@@ -1853,6 +1853,46 @@ describe('TestBed', () => {
18531853
fixture.detectChanges();
18541854
expect(fixture!.nativeElement.textContent).toContain('changed');
18551855
});
1856+
1857+
describe('TestBed.inject', () => {
1858+
describe('injection flags', () => {
1859+
it('should be able to optionally inject a token', () => {
1860+
const TOKEN = new InjectionToken<string>('TOKEN');
1861+
1862+
expect(TestBed.inject(TOKEN, undefined, {optional: true})).toBeNull();
1863+
expect(TestBed.inject(TOKEN, undefined, InjectFlags.Optional)).toBeNull();
1864+
1865+
expect(TestBed.inject(TOKEN, undefined, {optional: true})).toBeNull();
1866+
expect(TestBed.inject(TOKEN, undefined, InjectFlags.Optional)).toBeNull();
1867+
});
1868+
1869+
it('should include `null` into the result type when the optional flag is used', () => {
1870+
const TOKEN = new InjectionToken<string>('TOKEN');
1871+
1872+
const flags: InjectOptions = {optional: true};
1873+
let result = TestBed.inject(TOKEN, undefined, flags);
1874+
expect(result).toBe(null);
1875+
1876+
// Verify that `null` can be a valid value (from typing standpoint),
1877+
// the line below would fail a type check in case the result doesn't
1878+
// have `null` in the type.
1879+
result = null;
1880+
});
1881+
1882+
it('should be able to use skipSelf injection', () => {
1883+
const TOKEN = new InjectionToken<string>('TOKEN');
1884+
TestBed.configureTestingModule({
1885+
providers: [{provide: TOKEN, useValue: 'from TestBed'}],
1886+
});
1887+
1888+
expect(TestBed.inject(TOKEN)).toBe('from TestBed');
1889+
1890+
expect(TestBed.inject(TOKEN, undefined, {skipSelf: true, optional: true})).toBeNull();
1891+
expect(TestBed.inject(TOKEN, undefined, InjectFlags.SkipSelf | InjectFlags.Optional))
1892+
.toBeNull();
1893+
});
1894+
});
1895+
});
18561896
});
18571897

18581898

packages/core/testing/src/test_bed.ts

+30-4
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,15 @@ import {
1616
Directive,
1717
InjectFlags,
1818
InjectionToken,
19+
InjectOptions,
1920
Injector,
2021
NgModule,
2122
NgZone,
2223
Pipe,
2324
PlatformRef,
2425
ProviderToken,
2526
Type,
27+
ɵconvertToBitFlags as convertToBitFlags,
2628
ɵflushModuleScopingQueueAsMuchAsPossible as flushModuleScopingQueueAsMuchAsPossible,
2729
ɵgetUnknownElementStrictMode as getUnknownElementStrictMode,
2830
ɵgetUnknownPropertyStrictMode as getUnknownPropertyStrictMode,
@@ -87,7 +89,14 @@ export interface TestBed {
8789

8890
compileComponents(): Promise<any>;
8991

92+
inject<T>(token: ProviderToken<T>, notFoundValue: undefined, options: InjectOptions&{
93+
optional?: false
94+
}): T;
95+
inject<T>(token: ProviderToken<T>, notFoundValue: null|undefined, options: InjectOptions): T|null;
96+
inject<T>(token: ProviderToken<T>, notFoundValue?: T, options?: InjectOptions): T;
97+
/** @deprecated use object-based flags (`InjectOptions`) instead. */
9098
inject<T>(token: ProviderToken<T>, notFoundValue?: T, flags?: InjectFlags): T;
99+
/** @deprecated use object-based flags (`InjectOptions`) instead. */
91100
inject<T>(token: ProviderToken<T>, notFoundValue: null, flags?: InjectFlags): T|null;
92101

93102
/** @deprecated from v9.0.0 use TestBed.inject */
@@ -290,10 +299,19 @@ export class TestBedImpl implements TestBed {
290299
return TestBedImpl.INSTANCE.overrideProvider(token, provider);
291300
}
292301

302+
static inject<T>(token: ProviderToken<T>, notFoundValue: undefined, options: InjectOptions&{
303+
optional?: false
304+
}): T;
305+
static inject<T>(token: ProviderToken<T>, notFoundValue: null|undefined, options: InjectOptions):
306+
T|null;
307+
static inject<T>(token: ProviderToken<T>, notFoundValue?: T, options?: InjectOptions): T;
308+
/** @deprecated use object-based flags (`InjectOptions`) instead. */
293309
static inject<T>(token: ProviderToken<T>, notFoundValue?: T, flags?: InjectFlags): T;
310+
/** @deprecated use object-based flags (`InjectOptions`) instead. */
294311
static inject<T>(token: ProviderToken<T>, notFoundValue: null, flags?: InjectFlags): T|null;
295-
static inject<T>(token: ProviderToken<T>, notFoundValue?: T|null, flags?: InjectFlags): T|null {
296-
return TestBedImpl.INSTANCE.inject(token, notFoundValue, flags);
312+
static inject<T>(
313+
token: ProviderToken<T>, notFoundValue?: T|null, flags?: InjectFlags|InjectOptions): T|null {
314+
return TestBedImpl.INSTANCE.inject(token, notFoundValue, convertToBitFlags(flags));
297315
}
298316

299317
/** @deprecated from v9.0.0 use TestBed.inject */
@@ -468,14 +486,22 @@ export class TestBedImpl implements TestBed {
468486
return this.compiler.compileComponents();
469487
}
470488

489+
inject<T>(token: ProviderToken<T>, notFoundValue: undefined, options: InjectOptions&{
490+
optional: true
491+
}): T|null;
492+
inject<T>(token: ProviderToken<T>, notFoundValue?: T, options?: InjectOptions): T;
493+
inject<T>(token: ProviderToken<T>, notFoundValue: null, options?: InjectOptions): T|null;
494+
/** @deprecated use object-based flags (`InjectOptions`) instead. */
471495
inject<T>(token: ProviderToken<T>, notFoundValue?: T, flags?: InjectFlags): T;
496+
/** @deprecated use object-based flags (`InjectOptions`) instead. */
472497
inject<T>(token: ProviderToken<T>, notFoundValue: null, flags?: InjectFlags): T|null;
473-
inject<T>(token: ProviderToken<T>, notFoundValue?: T|null, flags?: InjectFlags): T|null {
498+
inject<T>(token: ProviderToken<T>, notFoundValue?: T|null, flags?: InjectFlags|InjectOptions): T
499+
|null {
474500
if (token as unknown === TestBed) {
475501
return this as any;
476502
}
477503
const UNDEFINED = {} as unknown as T;
478-
const result = this.testModuleRef.injector.get(token, UNDEFINED, flags);
504+
const result = this.testModuleRef.injector.get(token, UNDEFINED, convertToBitFlags(flags));
479505
return result === UNDEFINED ? this.compiler.injector.get(token, notFoundValue, flags) as any :
480506
result;
481507
}

0 commit comments

Comments
 (0)