diff --git a/packages/checkbox/checkbox.ts b/packages/checkbox/checkbox.ts index fbff9f92d..927d2305d 100644 --- a/packages/checkbox/checkbox.ts +++ b/packages/checkbox/checkbox.ts @@ -16,7 +16,7 @@ import { } from '@angular/core'; import {NG_VALUE_ACCESSOR, ControlValueAccessor} from '@angular/forms'; import {coerceBooleanProperty} from '@angular/cdk/coercion'; -import {Platform} from '@angular/cdk/platform'; +import {Platform, supportsPassiveEventListeners} from '@angular/cdk/platform'; import {fromEvent, Subject} from 'rxjs'; import {takeUntil, filter} from 'rxjs/operators'; @@ -102,14 +102,14 @@ export class MdcCheckbox extends MDCComponent implements _root!: Element; + private _initialized: boolean = false; private _uniqueId: string = `mdc-checkbox-${++nextUniqueId}`; @Input() id: string = this._uniqueId; /** Returns the unique id for the visual hidden input. */ get inputId(): string { - return `${this.id || this._uniqueId}-input` - ; + return `${this.id || this._uniqueId}-input`; } @Input() name: string | null = null; @@ -170,7 +170,7 @@ export class MdcCheckbox extends MDCComponent implements } this.indeterminateChange.emit({source: this, indeterminate: this._indeterminate}); this._changeDetectorRef.markForCheck(); - this._foundation.handleChange(); + this._foundation?.handleChange(); } } private _indeterminate: boolean = false; @@ -224,20 +224,23 @@ export class MdcCheckbox extends MDCComponent implements /** View to model callback called when component has been touched */ _onTouched: () => any = () => {}; - getDefaultFoundation() { + getDefaultFoundation(): any { + // Do not initialize foundation until ngAfterViewInit runs + if (!this._initialized) { + return undefined; + } + const adapter: MDCCheckboxAdapter = { - addClass: (className: string) => this._getHostElement().classList.add(className), - removeClass: (className: string) => this._getHostElement().classList.remove(className), + addClass: (className: string) => this._root.classList.add(className), + removeClass: (className: string) => this._root.classList.remove(className), setNativeControlAttr: (attr: string, value: string) => this._inputElement.nativeElement.setAttribute(attr, value), - removeNativeControlAttr: (attr: string) => - this._inputElement.nativeElement.removeAttribute(attr), + removeNativeControlAttr: (attr: string) => this._inputElement.nativeElement.removeAttribute(attr), isIndeterminate: () => this.indeterminate, isChecked: () => this.checked, hasNativeControl: () => true, - setNativeControlDisabled: (disabled: boolean) => - this._inputElement.nativeElement.disabled = disabled, - forceLayout: () => this._getHostElement().offsetWidth, + setNativeControlDisabled: (disabled: boolean) => this._inputElement.nativeElement.disabled = disabled, + forceLayout: () => (this._root).offsetWidth, isAttachedToDOM: () => true }; return new MDCCheckboxFoundation(adapter); @@ -253,16 +256,27 @@ export class MdcCheckbox extends MDCComponent implements super(elementRef); this._root = this.elementRef.nativeElement; - this.ripple = this._createRipple(); - this.ripple.init(); if (this._parentFormField) { _parentFormField.elementRef.nativeElement.classList.add('mdc-form-field'); } } + async _asyncBuildFoundation(): Promise { + this._foundation = this.getDefaultFoundation(); + } + ngAfterViewInit(): void { - this._foundation.init(); + this._initialized = true; + + this._asyncBuildFoundation() + .then(() => { + this._foundation.init(); + this.setDisabledState(this._inputElement.nativeElement.disabled); + + this.ripple = this._createRipple(); + this.ripple.init(); + }); this._loadListeners(); } @@ -314,9 +328,13 @@ export class MdcCheckbox extends MDCComponent implements } setDisabledState(disabled: boolean): void { - this._disabled = coerceBooleanProperty(disabled); - this._foundation.setDisabled(this._disabled); - this._changeDetectorRef.markForCheck(); + const newValue = coerceBooleanProperty(disabled); + + if (newValue !== this._disabled) { + this._disabled = newValue; + this._foundation?.setDisabled(newValue); + this._changeDetectorRef.markForCheck(); + } } private _setState(checked?: boolean): void { @@ -344,7 +362,11 @@ export class MdcCheckbox extends MDCComponent implements ...MdcRipple.createAdapter(this), isSurfaceActive: () => matches(this._inputElement.nativeElement, ':active'), isUnbounded: () => true, - isSurfaceDisabled: () => !this._disableRipple + isSurfaceDisabled: () => this.disableRipple, + deregisterInteractionHandler: (evtType: any, handler: any) => + this._inputElement.nativeElement.removeEventListener(evtType, handler, supportsPassiveEventListeners()), + registerInteractionHandler: (evtType: any, handler: any) => + this._inputElement.nativeElement.addEventListener(evtType, handler, supportsPassiveEventListeners()), }; return new MdcRipple(this.elementRef, new MDCRippleFoundation(adapter)); } @@ -355,15 +377,10 @@ export class MdcCheckbox extends MDCComponent implements } this._ngZone.runOutsideAngular(() => - fromEvent(this._getHostElement(), 'animationend') + fromEvent(this._root, 'animationend') .pipe(takeUntil(this._destroy), filter((e: AnimationEvent) => - e.target === this._getHostElement())) + e.target === this._root)) .subscribe(() => this._ngZone.run(() => this._foundation.handleAnimationEnd()))); } - - /** Retrieves the DOM element of the component host. */ - private _getHostElement(): HTMLElement { - return this.elementRef.nativeElement; - } } diff --git a/test/checkbox/checkbox.test.ts b/test/checkbox/checkbox.test.ts index 08b03b2d5..77ac40020 100644 --- a/test/checkbox/checkbox.test.ts +++ b/test/checkbox/checkbox.test.ts @@ -1,7 +1,8 @@ import {Component, DebugElement} from '@angular/core'; -import {ComponentFixture, fakeAsync, flushMicrotasks, TestBed} from '@angular/core/testing'; +import {async, ComponentFixture, fakeAsync, flushMicrotasks, TestBed} from '@angular/core/testing'; import {FormsModule, NgModel, ReactiveFormsModule} from '@angular/forms'; import {By} from '@angular/platform-browser'; +import {Platform} from '@angular/cdk/platform'; import {dispatchFakeEvent, dispatchMouseEvent} from '../testing/dispatch-events'; @@ -9,8 +10,12 @@ import {MdcCheckbox, MdcCheckboxModule} from '@angular-mdc/web'; describe('MdcCheckbox', () => { let fixture: ComponentFixture; + let platform: {isBrowser: boolean}; beforeEach(fakeAsync(() => { + // Set the default Platform override that can be updated before component creation. + platform = {isBrowser: true}; + TestBed.configureTestingModule({ imports: [MdcCheckboxModule, FormsModule, ReactiveFormsModule], declarations: [ @@ -19,11 +24,60 @@ describe('MdcCheckbox', () => { CheckboxWithAriaLabel, CheckboxWithTabIndex, CheckboxWithFormDirectives, + DisabledCheckbox, + ], + providers: [ + {provide: Platform, useFactory: () => platform} ] }); TestBed.compileComponents(); })); + describe('Tests for SSR', () => { + let testDebugElement: DebugElement; + let testInstance: MdcCheckbox; + let testComponent: SingleCheckbox; + + beforeEach(() => { + // Set the default Platform override that can be updated before component creation. + platform = {isBrowser: false}; + + fixture = TestBed.createComponent(SingleCheckbox); + fixture.detectChanges(); + + testDebugElement = fixture.debugElement.query(By.directive(MdcCheckbox)); + testInstance = testDebugElement.componentInstance; + testComponent = fixture.debugElement.componentInstance; + }); + + it('#should have mdc-checkbox by default', () => { + expect(testDebugElement.nativeElement.classList) + .toContain('mdc-checkbox', 'Expected to have mdc-checkbox'); + }); + }); + + describe('disabled checkbox', () => { + let testDebugElement: DebugElement; + let testNativeElement: HTMLElement; + let testInstance: MdcCheckbox; + let testComponent: DisabledCheckbox; + + beforeEach(async(() => { + fixture = TestBed.createComponent(DisabledCheckbox); + fixture.detectChanges(); + + testDebugElement = fixture.debugElement.query(By.directive(MdcCheckbox)); + testNativeElement = testDebugElement.nativeElement; + testInstance = testDebugElement.componentInstance; + testComponent = fixture.debugElement.componentInstance; + })); + + it('#should be disabled', () => { + fixture.detectChanges(); + expect(testInstance._inputElement.nativeElement.disabled).toBe(true); + }); + }); + describe('basic behaviors', () => { let checkboxDebugElement: DebugElement; let checkboxNativeElement: HTMLElement; @@ -194,10 +248,6 @@ describe('MdcCheckbox', () => { expect(checkboxInstance.indeterminate).toBe(true); }); - it('expect disableRipple to be false', () => { - expect(testComponent.disableRipple).toBe(false); - }); - it('expect disableRipple to be true', () => { testComponent.disableRipple = true; fixture.detectChanges(); @@ -348,7 +398,7 @@ class SingleCheckbox { indeterminate: boolean; checkboxValue: boolean = false; indeterminateToChecked: boolean = true; - disableRipple: boolean = false; + disableRipple: boolean; touch: boolean = false; } @@ -388,3 +438,8 @@ class CheckboxWithTabIndex { class CheckboxWithFormDirectives { isGood: boolean = false; } + +@Component({ + template: ``, +}) +class DisabledCheckbox {} diff --git a/test/select/select.test.ts b/test/select/select.test.ts index c7a8a8b92..d916e6d97 100644 --- a/test/select/select.test.ts +++ b/test/select/select.test.ts @@ -9,6 +9,7 @@ import { import {async, fakeAsync, ComponentFixture, TestBed, flush} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; import {DOWN_ARROW} from '@angular/cdk/keycodes'; +import {Platform} from '@angular/cdk/platform'; import {dispatchKeyboardEvent, dispatchMouseEvent} from '../testing/dispatch-events'; @@ -19,6 +20,11 @@ import { } from '@angular-mdc/web'; function configureMdcTestingModule(declarations: any[], providers: Provider[] = []) { + let platform: {isBrowser: boolean}; + + // Set the default Platform override that can be updated before component creation. + platform = {isBrowser: true}; + TestBed.configureTestingModule({ imports: [ MdcSelectModule, @@ -27,6 +33,7 @@ function configureMdcTestingModule(declarations: any[], providers: Provider[] = ], declarations: declarations, providers: [ + {provide: Platform, useFactory: () => platform}, ...providers ], }).compileComponents(); diff --git a/test/textfield/textfield.test.ts b/test/textfield/textfield.test.ts index 1a798071e..5ff26ce55 100644 --- a/test/textfield/textfield.test.ts +++ b/test/textfield/textfield.test.ts @@ -2,6 +2,7 @@ import {Component, DebugElement, Provider, ViewChild, Type} from '@angular/core' import {async, ComponentFixture, fakeAsync, TestBed, flush, tick} from '@angular/core/testing'; import {FormsModule} from '@angular/forms'; import {By} from '@angular/platform-browser'; +import {Platform} from '@angular/cdk/platform'; import { dispatchFakeEvent, @@ -17,6 +18,11 @@ import { } from '@angular-mdc/web'; function configureMdcTestingModule(declarations: any[], providers: Provider[] = []) { + let platform: {isBrowser: boolean}; + + // Set the default Platform override that can be updated before component creation. + platform = {isBrowser: true}; + TestBed.configureTestingModule({ imports: [ MdcTextFieldModule, @@ -25,6 +31,7 @@ function configureMdcTestingModule(declarations: any[], providers: Provider[] = ], declarations: declarations, providers: [ + {provide: Platform, useFactory: () => platform}, ...providers ], }).compileComponents();