diff --git a/terminus-ui/button/src/button.component.html b/terminus-ui/button/src/button.component.html
index 43b8f4d08..91ec704a0 100644
--- a/terminus-ui/button/src/button.component.html
+++ b/terminus-ui/button/src/button.component.html
@@ -7,7 +7,7 @@
}"
[attr.aria-label]="actionName"
[attr.type]="buttonType"
- [disabled]="isDisabled || showProgress"
+ [attr.disabled]="shouldBeDisabled ? '' : null"
tabindex="{{ tabIndex }}"
(click)="clicked($event)"
#button
diff --git a/terminus-ui/button/src/button.component.spec.ts b/terminus-ui/button/src/button.component.spec.ts
index 6944877a2..dcda730f9 100644
--- a/terminus-ui/button/src/button.component.spec.ts
+++ b/terminus-ui/button/src/button.component.spec.ts
@@ -1,73 +1,144 @@
-import { ElementRef } from '@angular/core';
+import { Component, ViewChild } from '@angular/core';
+import { By } from '@angular/platform-browser';
import {
- ChangeDetectorRefMock,
createMouseEvent,
- Renderer2Mock,
- TsWindowServiceMock,
+ createComponent,
} from '@terminus/ngx-tools/testing';
import { TsButtonComponent } from './button.component';
-
+import { TsButtonModule } from './button.module';
+import { ComponentFixture, tick } from '@angular/core/testing';
+
+@Component({
+ template: `
+ Click Me!
+ `,
+})
+class TestHostComponent implements OnInit, OnDestroy {
+ disabled!: boolean;
+ collapsed!: boolean;
+ showProgress!: boolean;
+ collapseDelay!: number | undefined;
+ format!: string;
+ iconName!: string | undefined;
+ theme!: string;
+
+ @ViewChild(TsButtonComponent)
+ buttonComponent!: TsButtonComponent;
+
+ changed = jest.fn();
+ clickEvent = jest.fn();
+ private COLLAPSE_DEFAULT_DELAY = undefined;
+ public ngOnInit() { }
+ public ngOnDestroy() { }
+}
describe(`TsButtonComponent`, function() {
- let component: TsButtonComponent;
-
- beforeEach(() => {
- component = new TsButtonComponent(
- new ChangeDetectorRefMock(),
- new TsWindowServiceMock(),
- new Renderer2Mock(),
- );
- component['changeDetectorRef'].detectChanges = jest.fn();
- component.button = {
- _elementRef: new ElementRef({}),
- } as any;
- component['renderer'].addClass = jest.fn();
- component['renderer'].removeClass = jest.fn();
- });
-
-
- it(`should exist`, () => {
- expect(component).toBeTruthy();
- });
-
- describe(`showProgress`, () => {
- test(`should set and retrieve`, () => {
- component.showProgress = true;
- expect(component.showProgress).toEqual(true);
+
+ let component: TestHostComponent;
+ let fixture: ComponentFixture;
+ let button: HTMLButtonElement;
+ let buttonComponent: TsButtonComponent;
+
+ beforeEach(() => {
+ fixture = createComponent(TestHostComponent, [], [TsButtonModule]);
+ component = fixture.componentInstance;
+ buttonComponent = component.buttonComponent;
+ fixture.detectChanges();
+ button = fixture.debugElement.query(By.css('.c-button')).nativeElement as HTMLButtonElement;
});
- });
- describe(`set collapsed`, () => {
+ describe(`isDisabled`, () => {
- describe(`when format === collapsable`, () => {
+ test(`should not have button disabled`, () => {
+ component.disabled = false;
+ fixture.detectChanges();
+ expect(buttonComponent.isDisabled).toEqual(false);
+ expect(button.disabled).toEqual(false);
+ expect(button.getAttribute('disabled')).toEqual(null);
+ });
- it(`should call collapseWithDelay if a delay is set and the value is FALSE`, () => {
- component['collapseWithDelay'] = jest.fn();
- component.collapseDelay = component['COLLAPSE_DEFAULT_DELAY'];
- component.collapsed = false;
+ test(`should have button disabled`, () => {
+ component.disabled = true;
+ fixture.detectChanges();
+ expect(buttonComponent.isDisabled).toEqual(true);
+ expect(button.disabled).toEqual(true);
+ expect(button.getAttribute('disabled')).toEqual('');
+ });
+ });
+
+ test(`click`, () => {
+ component.buttonComponent.clickEvent.emit = jest.fn();
+ button.click();
+ expect(buttonComponent.clickEvent.emit).toHaveBeenCalled();
+ });
- expect(component['collapseWithDelay']).toHaveBeenCalled();
- expect(component.isCollapsed).toEqual(false);
+ describe(`showProgress`, () => {
+ test(`should set disabled attribute if showProgress is true`, () => {
+ component.showProgress = true;
+ fixture.detectChanges();
+ expect(buttonComponent.showProgress).toEqual(true);
+ expect(button.getAttribute('disabled')).toEqual('');
});
+ test(`should not set disabled if showProgress and disabled are false`, () => {
+ component.showProgress = false;
+ component.disabled = false;
+ fixture.detectChanges();
+ expect(buttonComponent.showProgress).toEqual(false);
+ expect(button.getAttribute('disabled')).toEqual(null);
+ });
+ });
- it(`should not call collapseWithDelay if no delay is set and the value is FALSE`, () => {
+ describe(`when collapsed is true`, function() {
+ test(`should have button collapsed class set`, function() {
+ component.collapsed = true;
+ fixture.detectChanges();
+ expect(buttonComponent.isCollapsed).toEqual(true);
+ expect(button.classList).toContain('c-button--collapsed');
+ });
+ });
+
+ describe(`when format === collapsable`, function() {
+
+ test(`should set isCollapsed to false if a delay is set and the value is FALSE`, () => {
+ buttonComponent['collapseWithDelay'] = jest.fn();
+ buttonComponent.collapseDelay = 400;
+ buttonComponent.collapsed = false;
+ fixture.detectChanges();
+
+ expect(buttonComponent['collapseWithDelay']).toHaveBeenCalled();
+ expect(buttonComponent.isCollapsed).toEqual(false);
+ expect(button.classList).not.toContain('c-button--collapsed');
+ });
+
+
+ test(`should not call collapseWithDelay if no delay is set and the value is FALSE`, () => {
component['collapseWithDelay'] = jest.fn();
component.collapsed = false;
expect(component['collapseWithDelay']).not.toHaveBeenCalled();
+ expect(button.classList).not.toContain('c-button--collapsed');
});
- it(`should not call collapseWithDelay if delay is set and the value is TRUE`, () => {
+ test(`should not call collapseWithDelay if delay is set and the value is TRUE`, () => {
component['collapseWithDelay'] = jest.fn();
- component.collapseDelay = component['COLLAPSE_DEFAULT_DELAY'];
+ component.collapseDelay = 400;
component.collapsed = true;
expect(component['collapseWithDelay']).not.toHaveBeenCalled();
+ expect(button.classList).not.toContain('c-button--collapsed');
});
-
});
@@ -75,10 +146,11 @@ describe(`TsButtonComponent`, function() {
it(`should not call collapseWithDelay if the type is not collapsable`, () => {
component['collapseWithDelay'] = jest.fn();
- component.format = 'filled';
+ component.buttonComponent.format = 'filled';
component.collapsed = false;
expect(component['collapseWithDelay']).not.toHaveBeenCalled();
+ expect(button.classList).not.toContain('c-button--collapsed');
});
});
@@ -89,7 +161,7 @@ describe(`TsButtonComponent`, function() {
describe('when format === collapsable', () => {
it(`should set the collapseDelay to default if unset`, () => {
- component.format = 'collapsable';
+ buttonComponent.format = 'collapsable';
expect(component.collapseDelay).toEqual(component['COLLAPSE_DEFAULT_DELAY']);
});
@@ -98,20 +170,23 @@ describe(`TsButtonComponent`, function() {
it(`should not set the collapseDelay to default if a value is passed in`, () => {
component.collapseDelay = 1000;
component.format = 'collapsable';
+ fixture.detectChanges();
expect(component.collapseDelay).toEqual(1000);
+ expect(button.classList).toContain('c-button--collapsable');
});
});
- describe('when format === collapsable', () => {
+ describe('when format !== collapsable', function() {
- it(`should remove any existing collapseDelay`, () => {
- component.collapseDelay = 500;
- component.format = 'filled';
+ test(`should remove any existing collapseDelay`, () => {
+ buttonComponent.collapseDelay = 400;
+ buttonComponent.format = 'filled';
+ fixture.detectChanges();
- expect(component.collapseDelay).toBeUndefined();
+ expect(buttonComponent.collapseDelay).toBeUndefined();
});
});
@@ -122,16 +197,26 @@ describe(`TsButtonComponent`, function() {
component.format = null as any;
expect(component['updateClasses']).not.toHaveBeenCalled();
+ expect(button.classList).toContain('c-button--filled');
});
test(`should log a warning if an invalid value was passed in`, () => {
window.console.warn = jest.fn();
- component['updateClasses'] = jest.fn();
- component.format = 'foo' as any;
+ buttonComponent['updateClasses'] = jest.fn();
+ buttonComponent.format = 'foo' as any;
+ fixture.detectChanges();
expect(window.console.warn).toHaveBeenCalled();
+ expect(buttonComponent['updateClasses']).not.toHaveBeenCalled();
+ });
+
+ test(`should update classes if correct format is passed in`, () => {
+ component['updateClasses'] = jest.fn();
+ component.format = 'filled' as any;
+
expect(component['updateClasses']).not.toHaveBeenCalled();
+ expect(button.classList).toContain('c-button--filled');
});
});
@@ -144,29 +229,42 @@ describe(`TsButtonComponent`, function() {
component.theme = null as any;
expect(component['updateClasses']).not.toHaveBeenCalled();
+ expect(button.classList).toContain('c-button--primary');
+ expect(button.classList).not.toContain('c-button--accent');
});
test(`should log a warning if an invalid value was passed in`, () => {
window.console.warn = jest.fn();
- component['updateClasses'] = jest.fn();
- component.theme = 'foo' as any;
+ buttonComponent['updateClasses'] = jest.fn();
+ buttonComponent.theme = 'foo' as any;
+ fixture.detectChanges();
expect(window.console.warn).toHaveBeenCalled();
- expect(component['updateClasses']).not.toHaveBeenCalled();
+ expect(buttonComponent['updateClasses']).not.toHaveBeenCalled();
+ expect(button.classList).toContain('c-button--primary');
+ expect(button.classList).not.toContain('c-button--accent');
+
});
});
- describe(`ngOnInit()`, () => {
+ describe(`ngOnInit()`, function() {
- it(`should call collapseWithDelay if collapseDelay is set`, () => {
- component['collapseWithDelay'] = jest.fn();
+
+ test(`should call collapseWithDelay if collapseDelay is set`, () => {
+ jest.useFakeTimers();
+ component.format = 'collapsable';
+ component.iconName = 'search';
component.collapseDelay = 500;
- component.ngOnInit();
+ fixture.detectChanges();
+ buttonComponent.ngOnInit();
+ jest.advanceTimersByTime(6000);
+ fixture.detectChanges();
- expect(component['collapseWithDelay']).toHaveBeenCalled();
+ expect(button.classList).toContain('c-button--collapsed');
+ jest.runAllTimers();
});
@@ -176,20 +274,21 @@ describe(`TsButtonComponent`, function() {
component.ngOnInit();
expect(component['collapseWithDelay']).not.toHaveBeenCalled();
+ expect(button.classList).not.toContain('c-button--collapsable');
});
describe(`when format === collapsable`, () => {
beforeEach(() => {
- component.format = 'collapsable';
- component['collapseWithDelay'] = jest.fn();
- component.collapseDelay = 500;
+ buttonComponent.format = 'collapsable';
+ buttonComponent['collapseWithDelay'] = jest.fn();
+ buttonComponent.collapseDelay = 500;
});
it(`should throw an error if the format is collapsable and no icon is set`, () => {
- expect(() => {component.ngOnInit(); }).toThrow();
+ expect(() => {buttonComponent.ngOnInit(); }).toThrow();
});
@@ -197,6 +296,7 @@ describe(`TsButtonComponent`, function() {
component.iconName = 'home';
expect(() => {component.ngOnInit(); }).not.toThrow();
+ expect(button.classList).not.toContain('c-button__icon');
});
});
@@ -206,20 +306,20 @@ describe(`TsButtonComponent`, function() {
describe(`ngOnDestroy()`, () => {
beforeEach(() => {
- component.format = 'collapsable';
- component.iconName = 'home';
- component['changeDetectorRef'].detectChanges();
- component['windowService'].nativeWindow.clearTimeout = jest.fn();
- component['windowService'].nativeWindow.setTimeout = jest.fn().mockReturnValue(123);
+ buttonComponent.format = 'collapsable';
+ buttonComponent.iconName = 'home';
+ buttonComponent['changeDetectorRef'].detectChanges = jest.fn();
+ buttonComponent['windowService'].nativeWindow.clearTimeout = jest.fn();
+ buttonComponent['windowService'].nativeWindow.setTimeout = jest.fn().mockReturnValue(123);
});
it(`should clear any existing timeouts`, () => {
- component.ngOnInit();
- expect(component['collapseTimeoutId']).toEqual(123);
+ buttonComponent.ngOnInit();
+ expect(buttonComponent['collapseTimeoutId']).toEqual(123);
- component.ngOnDestroy();
- expect(component['windowService'].nativeWindow.clearTimeout).toHaveBeenCalledWith(123);
+ buttonComponent.ngOnDestroy();
+ expect(buttonComponent['windowService'].nativeWindow.clearTimeout).toHaveBeenCalledWith(123);
});
});
@@ -229,24 +329,24 @@ describe(`TsButtonComponent`, function() {
let mouseEvent: MouseEvent;
beforeEach(() => {
- component.clickEvent.emit = jest.fn();
+ buttonComponent.clickEvent.emit = jest.fn();
mouseEvent = createMouseEvent('click');
});
test(`should emit the click when interceptClick is false`, () => {
- component.clicked(mouseEvent);
+ buttonComponent.clicked(mouseEvent);
- expect(component.clickEvent.emit).toHaveBeenCalledWith(mouseEvent);
+ expect(buttonComponent.clickEvent.emit).toHaveBeenCalledWith(mouseEvent);
});
test(`should not emit the click when interceptClick is true`, () => {
- component.interceptClick = true;
- component.clicked(mouseEvent);
+ buttonComponent.interceptClick = true;
+ buttonComponent.clicked(mouseEvent);
- expect(component.clickEvent.emit).not.toHaveBeenCalledWith();
- expect(component.originalClickEvent).toEqual(mouseEvent);
+ expect(buttonComponent.clickEvent.emit).not.toHaveBeenCalledWith();
+ expect(buttonComponent.originalClickEvent).toEqual(mouseEvent);
});
});
@@ -254,20 +354,25 @@ describe(`TsButtonComponent`, function() {
describe(`collapseWithDelay()`, () => {
+ beforeEach(() => {
+ buttonComponent.format = 'collapsable';
+ buttonComponent['windowService'].nativeWindow.setTimeout = window.setTimeout;
+ });
+
test(`should set isCollapsed and trigger change detection after the delay`, () => {
jest.useFakeTimers();
- component['windowService'].nativeWindow.setTimeout = window.setTimeout;
+
const DELAY = 100;
- component['collapseWithDelay'](DELAY);
+ buttonComponent['collapseWithDelay'](DELAY);
jest.advanceTimersByTime(2000);
+ fixture.detectChanges();
- expect(component['changeDetectorRef'].detectChanges).toHaveBeenCalled();
- expect(component.isCollapsed).toEqual(true);
+ expect(buttonComponent.isCollapsed).toEqual(true);
jest.runAllTimers();
+ expect(button.classList).toContain('c-button--collapsable');
});
});
- });
-
});
+
diff --git a/terminus-ui/button/src/button.component.ts b/terminus-ui/button/src/button.component.ts
index 2ad193809..25d373cf1 100644
--- a/terminus-ui/button/src/button.component.ts
+++ b/terminus-ui/button/src/button.component.ts
@@ -356,6 +356,13 @@ export class TsButtonComponent implements OnInit, OnDestroy {
}, delay);
}
+ /**
+ * Getter returning a boolean based on both the component `isDisabled` flag and the FormControl's disabled status
+ */
+ public get shouldBeDisabled(): boolean {
+ return this.isDisabled || this.showProgress;
+ }
+
/**
* Update button classes (theme|format)