443 lines
16 KiB
TypeScript
443 lines
16 KiB
TypeScript
|
|
import {createMouseEvent, dispatchEvent} from '@angular/cdk/testing/private';
|
||
|
|
import {ApplicationRef, Component} from '@angular/core';
|
||
|
|
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
|
||
|
|
import {ThemePalette} from '@angular/material/core';
|
||
|
|
import {By} from '@angular/platform-browser';
|
||
|
|
import {
|
||
|
|
MAT_BUTTON_CONFIG,
|
||
|
|
MAT_FAB_DEFAULT_OPTIONS,
|
||
|
|
MatButtonModule,
|
||
|
|
MatFabDefaultOptions,
|
||
|
|
} from './index';
|
||
|
|
|
||
|
|
describe('MatButton', () => {
|
||
|
|
beforeEach(waitForAsync(() => {
|
||
|
|
TestBed.configureTestingModule({
|
||
|
|
imports: [MatButtonModule, TestApp],
|
||
|
|
});
|
||
|
|
}));
|
||
|
|
|
||
|
|
// General button tests
|
||
|
|
it('should apply class based on color attribute', () => {
|
||
|
|
let fixture = TestBed.createComponent(TestApp);
|
||
|
|
|
||
|
|
let testComponent = fixture.debugElement.componentInstance;
|
||
|
|
let buttonDebugElement = fixture.debugElement.query(By.css('button'))!;
|
||
|
|
let aDebugElement = fixture.debugElement.query(By.css('a'))!;
|
||
|
|
|
||
|
|
testComponent.buttonColor = 'primary';
|
||
|
|
fixture.changeDetectorRef.markForCheck();
|
||
|
|
fixture.detectChanges();
|
||
|
|
expect(buttonDebugElement.nativeElement.classList.contains('mat-primary')).toBe(true);
|
||
|
|
expect(aDebugElement.nativeElement.classList.contains('mat-primary')).toBe(true);
|
||
|
|
|
||
|
|
testComponent.buttonColor = 'accent';
|
||
|
|
fixture.changeDetectorRef.markForCheck();
|
||
|
|
fixture.detectChanges();
|
||
|
|
expect(buttonDebugElement.nativeElement.classList.contains('mat-accent')).toBe(true);
|
||
|
|
expect(aDebugElement.nativeElement.classList.contains('mat-accent')).toBe(true);
|
||
|
|
|
||
|
|
testComponent.buttonColor = null;
|
||
|
|
fixture.changeDetectorRef.markForCheck();
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(buttonDebugElement.nativeElement.classList).not.toContain('mat-accent');
|
||
|
|
expect(aDebugElement.nativeElement.classList).not.toContain('mat-accent');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should apply class based on the disabled state', () => {
|
||
|
|
const fixture = TestBed.createComponent(TestApp);
|
||
|
|
const button = fixture.debugElement.query(By.css('button'))!.nativeElement;
|
||
|
|
const anchor = fixture.debugElement.query(By.css('a'))!.nativeElement;
|
||
|
|
|
||
|
|
expect(button.classList).not.toContain('mat-mdc-button-disabled');
|
||
|
|
expect(anchor.classList).not.toContain('mat-mdc-button-disabled');
|
||
|
|
|
||
|
|
fixture.componentInstance.isDisabled = true;
|
||
|
|
fixture.changeDetectorRef.markForCheck();
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(button.classList).toContain('mat-mdc-button-disabled');
|
||
|
|
expect(anchor.classList).toContain('mat-mdc-button-disabled');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should not clear previous defined classes', () => {
|
||
|
|
let fixture = TestBed.createComponent(TestApp);
|
||
|
|
let testComponent = fixture.debugElement.componentInstance;
|
||
|
|
let buttonDebugElement = fixture.debugElement.query(By.css('button'))!;
|
||
|
|
|
||
|
|
buttonDebugElement.nativeElement.classList.add('custom-class');
|
||
|
|
|
||
|
|
testComponent.buttonColor = 'primary';
|
||
|
|
fixture.changeDetectorRef.markForCheck();
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(buttonDebugElement.nativeElement.classList.contains('mat-primary')).toBe(true);
|
||
|
|
expect(buttonDebugElement.nativeElement.classList.contains('custom-class')).toBe(true);
|
||
|
|
|
||
|
|
testComponent.buttonColor = 'accent';
|
||
|
|
fixture.changeDetectorRef.markForCheck();
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(buttonDebugElement.nativeElement.classList.contains('mat-primary')).toBe(false);
|
||
|
|
expect(buttonDebugElement.nativeElement.classList.contains('mat-accent')).toBe(true);
|
||
|
|
expect(buttonDebugElement.nativeElement.classList.contains('custom-class')).toBe(true);
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('button[mat-fab]', () => {
|
||
|
|
it('should have accent palette by default', () => {
|
||
|
|
const fixture = TestBed.createComponent(TestApp);
|
||
|
|
const fabButtonDebugEl = fixture.debugElement.query(By.css('button[mat-fab]'))!;
|
||
|
|
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(fabButtonDebugEl.nativeElement.classList)
|
||
|
|
.withContext('Expected fab buttons to use accent palette by default')
|
||
|
|
.toContain('mat-accent');
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('button[mat-mini-fab]', () => {
|
||
|
|
it('should have accent palette by default', () => {
|
||
|
|
const fixture = TestBed.createComponent(TestApp);
|
||
|
|
const miniFabButtonDebugEl = fixture.debugElement.query(By.css('button[mat-mini-fab]'))!;
|
||
|
|
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(miniFabButtonDebugEl.nativeElement.classList)
|
||
|
|
.withContext('Expected mini-fab buttons to use accent palette by default')
|
||
|
|
.toContain('mat-accent');
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('button[mat-fab] extended', () => {
|
||
|
|
it('should be extended', () => {
|
||
|
|
const fixture = TestBed.createComponent(TestApp);
|
||
|
|
fixture.detectChanges();
|
||
|
|
const extendedFabButtonDebugEl = fixture.debugElement.query(By.css('.extended-fab-test'))!;
|
||
|
|
|
||
|
|
expect(
|
||
|
|
extendedFabButtonDebugEl.nativeElement.classList.contains('mat-mdc-extended-fab'),
|
||
|
|
).toBeFalse();
|
||
|
|
|
||
|
|
fixture.componentInstance.extended = true;
|
||
|
|
fixture.changeDetectorRef.markForCheck();
|
||
|
|
|
||
|
|
fixture.detectChanges();
|
||
|
|
expect(extendedFabButtonDebugEl.nativeElement.classList).toContain('mat-mdc-extended-fab');
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
// Regular button tests
|
||
|
|
describe('button[mat-button]', () => {
|
||
|
|
it('should handle a click on the button', () => {
|
||
|
|
let fixture = TestBed.createComponent(TestApp);
|
||
|
|
let testComponent = fixture.debugElement.componentInstance;
|
||
|
|
let buttonDebugElement = fixture.debugElement.query(By.css('button'))!;
|
||
|
|
|
||
|
|
buttonDebugElement.nativeElement.click();
|
||
|
|
expect(testComponent.clickCount).toBe(1);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should not increment if disabled', () => {
|
||
|
|
let fixture = TestBed.createComponent(TestApp);
|
||
|
|
let testComponent = fixture.debugElement.componentInstance;
|
||
|
|
let buttonDebugElement = fixture.debugElement.query(By.css('button'))!;
|
||
|
|
|
||
|
|
testComponent.isDisabled = true;
|
||
|
|
fixture.changeDetectorRef.markForCheck();
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
buttonDebugElement.nativeElement.click();
|
||
|
|
|
||
|
|
expect(testComponent.clickCount).toBe(0);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should disable the native button element', () => {
|
||
|
|
let fixture = TestBed.createComponent(TestApp);
|
||
|
|
let buttonNativeElement = fixture.nativeElement.querySelector('button');
|
||
|
|
expect(buttonNativeElement.disabled)
|
||
|
|
.withContext('Expected button not to be disabled')
|
||
|
|
.toBeFalsy();
|
||
|
|
|
||
|
|
fixture.componentInstance.isDisabled = true;
|
||
|
|
fixture.changeDetectorRef.markForCheck();
|
||
|
|
fixture.detectChanges();
|
||
|
|
expect(buttonNativeElement.disabled)
|
||
|
|
.withContext('Expected button to be disabled')
|
||
|
|
.toBeTruthy();
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
// Anchor button tests
|
||
|
|
describe('a[mat-button]', () => {
|
||
|
|
it('should not redirect if disabled', () => {
|
||
|
|
let fixture = TestBed.createComponent(TestApp);
|
||
|
|
let testComponent = fixture.debugElement.componentInstance;
|
||
|
|
let buttonDebugElement = fixture.debugElement.query(By.css('a'))!;
|
||
|
|
|
||
|
|
testComponent.isDisabled = true;
|
||
|
|
fixture.changeDetectorRef.markForCheck();
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
buttonDebugElement.nativeElement.click();
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should remove tabindex if disabled', () => {
|
||
|
|
let fixture = TestBed.createComponent(TestApp);
|
||
|
|
let testComponent = fixture.debugElement.componentInstance;
|
||
|
|
let buttonDebugElement = fixture.debugElement.query(By.css('a'))!;
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(buttonDebugElement.nativeElement.hasAttribute('tabindex')).toBe(false);
|
||
|
|
|
||
|
|
testComponent.isDisabled = true;
|
||
|
|
fixture.changeDetectorRef.markForCheck();
|
||
|
|
fixture.detectChanges();
|
||
|
|
expect(buttonDebugElement.nativeElement.getAttribute('tabindex')).toBe('-1');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should add aria-disabled attribute if disabled', () => {
|
||
|
|
let fixture = TestBed.createComponent(TestApp);
|
||
|
|
let testComponent = fixture.debugElement.componentInstance;
|
||
|
|
let buttonDebugElement = fixture.debugElement.query(By.css('a'))!;
|
||
|
|
fixture.detectChanges();
|
||
|
|
expect(buttonDebugElement.nativeElement.hasAttribute('aria-disabled')).toBe(false);
|
||
|
|
|
||
|
|
testComponent.isDisabled = true;
|
||
|
|
fixture.changeDetectorRef.markForCheck();
|
||
|
|
fixture.detectChanges();
|
||
|
|
expect(buttonDebugElement.nativeElement.getAttribute('aria-disabled')).toBe('true');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should not add aria-disabled attribute if disabled is false', () => {
|
||
|
|
let fixture = TestBed.createComponent(TestApp);
|
||
|
|
let testComponent = fixture.debugElement.componentInstance;
|
||
|
|
let buttonDebugElement = fixture.debugElement.query(By.css('a'))!;
|
||
|
|
fixture.detectChanges();
|
||
|
|
expect(buttonDebugElement.nativeElement.hasAttribute('aria-disabled')).toBe(false);
|
||
|
|
expect(buttonDebugElement.nativeElement.getAttribute('disabled')).toBeNull();
|
||
|
|
|
||
|
|
testComponent.isDisabled = false;
|
||
|
|
fixture.changeDetectorRef.markForCheck();
|
||
|
|
fixture.detectChanges();
|
||
|
|
expect(buttonDebugElement.nativeElement.hasAttribute('aria-disabled')).toBe(false);
|
||
|
|
expect(buttonDebugElement.nativeElement.getAttribute('disabled')).toBeNull();
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should be able to set a custom tabindex', () => {
|
||
|
|
let fixture = TestBed.createComponent(TestApp);
|
||
|
|
let testComponent = fixture.debugElement.componentInstance;
|
||
|
|
let buttonElement = fixture.debugElement.query(By.css('a'))!.nativeElement;
|
||
|
|
|
||
|
|
fixture.componentInstance.tabIndex = 3;
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(buttonElement.getAttribute('tabindex'))
|
||
|
|
.withContext('Expected custom tabindex to be set')
|
||
|
|
.toBe('3');
|
||
|
|
|
||
|
|
testComponent.isDisabled = true;
|
||
|
|
fixture.changeDetectorRef.markForCheck();
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(buttonElement.getAttribute('tabindex'))
|
||
|
|
.withContext('Expected custom tabindex to be overwritten when disabled.')
|
||
|
|
.toBe('-1');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should not set a default tabindex on enabled links', () => {
|
||
|
|
const fixture = TestBed.createComponent(TestApp);
|
||
|
|
const buttonElement = fixture.debugElement.query(By.css('a'))!.nativeElement;
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(buttonElement.hasAttribute('tabindex')).toBe(false);
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('change detection behavior', () => {
|
||
|
|
it('should not run change detection for disabled anchor but should prevent the default behavior and stop event propagation', () => {
|
||
|
|
const appRef = TestBed.inject(ApplicationRef);
|
||
|
|
const fixture = TestBed.createComponent(TestApp);
|
||
|
|
fixture.componentInstance.isDisabled = true;
|
||
|
|
fixture.changeDetectorRef.markForCheck();
|
||
|
|
fixture.detectChanges();
|
||
|
|
const anchorElement = fixture.debugElement.query(By.css('a'))!.nativeElement;
|
||
|
|
|
||
|
|
spyOn(appRef, 'tick');
|
||
|
|
|
||
|
|
const event = createMouseEvent('click');
|
||
|
|
spyOn(event, 'preventDefault').and.callThrough();
|
||
|
|
spyOn(event, 'stopImmediatePropagation').and.callThrough();
|
||
|
|
|
||
|
|
dispatchEvent(anchorElement, event);
|
||
|
|
|
||
|
|
expect(appRef.tick).not.toHaveBeenCalled();
|
||
|
|
expect(event.preventDefault).toHaveBeenCalled();
|
||
|
|
expect(event.stopImmediatePropagation).toHaveBeenCalled();
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should have a focus indicator', () => {
|
||
|
|
const fixture = TestBed.createComponent(TestApp);
|
||
|
|
const buttonNativeElements = [
|
||
|
|
...fixture.debugElement.nativeElement.querySelectorAll('a, button'),
|
||
|
|
];
|
||
|
|
|
||
|
|
expect(
|
||
|
|
buttonNativeElements.every(element => !!element.querySelector('.mat-focus-indicator')),
|
||
|
|
).toBe(true);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should be able to configure the default color of buttons', () => {
|
||
|
|
@Component({
|
||
|
|
template: `<button mat-button>Click me</button>`,
|
||
|
|
standalone: true,
|
||
|
|
imports: [MatButtonModule],
|
||
|
|
})
|
||
|
|
class ConfigTestApp {}
|
||
|
|
|
||
|
|
TestBed.resetTestingModule();
|
||
|
|
TestBed.configureTestingModule({
|
||
|
|
imports: [MatButtonModule, ConfigTestApp],
|
||
|
|
providers: [
|
||
|
|
{
|
||
|
|
provide: MAT_BUTTON_CONFIG,
|
||
|
|
useValue: {color: 'warn'},
|
||
|
|
},
|
||
|
|
],
|
||
|
|
});
|
||
|
|
const fixture = TestBed.createComponent(ConfigTestApp);
|
||
|
|
fixture.detectChanges();
|
||
|
|
const button = fixture.nativeElement.querySelector('button');
|
||
|
|
expect(button.classList).toContain('mat-warn');
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('interactive disabled buttons', () => {
|
||
|
|
let fixture: ComponentFixture<TestApp>;
|
||
|
|
let button: HTMLButtonElement;
|
||
|
|
let anchor: HTMLAnchorElement;
|
||
|
|
|
||
|
|
beforeEach(() => {
|
||
|
|
fixture = TestBed.createComponent(TestApp);
|
||
|
|
fixture.componentInstance.isDisabled = true;
|
||
|
|
fixture.changeDetectorRef.markForCheck();
|
||
|
|
fixture.detectChanges();
|
||
|
|
button = fixture.nativeElement.querySelector('button');
|
||
|
|
anchor = fixture.nativeElement.querySelector('a');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should set a class when allowing disabled interactivity', () => {
|
||
|
|
expect(button.classList).not.toContain('mat-mdc-button-disabled-interactive');
|
||
|
|
|
||
|
|
fixture.componentInstance.disabledInteractive = true;
|
||
|
|
fixture.changeDetectorRef.markForCheck();
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(button.classList).toContain('mat-mdc-button-disabled-interactive');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should set aria-disabled when allowing disabled interactivity', () => {
|
||
|
|
expect(button.hasAttribute('aria-disabled')).toBe(false);
|
||
|
|
|
||
|
|
fixture.componentInstance.disabledInteractive = true;
|
||
|
|
fixture.changeDetectorRef.markForCheck();
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(button.getAttribute('aria-disabled')).toBe('true');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should not set the disabled attribute when allowing disabled interactivity', () => {
|
||
|
|
expect(button.getAttribute('disabled')).toBe('true');
|
||
|
|
|
||
|
|
fixture.componentInstance.disabledInteractive = true;
|
||
|
|
fixture.changeDetectorRef.markForCheck();
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
expect(button.hasAttribute('disabled')).toBe(false);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should set aria-disabled on anchor when disabledInteractive is enabled', () => {
|
||
|
|
fixture.componentInstance.isDisabled = false;
|
||
|
|
fixture.changeDetectorRef.markForCheck();
|
||
|
|
fixture.detectChanges();
|
||
|
|
expect(anchor.hasAttribute('aria-disabled')).toBe(false);
|
||
|
|
expect(anchor.hasAttribute('disabled')).toBe(false);
|
||
|
|
expect(anchor.classList).not.toContain('mat-mdc-button-disabled-interactive');
|
||
|
|
|
||
|
|
fixture.componentInstance.isDisabled = true;
|
||
|
|
fixture.changeDetectorRef.markForCheck();
|
||
|
|
fixture.detectChanges();
|
||
|
|
expect(anchor.getAttribute('aria-disabled')).toBe('true');
|
||
|
|
expect(anchor.hasAttribute('disabled')).toBe(true);
|
||
|
|
expect(anchor.classList).not.toContain('mat-mdc-button-disabled-interactive');
|
||
|
|
|
||
|
|
fixture.componentInstance.disabledInteractive = true;
|
||
|
|
fixture.changeDetectorRef.markForCheck();
|
||
|
|
fixture.detectChanges();
|
||
|
|
expect(anchor.getAttribute('aria-disabled')).toBe('true');
|
||
|
|
expect(anchor.hasAttribute('disabled')).toBe(false);
|
||
|
|
expect(anchor.classList).toContain('mat-mdc-button-disabled-interactive');
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('MatFabDefaultOptions', () => {
|
||
|
|
function configure(defaults: MatFabDefaultOptions) {
|
||
|
|
TestBed.configureTestingModule({
|
||
|
|
imports: [MatButtonModule, TestApp],
|
||
|
|
providers: [{provide: MAT_FAB_DEFAULT_OPTIONS, useValue: defaults}],
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
it('should override default color in component', () => {
|
||
|
|
configure({color: 'primary'});
|
||
|
|
const fixture: ComponentFixture<TestApp> = TestBed.createComponent(TestApp);
|
||
|
|
fixture.detectChanges();
|
||
|
|
const fabButtonDebugEl = fixture.debugElement.query(By.css('button[mat-fab]'))!;
|
||
|
|
expect(fabButtonDebugEl.nativeElement.classList).toContain('mat-primary');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should default to accent if config does not specify color', () => {
|
||
|
|
configure({});
|
||
|
|
const fixture: ComponentFixture<TestApp> = TestBed.createComponent(TestApp);
|
||
|
|
fixture.detectChanges();
|
||
|
|
const fabButtonDebugEl = fixture.debugElement.query(By.css('button[mat-fab]'))!;
|
||
|
|
expect(fabButtonDebugEl.nativeElement.classList).toContain('mat-accent');
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
/** Test component that contains an MatButton. */
|
||
|
|
@Component({
|
||
|
|
selector: 'test-app',
|
||
|
|
template: `
|
||
|
|
<button [tabIndex]="tabIndex" mat-button type="button" (click)="increment()"
|
||
|
|
[disabled]="isDisabled" [color]="buttonColor" [disableRipple]="rippleDisabled"
|
||
|
|
[disabledInteractive]="disabledInteractive">
|
||
|
|
Go
|
||
|
|
</button>
|
||
|
|
<a [tabIndex]="tabIndex" href="https://www.google.com" mat-button [disabled]="isDisabled"
|
||
|
|
[color]="buttonColor" [disabledInteractive]="disabledInteractive">
|
||
|
|
Link
|
||
|
|
</a>
|
||
|
|
<button mat-fab>Fab Button</button>
|
||
|
|
<button mat-fab [extended]="extended" class="extended-fab-test">Extended</button>
|
||
|
|
<button mat-mini-fab>Mini Fab Button</button>
|
||
|
|
`,
|
||
|
|
standalone: true,
|
||
|
|
imports: [MatButtonModule],
|
||
|
|
})
|
||
|
|
class TestApp {
|
||
|
|
clickCount = 0;
|
||
|
|
isDisabled = false;
|
||
|
|
rippleDisabled = false;
|
||
|
|
buttonColor: ThemePalette;
|
||
|
|
tabIndex: number;
|
||
|
|
extended = false;
|
||
|
|
disabledInteractive = false;
|
||
|
|
|
||
|
|
increment() {
|
||
|
|
this.clickCount++;
|
||
|
|
}
|
||
|
|
}
|