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; 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