From ca40565f9a9b3850ea27544081da49c8213b826f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mi=C5=A1ko=20Hevery?= Date: Mon, 19 Nov 2018 14:55:57 -0800 Subject: [PATCH] fix(ivy): hack implementation of host styles (#27180) PR Close #27180 --- packages/core/src/render3/instructions.ts | 109 ++++++++++- .../bundle.golden_symbols.json | 12 ++ .../bundling/todo/bundle.golden_symbols.json | 12 ++ .../todo_r2/bundle.golden_symbols.json | 12 ++ .../forms/test/reactive_integration_spec.ts | 183 +++++++++--------- .../forms/test/template_integration_spec.ts | 137 ++++++------- .../test/value_accessor_integration_spec.ts | 1 - 7 files changed, 284 insertions(+), 182 deletions(-) diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index df1d347ee4..3f2b0a5add 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import './ng_dev_mode'; + import {resolveForwardRef} from '../di/forward_ref'; import {InjectionToken} from '../di/injection_token'; import {Injector} from '../di/injector'; @@ -16,6 +16,7 @@ import {Sanitizer} from '../sanitization/security'; import {StyleSanitizeFn} from '../sanitization/style_sanitizer'; import {Type} from '../type'; import {noop} from '../util/noop'; + import {assertDefined, assertEqual, assertLessThan, assertNotEqual} from './assert'; import {attachPatchData, getComponentViewByInstance} from './context_discovery'; import {diPublicInInjector, getNodeInjectable, getOrCreateInjectable, getOrCreateNodeInjectorForNode, injectAttributeImpl} from './di'; @@ -40,7 +41,7 @@ import {createStylingContextTemplate, renderStyleAndClassBindings, updateClassPr import {BoundPlayerFactory} from './styling/player_factory'; import {getStylingContext} from './styling/util'; import {NO_CHANGE} from './tokens'; -import {getComponentViewByIndex, getNativeByIndex, getNativeByTNode, getRootContext, getRootView, getTNode, isComponent, isComponentDef, isDifferent, loadInternal, readPatchedLViewData, stringify} from './util'; +import {getComponentViewByIndex, getNativeByIndex, getNativeByTNode, getRootContext, getRootView, getTNode, isComponent, isComponentDef, isDifferent, loadInternal, readElementValue, readPatchedLViewData, stringify} from './util'; /** * A permanent marker promise which signifies that the current CD tree is @@ -125,6 +126,12 @@ export function setHostBindings(tView: TView, viewData: LViewData): void { viewData[BINDING_INDEX] = bindingRootIndex; // We must subtract the header offset because the load() instruction // expects a raw, unadjusted index. + // : set the `previousOrParentTNode` so that hostBindings functions can + // correctly retrieve it. This should be removed once we call the hostBindings function + // inline as part of the `RenderFlags.Create` because in that case the value will already be + // correctly set. + setPreviousOrParentTNode(getTView().data[currentElementIndex + HEADER_OFFSET] as TNode); + // instruction(currentDirectiveIndex - HEADER_OFFSET, currentElementIndex); currentDirectiveIndex++; } @@ -1069,17 +1076,21 @@ function generatePropertyAliases( * This instruction is meant to handle the [class.foo]="exp" case * * @param index The index of the element to update in the data array - * @param className Name of class to toggle. Because it is going to DOM, this is not subject to + * @param classIndex Index of class to toggle. Because it is going to DOM, this is not subject to * renaming as part of minification. * @param value A value indicating if a given class should be added or removed. * @param directiveIndex the index for the directive that is attempting to change styling. */ export function elementClassProp( - index: number, stylingIndex: number, value: boolean | PlayerFactory, + index: number, classIndex: number, value: boolean | PlayerFactory, directiveIndex?: number): void { + if (directiveIndex != undefined) { + return hackImplementationOfElementClassProp( + index, classIndex, value, directiveIndex); // proper supported in next PR + } const val = (value instanceof BoundPlayerFactory) ? (value as BoundPlayerFactory) : (!!value); - updateElementClassProp(getStylingContext(index, getViewData()), stylingIndex, val); + updateElementClassProp(getStylingContext(index, getViewData()), classIndex, val); } /** @@ -1115,7 +1126,13 @@ export function elementStyling( classDeclarations?: (string | boolean | InitialStylingFlags)[] | null, styleDeclarations?: (string | boolean | InitialStylingFlags)[] | null, styleSanitizer?: StyleSanitizeFn | null, directiveIndex?: number): void { - if (directiveIndex) return; // supported in next PR + if (directiveIndex !== undefined) { + getCreationMode() && + hackImplementationOfElementStyling( + classDeclarations || null, styleDeclarations || null, styleSanitizer || null, + directiveIndex); // supported in next PR + return; + } const tNode = getPreviousOrParentTNode(); const inputData = initializeTNodeInputs(tNode); @@ -1159,7 +1176,9 @@ export function elementStyling( * @param directiveIndex the index for the directive that is attempting to change styling. */ export function elementStylingApply(index: number, directiveIndex?: number): void { - if (directiveIndex) return; // supported in next PR + if (directiveIndex != undefined) { + return hackImplementationOfElementStylingApply(index, directiveIndex); // supported in next PR + } const viewData = getViewData(); const isFirstRender = (viewData[FLAGS] & LViewFlags.CreationMode) !== 0; const totalPlayersQueued = renderStyleAndClassBindings( @@ -1194,7 +1213,9 @@ export function elementStylingApply(index: number, directiveIndex?: number): voi export function elementStyleProp( index: number, styleIndex: number, value: string | number | String | PlayerFactory | null, suffix?: string, directiveIndex?: number): void { - if (directiveIndex) return; // supported in next PR + if (directiveIndex != undefined) + return hackImplementationOfElementStyleProp( + index, styleIndex, value, suffix, directiveIndex); // supported in next PR let valueToAdd: string|null = null; if (value) { if (suffix) { @@ -1237,7 +1258,9 @@ export function elementStyleProp( export function elementStylingMap( index: number, classes: {[key: string]: any} | string | NO_CHANGE | null, styles?: {[styleName: string]: any} | NO_CHANGE | null, directiveIndex?: number): void { - if (directiveIndex) return; // supported in next PR + if (directiveIndex != undefined) + return hackImplementationOfElementStylingMap( + index, classes, styles, directiveIndex); // supported in next PR const viewData = getViewData(); const tNode = getTNode(index, viewData); const stylingContext = getStylingContext(index, viewData); @@ -1250,6 +1273,74 @@ export function elementStylingMap( updateStylingMap(stylingContext, classes, styles); } +/* START OF HACK BLOCK */ +/* + * HACK + * The code below is a quick and dirty implementation of the host style binding so that we can make + * progress on TestBed. Once the correct implementation is created this code should be removed. + */ +interface HostStylingHack { + classDeclarations: string[]; + styleDeclarations: string[]; + styleSanitizer: StyleSanitizeFn|null; +} +interface HostStylingHackMap { + [directiveIndex: number]: HostStylingHack; +} + +function hackImplementationOfElementStyling( + classDeclarations: (string | boolean | InitialStylingFlags)[] | null, + styleDeclarations: (string | boolean | InitialStylingFlags)[] | null, + styleSanitizer: StyleSanitizeFn | null, directiveIndex: number): void { + const node = getNativeByTNode(getPreviousOrParentTNode(), getViewData()); + ngDevMode && assertDefined(node, 'expecting parent DOM node'); + const hostStylingHackMap: HostStylingHackMap = + ((node as any).hostStylingHack || ((node as any).hostStylingHack = {})); + hostStylingHackMap[directiveIndex] = { + classDeclarations: hackSquashDeclaration(classDeclarations), + styleDeclarations: hackSquashDeclaration(styleDeclarations), styleSanitizer + }; +} + +function hackSquashDeclaration(declarations: (string | boolean | InitialStylingFlags)[] | null): + string[] { + // assume the array is correct. This should be fine for View Engine compatibility. + return declarations || [] as any; +} + +function hackImplementationOfElementClassProp( + index: number, classIndex: number, value: boolean | PlayerFactory, + directiveIndex: number): void { + const node = getNativeByIndex(index, getViewData()); + ngDevMode && assertDefined(node, 'could not locate node'); + const hostStylingHack: HostStylingHack = (node as any).hostStylingHack[directiveIndex]; + const className = hostStylingHack.classDeclarations[classIndex]; + const renderer = getRenderer(); + if (isProceduralRenderer(renderer)) { + value ? renderer.addClass(node, className) : renderer.removeClass(node, className); + } else { + const classList = (node as HTMLElement).classList; + value ? classList.add(className) : classList.remove(className); + } +} + +function hackImplementationOfElementStylingApply(index: number, directiveIndex?: number): void { + // Do nothing because the hack implementation is eager. +} + +function hackImplementationOfElementStyleProp( + index: number, styleIndex: number, value: string | number | String | PlayerFactory | null, + suffix?: string, directiveIndex?: number): void { + throw new Error('unimplemented. Should not be needed by ViewEngine compatibility'); +} + +function hackImplementationOfElementStylingMap( + index: number, classes: {[key: string]: any} | string | NO_CHANGE | null, + styles?: {[styleName: string]: any} | NO_CHANGE | null, directiveIndex?: number): void { + throw new Error('unimplemented. Should not be needed by ViewEngine compatibility'); +} + +/* END OF HACK BLOCK */ ////////////////////////// //// Text ////////////////////////// diff --git a/packages/core/test/bundling/animation_world/bundle.golden_symbols.json b/packages/core/test/bundling/animation_world/bundle.golden_symbols.json index ec84f12fa5..0fdfdf721e 100644 --- a/packages/core/test/bundling/animation_world/bundle.golden_symbols.json +++ b/packages/core/test/bundling/animation_world/bundle.golden_symbols.json @@ -785,6 +785,18 @@ { "name": "getViewData" }, + { + "name": "hackImplementationOfElementStyling" + }, + { + "name": "hackImplementationOfElementStylingApply" + }, + { + "name": "hackImplementationOfElementStylingMap" + }, + { + "name": "hackSquashDeclaration" + }, { "name": "hasParentInjector" }, diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json index 6a2f61b698..427f0c9959 100644 --- a/packages/core/test/bundling/todo/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json @@ -818,6 +818,18 @@ { "name": "getViewData" }, + { + "name": "hackImplementationOfElementClassProp" + }, + { + "name": "hackImplementationOfElementStyling" + }, + { + "name": "hackImplementationOfElementStylingApply" + }, + { + "name": "hackSquashDeclaration" + }, { "name": "hasParentInjector" }, diff --git a/packages/core/test/bundling/todo_r2/bundle.golden_symbols.json b/packages/core/test/bundling/todo_r2/bundle.golden_symbols.json index 2ec554f072..bf92044df9 100644 --- a/packages/core/test/bundling/todo_r2/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo_r2/bundle.golden_symbols.json @@ -1943,6 +1943,18 @@ { "name": "globalListener" }, + { + "name": "hackImplementationOfElementClassProp" + }, + { + "name": "hackImplementationOfElementStyling" + }, + { + "name": "hackImplementationOfElementStylingApply" + }, + { + "name": "hackSquashDeclaration" + }, { "name": "hasBalancedQuotes" }, diff --git a/packages/forms/test/reactive_integration_spec.ts b/packages/forms/test/reactive_integration_spec.ts index 2ba8f67604..22a1c16191 100644 --- a/packages/forms/test/reactive_integration_spec.ts +++ b/packages/forms/test/reactive_integration_spec.ts @@ -12,7 +12,6 @@ import {AbstractControl, AsyncValidator, AsyncValidatorFn, COMPOSITION_BUFFER_MO import {By} from '@angular/platform-browser/src/dom/debug/by'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; import {dispatchEvent} from '@angular/platform-browser/testing/src/browser_util'; -import {fixmeIvy} from '@angular/private/testing'; import {merge, timer} from 'rxjs'; import {tap} from 'rxjs/operators'; @@ -713,133 +712,127 @@ import {MyInput, MyInputForm} from './value_accessor_integration_spec'; }); - fixmeIvy('Host bindings to styles do not yet work') && - describe('setting status classes', () => { - it('should work with single fields', () => { - const fixture = initTest(FormControlComp); - const control = new FormControl('', Validators.required); - fixture.componentInstance.control = control; - fixture.detectChanges(); + describe('setting status classes', () => { + it('should work with single fields', () => { + const fixture = initTest(FormControlComp); + const control = new FormControl('', Validators.required); + fixture.componentInstance.control = control; + fixture.detectChanges(); - const input = fixture.debugElement.query(By.css('input')).nativeElement; - expect(sortedClassList(input)).toEqual(['ng-invalid', 'ng-pristine', 'ng-untouched']); + const input = fixture.debugElement.query(By.css('input')).nativeElement; + expect(sortedClassList(input)).toEqual(['ng-invalid', 'ng-pristine', 'ng-untouched']); - dispatchEvent(input, 'blur'); - fixture.detectChanges(); + dispatchEvent(input, 'blur'); + fixture.detectChanges(); - expect(sortedClassList(input)).toEqual(['ng-invalid', 'ng-pristine', 'ng-touched']); + expect(sortedClassList(input)).toEqual(['ng-invalid', 'ng-pristine', 'ng-touched']); - input.value = 'updatedValue'; - dispatchEvent(input, 'input'); - fixture.detectChanges(); + input.value = 'updatedValue'; + dispatchEvent(input, 'input'); + fixture.detectChanges(); - expect(sortedClassList(input)).toEqual(['ng-dirty', 'ng-touched', 'ng-valid']); - }); + expect(sortedClassList(input)).toEqual(['ng-dirty', 'ng-touched', 'ng-valid']); + }); - it('should work with single fields and async validators', fakeAsync(() => { - const fixture = initTest(FormControlComp); - const control = new FormControl('', null !, uniqLoginAsyncValidator('good')); - fixture.debugElement.componentInstance.control = control; - fixture.detectChanges(); + it('should work with single fields and async validators', fakeAsync(() => { + const fixture = initTest(FormControlComp); + const control = new FormControl('', null !, uniqLoginAsyncValidator('good')); + fixture.debugElement.componentInstance.control = control; + fixture.detectChanges(); - const input = fixture.debugElement.query(By.css('input')).nativeElement; - expect(sortedClassList(input)).toEqual([ - 'ng-pending', 'ng-pristine', 'ng-untouched' - ]); + const input = fixture.debugElement.query(By.css('input')).nativeElement; + expect(sortedClassList(input)).toEqual(['ng-pending', 'ng-pristine', 'ng-untouched']); - dispatchEvent(input, 'blur'); - fixture.detectChanges(); - expect(sortedClassList(input)).toEqual(['ng-pending', 'ng-pristine', 'ng-touched']); + dispatchEvent(input, 'blur'); + fixture.detectChanges(); + expect(sortedClassList(input)).toEqual(['ng-pending', 'ng-pristine', 'ng-touched']); - input.value = 'good'; - dispatchEvent(input, 'input'); - tick(); - fixture.detectChanges(); + input.value = 'good'; + dispatchEvent(input, 'input'); + tick(); + fixture.detectChanges(); - expect(sortedClassList(input)).toEqual(['ng-dirty', 'ng-touched', 'ng-valid']); - })); + expect(sortedClassList(input)).toEqual(['ng-dirty', 'ng-touched', 'ng-valid']); + })); - it('should work with single fields that combines async and sync validators', - fakeAsync(() => { - const fixture = initTest(FormControlComp); - const control = - new FormControl('', Validators.required, uniqLoginAsyncValidator('good')); - fixture.debugElement.componentInstance.control = control; - fixture.detectChanges(); + it('should work with single fields that combines async and sync validators', fakeAsync(() => { + const fixture = initTest(FormControlComp); + const control = + new FormControl('', Validators.required, uniqLoginAsyncValidator('good')); + fixture.debugElement.componentInstance.control = control; + fixture.detectChanges(); - const input = fixture.debugElement.query(By.css('input')).nativeElement; - expect(sortedClassList(input)).toEqual([ - 'ng-invalid', 'ng-pristine', 'ng-untouched' - ]); + const input = fixture.debugElement.query(By.css('input')).nativeElement; + expect(sortedClassList(input)).toEqual(['ng-invalid', 'ng-pristine', 'ng-untouched']); - dispatchEvent(input, 'blur'); - fixture.detectChanges(); - expect(sortedClassList(input)).toEqual(['ng-invalid', 'ng-pristine', 'ng-touched']); + dispatchEvent(input, 'blur'); + fixture.detectChanges(); + expect(sortedClassList(input)).toEqual(['ng-invalid', 'ng-pristine', 'ng-touched']); - input.value = 'bad'; - dispatchEvent(input, 'input'); - fixture.detectChanges(); + input.value = 'bad'; + dispatchEvent(input, 'input'); + fixture.detectChanges(); - expect(sortedClassList(input)).toEqual(['ng-dirty', 'ng-pending', 'ng-touched']); + expect(sortedClassList(input)).toEqual(['ng-dirty', 'ng-pending', 'ng-touched']); - tick(); - fixture.detectChanges(); + tick(); + fixture.detectChanges(); - expect(sortedClassList(input)).toEqual(['ng-dirty', 'ng-invalid', 'ng-touched']); + expect(sortedClassList(input)).toEqual(['ng-dirty', 'ng-invalid', 'ng-touched']); - input.value = 'good'; - dispatchEvent(input, 'input'); - tick(); - fixture.detectChanges(); + input.value = 'good'; + dispatchEvent(input, 'input'); + tick(); + fixture.detectChanges(); - expect(sortedClassList(input)).toEqual(['ng-dirty', 'ng-touched', 'ng-valid']); - })); + expect(sortedClassList(input)).toEqual(['ng-dirty', 'ng-touched', 'ng-valid']); + })); - it('should work with single fields in parent forms', () => { - const fixture = initTest(FormGroupComp); - const form = new FormGroup({'login': new FormControl('', Validators.required)}); - fixture.componentInstance.form = form; - fixture.detectChanges(); + it('should work with single fields in parent forms', () => { + const fixture = initTest(FormGroupComp); + const form = new FormGroup({'login': new FormControl('', Validators.required)}); + fixture.componentInstance.form = form; + fixture.detectChanges(); - const input = fixture.debugElement.query(By.css('input')).nativeElement; - expect(sortedClassList(input)).toEqual(['ng-invalid', 'ng-pristine', 'ng-untouched']); + const input = fixture.debugElement.query(By.css('input')).nativeElement; + expect(sortedClassList(input)).toEqual(['ng-invalid', 'ng-pristine', 'ng-untouched']); - dispatchEvent(input, 'blur'); - fixture.detectChanges(); + dispatchEvent(input, 'blur'); + fixture.detectChanges(); - expect(sortedClassList(input)).toEqual(['ng-invalid', 'ng-pristine', 'ng-touched']); + expect(sortedClassList(input)).toEqual(['ng-invalid', 'ng-pristine', 'ng-touched']); - input.value = 'updatedValue'; - dispatchEvent(input, 'input'); - fixture.detectChanges(); + input.value = 'updatedValue'; + dispatchEvent(input, 'input'); + fixture.detectChanges(); - expect(sortedClassList(input)).toEqual(['ng-dirty', 'ng-touched', 'ng-valid']); - }); + expect(sortedClassList(input)).toEqual(['ng-dirty', 'ng-touched', 'ng-valid']); + }); - it('should work with formGroup', () => { - const fixture = initTest(FormGroupComp); - const form = new FormGroup({'login': new FormControl('', Validators.required)}); - fixture.componentInstance.form = form; - fixture.detectChanges(); + it('should work with formGroup', () => { + const fixture = initTest(FormGroupComp); + const form = new FormGroup({'login': new FormControl('', Validators.required)}); + fixture.componentInstance.form = form; + fixture.detectChanges(); - const input = fixture.debugElement.query(By.css('input')).nativeElement; - const formEl = fixture.debugElement.query(By.css('form')).nativeElement; + const input = fixture.debugElement.query(By.css('input')).nativeElement; + const formEl = fixture.debugElement.query(By.css('form')).nativeElement; - expect(sortedClassList(formEl)).toEqual(['ng-invalid', 'ng-pristine', 'ng-untouched']); + expect(sortedClassList(formEl)).toEqual(['ng-invalid', 'ng-pristine', 'ng-untouched']); - dispatchEvent(input, 'blur'); - fixture.detectChanges(); + dispatchEvent(input, 'blur'); + fixture.detectChanges(); - expect(sortedClassList(formEl)).toEqual(['ng-invalid', 'ng-pristine', 'ng-touched']); + expect(sortedClassList(formEl)).toEqual(['ng-invalid', 'ng-pristine', 'ng-touched']); - input.value = 'updatedValue'; - dispatchEvent(input, 'input'); - fixture.detectChanges(); + input.value = 'updatedValue'; + dispatchEvent(input, 'input'); + fixture.detectChanges(); - expect(sortedClassList(formEl)).toEqual(['ng-dirty', 'ng-touched', 'ng-valid']); - }); + expect(sortedClassList(formEl)).toEqual(['ng-dirty', 'ng-touched', 'ng-valid']); + }); - }); + }); describe('updateOn options', () => { diff --git a/packages/forms/test/template_integration_spec.ts b/packages/forms/test/template_integration_spec.ts index 7d2f8bbb18..ed487c032e 100644 --- a/packages/forms/test/template_integration_spec.ts +++ b/packages/forms/test/template_integration_spec.ts @@ -12,7 +12,6 @@ import {AbstractControl, AsyncValidator, COMPOSITION_BUFFER_MODE, FormControl, F import {By} from '@angular/platform-browser/src/dom/debug/by'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; import {dispatchEvent} from '@angular/platform-browser/testing/src/browser_util'; -import {fixmeIvy} from '@angular/private/testing'; import {merge} from 'rxjs'; import {NgModelCustomComp, NgModelCustomWrapper} from './value_accessor_integration_spec'; @@ -149,101 +148,85 @@ import {NgModelCustomComp, NgModelCustomWrapper} from './value_accessor_integrat expect(form.value).toEqual({}); })); - fixmeIvy('host bindings do not yet work with classes or styles') && - it('should set status classes with ngModel', async(() => { - const fixture = initTest(NgModelForm); - fixture.componentInstance.name = 'aa'; - fixture.detectChanges(); - fixture.whenStable().then(() => { - fixture.detectChanges(); + it('should set status classes with ngModel', async(() => { + const fixture = initTest(NgModelForm); + fixture.componentInstance.name = 'aa'; + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); - const input = fixture.debugElement.query(By.css('input')).nativeElement; - expect(sortedClassList(input)).toEqual([ - 'ng-invalid', 'ng-pristine', 'ng-untouched' - ]); + const input = fixture.debugElement.query(By.css('input')).nativeElement; + expect(sortedClassList(input)).toEqual(['ng-invalid', 'ng-pristine', 'ng-untouched']); - dispatchEvent(input, 'blur'); - fixture.detectChanges(); + dispatchEvent(input, 'blur'); + fixture.detectChanges(); - expect(sortedClassList(input)).toEqual([ - 'ng-invalid', 'ng-pristine', 'ng-touched' - ]); + expect(sortedClassList(input)).toEqual(['ng-invalid', 'ng-pristine', 'ng-touched']); - input.value = 'updatedValue'; - dispatchEvent(input, 'input'); - fixture.detectChanges(); - expect(sortedClassList(input)).toEqual(['ng-dirty', 'ng-touched', 'ng-valid']); - }); - })); + input.value = 'updatedValue'; + dispatchEvent(input, 'input'); + fixture.detectChanges(); + expect(sortedClassList(input)).toEqual(['ng-dirty', 'ng-touched', 'ng-valid']); + }); + })); - fixmeIvy('host bindings do not yet work with classes or styles') && - it('should set status classes with ngModel and async validators', fakeAsync(() => { + it('should set status classes with ngModel and async validators', fakeAsync(() => { - const fixture = initTest(NgModelAsyncValidation, NgAsyncValidator); - fixture.whenStable().then(() => { - fixture.detectChanges(); + const fixture = initTest(NgModelAsyncValidation, NgAsyncValidator); + fixture.whenStable().then(() => { + fixture.detectChanges(); - const input = fixture.debugElement.query(By.css('input')).nativeElement; - expect(sortedClassList(input)).toEqual([ - 'ng-pending', 'ng-pristine', 'ng-untouched' - ]); + const input = fixture.debugElement.query(By.css('input')).nativeElement; + expect(sortedClassList(input)).toEqual(['ng-pending', 'ng-pristine', 'ng-untouched']); - dispatchEvent(input, 'blur'); - fixture.detectChanges(); + dispatchEvent(input, 'blur'); + fixture.detectChanges(); - expect(sortedClassList(input)).toEqual([ - 'ng-pending', 'ng-pristine', 'ng-touched' - ]); + expect(sortedClassList(input)).toEqual(['ng-pending', 'ng-pristine', 'ng-touched']); - input.value = 'updatedValue'; - dispatchEvent(input, 'input'); - tick(); - fixture.detectChanges(); + input.value = 'updatedValue'; + dispatchEvent(input, 'input'); + tick(); + fixture.detectChanges(); - expect(sortedClassList(input)).toEqual(['ng-dirty', 'ng-touched', 'ng-valid']); - }); - })); + expect(sortedClassList(input)).toEqual(['ng-dirty', 'ng-touched', 'ng-valid']); + }); + })); - fixmeIvy('host bindings do not yet work with classes or styles') && - it('should set status classes with ngModelGroup and ngForm', async(() => { - const fixture = initTest(NgModelGroupForm); - fixture.componentInstance.first = ''; - fixture.detectChanges(); + it('should set status classes with ngModelGroup and ngForm', async(() => { + const fixture = initTest(NgModelGroupForm); + fixture.componentInstance.first = ''; + fixture.detectChanges(); - const form = fixture.debugElement.query(By.css('form')).nativeElement; - const modelGroup = - fixture.debugElement.query(By.css('[ngModelGroup]')).nativeElement; - const input = fixture.debugElement.query(By.css('input')).nativeElement; + const form = fixture.debugElement.query(By.css('form')).nativeElement; + const modelGroup = fixture.debugElement.query(By.css('[ngModelGroup]')).nativeElement; + const input = fixture.debugElement.query(By.css('input')).nativeElement; - // ngModelGroup creates its control asynchronously - fixture.whenStable().then(() => { - fixture.detectChanges(); - expect(sortedClassList(modelGroup)).toEqual([ - 'ng-invalid', 'ng-pristine', 'ng-untouched' - ]); + // ngModelGroup creates its control asynchronously + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(sortedClassList(modelGroup)).toEqual([ + 'ng-invalid', 'ng-pristine', 'ng-untouched' + ]); - expect(sortedClassList(form)).toEqual([ - 'ng-invalid', 'ng-pristine', 'ng-untouched' - ]); + expect(sortedClassList(form)).toEqual(['ng-invalid', 'ng-pristine', 'ng-untouched']); - dispatchEvent(input, 'blur'); - fixture.detectChanges(); + dispatchEvent(input, 'blur'); + fixture.detectChanges(); - expect(sortedClassList(modelGroup)).toEqual([ - 'ng-invalid', 'ng-pristine', 'ng-touched' - ]); - expect(sortedClassList(form)).toEqual(['ng-invalid', 'ng-pristine', 'ng-touched']); + expect(sortedClassList(modelGroup)).toEqual([ + 'ng-invalid', 'ng-pristine', 'ng-touched' + ]); + expect(sortedClassList(form)).toEqual(['ng-invalid', 'ng-pristine', 'ng-touched']); - input.value = 'updatedValue'; - dispatchEvent(input, 'input'); - fixture.detectChanges(); + input.value = 'updatedValue'; + dispatchEvent(input, 'input'); + fixture.detectChanges(); - expect(sortedClassList(modelGroup)).toEqual([ - 'ng-dirty', 'ng-touched', 'ng-valid' - ]); - expect(sortedClassList(form)).toEqual(['ng-dirty', 'ng-touched', 'ng-valid']); - }); - })); + expect(sortedClassList(modelGroup)).toEqual(['ng-dirty', 'ng-touched', 'ng-valid']); + expect(sortedClassList(form)).toEqual(['ng-dirty', 'ng-touched', 'ng-valid']); + }); + })); it('should not create a template-driven form when ngNoForm is used', () => { const fixture = initTest(NgNoFormComp); diff --git a/packages/forms/test/value_accessor_integration_spec.ts b/packages/forms/test/value_accessor_integration_spec.ts index 59b533c24f..a8061fe60c 100644 --- a/packages/forms/test/value_accessor_integration_spec.ts +++ b/packages/forms/test/value_accessor_integration_spec.ts @@ -11,7 +11,6 @@ import {ComponentFixture, TestBed, async, fakeAsync, tick} from '@angular/core/t import {AbstractControl, ControlValueAccessor, FormControl, FormGroup, FormsModule, NG_VALIDATORS, NG_VALUE_ACCESSOR, NgControl, NgForm, NgModel, ReactiveFormsModule, Validators} from '@angular/forms'; import {By} from '@angular/platform-browser/src/dom/debug/by'; import {dispatchEvent} from '@angular/platform-browser/testing/src/browser_util'; -import {fixmeIvy} from '@angular/private/testing'; { describe('value accessors', () => {