From 16497438d6656c044c4974cbbe4ed32a9127f392 Mon Sep 17 00:00:00 2001 From: Andrew Kushnir Date: Tue, 24 Mar 2020 17:19:47 -0700 Subject: [PATCH] fix(core): run `APP_INITIALIZER`s before accessing `LOCALE_ID` token in Ivy TestBed (#36237) Prior to this commit, Ivy TestBed was accessing locale ID before `APP_INITIALIZER` functions were called. This execution order is not consistent with the app bootstrap logic in `application_ref.ts`. This commit updates Ivy TestBed execution order to call initializers first (since they might affect `LOCALE_ID` token value) and accessing and setting locale ID after that. Fixes #36230. PR Close #36237 --- packages/core/test/test_bed_spec.ts | 17 ++++++++++++++++- .../core/testing/src/r3_test_bed_compiler.ts | 10 ++++++---- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/packages/core/test/test_bed_spec.ts b/packages/core/test/test_bed_spec.ts index 5ec2d3f556..6a3eecf927 100644 --- a/packages/core/test/test_bed_spec.ts +++ b/packages/core/test/test_bed_spec.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {ChangeDetectorRef, Compiler, Component, Directive, ErrorHandler, Inject, Injectable, InjectionToken, Injector, Input, ModuleWithProviders, NgModule, Optional, Pipe, Type, ViewChild, ɵsetClassMetadata as setClassMetadata, ɵɵdefineComponent as defineComponent, ɵɵdefineInjector as defineInjector, ɵɵdefineNgModule as defineNgModule, ɵɵsetNgModuleScope as setNgModuleScope, ɵɵtext as text} from '@angular/core'; +import {APP_INITIALIZER, ChangeDetectorRef, Compiler, Component, Directive, ErrorHandler, 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, ɵɵsetNgModuleScope as setNgModuleScope, ɵɵtext as text} from '@angular/core'; import {TestBed, getTestBed} from '@angular/core/testing/src/test_bed'; import {By} from '@angular/platform-browser'; import {expect} from '@angular/platform-browser/testing/src/matchers'; @@ -245,6 +245,21 @@ describe('TestBed', () => { expect(hello.nativeElement).toHaveText('Hello World!'); }); + it('should run `APP_INITIALIZER` before accessing `LOCALE_ID` provider', () => { + let locale: string = ''; + @NgModule({ + providers: [ + {provide: APP_INITIALIZER, useValue: () => locale = 'fr-FR', multi: true}, + {provide: LOCALE_ID, useFactory: () => locale} + ] + }) + class TestModule { + } + + TestBed.configureTestingModule({imports: [TestModule]}); + expect(TestBed.inject(LOCALE_ID)).toBe('fr-FR'); + }); + it('allow to override a provider', () => { TestBed.overrideProvider(NAME, {useValue: 'injected World!'}); const hello = TestBed.createComponent(HelloWorld); diff --git a/packages/core/testing/src/r3_test_bed_compiler.ts b/packages/core/testing/src/r3_test_bed_compiler.ts index 5a46e92403..a01aec19cf 100644 --- a/packages/core/testing/src/r3_test_bed_compiler.ts +++ b/packages/core/testing/src/r3_test_bed_compiler.ts @@ -257,14 +257,16 @@ export class R3TestBedCompiler { const parentInjector = this.platform.injector; this.testModuleRef = new NgModuleRef(this.testModuleType, parentInjector); - // Set the locale ID, it can be overridden for the tests - const localeId = this.testModuleRef.injector.get(LOCALE_ID, DEFAULT_LOCALE_ID); - setLocaleId(localeId); - // ApplicationInitStatus.runInitializers() is marked @internal to core. // Cast it to any before accessing it. (this.testModuleRef.injector.get(ApplicationInitStatus) as any).runInitializers(); + // Set locale ID after running app initializers, since locale information might be updated while + // running initializers. This is also consistent with the execution order while bootstrapping an + // app (see `packages/core/src/application_ref.ts` file). + const localeId = this.testModuleRef.injector.get(LOCALE_ID, DEFAULT_LOCALE_ID); + setLocaleId(localeId); + return this.testModuleRef; }