sass-references/angular-material/material/snack-bar/snack-bar.spec.ts

1115 lines
40 KiB
TypeScript
Raw Permalink Normal View History

2024-12-06 10:42:08 +08:00
import {LiveAnnouncer} from '@angular/cdk/a11y';
import {OverlayContainer} from '@angular/cdk/overlay';
import {Platform} from '@angular/cdk/platform';
import {
ChangeDetectionStrategy,
Component,
Directive,
TemplateRef,
ViewChild,
ViewContainerRef,
signal,
inject,
} from '@angular/core';
import {ComponentFixture, TestBed, fakeAsync, flush, tick} from '@angular/core/testing';
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
import {
MAT_SNACK_BAR_DATA,
MatSnackBar,
MatSnackBarConfig,
MatSnackBarContainer,
MatSnackBarModule,
MatSnackBarRef,
SimpleSnackBar,
} from './index';
import {MAT_SNACK_BAR_DEFAULT_OPTIONS} from './snack-bar';
describe('MatSnackBar', () => {
let snackBar: MatSnackBar;
let liveAnnouncer: LiveAnnouncer;
let overlayContainerElement: HTMLElement;
let testViewContainerRef: ViewContainerRef;
let viewContainerFixture: ComponentFixture<ComponentWithChildViewContainer>;
let simpleMessage = 'Burritos are here!';
let simpleActionLabel = 'pickup';
const announceDelay = 150;
const animationFrameDelay = 16;
beforeEach(fakeAsync(() => {
TestBed.configureTestingModule({
imports: [
MatSnackBarModule,
NoopAnimationsModule,
ComponentWithChildViewContainer,
BurritosNotification,
DirectiveWithViewContainer,
],
});
snackBar = TestBed.inject(MatSnackBar);
liveAnnouncer = TestBed.inject(LiveAnnouncer);
overlayContainerElement = TestBed.inject(OverlayContainer).getContainerElement();
viewContainerFixture = TestBed.createComponent(ComponentWithChildViewContainer);
viewContainerFixture.detectChanges();
testViewContainerRef = viewContainerFixture.componentInstance.childViewContainer;
}));
it('should open with content first in the inert region', () => {
snackBar.open('Snack time!', 'Chew');
viewContainerFixture.detectChanges();
const containerElement = overlayContainerElement.querySelector('mat-snack-bar-container')!;
const inertElement = containerElement.querySelector('[aria-hidden]')!;
expect(inertElement.getAttribute('aria-hidden'))
.withContext('Expected the non-live region to be aria-hidden')
.toBe('true');
expect(inertElement.textContent)
.withContext('Expected non-live region to contain the snack bar content')
.toContain('Snack time!');
const liveElement = containerElement.querySelector('[aria-live]')!;
expect(liveElement.childNodes.length)
.withContext('Expected live region to not contain any content')
.toBe(0);
});
it('should move content to the live region after 150ms', fakeAsync(() => {
snackBar.open('Snack time!', 'Chew');
viewContainerFixture.detectChanges();
const containerElement = overlayContainerElement.querySelector('mat-snack-bar-container')!;
const liveElement = containerElement.querySelector('[aria-live]')!;
tick(announceDelay);
expect(liveElement.textContent)
.withContext('Expected live region to contain the snack bar content')
.toContain('Snack time!');
const inertElement = containerElement.querySelector('[aria-hidden]')!;
expect(inertElement)
.withContext('Expected non-live region to not contain any content')
.toBeFalsy();
flush();
}));
it('should preserve focus when moving content to the live region', fakeAsync(() => {
snackBar.open('Snack time!', 'Chew');
viewContainerFixture.detectChanges();
tick(animationFrameDelay);
const actionButton = overlayContainerElement.querySelector(
'.mat-mdc-simple-snack-bar .mat-mdc-snack-bar-action',
)! as HTMLElement;
actionButton.focus();
expect(document.activeElement)
.withContext('Expected the focus to move to the action button')
.toBe(actionButton);
flush();
expect(document.activeElement)
.withContext('Expected the focus to remain on the action button')
.toBe(actionButton);
}));
it(
'should have aria-live of `assertive` with an `assertive` politeness if no announcement ' +
'message is provided',
() => {
snackBar.openFromComponent(BurritosNotification, {
announcementMessage: '',
politeness: 'assertive',
});
viewContainerFixture.detectChanges();
const containerElement = overlayContainerElement.querySelector('mat-snack-bar-container')!;
const liveElement = containerElement.querySelector('[aria-live]')!;
expect(liveElement.getAttribute('aria-live'))
.withContext('Expected snack bar container live region to have aria-live="assertive"')
.toBe('assertive');
},
);
it(
'should have aria-live of `polite` with an `assertive` politeness if an announcement ' +
'message is provided',
() => {
snackBar.openFromComponent(BurritosNotification, {
announcementMessage: 'Yay Burritos',
politeness: 'assertive',
});
viewContainerFixture.detectChanges();
const containerElement = overlayContainerElement.querySelector('mat-snack-bar-container')!;
const liveElement = containerElement.querySelector('[aria-live]')!;
expect(liveElement.getAttribute('aria-live'))
.withContext('Expected snack bar container live region to have aria-live="polite"')
.toBe('polite');
},
);
it('should have aria-live of `polite` with a `polite` politeness', () => {
snackBar.openFromComponent(BurritosNotification, {politeness: 'polite'});
viewContainerFixture.detectChanges();
const containerElement = overlayContainerElement.querySelector('mat-snack-bar-container')!;
const liveElement = containerElement.querySelector('[aria-live]')!;
expect(liveElement.getAttribute('aria-live'))
.withContext('Expected snack bar container live region to have aria-live="polite"')
.toBe('polite');
});
it('should have aria-live of `off` if the politeness is turned off', () => {
snackBar.openFromComponent(BurritosNotification, {politeness: 'off'});
viewContainerFixture.detectChanges();
const containerElement = overlayContainerElement.querySelector('mat-snack-bar-container')!;
const liveElement = containerElement.querySelector('[aria-live]')!;
expect(liveElement.getAttribute('aria-live'))
.withContext('Expected snack bar container live region to have aria-live="off"')
.toBe('off');
});
it('should have role of `alert` with an `assertive` politeness (Firefox only)', () => {
const platform = TestBed.inject(Platform);
snackBar.openFromComponent(BurritosNotification, {politeness: 'assertive'});
viewContainerFixture.detectChanges();
const containerElement = overlayContainerElement.querySelector('mat-snack-bar-container')!;
const liveElement = containerElement.querySelector('[aria-live]')!;
expect(liveElement.getAttribute('role')).toBe(platform.FIREFOX ? 'alert' : null);
});
it('should have role of `status` with an `polite` politeness (Firefox only)', () => {
const platform = TestBed.inject(Platform);
snackBar.openFromComponent(BurritosNotification, {politeness: 'polite'});
viewContainerFixture.detectChanges();
const containerElement = overlayContainerElement.querySelector('mat-snack-bar-container')!;
const liveElement = containerElement.querySelector('[aria-live]')!;
expect(liveElement.getAttribute('role')).toBe(platform.FIREFOX ? 'status' : null);
});
it('should have exactly one MDC label element when opened through simple snack bar', () => {
let config: MatSnackBarConfig = {viewContainerRef: testViewContainerRef};
snackBar.open(simpleMessage, simpleActionLabel, config);
viewContainerFixture.detectChanges();
expect(overlayContainerElement.querySelectorAll('.mdc-snackbar__label').length).toBe(1);
});
it('should open and close a snackbar without a ViewContainerRef', fakeAsync(() => {
let snackBarRef = snackBar.open('Snack time!', 'Chew');
viewContainerFixture.detectChanges();
let messageElement = overlayContainerElement.querySelector('mat-snack-bar-container')!;
expect(messageElement.textContent)
.withContext('Expected snack bar to show a message without a ViewContainerRef')
.toContain('Snack time!');
snackBarRef.dismiss();
viewContainerFixture.detectChanges();
flush();
expect(overlayContainerElement.childNodes.length)
.withContext('Expected snack bar to be dismissed without a ViewContainerRef')
.toBe(0);
}));
it('should open a simple message with a button', () => {
let config: MatSnackBarConfig = {viewContainerRef: testViewContainerRef};
let snackBarRef = snackBar.open(simpleMessage, simpleActionLabel, config);
viewContainerFixture.detectChanges();
expect(snackBarRef.instance instanceof SimpleSnackBar)
.withContext('Expected the snack bar content component to be SimpleSnackBar')
.toBe(true);
expect(snackBarRef.instance.snackBarRef)
.withContext('Expected the snack bar reference to be placed in the component instance')
.toBe(snackBarRef);
let messageElement = overlayContainerElement.querySelector('mat-snack-bar-container')!;
expect(messageElement.textContent)
.withContext(`Expected the snack bar message to be '${simpleMessage}'`)
.toContain(simpleMessage);
let buttonElement = overlayContainerElement.querySelector('button.mat-mdc-button')!;
expect(buttonElement.tagName)
.withContext('Expected snack bar action label to be a <button>')
.toBe('BUTTON');
expect((buttonElement.textContent || '').trim())
.withContext(`Expected the snack bar action label to be '${simpleActionLabel}'`)
.toBe(simpleActionLabel);
});
it('should open a simple message with no button', () => {
let config: MatSnackBarConfig = {viewContainerRef: testViewContainerRef};
let snackBarRef = snackBar.open(simpleMessage, undefined, config);
viewContainerFixture.detectChanges();
expect(snackBarRef.instance instanceof SimpleSnackBar)
.withContext('Expected the snack bar content component to be SimpleSnackBar')
.toBe(true);
expect(snackBarRef.instance.snackBarRef)
.withContext('Expected the snack bar reference to be placed in the component instance')
.toBe(snackBarRef);
let messageElement = overlayContainerElement.querySelector('mat-snack-bar-container')!;
expect(messageElement.textContent)
.withContext(`Expected the snack bar message to be '${simpleMessage}'`)
.toContain(simpleMessage);
expect(overlayContainerElement.querySelector('button.mat-mdc-button'))
.withContext('Expected the query selection for action label to be null')
.toBeNull();
});
it('should dismiss the snack bar and remove itself from the view', fakeAsync(() => {
let config: MatSnackBarConfig = {viewContainerRef: testViewContainerRef};
let dismissCompleteSpy = jasmine.createSpy('dismiss complete spy');
let snackBarRef = snackBar.open(simpleMessage, undefined, config);
viewContainerFixture.detectChanges();
flush();
expect(overlayContainerElement.childElementCount)
.withContext('Expected overlay container element to have at least one child')
.toBeGreaterThan(0);
snackBarRef.afterDismissed().subscribe({complete: dismissCompleteSpy});
const messageElement = overlayContainerElement.querySelector('mat-snack-bar-container')!;
snackBarRef.dismiss();
viewContainerFixture.detectChanges();
expect(messageElement.hasAttribute('mat-exit'))
.withContext('Expected the snackbar container to have the "exit" attribute upon dismiss')
.toBe(true);
flush();
expect(dismissCompleteSpy).toHaveBeenCalled();
expect(overlayContainerElement.childElementCount)
.withContext('Expected the overlay container element to have no child elements')
.toBe(0);
}));
it('should clear the announcement message if it is the same as main message', fakeAsync(() => {
spyOn(liveAnnouncer, 'announce');
snackBar.open(simpleMessage, undefined, {announcementMessage: simpleMessage});
viewContainerFixture.detectChanges();
flush();
expect(overlayContainerElement.childElementCount)
.withContext('Expected the overlay with the default announcement message to be added')
.toBe(1);
expect(liveAnnouncer.announce).not.toHaveBeenCalled();
}));
it('should be able to specify a custom announcement message', fakeAsync(() => {
spyOn(liveAnnouncer, 'announce');
snackBar.open(simpleMessage, '', {
announcementMessage: 'Custom announcement',
politeness: 'assertive',
});
viewContainerFixture.detectChanges();
flush();
expect(overlayContainerElement.childElementCount)
.withContext('Expected the overlay with a custom `announcementMessage` to be added')
.toBe(1);
expect(liveAnnouncer.announce).toHaveBeenCalledWith('Custom announcement', 'assertive');
}));
it('should be able to get dismissed through the service', fakeAsync(() => {
snackBar.open(simpleMessage);
viewContainerFixture.detectChanges();
expect(overlayContainerElement.childElementCount).toBeGreaterThan(0);
snackBar.dismiss();
viewContainerFixture.detectChanges();
flush();
expect(overlayContainerElement.childElementCount).toBe(0);
}));
it('should clean itself up when the view container gets destroyed', fakeAsync(() => {
snackBar.open(simpleMessage, undefined, {viewContainerRef: testViewContainerRef});
viewContainerFixture.detectChanges();
expect(overlayContainerElement.childElementCount).toBeGreaterThan(0);
viewContainerFixture.componentInstance.childComponentExists.set(false);
viewContainerFixture.detectChanges();
flush();
expect(overlayContainerElement.childElementCount)
.withContext('Expected snack bar to be removed after the view container was destroyed')
.toBe(0);
}));
it('should set the animation state to visible on entry', () => {
const config: MatSnackBarConfig = {viewContainerRef: testViewContainerRef};
const snackBarRef = snackBar.open(simpleMessage, undefined, config);
viewContainerFixture.detectChanges();
const container = snackBarRef.containerInstance as MatSnackBarContainer;
expect(container._animationState)
.withContext(`Expected the animation state would be 'visible'.`)
.toBe('visible');
snackBarRef.dismiss();
viewContainerFixture.detectChanges();
expect(container._animationState)
.withContext(`Expected the animation state would be 'hidden'.`)
.toBe('hidden');
});
it('should set the animation state to complete on exit', () => {
const config: MatSnackBarConfig = {viewContainerRef: testViewContainerRef};
const snackBarRef = snackBar.open(simpleMessage, undefined, config);
snackBarRef.dismiss();
viewContainerFixture.detectChanges();
const container = snackBarRef.containerInstance as MatSnackBarContainer;
expect(container._animationState)
.withContext(`Expected the animation state would be 'hidden'.`)
.toBe('hidden');
});
it(`should set the old snack bar animation state to complete and the new snack bar animation
state to visible on entry of new snack bar`, fakeAsync(() => {
const config: MatSnackBarConfig = {viewContainerRef: testViewContainerRef};
const snackBarRef = snackBar.open(simpleMessage, undefined, config);
const dismissCompleteSpy = jasmine.createSpy('dismiss complete spy');
viewContainerFixture.detectChanges();
const containerElement = document.querySelector('mat-snack-bar-container')!;
expect(containerElement.classList).toContain('ng-animating');
const container1 = snackBarRef.containerInstance as MatSnackBarContainer;
expect(container1._animationState)
.withContext(`Expected the animation state would be 'visible'.`)
.toBe('visible');
const config2 = {viewContainerRef: testViewContainerRef};
const snackBarRef2 = snackBar.open(simpleMessage, undefined, config2);
viewContainerFixture.detectChanges();
snackBarRef.afterDismissed().subscribe({complete: dismissCompleteSpy});
flush();
expect(dismissCompleteSpy).toHaveBeenCalled();
const container2 = snackBarRef2.containerInstance as MatSnackBarContainer;
expect(container1._animationState)
.withContext(`Expected the animation state would be 'hidden'.`)
.toBe('hidden');
expect(container2._animationState)
.withContext(`Expected the animation state would be 'visible'.`)
.toBe('visible');
}));
it('should open a new snackbar after dismissing a previous snackbar', fakeAsync(() => {
let config: MatSnackBarConfig = {viewContainerRef: testViewContainerRef};
let snackBarRef = snackBar.open(simpleMessage, 'Dismiss', config);
viewContainerFixture.detectChanges();
snackBarRef.dismiss();
viewContainerFixture.detectChanges();
// Wait for the snackbar dismiss animation to finish.
flush();
snackBar.open('Second snackbar');
viewContainerFixture.detectChanges();
// Wait for the snackbar open animation to finish.
flush();
expect(overlayContainerElement.textContent!.trim()).toBe('Second snackbar');
}));
it('should remove past snackbars when opening new snackbars', fakeAsync(() => {
snackBar.open('First snackbar');
viewContainerFixture.detectChanges();
snackBar.open('Second snackbar');
viewContainerFixture.detectChanges();
flush();
snackBar.open('Third snackbar');
viewContainerFixture.detectChanges();
flush();
expect(overlayContainerElement.textContent!.trim()).toBe('Third snackbar');
}));
it('should remove snackbar if another is shown while its still animating open', fakeAsync(() => {
snackBar.open('First snackbar');
viewContainerFixture.detectChanges();
snackBar.open('Second snackbar');
viewContainerFixture.detectChanges();
flush();
expect(overlayContainerElement.textContent!.trim()).toBe('Second snackbar');
}));
it('should dismiss the snackbar when the action is called, notifying of both action and dismiss', fakeAsync(() => {
const dismissNextSpy = jasmine.createSpy('dismiss next spy');
const dismissCompleteSpy = jasmine.createSpy('dismiss complete spy');
const actionNextSpy = jasmine.createSpy('action next spy');
const actionCompleteSpy = jasmine.createSpy('action complete spy');
const snackBarRef = snackBar.open('Some content', 'Dismiss');
viewContainerFixture.detectChanges();
snackBarRef.afterDismissed().subscribe({next: dismissNextSpy, complete: dismissCompleteSpy});
snackBarRef.onAction().subscribe({next: actionNextSpy, complete: actionCompleteSpy});
const actionButton = overlayContainerElement.querySelector(
'button.mat-mdc-button',
) as HTMLButtonElement;
actionButton.click();
viewContainerFixture.detectChanges();
tick();
expect(dismissNextSpy).toHaveBeenCalled();
expect(dismissCompleteSpy).toHaveBeenCalled();
expect(actionNextSpy).toHaveBeenCalled();
expect(actionCompleteSpy).toHaveBeenCalled();
tick(500);
}));
it('should allow manually dismissing with an action', fakeAsync(() => {
const dismissCompleteSpy = jasmine.createSpy('dismiss complete spy');
const actionCompleteSpy = jasmine.createSpy('action complete spy');
const snackBarRef = snackBar.open('Some content');
viewContainerFixture.detectChanges();
snackBarRef.afterDismissed().subscribe({complete: dismissCompleteSpy});
snackBarRef.onAction().subscribe({complete: actionCompleteSpy});
snackBarRef.dismissWithAction();
viewContainerFixture.detectChanges();
flush();
expect(dismissCompleteSpy).toHaveBeenCalled();
expect(actionCompleteSpy).toHaveBeenCalled();
}));
it('should indicate in `afterClosed` whether it was dismissed by an action', fakeAsync(() => {
const dismissSpy = jasmine.createSpy('dismiss spy');
const snackBarRef = snackBar.open('Some content');
viewContainerFixture.detectChanges();
snackBarRef.afterDismissed().subscribe(dismissSpy);
snackBarRef.dismissWithAction();
viewContainerFixture.detectChanges();
flush();
expect(dismissSpy).toHaveBeenCalledWith(jasmine.objectContaining({dismissedByAction: true}));
}));
it('should complete the onAction stream when not closing via an action', fakeAsync(() => {
const actionCompleteSpy = jasmine.createSpy('action complete spy');
const snackBarRef = snackBar.open('Some content');
viewContainerFixture.detectChanges();
snackBarRef.onAction().subscribe({complete: actionCompleteSpy});
snackBarRef.dismiss();
viewContainerFixture.detectChanges();
flush();
expect(actionCompleteSpy).toHaveBeenCalled();
}));
it('should dismiss automatically after a specified timeout', fakeAsync(() => {
const config = new MatSnackBarConfig();
config.duration = 250;
const snackBarRef = snackBar.open('content', 'test', config);
const afterDismissSpy = jasmine.createSpy('after dismiss spy');
snackBarRef.afterDismissed().subscribe(afterDismissSpy);
viewContainerFixture.detectChanges();
tick();
expect(afterDismissSpy).not.toHaveBeenCalled();
tick(1000);
viewContainerFixture.detectChanges();
tick();
expect(afterDismissSpy).toHaveBeenCalled();
}));
it('should add extra classes to the container', () => {
snackBar.open(simpleMessage, simpleActionLabel, {panelClass: ['one', 'two']});
viewContainerFixture.detectChanges();
let containerClasses =
overlayContainerElement.querySelector('mat-snack-bar-container')!.classList;
expect(containerClasses).toContain('one');
expect(containerClasses).toContain('two');
});
it('should set the layout direction', () => {
snackBar.open(simpleMessage, simpleActionLabel, {direction: 'rtl'});
viewContainerFixture.detectChanges();
let pane = overlayContainerElement.querySelector('.cdk-global-overlay-wrapper')!;
expect(pane.getAttribute('dir'))
.withContext('Expected the pane to be in RTL mode.')
.toBe('rtl');
});
it('should be able to override the default config', fakeAsync(() => {
viewContainerFixture.destroy();
TestBed.resetTestingModule()
.overrideProvider(MAT_SNACK_BAR_DEFAULT_OPTIONS, {
deps: [],
useFactory: () => ({panelClass: 'custom-class'}),
})
.configureTestingModule({imports: [MatSnackBarModule, NoopAnimationsModule]});
snackBar = TestBed.inject(MatSnackBar);
overlayContainerElement = TestBed.inject(OverlayContainer).getContainerElement();
snackBar.open(simpleMessage);
flush();
expect(overlayContainerElement.querySelector('mat-snack-bar-container')!.classList)
.withContext('Expected class applied through the defaults to be applied.')
.toContain('custom-class');
}));
it('should dismiss the open snack bar on destroy', fakeAsync(() => {
snackBar.open(simpleMessage);
viewContainerFixture.detectChanges();
expect(overlayContainerElement.childElementCount).toBeGreaterThan(0);
snackBar.ngOnDestroy();
viewContainerFixture.detectChanges();
flush();
expect(overlayContainerElement.childElementCount).toBe(0);
}));
it('should cap the timeout to the maximum accepted delay in setTimeout', fakeAsync(() => {
const config = new MatSnackBarConfig();
config.duration = Infinity;
snackBar.open('content', 'test', config);
viewContainerFixture.detectChanges();
spyOn(window, 'setTimeout').and.callThrough();
tick(100);
expect(window.setTimeout).toHaveBeenCalledWith(jasmine.any(Function), Math.pow(2, 31) - 1);
flush();
}));
it('should only keep one snack bar in the DOM if multiple are opened at the same time', fakeAsync(() => {
for (let i = 0; i < 10; i++) {
snackBar.open('Snack time!', 'Chew');
viewContainerFixture.detectChanges();
}
flush();
expect(overlayContainerElement.querySelectorAll('mat-snack-bar-container').length).toBe(1);
}));
describe('with custom component', () => {
it('should open a custom component', () => {
const snackBarRef = snackBar.openFromComponent(BurritosNotification);
expect(snackBarRef.instance instanceof BurritosNotification)
.withContext('Expected the snack bar content component to be BurritosNotification')
.toBe(true);
expect(overlayContainerElement.textContent!.trim())
.withContext('Expected component to have the proper text.')
.toBe('Burritos are on the way.');
});
it('should inject the snack bar reference into the component', () => {
const snackBarRef = snackBar.openFromComponent(BurritosNotification);
expect(snackBarRef.instance.snackBarRef)
.withContext('Expected component to have an injected snack bar reference.')
.toBe(snackBarRef);
});
it('should have exactly one MDC label element', () => {
snackBar.openFromComponent(BurritosNotification);
viewContainerFixture.detectChanges();
expect(overlayContainerElement.querySelectorAll('.mdc-snackbar__label').length).toBe(1);
});
it('should be able to inject arbitrary user data', () => {
const snackBarRef = snackBar.openFromComponent(BurritosNotification, {
data: {
burritoType: 'Chimichanga',
},
});
expect(snackBarRef.instance.data)
.withContext('Expected component to have a data object.')
.toBeTruthy();
expect(snackBarRef.instance.data.burritoType)
.withContext('Expected the injected data object to be the one the user provided.')
.toBe('Chimichanga');
});
it('should allow manually dismissing with an action', fakeAsync(() => {
const dismissCompleteSpy = jasmine.createSpy('dismiss complete spy');
const actionCompleteSpy = jasmine.createSpy('action complete spy');
const snackBarRef = snackBar.openFromComponent(BurritosNotification);
viewContainerFixture.detectChanges();
snackBarRef.afterDismissed().subscribe({complete: dismissCompleteSpy});
snackBarRef.onAction().subscribe({complete: actionCompleteSpy});
snackBarRef.dismissWithAction();
viewContainerFixture.detectChanges();
flush();
expect(dismissCompleteSpy).toHaveBeenCalled();
expect(actionCompleteSpy).toHaveBeenCalled();
}));
});
describe('with TemplateRef', () => {
let templateFixture: ComponentFixture<ComponentWithTemplateRef>;
beforeEach(() => {
templateFixture = TestBed.createComponent(ComponentWithTemplateRef);
templateFixture.detectChanges();
});
it('should be able to open a snack bar using a TemplateRef', () => {
templateFixture.componentInstance.localValue = 'Pizza';
templateFixture.changeDetectorRef.markForCheck();
snackBar.openFromTemplate(templateFixture.componentInstance.templateRef);
templateFixture.detectChanges();
const containerElement = overlayContainerElement.querySelector('mat-snack-bar-container')!;
expect(containerElement.textContent).toContain('Fries');
expect(containerElement.textContent).toContain('Pizza');
templateFixture.componentInstance.localValue = 'Pasta';
templateFixture.changeDetectorRef.markForCheck();
templateFixture.detectChanges();
expect(containerElement.textContent).toContain('Pasta');
});
it('should be able to pass in contextual data when opening with a TemplateRef', () => {
snackBar.openFromTemplate(templateFixture.componentInstance.templateRef, {
data: {value: 'Oranges'},
});
templateFixture.detectChanges();
const containerElement = overlayContainerElement.querySelector('mat-snack-bar-container')!;
expect(containerElement.textContent).toContain('Oranges');
});
});
});
describe('MatSnackBar with parent MatSnackBar', () => {
let parentSnackBar: MatSnackBar;
let childSnackBar: MatSnackBar;
let overlayContainerElement: HTMLElement;
let fixture: ComponentFixture<ComponentThatProvidesMatSnackBar>;
beforeEach(fakeAsync(() => {
TestBed.configureTestingModule({
imports: [
MatSnackBarModule,
NoopAnimationsModule,
ComponentThatProvidesMatSnackBar,
DirectiveWithViewContainer,
],
});
parentSnackBar = TestBed.inject(MatSnackBar);
overlayContainerElement = TestBed.inject(OverlayContainer).getContainerElement();
fixture = TestBed.createComponent(ComponentThatProvidesMatSnackBar);
childSnackBar = fixture.componentInstance.snackBar;
fixture.detectChanges();
}));
it('should close snackBars opened by parent when opening from child', fakeAsync(() => {
parentSnackBar.open('Pizza');
fixture.detectChanges();
tick(1000);
expect(overlayContainerElement.textContent)
.withContext('Expected a snackBar to be opened')
.toContain('Pizza');
childSnackBar.open('Taco');
fixture.detectChanges();
tick(1000);
expect(overlayContainerElement.textContent)
.withContext('Expected parent snackbar msg to be dismissed by opening from child')
.toContain('Taco');
}));
it('should close snackBars opened by child when opening from parent', fakeAsync(() => {
childSnackBar.open('Pizza');
fixture.detectChanges();
tick(1000);
expect(overlayContainerElement.textContent)
.withContext('Expected a snackBar to be opened')
.toContain('Pizza');
parentSnackBar.open('Taco');
fixture.detectChanges();
tick(1000);
expect(overlayContainerElement.textContent)
.withContext('Expected child snackbar msg to be dismissed by opening from parent')
.toContain('Taco');
}));
it('should not dismiss parent snack bar if child is destroyed', fakeAsync(() => {
parentSnackBar.open('Pizza');
fixture.detectChanges();
expect(overlayContainerElement.childElementCount).toBeGreaterThan(0);
childSnackBar.ngOnDestroy();
fixture.detectChanges();
flush();
expect(overlayContainerElement.childElementCount).toBeGreaterThan(0);
}));
});
describe('MatSnackBar Positioning', () => {
let snackBar: MatSnackBar;
let overlayContainerEl: HTMLElement;
let viewContainerFixture: ComponentFixture<ComponentWithChildViewContainer>;
let simpleMessage = 'Burritos are here!';
let simpleActionLabel = 'pickup';
beforeEach(fakeAsync(() => {
TestBed.configureTestingModule({
imports: [
MatSnackBarModule,
NoopAnimationsModule,
ComponentWithChildViewContainer,
DirectiveWithViewContainer,
],
});
snackBar = TestBed.inject(MatSnackBar);
overlayContainerEl = TestBed.inject(OverlayContainer).getContainerElement();
viewContainerFixture = TestBed.createComponent(ComponentWithChildViewContainer);
viewContainerFixture.detectChanges();
}));
it('should default to bottom center', fakeAsync(() => {
snackBar.open(simpleMessage, simpleActionLabel);
viewContainerFixture.detectChanges();
flush();
const overlayPaneEl = overlayContainerEl.querySelector('.cdk-overlay-pane') as HTMLElement;
expect(overlayPaneEl.style.marginBottom)
.withContext('Expected margin-bottom to be "0px"')
.toBe('0px');
expect(overlayPaneEl.style.marginTop).withContext('Expected margin-top to be ""').toBe('');
expect(overlayPaneEl.style.marginRight).withContext('Expected margin-right to be ""').toBe('');
expect(overlayPaneEl.style.marginLeft).withContext('Expected margin-left to be ""').toBe('');
}));
it('should be in the bottom left corner', fakeAsync(() => {
snackBar.open(simpleMessage, simpleActionLabel, {
verticalPosition: 'bottom',
horizontalPosition: 'left',
});
viewContainerFixture.detectChanges();
flush();
const overlayPaneEl = overlayContainerEl.querySelector('.cdk-overlay-pane') as HTMLElement;
expect(overlayPaneEl.style.marginBottom)
.withContext('Expected margin-bottom to be "0px"')
.toBe('0px');
expect(overlayPaneEl.style.marginTop).withContext('Expected margin-top to be ""').toBe('');
expect(overlayPaneEl.style.marginRight).withContext('Expected margin-right to be ""').toBe('');
expect(overlayPaneEl.style.marginLeft)
.withContext('Expected margin-left to be "0px"')
.toBe('0px');
}));
it('should be in the bottom right corner', fakeAsync(() => {
snackBar.open(simpleMessage, simpleActionLabel, {
verticalPosition: 'bottom',
horizontalPosition: 'right',
});
viewContainerFixture.detectChanges();
flush();
const overlayPaneEl = overlayContainerEl.querySelector('.cdk-overlay-pane') as HTMLElement;
expect(overlayPaneEl.style.marginBottom)
.withContext('Expected margin-bottom to be "0px"')
.toBe('0px');
expect(overlayPaneEl.style.marginTop).withContext('Expected margin-top to be ""').toBe('');
expect(overlayPaneEl.style.marginRight)
.withContext('Expected margin-right to be "0px"')
.toBe('0px');
expect(overlayPaneEl.style.marginLeft).withContext('Expected margin-left to be ""').toBe('');
}));
it('should be in the bottom center', fakeAsync(() => {
snackBar.open(simpleMessage, simpleActionLabel, {
verticalPosition: 'bottom',
horizontalPosition: 'center',
});
viewContainerFixture.detectChanges();
flush();
const overlayPaneEl = overlayContainerEl.querySelector('.cdk-overlay-pane') as HTMLElement;
expect(overlayPaneEl.style.marginBottom)
.withContext('Expected margin-bottom to be "0px"')
.toBe('0px');
expect(overlayPaneEl.style.marginTop).withContext('Expected margin-top to be ""').toBe('');
expect(overlayPaneEl.style.marginRight).withContext('Expected margin-right to be ""').toBe('');
expect(overlayPaneEl.style.marginLeft).withContext('Expected margin-left to be ""').toBe('');
}));
it('should be in the top left corner', fakeAsync(() => {
snackBar.open(simpleMessage, simpleActionLabel, {
verticalPosition: 'top',
horizontalPosition: 'left',
});
viewContainerFixture.detectChanges();
flush();
const overlayPaneEl = overlayContainerEl.querySelector('.cdk-overlay-pane') as HTMLElement;
expect(overlayPaneEl.style.marginBottom)
.withContext('Expected margin-bottom to be ""')
.toBe('');
expect(overlayPaneEl.style.marginTop)
.withContext('Expected margin-top to be "0px"')
.toBe('0px');
expect(overlayPaneEl.style.marginRight).withContext('Expected margin-right to be ""').toBe('');
expect(overlayPaneEl.style.marginLeft)
.withContext('Expected margin-left to be "0px"')
.toBe('0px');
}));
it('should be in the top right corner', fakeAsync(() => {
snackBar.open(simpleMessage, simpleActionLabel, {
verticalPosition: 'top',
horizontalPosition: 'right',
});
viewContainerFixture.detectChanges();
flush();
const overlayPaneEl = overlayContainerEl.querySelector('.cdk-overlay-pane') as HTMLElement;
expect(overlayPaneEl.style.marginBottom)
.withContext('Expected margin-bottom to be ""')
.toBe('');
expect(overlayPaneEl.style.marginTop)
.withContext('Expected margin-top to be "0px"')
.toBe('0px');
expect(overlayPaneEl.style.marginRight)
.withContext('Expected margin-right to be "0px"')
.toBe('0px');
expect(overlayPaneEl.style.marginLeft).withContext('Expected margin-left to be ""').toBe('');
}));
it('should be in the top center', fakeAsync(() => {
snackBar.open(simpleMessage, simpleActionLabel, {
verticalPosition: 'top',
horizontalPosition: 'center',
});
viewContainerFixture.detectChanges();
flush();
const overlayPaneEl = overlayContainerEl.querySelector('.cdk-overlay-pane') as HTMLElement;
expect(overlayPaneEl.style.marginBottom)
.withContext('Expected margin-bottom to be ""')
.toBe('');
expect(overlayPaneEl.style.marginTop)
.withContext('Expected margin-top to be "0px"')
.toBe('0px');
expect(overlayPaneEl.style.marginRight).withContext('Expected margin-right to be ""').toBe('');
expect(overlayPaneEl.style.marginLeft).withContext('Expected margin-left to be ""').toBe('');
}));
it('should handle start based on direction (rtl)', fakeAsync(() => {
snackBar.open(simpleMessage, simpleActionLabel, {
verticalPosition: 'top',
horizontalPosition: 'start',
direction: 'rtl',
});
viewContainerFixture.detectChanges();
flush();
const overlayPaneEl = overlayContainerEl.querySelector('.cdk-overlay-pane') as HTMLElement;
expect(overlayPaneEl.style.marginBottom)
.withContext('Expected margin-bottom to be ""')
.toBe('');
expect(overlayPaneEl.style.marginTop)
.withContext('Expected margin-top to be "0px"')
.toBe('0px');
expect(overlayPaneEl.style.marginRight)
.withContext('Expected margin-right to be "0px"')
.toBe('0px');
expect(overlayPaneEl.style.marginLeft).withContext('Expected margin-left to be ""').toBe('');
}));
it('should handle start based on direction (ltr)', fakeAsync(() => {
snackBar.open(simpleMessage, simpleActionLabel, {
verticalPosition: 'top',
horizontalPosition: 'start',
direction: 'ltr',
});
viewContainerFixture.detectChanges();
flush();
const overlayPaneEl = overlayContainerEl.querySelector('.cdk-overlay-pane') as HTMLElement;
expect(overlayPaneEl.style.marginBottom)
.withContext('Expected margin-bottom to be ""')
.toBe('');
expect(overlayPaneEl.style.marginTop)
.withContext('Expected margin-top to be "0px"')
.toBe('0px');
expect(overlayPaneEl.style.marginRight).withContext('Expected margin-right to be ""').toBe('');
expect(overlayPaneEl.style.marginLeft)
.withContext('Expected margin-left to be "0px"')
.toBe('0px');
}));
it('should handle end based on direction (rtl)', fakeAsync(() => {
snackBar.open(simpleMessage, simpleActionLabel, {
verticalPosition: 'top',
horizontalPosition: 'end',
direction: 'rtl',
});
viewContainerFixture.detectChanges();
flush();
const overlayPaneEl = overlayContainerEl.querySelector('.cdk-overlay-pane') as HTMLElement;
expect(overlayPaneEl.style.marginBottom)
.withContext('Expected margin-bottom to be ""')
.toBe('');
expect(overlayPaneEl.style.marginTop)
.withContext('Expected margin-top to be "0px"')
.toBe('0px');
expect(overlayPaneEl.style.marginRight).withContext('Expected margin-right to be ""').toBe('');
expect(overlayPaneEl.style.marginLeft)
.withContext('Expected margin-left to be "0px"')
.toBe('0px');
}));
it('should handle end based on direction (ltr)', fakeAsync(() => {
snackBar.open(simpleMessage, simpleActionLabel, {
verticalPosition: 'top',
horizontalPosition: 'end',
direction: 'ltr',
});
viewContainerFixture.detectChanges();
flush();
const overlayPaneEl = overlayContainerEl.querySelector('.cdk-overlay-pane') as HTMLElement;
expect(overlayPaneEl.style.marginBottom)
.withContext('Expected margin-bottom to be ""')
.toBe('');
expect(overlayPaneEl.style.marginTop)
.withContext('Expected margin-top to be "0px"')
.toBe('0px');
expect(overlayPaneEl.style.marginRight)
.withContext('Expected margin-right to be "0px"')
.toBe('0px');
expect(overlayPaneEl.style.marginLeft).withContext('Expected margin-left to be ""').toBe('');
}));
});
@Directive({
selector: 'dir-with-view-container',
standalone: true,
})
class DirectiveWithViewContainer {
viewContainerRef = inject(ViewContainerRef);
}
@Component({
selector: 'arbitrary-component',
template: `@if (childComponentExists()) {<dir-with-view-container></dir-with-view-container>}`,
standalone: true,
imports: [DirectiveWithViewContainer],
changeDetection: ChangeDetectionStrategy.OnPush,
})
class ComponentWithChildViewContainer {
@ViewChild(DirectiveWithViewContainer) childWithViewContainer: DirectiveWithViewContainer;
childComponentExists = signal(true);
get childViewContainer() {
return this.childWithViewContainer.viewContainerRef;
}
}
@Component({
selector: 'arbitrary-component-with-template-ref',
template: `
<ng-template let-data>
Fries {{localValue}} {{data?.value}}
</ng-template>
`,
standalone: true,
})
class ComponentWithTemplateRef {
@ViewChild(TemplateRef) templateRef: TemplateRef<any>;
localValue: string;
}
/** Simple component for testing ComponentPortal. */
@Component({
template: '<p>Burritos are on the way.</p>',
standalone: true,
})
class BurritosNotification {
snackBarRef = inject<MatSnackBarRef<BurritosNotification>>(MatSnackBarRef);
data = inject(MAT_SNACK_BAR_DATA);
}
@Component({
template: '',
providers: [MatSnackBar],
standalone: true,
})
class ComponentThatProvidesMatSnackBar {
snackBar = inject(MatSnackBar);
}