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

fix(switch): Exception thrown in foundation if disabled #2111

Merged
merged 2 commits into from
Feb 18, 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
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 = () => {};
}