349 lines
12 KiB
TypeScript
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 {}
|