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

Commit

Permalink
fix(switch): Exception thrown in foundation if disabled (#2111)
Browse files Browse the repository at this point in the history
closes #2107
  • Loading branch information
trimox committed Feb 18, 2020
1 parent 23b1098 commit ec8183e
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 19 deletions.
36 changes: 27 additions & 9 deletions packages/switch/switch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ export class MdcSwitch extends MDCComponent<MDCSwitchFoundation> implements MdcF
AfterViewInit, ControlValueAccessor, OnDestroy, MDCRippleCapableSurface {
private _uniqueId: string = `mdc-switch-${++nextUniqueId}`;

private _initialized: boolean = false;

_root!: Element;

@Input() id: string = this._uniqueId;
Expand Down Expand Up @@ -148,7 +150,12 @@ export class MdcSwitch extends MDCComponent<MDCSwitchFoundation> implements MdcF
return `${this.id || this._uniqueId}-input`;
}

getDefaultFoundation() {
getDefaultFoundation(): any {
// Do not initialize foundation until ngAfterViewInit runs
if (!this._initialized) {
return undefined;
}

const adapter: MDCSwitchAdapter = {
addClass: (className: string) => this._getHostElement().classList.add(className),
removeClass: (className: string) => this._getHostElement().classList.remove(className),
Expand All @@ -173,22 +180,29 @@ export class MdcSwitch extends MDCComponent<MDCSwitchFoundation> implements MdcF
}

ngAfterViewInit(): void {
this._initialized = true;
this.ripple = this._createRipple();
this.ripple.init();
this._foundation.init();

this._asyncBuildFoundation()
.then(() => {
this._foundation.init();
this.setDisabledState(this._inputElement.nativeElement.disabled);
});
}

ngOnDestroy(): void {
this.ripple.destroy();
this.destroy();
}

async _asyncBuildFoundation(): Promise<void> {
this._foundation = this.getDefaultFoundation();
}

onChange(evt: Event): void {
evt.stopPropagation();

if (this.disabled) {
return;
}

this._foundation.handleChange(evt);
this._checked = this._inputElement.nativeElement.checked;
this._foundation.setChecked(this._checked);
Expand Down Expand Up @@ -224,9 +238,13 @@ export class MdcSwitch extends MDCComponent<MDCSwitchFoundation> implements MdcF
}

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();
}
}

focus(): void {
Expand Down
48 changes: 38 additions & 10 deletions test/switch/switch.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { Component, DebugElement } from '@angular/core';
import { ComponentFixture, fakeAsync, flushMicrotasks, TestBed, tick, flush } from '@angular/core/testing';
import { FormControl, FormsModule, NgModel, ReactiveFormsModule } from '@angular/forms';
import { By } from '@angular/platform-browser';
import {Component, DebugElement} from '@angular/core';
import {ComponentFixture, fakeAsync, flushMicrotasks, TestBed, tick, flush, async} from '@angular/core/testing';
import {FormControl, FormsModule, NgModel, ReactiveFormsModule} from '@angular/forms';
import {By} from '@angular/platform-browser';

import { dispatchFakeEvent, dispatchMouseEvent } from '../testing/dispatch-events';
import {dispatchFakeEvent, dispatchMouseEvent} from '../testing/dispatch-events';

import { MdcSwitch, MdcSwitchModule } from '@angular-mdc/web';
import {MdcSwitch, MdcSwitchModule} from '@angular-mdc/web';

describe('MdcSwitch', () => {
let fixture: ComponentFixture<any>;
Expand All @@ -16,13 +16,36 @@ describe('MdcSwitch', () => {
declarations: [
SingleSwitch,
SwitchWithModel,
SwitchWithFormControl
SwitchWithFormControl,
DisabledSwitch
]
});

TestBed.compileComponents();
}));

describe('disabled switch', () => {
let switchDebugElement: DebugElement;
let switchNativeElement: HTMLElement;
let switchInstance: MdcSwitch;
let testComponent: DisabledSwitch;

beforeEach(async(() => {
fixture = TestBed.createComponent(DisabledSwitch);
fixture.detectChanges();

switchDebugElement = fixture.debugElement.query(By.directive(MdcSwitch));
switchNativeElement = switchDebugElement.nativeElement;
switchInstance = switchDebugElement.componentInstance;
testComponent = fixture.debugElement.componentInstance;
}));

it('#should be disabled', () => {
fixture.detectChanges();
expect(switchInstance._inputElement.nativeElement.disabled).toBe(true);
});
});

describe('basic behaviors', () => {
let switchDebugElement: DebugElement;
let switchNativeElement: HTMLElement;
Expand Down Expand Up @@ -243,7 +266,7 @@ describe('MdcSwitch with forms', () => {
fixture.detectChanges();
tick();

expect(spy).toHaveBeenCalledWith(jasmine.objectContaining({ checked: true }));
expect(spy).toHaveBeenCalledWith(jasmine.objectContaining({checked: true}));
subscription.unsubscribe();
}));

Expand Down Expand Up @@ -313,7 +336,7 @@ class SingleSwitch {
slideTabindex: number;
switchValue: string = 'single_switch';

onChange: () => void = () => { };
onChange: () => void = () => {};
}

@Component({
Expand All @@ -328,11 +351,16 @@ class SwitchWithFormControl {
formControl = new FormControl();
}

@Component({
template: `<mdc-switch disabled></mdc-switch>`,
})
class DisabledSwitch {}

/** Simple component for testing with ngModel in a form. */
@Component({
template: `<mdc-switch name="cb" [(ngModel)]="modelValue" (change)="onChange()">Be good</mdc-switch>`,
})
class SwitchWithModel {
modelValue: boolean = false;
onChange: () => void = () => { };
onChange: () => void = () => {};
}

0 comments on commit ec8183e

Please sign in to comment.