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

fix(checkbox): Throws exception with Ivy #2115

Merged
merged 1 commit into from
Feb 19, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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