Skip to content

Commit

Permalink
fixup! feat(core): support object-based DI flags in TestBed.inject()
Browse files Browse the repository at this point in the history
  • Loading branch information
AndrewKushnir committed Sep 24, 2022
1 parent ad411b8 commit 6159102
Show file tree
Hide file tree
Showing 8 changed files with 102 additions and 14 deletions.
8 changes: 8 additions & 0 deletions goldens/public-api/core/index.md
Expand Up @@ -492,6 +492,10 @@ export const ENVIRONMENT_INITIALIZER: InjectionToken<() => void>;
export abstract class EnvironmentInjector implements Injector {
// (undocumented)
abstract destroy(): void;
abstract get<T>(token: ProviderToken<T>, notFoundValue: undefined, options: InjectOptions & {
optional?: false;
}): T;
abstract get<T>(token: ProviderToken<T>, notFoundValue: null | undefined, options: InjectOptions): T | null;
abstract get<T>(token: ProviderToken<T>, notFoundValue?: T, options?: InjectOptions): T;
// @deprecated
abstract get<T>(token: ProviderToken<T>, notFoundValue?: T, flags?: InjectFlags): T;
Expand Down Expand Up @@ -733,6 +737,10 @@ export abstract class Injector {
parent?: Injector;
name?: string;
}): Injector;
abstract get<T>(token: ProviderToken<T>, notFoundValue: undefined, options: InjectOptions & {
optional?: false;
}): T;
abstract get<T>(token: ProviderToken<T>, notFoundValue: null | undefined, options: InjectOptions): T | null;
abstract get<T>(token: ProviderToken<T>, notFoundValue?: T, options?: InjectOptions | InjectFlags): T;
// @deprecated
abstract get<T>(token: ProviderToken<T>, notFoundValue?: T, flags?: InjectFlags): T;
Expand Down
8 changes: 4 additions & 4 deletions goldens/public-api/core/testing/index.md
Expand Up @@ -117,12 +117,12 @@ export interface TestBed {
initTestEnvironment(ngModule: Type<any> | Type<any>[], platform: PlatformRef, options?: TestEnvironmentOptions): void;
// (undocumented)
inject<T>(token: ProviderToken<T>, notFoundValue: undefined, options: InjectOptions & {
optional: true;
}): T | null;
optional?: false;
}): T;
// (undocumented)
inject<T>(token: ProviderToken<T>, notFoundValue?: T, options?: InjectOptions): T;
inject<T>(token: ProviderToken<T>, notFoundValue: null | undefined, options: InjectOptions): T | null;
// (undocumented)
inject<T>(token: ProviderToken<T>, notFoundValue: null, options?: InjectOptions): T | null;
inject<T>(token: ProviderToken<T>, notFoundValue?: T, options?: InjectOptions): T;
// @deprecated (undocumented)
inject<T>(token: ProviderToken<T>, notFoundValue?: T, flags?: InjectFlags): T;
// @deprecated (undocumented)
Expand Down
16 changes: 16 additions & 0 deletions packages/core/src/di/injector.ts
Expand Up @@ -51,6 +51,22 @@ export abstract class Injector {
* It can **not** be done in minor/patch, since it's breaking for custom injectors
* that only implement the old `InjectorFlags` interface.
*/

/**
* Retrieves an instance from the injector based on the provided token.
* @returns The instance from the injector if defined, otherwise the `notFoundValue`.
* @throws When the `notFoundValue` is `undefined` or `Injector.THROW_IF_NOT_FOUND`.
*/
abstract get<T>(token: ProviderToken<T>, notFoundValue: undefined, options: InjectOptions&{
optional?: false;
}): T;
/**
* Retrieves an instance from the injector based on the provided token.
* @returns The instance from the injector if defined, otherwise the `notFoundValue`.
* @throws When the `notFoundValue` is `undefined` or `Injector.THROW_IF_NOT_FOUND`.
*/
abstract get<T>(token: ProviderToken<T>, notFoundValue: null|undefined, options: InjectOptions): T
|null;
/**
* Retrieves an instance from the injector based on the provided token.
* @returns The instance from the injector if defined, otherwise the `notFoundValue`.
Expand Down
15 changes: 15 additions & 0 deletions packages/core/src/di/r3_injector.ts
Expand Up @@ -77,6 +77,21 @@ interface Record<T> {
* @developerPreview
*/
export abstract class EnvironmentInjector implements Injector {
/**
* Retrieves an instance from the injector based on the provided token.
* @returns The instance from the injector if defined, otherwise the `notFoundValue`.
* @throws When the `notFoundValue` is `undefined` or `Injector.THROW_IF_NOT_FOUND`.
*/
abstract get<T>(token: ProviderToken<T>, notFoundValue: undefined, options: InjectOptions&{
optional?: false;
}): T;
/**
* Retrieves an instance from the injector based on the provided token.
* @returns The instance from the injector if defined, otherwise the `notFoundValue`.
* @throws When the `notFoundValue` is `undefined` or `Injector.THROW_IF_NOT_FOUND`.
*/
abstract get<T>(token: ProviderToken<T>, notFoundValue: null|undefined, options: InjectOptions): T
|null;
/**
* Retrieves an instance from the injector based on the provided token.
* @returns The instance from the injector if defined, otherwise the `notFoundValue`.
Expand Down
35 changes: 34 additions & 1 deletion packages/core/test/acceptance/di_spec.ts
Expand Up @@ -7,7 +7,7 @@
*/

import {CommonModule} from '@angular/common';
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';
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';
import {ViewRef as ViewRefInternal} from '@angular/core/src/render3/view_ref';
import {TestBed} from '@angular/core/testing';
import {By} from '@angular/platform-browser';
Expand Down Expand Up @@ -3467,6 +3467,39 @@ describe('di', () => {
expect(envInjector.get(TOKEN, undefined, InjectFlags.Optional)).toBeNull();
});

it('should include `null` into the result type when the optional flag is used', () => {
const TOKEN = new InjectionToken<string>('TOKEN');

@Component({
standalone: true,
template: '',
})
class TestCmp {
nodeInjector = inject(Injector);
envInjector = inject(EnvironmentInjector);
}

const {nodeInjector, envInjector} = TestBed.createComponent(TestCmp).componentInstance;

const flags: InjectOptions = {optional: true};

let nodeInjectorResult = nodeInjector.get(TOKEN, undefined, flags);
expect(nodeInjectorResult).toBe(null);

// Verify that `null` can be a valid value (from typing standpoint),
// the line below would fail a type check in case the result doesn't
// have `null` in the type.
nodeInjectorResult = null;

let envInjectorResult = envInjector.get(TOKEN, undefined, flags);
expect(envInjectorResult).toBe(null);

// Verify that `null` can be a valid value (from typing standpoint),
// the line below would fail a type check in case the result doesn't
// have `null` in the type.
envInjectorResult = null;
});

it('should be able to use skipSelf injection in NodeInjector', () => {
const TOKEN = new InjectionToken<string>('TOKEN', {
providedIn: 'root',
Expand Down
15 changes: 14 additions & 1 deletion packages/core/test/test_bed_spec.ts
Expand Up @@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/

import {APP_INITIALIZER, ChangeDetectorRef, Compiler, Component, Directive, ElementRef, ErrorHandler, getNgModuleById, Inject, Injectable, InjectFlags, 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';
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';
import {TestBed, TestBedImpl} from '@angular/core/testing/src/test_bed';
import {By} from '@angular/platform-browser';
import {expect} from '@angular/platform-browser/testing/src/matchers';
Expand Down Expand Up @@ -1866,6 +1866,19 @@ describe('TestBed', () => {
expect(TestBed.inject(TOKEN, undefined, InjectFlags.Optional)).toBeNull();
});

it('should include `null` into the result type when the optional flag is used', () => {
const TOKEN = new InjectionToken<string>('TOKEN');

const flags: InjectOptions = {optional: true};
let result = TestBed.inject(TOKEN, undefined, flags);
expect(result).toBe(null);

// Verify that `null` can be a valid value (from typing standpoint),
// the line below would fail a type check in case the result doesn't
// have `null` in the type.
result = null;
});

it('should be able to use skipSelf injection', () => {
const TOKEN = new InjectionToken<string>('TOKEN');
TestBed.configureTestingModule({
Expand Down
13 changes: 7 additions & 6 deletions packages/core/testing/src/test_bed.ts
Expand Up @@ -90,10 +90,10 @@ export interface TestBed {
compileComponents(): Promise<any>;

inject<T>(token: ProviderToken<T>, notFoundValue: undefined, options: InjectOptions&{
optional: true
}): T|null;
optional?: false
}): T;
inject<T>(token: ProviderToken<T>, notFoundValue: null|undefined, options: InjectOptions): T|null;
inject<T>(token: ProviderToken<T>, notFoundValue?: T, options?: InjectOptions): T;
inject<T>(token: ProviderToken<T>, notFoundValue: null, options?: InjectOptions): T|null;
/** @deprecated use object-based flags (`InjectOptions`) instead. */
inject<T>(token: ProviderToken<T>, notFoundValue?: T, flags?: InjectFlags): T;
/** @deprecated use object-based flags (`InjectOptions`) instead. */
Expand Down Expand Up @@ -299,11 +299,12 @@ export class TestBedImpl implements TestBed {
return TestBedImpl.INSTANCE.overrideProvider(token, provider);
}

static inject<T>(token: ProviderToken<T>, notFoundValue?: T, options?: InjectOptions): T;
static inject<T>(token: ProviderToken<T>, notFoundValue?: T, options?: InjectOptions&{
static inject<T>(token: ProviderToken<T>, notFoundValue: undefined, options: InjectOptions&{
optional?: false
}): T;
static inject<T>(token: ProviderToken<T>, notFoundValue: null, options?: InjectOptions): T|null;
static inject<T>(token: ProviderToken<T>, notFoundValue: null|undefined, options: InjectOptions):
T|null;
static inject<T>(token: ProviderToken<T>, notFoundValue?: T, options?: InjectOptions): T;
/** @deprecated use object-based flags (`InjectOptions`) instead. */
static inject<T>(token: ProviderToken<T>, notFoundValue?: T, flags?: InjectFlags): T;
/** @deprecated use object-based flags (`InjectOptions`) instead. */
Expand Down
6 changes: 4 additions & 2 deletions packages/examples/core/di/ts/injector_spec.ts
Expand Up @@ -6,12 +6,14 @@
* found in the LICENSE file at https://angular.io/license
*/

import {inject, InjectFlags, InjectionToken, Injector, ProviderToken, ɵsetCurrentInjector as setCurrentInjector} from '@angular/core';
import {inject, InjectFlags, InjectionToken, InjectOptions, Injector, ProviderToken, ɵsetCurrentInjector as setCurrentInjector} from '@angular/core';

class MockRootScopeInjector implements Injector {
constructor(readonly parent: Injector) {}

get<T>(token: ProviderToken<T>, defaultValue?: any, flags: InjectFlags = InjectFlags.Default): T {
get<T>(
token: ProviderToken<T>, defaultValue?: any,
flags: InjectFlags|InjectOptions = InjectFlags.Default): T {
if ((token as any).ɵprov && (token as any).ɵprov.providedIn === 'root') {
const old = setCurrentInjector(this);
try {
Expand Down

0 comments on commit 6159102

Please sign in to comment.