sass-references/angular-material/material/button/button.spec.ts

443 lines
16 KiB
TypeScript
Raw Permalink Normal View History

2024-12-06 10:42:08 +08:00
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++;
}
}