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

349 lines
12 KiB
TypeScript

import {ComponentFixture, TestBed} from '@angular/core/testing';
import {Component, DebugElement, ViewEncapsulation, ViewChild, signal} from '@angular/core';
import {By} from '@angular/platform-browser';
import {MatBadge, MatBadgeModule} from './index';
import {ThemePalette} from '@angular/material/core';
describe('MatBadge', () => {
let fixture: ComponentFixture<any>;
let badgeHostNativeElement: HTMLElement;
let badgeHostDebugElement: DebugElement;
describe('on an interative host', () => {
let testComponent: BadgeOnInteractiveElement;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
MatBadgeModule,
BadgeOnInteractiveElement,
PreExistingBadge,
NestedBadge,
BadgeOnTemplate,
],
});
fixture = TestBed.createComponent(BadgeOnInteractiveElement);
testComponent = fixture.debugElement.componentInstance;
fixture.detectChanges();
badgeHostDebugElement = fixture.debugElement.query(By.directive(MatBadge))!;
badgeHostNativeElement = badgeHostDebugElement.nativeElement;
});
it('should update the badge based on attribute', () => {
const badgeElement = badgeHostNativeElement.querySelector('.mat-badge-content')!;
expect(badgeElement.textContent).toContain('1');
testComponent.badgeContent.set('22');
fixture.detectChanges();
expect(badgeElement.textContent).toContain('22');
});
it('should be able to pass in falsy values to the badge content', () => {
const badgeElement = badgeHostNativeElement.querySelector('.mat-badge-content')!;
expect(badgeElement.textContent).toContain('1');
testComponent.badgeContent.set(0);
fixture.detectChanges();
expect(badgeElement.textContent).toContain('0');
});
it('should treat null and undefined as empty strings in the badge content', () => {
const badgeElement = badgeHostNativeElement.querySelector('.mat-badge-content')!;
expect(badgeElement.textContent).toContain('1');
testComponent.badgeContent.set(null);
fixture.detectChanges();
expect(badgeElement.textContent?.trim()).toBe('');
testComponent.badgeContent.set(undefined);
fixture.detectChanges();
expect(badgeElement.textContent?.trim()).toBe('');
});
it('should apply class based on color attribute', () => {
testComponent.badgeColor.set('primary');
fixture.detectChanges();
expect(badgeHostNativeElement.classList.contains('mat-badge-primary')).toBe(true);
testComponent.badgeColor.set('accent');
fixture.detectChanges();
expect(badgeHostNativeElement.classList.contains('mat-badge-accent')).toBe(true);
testComponent.badgeColor.set('warn');
fixture.detectChanges();
expect(badgeHostNativeElement.classList.contains('mat-badge-warn')).toBe(true);
testComponent.badgeColor.set(undefined);
fixture.detectChanges();
expect(badgeHostNativeElement.classList).not.toContain('mat-badge-accent');
});
it('should update the badge position on direction change', () => {
expect(badgeHostNativeElement.classList.contains('mat-badge-above')).toBe(true);
expect(badgeHostNativeElement.classList.contains('mat-badge-after')).toBe(true);
testComponent.badgeDirection.set('below before');
fixture.detectChanges();
expect(badgeHostNativeElement.classList.contains('mat-badge-below')).toBe(true);
expect(badgeHostNativeElement.classList.contains('mat-badge-before')).toBe(true);
});
it('should change visibility to hidden', () => {
expect(badgeHostNativeElement.classList.contains('mat-badge-hidden')).toBe(false);
testComponent.badgeHidden.set(true);
fixture.detectChanges();
expect(badgeHostNativeElement.classList.contains('mat-badge-hidden')).toBe(true);
});
it('should change badge sizes', () => {
expect(badgeHostNativeElement.classList.contains('mat-badge-medium')).toBe(true);
testComponent.badgeSize.set('small');
fixture.detectChanges();
expect(badgeHostNativeElement.classList.contains('mat-badge-small')).toBe(true);
testComponent.badgeSize.set('large');
fixture.detectChanges();
expect(badgeHostNativeElement.classList.contains('mat-badge-large')).toBe(true);
});
it('should change badge overlap', () => {
expect(badgeHostNativeElement.classList.contains('mat-badge-overlap')).toBe(false);
testComponent.badgeOverlap.set(true);
fixture.detectChanges();
expect(badgeHostNativeElement.classList.contains('mat-badge-overlap')).toBe(true);
});
it('should toggle `aria-describedby` depending on whether the badge has a description', () => {
expect(badgeHostNativeElement.hasAttribute('aria-describedby')).toBeFalse();
testComponent.badgeDescription.set('Describing a badge');
fixture.detectChanges();
const describedById = badgeHostNativeElement.getAttribute('aria-describedby') || '';
const description = document.getElementById(describedById)?.textContent;
expect(description).toBe('Describing a badge');
testComponent.badgeDescription.set('');
fixture.detectChanges();
expect(badgeHostNativeElement.hasAttribute('aria-describedby')).toBeFalse();
});
it('should toggle visibility based on whether the badge has content', () => {
const classList = badgeHostNativeElement.classList;
expect(classList.contains('mat-badge-hidden')).toBe(false);
testComponent.badgeContent.set('');
fixture.detectChanges();
expect(classList.contains('mat-badge-hidden')).toBe(true);
testComponent.badgeContent.set('hello');
fixture.detectChanges();
expect(classList.contains('mat-badge-hidden')).toBe(false);
testComponent.badgeContent.set(' ');
fixture.detectChanges();
expect(classList.contains('mat-badge-hidden')).toBe(true);
testComponent.badgeContent.set(0);
fixture.detectChanges();
expect(classList.contains('mat-badge-hidden')).toBe(false);
});
it('should apply view encapsulation on create badge content', () => {
const badge = badgeHostNativeElement.querySelector('.mat-badge-content')!;
let encapsulationAttr: Attr | undefined;
for (let i = 0; i < badge.attributes.length; i++) {
if (badge.attributes[i].name.startsWith('_ngcontent-')) {
encapsulationAttr = badge.attributes[i];
break;
}
}
expect(encapsulationAttr).toBeTruthy();
});
it('should toggle a class depending on the badge disabled state', () => {
const element: HTMLElement = badgeHostDebugElement.nativeElement;
expect(element.classList).not.toContain('mat-badge-disabled');
testComponent.badgeDisabled.set(true);
fixture.detectChanges();
expect(element.classList).toContain('mat-badge-disabled');
});
it('should clear any pre-existing badges', () => {
const preExistingFixture = TestBed.createComponent(PreExistingBadge);
preExistingFixture.detectChanges();
expect(preExistingFixture.nativeElement.querySelectorAll('.mat-badge-content').length).toBe(
1,
);
});
it('should not clear badge content from child elements', () => {
const preExistingFixture = TestBed.createComponent(NestedBadge);
preExistingFixture.detectChanges();
expect(preExistingFixture.nativeElement.querySelectorAll('.mat-badge-content').length).toBe(
2,
);
});
it('should expose the badge element', () => {
const badgeElement = badgeHostNativeElement.querySelector('.mat-badge-content')!;
expect(fixture.componentInstance.badgeInstance.getBadgeElement()).toBe(badgeElement);
});
it('should throw if badge is not attached to an element node', () => {
expect(() => {
TestBed.createComponent(BadgeOnTemplate);
}).toThrowError(/matBadge must be attached to an element node/);
});
it('should not insert an inline description', () => {
expect(badgeHostNativeElement.nextSibling)
.withContext('The badge host should not have an inline sibling description')
.toBeNull();
});
});
describe('on an non-interactive host', () => {
let testComponent: BadgeOnNonInteractiveElement;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [MatBadgeModule, BadgeOnNonInteractiveElement],
});
fixture = TestBed.createComponent(BadgeOnNonInteractiveElement);
testComponent = fixture.debugElement.componentInstance;
fixture.detectChanges();
badgeHostDebugElement = fixture.debugElement.query(By.directive(MatBadge))!;
badgeHostNativeElement = badgeHostDebugElement.nativeElement;
});
it('should insert the description inline after the host', () => {
testComponent.description.set('Extra info');
fixture.detectChanges();
const inlineDescription = badgeHostNativeElement.querySelector('.cdk-visually-hidden')!;
expect(inlineDescription)
.withContext('A visually hidden description element should exist')
.toBeDefined();
expect(inlineDescription.textContent)
.withContext('The badge host next sibling should contain its description')
.toBe('Extra info');
testComponent.description.set('Different info');
fixture.detectChanges();
expect(inlineDescription.textContent)
.withContext('The inline description should update')
.toBe('Different info');
});
it('should not apply aria-describedby for non-interactive hosts', () => {
testComponent.description.set('Extra info');
fixture.detectChanges();
expect(badgeHostNativeElement.hasAttribute('aria-description'))
.withContext('Non-interactive hosts should not have aria-describedby')
.toBeFalse();
});
});
});
/** Test component that contains a MatBadge. */
@Component({
// Explicitly set the view encapsulation since we have a test that checks for it.
encapsulation: ViewEncapsulation.Emulated,
styles: 'button { color: hotpink; }',
template: `
<button [matBadge]="badgeContent()"
[matBadgeColor]="badgeColor()"
[matBadgePosition]="badgeDirection()"
[matBadgeHidden]="badgeHidden()"
[matBadgeSize]="badgeSize()"
[matBadgeOverlap]="badgeOverlap()"
[matBadgeDescription]="badgeDescription()"
[matBadgeDisabled]="badgeDisabled()">
home
</button>
`,
standalone: true,
imports: [MatBadgeModule],
})
class BadgeOnInteractiveElement {
@ViewChild(MatBadge) badgeInstance: MatBadge;
badgeColor = signal<ThemePalette>(undefined);
badgeContent = signal<string | number | undefined | null>('1');
badgeDirection = signal('above after');
badgeHidden = signal(false);
badgeSize = signal('medium');
badgeOverlap = signal(false);
badgeDescription = signal<string | undefined>(undefined);
badgeDisabled = signal(false);
}
@Component({
template: '<span matBadge="7" [matBadgeDescription]="description()">Hello</span>',
standalone: true,
imports: [MatBadgeModule],
})
class BadgeOnNonInteractiveElement {
description = signal('');
}
@Component({
template: `
<span matBadge="Hello">
home
<div class="mat-badge-content">Pre-existing badge</div>
</span>
`,
standalone: true,
imports: [MatBadgeModule],
})
class PreExistingBadge {}
@Component({
template: `
<span matBadge="Hello">
home
<span matBadge="Hi">Something</span>
</span>
`,
standalone: true,
imports: [MatBadgeModule],
})
class NestedBadge {}
@Component({
template: `
<ng-template matBadge="1">Notifications</ng-template>`,
standalone: true,
imports: [MatBadgeModule],
})
class BadgeOnTemplate {}