Skip to content
This repository has been archived by the owner on Oct 7, 2020. It is now read-only.

Commit

Permalink
fix(checkbox): Throws exception with Ivy (#2115)
Browse files Browse the repository at this point in the history
closes #2109
  • Loading branch information
trimox committed Feb 19, 2020
1 parent abfd276 commit 18d3e37
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 32 deletions.
69 changes: 43 additions & 26 deletions packages/checkbox/checkbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -102,14 +102,14 @@ export class MdcCheckbox extends MDCComponent<MDCCheckboxFoundation> 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;
Expand Down Expand Up @@ -170,7 +170,7 @@ export class MdcCheckbox extends MDCComponent<MDCCheckboxFoundation> implements
}
this.indeterminateChange.emit({source: this, indeterminate: this._indeterminate});
this._changeDetectorRef.markForCheck();
this._foundation.handleChange();
this._foundation?.handleChange();
}
}
private _indeterminate: boolean = false;
Expand Down Expand Up @@ -224,20 +224,23 @@ export class MdcCheckbox extends MDCComponent<MDCCheckboxFoundation> 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: () => (<HTMLElement>this._root).offsetWidth,
isAttachedToDOM: () => true
};
return new MDCCheckboxFoundation(adapter);
Expand All @@ -253,16 +256,27 @@ export class MdcCheckbox extends MDCComponent<MDCCheckboxFoundation> 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<void> {
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();
}

Expand Down Expand Up @@ -314,9 +328,13 @@ export class MdcCheckbox extends MDCComponent<MDCCheckboxFoundation> 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 {
Expand Down Expand Up @@ -344,7 +362,11 @@ export class MdcCheckbox extends MDCComponent<MDCCheckboxFoundation> 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));
}
Expand All @@ -355,15 +377,10 @@ export class MdcCheckbox extends MDCComponent<MDCCheckboxFoundation> implements
}

this._ngZone.runOutsideAngular(() =>
fromEvent<AnimationEvent>(this._getHostElement(), 'animationend')
fromEvent<AnimationEvent>(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;
}
}
67 changes: 61 additions & 6 deletions test/checkbox/checkbox.test.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
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';

import {MdcCheckbox, MdcCheckboxModule} from '@angular-mdc/web';

describe('MdcCheckbox', () => {
let fixture: ComponentFixture<any>;
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: [
Expand All @@ -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;
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -348,7 +398,7 @@ class SingleCheckbox {
indeterminate: boolean;
checkboxValue: boolean = false;
indeterminateToChecked: boolean = true;
disableRipple: boolean = false;
disableRipple: boolean;
touch: boolean = false;
}

Expand Down Expand Up @@ -388,3 +438,8 @@ class CheckboxWithTabIndex {
class CheckboxWithFormDirectives {
isGood: boolean = false;
}

@Component({
template: `<mdc-checkbox disabled></mdc-checkbox>`,
})
class DisabledCheckbox {}
7 changes: 7 additions & 0 deletions test/select/select.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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,
Expand All @@ -27,6 +33,7 @@ function configureMdcTestingModule(declarations: any[], providers: Provider[] =
],
declarations: declarations,
providers: [
{provide: Platform, useFactory: () => platform},
...providers
],
}).compileComponents();
Expand Down
7 changes: 7 additions & 0 deletions test/textfield/textfield.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -25,6 +31,7 @@ function configureMdcTestingModule(declarations: any[], providers: Provider[] =
],
declarations: declarations,
providers: [
{provide: Platform, useFactory: () => platform},
...providers
],
}).compileComponents();
Expand Down

0 comments on commit 18d3e37

Please sign in to comment.