/
githubmirror
/
components
Обзор
Документация
Войти
/
githubmirror
/
components
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/material/dialog/dialog.spec.ts
2 544 строки
86 KB
Kristiyan Kostadinov
fix(material/dialog): do not expose CDK dialog ref to users (#33603)
30 июл 2026, 18:45
Не верифицирован
30 июл 2026, 18:45
d61a26d
Код
Авторство
О чём код?
import {FocusMonitor, FocusOrigin} from '@angular/cdk/a11y'; import {Directionality} from '@angular/cdk/bidi'; import {A, ESCAPE} from '@angular/cdk/keycodes'; import {createCloseScrollStrategy, OverlayContainer, ScrollStrategy} from '@angular/cdk/overlay'; import {_supportsShadowDom} from '@angular/cdk/platform'; import {ScrollDispatcher} from '@angular/cdk/scrolling'; import { createKeyboardEvent, dispatchEvent, dispatchKeyboardEvent, dispatchMouseEvent, patchElementFocus, } from '@angular/cdk/testing/private'; import {AsyncPipe, Location} from '@angular/common'; import {SpyLocation} from '@angular/common/testing'; import { ChangeDetectionStrategy, Component, createNgModule, Directive, Service, Injector, Input, NgModule, TemplateRef, ViewChild, ViewContainerRef, ViewEncapsulation, forwardRef, signal, inject, inputBinding, } from '@angular/core'; import {DialogRef} from '@angular/cdk/dialog'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; import {Subject} from 'rxjs'; import {map} from 'rxjs/operators'; import {CLOSE_ANIMATION_DURATION, OPEN_ANIMATION_DURATION} from './dialog-container'; import { MAT_DIALOG_DATA, MAT_DIALOG_DEFAULT_OPTIONS, MatDialog, MatDialogActions, MatDialogClose, MatDialogContent, MatDialogRef, MatDialogState, MatDialogTitle, } from './index'; import {MATERIAL_ANIMATIONS} from '../core'; describe('MatDialog', () => { let dialog: MatDialog; let overlayContainerElement: HTMLElement; let scrolledSubject = new Subject(); let focusMonitor: FocusMonitor; let testViewContainerRef: ViewContainerRef; let viewContainerFixture: ComponentFixture<ComponentWithChildViewContainer>; let mockLocation: SpyLocation; beforeEach(() => { TestBed.configureTestingModule({ providers: [ {provide: Location, useClass: SpyLocation}, {provide: MATERIAL_ANIMATIONS, useValue: {animationsDisabled: true}}, { provide: ScrollDispatcher, useFactory: () => ({ scrolled: () => scrolledSubject, register: () => {}, deregister: () => {}, }), }, ], }); dialog = TestBed.inject(MatDialog); mockLocation = TestBed.inject(Location) as SpyLocation; overlayContainerElement = TestBed.inject(OverlayContainer).getContainerElement(); focusMonitor = TestBed.inject(FocusMonitor); viewContainerFixture = TestBed.createComponent(ComponentWithChildViewContainer); viewContainerFixture.detectChanges(); testViewContainerRef = viewContainerFixture.componentInstance.childViewContainer; }); it('should open a dialog with a component', () => { let dialogRef = dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); viewContainerFixture.detectChanges(); expect(overlayContainerElement.textContent).toContain('Pizza'); expect(dialogRef.componentInstance instanceof PizzaMsg).toBe(true); expect(dialogRef.componentInstance.dialogRef).toBe(dialogRef); viewContainerFixture.detectChanges(); let dialogContainerElement = overlayContainerElement.querySelector('mat-dialog-container')!; expect(dialogContainerElement.getAttribute('role')).toBe('dialog'); expect(dialogContainerElement.getAttribute('aria-modal')).toBe('false'); }); it('should be able to set aria-modal', () => { dialog.open(PizzaMsg, { viewContainerRef: testViewContainerRef, ariaModal: true, }); viewContainerFixture.detectChanges(); const container = overlayContainerElement.querySelector('mat-dialog-container')!; expect(container.getAttribute('aria-modal')).toBe('true'); }); it('should open a dialog with a template', () => { const templateRefFixture = TestBed.createComponent(ComponentWithTemplateRef); templateRefFixture.componentInstance.localValue = 'Bees'; templateRefFixture.changeDetectorRef.markForCheck(); templateRefFixture.detectChanges(); const data = {value: 'Knees'}; let dialogRef = dialog.open(templateRefFixture.componentInstance.templateRef, {data}); viewContainerFixture.detectChanges(); expect(overlayContainerElement.textContent).toContain('Cheese Bees Knees'); expect(templateRefFixture.componentInstance.dialogRef).toBe(dialogRef); viewContainerFixture.detectChanges(); let dialogContainerElement = overlayContainerElement.querySelector('mat-dialog-container')!; expect(dialogContainerElement.getAttribute('role')).toBe('dialog'); expect(dialogContainerElement.getAttribute('aria-modal')).toBe('false'); dialogRef.close(); }); it('should emit when dialog opening animation is complete', async () => { const dialogRef = dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); const spy = jasmine.createSpy('afterOpen spy'); dialogRef.afterOpened().subscribe(spy); viewContainerFixture.detectChanges(); // callback should not be called before animation is complete expect(spy).not.toHaveBeenCalled(); await viewContainerFixture.whenStable(); expect(spy).toHaveBeenCalled(); }); it('should use injector from viewContainerRef for DialogInjector', () => { let dialogRef = dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); viewContainerFixture.detectChanges(); let dialogInjector = dialogRef.componentInstance.dialogInjector; expect(dialogRef.componentInstance.dialogRef).toBe(dialogRef); expect(dialogInjector.get<DirectiveWithViewContainer>(DirectiveWithViewContainer)) .withContext( 'Expected the dialog component to be created with the injector from ' + 'the viewContainerRef.', ) .toBeTruthy(); }); it('should open a dialog with a component and no ViewContainerRef', () => { let dialogRef = dialog.open(PizzaMsg); viewContainerFixture.detectChanges(); expect(overlayContainerElement.textContent).toContain('Pizza'); expect(dialogRef.componentInstance instanceof PizzaMsg).toBe(true); expect(dialogRef.componentInstance.dialogRef).toBe(dialogRef); viewContainerFixture.detectChanges(); let dialogContainerElement = overlayContainerElement.querySelector('mat-dialog-container')!; expect(dialogContainerElement.getAttribute('role')).toBe('dialog'); }); it('should apply the configured role to the dialog element', () => { dialog.open(PizzaMsg, {role: 'alertdialog'}); viewContainerFixture.detectChanges(); let dialogContainerElement = overlayContainerElement.querySelector('mat-dialog-container')!; expect(dialogContainerElement.getAttribute('role')).toBe('alertdialog'); }); it('should apply the specified `aria-describedby`', () => { dialog.open(PizzaMsg, {ariaDescribedBy: 'description-element'}); viewContainerFixture.detectChanges(); let dialogContainerElement = overlayContainerElement.querySelector('mat-dialog-container')!; expect(dialogContainerElement.getAttribute('aria-describedby')).toBe('description-element'); }); it('should close a dialog and get back a result', async () => { let dialogRef = dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); let afterCloseCallback = jasmine.createSpy('afterClose callback'); dialogRef.afterClosed().subscribe(afterCloseCallback); dialogRef.close('Charmander'); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); expect(afterCloseCallback).toHaveBeenCalledWith('Charmander'); expect(overlayContainerElement.querySelector('mat-dialog-container')).toBeNull(); }); it('should dispose of dialog if view container is destroyed while animating', async () => { const dialogRef = dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); dialogRef.close(); viewContainerFixture.detectChanges(); viewContainerFixture.destroy(); await wait(100); expect(overlayContainerElement.querySelector('mat-dialog-container')).toBeNull(); }); it('should dispatch the beforeClosed and afterClosed events when the overlay is detached externally', async () => { const dialogRef = dialog.open(PizzaMsg, { viewContainerRef: testViewContainerRef, scrollStrategy: createCloseScrollStrategy(TestBed.inject(Injector)), }); const beforeClosedCallback = jasmine.createSpy('beforeClosed callback'); const afterCloseCallback = jasmine.createSpy('afterClosed callback'); dialogRef.beforeClosed().subscribe(beforeClosedCallback); dialogRef.afterClosed().subscribe(afterCloseCallback); scrolledSubject.next(); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); expect(beforeClosedCallback).toHaveBeenCalledTimes(1); expect(afterCloseCallback).toHaveBeenCalledTimes(1); }); it('should close a dialog and get back a result before it is closed', async () => { const dialogRef = dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); await viewContainerFixture.whenStable(); viewContainerFixture.detectChanges(); // beforeClose should emit before dialog container is destroyed const beforeCloseHandler = jasmine.createSpy('beforeClose callback').and.callFake(() => { expect(overlayContainerElement.querySelector('mat-dialog-container')) .not.withContext('dialog container exists when beforeClose is called') .toBeNull(); }); dialogRef.beforeClosed().subscribe(beforeCloseHandler); dialogRef.close('Bulbasaur'); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); expect(beforeCloseHandler).toHaveBeenCalledWith('Bulbasaur'); expect(overlayContainerElement.querySelector('mat-dialog-container')).toBeNull(); }); it('should close a dialog via the escape key', async () => { dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); const event = dispatchKeyboardEvent(document.body, 'keydown', ESCAPE); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); expect(overlayContainerElement.querySelector('mat-dialog-container')).toBeNull(); expect(event.defaultPrevented).toBe(true); }); it('should not close a dialog via the escape key with a modifier', async () => { dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); const event = createKeyboardEvent('keydown', ESCAPE, undefined, {alt: true}); dispatchEvent(document.body, event); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); expect(overlayContainerElement.querySelector('mat-dialog-container')).toBeTruthy(); expect(event.defaultPrevented).toBe(false); }); it('should close from a ViewContainerRef with OnPush change detection', async () => { const onPushFixture = TestBed.createComponent(ComponentWithOnPushViewContainer); onPushFixture.detectChanges(); const dialogRef = dialog.open(PizzaMsg, { viewContainerRef: onPushFixture.componentInstance.viewContainerRef, }); await onPushFixture.whenStable(); onPushFixture.detectChanges(); await onPushFixture.whenStable(); expect(overlayContainerElement.querySelectorAll('mat-dialog-container').length) .withContext('Expected one open dialog.') .toBe(1); dialogRef.close(); await onPushFixture.whenStable(); onPushFixture.detectChanges(); await wait(100); expect(overlayContainerElement.querySelectorAll('mat-dialog-container').length) .withContext('Expected no open dialogs.') .toBe(0); }); it('should close when clicking on the overlay backdrop', async () => { dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); viewContainerFixture.detectChanges(); let backdrop = overlayContainerElement.querySelector('.cdk-overlay-backdrop') as HTMLElement; backdrop.click(); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); expect(overlayContainerElement.querySelector('mat-dialog-container')).toBeFalsy(); }); it('should emit the backdropClick stream when clicking on the overlay backdrop', async () => { const dialogRef = dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); const spy = jasmine.createSpy('backdropClick spy'); dialogRef.backdropClick().subscribe(spy); viewContainerFixture.detectChanges(); let backdrop = overlayContainerElement.querySelector('.cdk-overlay-backdrop') as HTMLElement; backdrop.click(); expect(spy).toHaveBeenCalledTimes(1); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); // Additional clicks after the dialog has closed should not be emitted backdrop.click(); expect(spy).toHaveBeenCalledTimes(1); }); it('should emit the keyboardEvent stream when key events target the overlay', async () => { const dialogRef = dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); const spy = jasmine.createSpy('keyboardEvent spy'); dialogRef.keydownEvents().subscribe(spy); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); let backdrop = overlayContainerElement.querySelector('.cdk-overlay-backdrop') as HTMLElement; let container = overlayContainerElement.querySelector('mat-dialog-container') as HTMLElement; dispatchKeyboardEvent(document.body, 'keydown', A); dispatchKeyboardEvent(backdrop, 'keydown', A); dispatchKeyboardEvent(container, 'keydown', A); expect(spy).toHaveBeenCalledTimes(3); }); it('should notify the observers if a dialog has been opened', () => { dialog.afterOpened.subscribe(ref => { expect(dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef})).toBe(ref); }); }); it('should notify the observers if all open dialogs have finished closing', async () => { const ref1 = dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); const ref2 = dialog.open(ContentElementDialog, {viewContainerRef: testViewContainerRef}); const spy = jasmine.createSpy('afterAllClosed spy'); dialog.afterAllClosed.subscribe(spy); ref1.close(); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); expect(spy).not.toHaveBeenCalled(); ref2.close(); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); expect(spy).toHaveBeenCalled(); }); it('should emit the afterAllClosed stream on subscribe if there are no open dialogs', () => { const spy = jasmine.createSpy('afterAllClosed spy'); dialog.afterAllClosed.subscribe(spy); expect(spy).toHaveBeenCalled(); }); it('should override the width of the overlay pane', () => { dialog.open(PizzaMsg, {width: '500px'}); viewContainerFixture.detectChanges(); let overlayPane = overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement; expect(overlayPane.style.width).toBe('500px'); }); it('should override the height of the overlay pane', () => { dialog.open(PizzaMsg, {height: '100px'}); viewContainerFixture.detectChanges(); let overlayPane = overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement; expect(overlayPane.style.height).toBe('100px'); }); it('should override the min-width of the overlay pane', () => { dialog.open(PizzaMsg, {minWidth: '500px'}); viewContainerFixture.detectChanges(); let overlayPane = overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement; expect(overlayPane.style.minWidth).toBe('500px'); }); it('should override the max-width of the overlay pane', async () => { let dialogRef = dialog.open(PizzaMsg); viewContainerFixture.detectChanges(); let overlayPane = overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement; expect(overlayPane.style.maxWidth).toBe(''); dialogRef.close(); await wait(100); viewContainerFixture.detectChanges(); dialogRef = dialog.open(PizzaMsg, {maxWidth: '100px'}); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); overlayPane = overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement; expect(overlayPane.style.maxWidth).toBe('100px'); }); it('should override the min-height of the overlay pane', () => { dialog.open(PizzaMsg, {minHeight: '300px'}); viewContainerFixture.detectChanges(); let overlayPane = overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement; expect(overlayPane.style.minHeight).toBe('300px'); }); it('should override the max-height of the overlay pane', () => { dialog.open(PizzaMsg, {maxHeight: '100px'}); viewContainerFixture.detectChanges(); let overlayPane = overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement; expect(overlayPane.style.maxHeight).toBe('100px'); }); it('should override the top offset of the overlay pane', () => { dialog.open(PizzaMsg, {position: {top: '100px'}}); viewContainerFixture.detectChanges(); let overlayPane = overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement; expect(overlayPane.style.marginTop).toBe('100px'); }); it('should override the bottom offset of the overlay pane', () => { dialog.open(PizzaMsg, {position: {bottom: '200px'}}); viewContainerFixture.detectChanges(); let overlayPane = overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement; expect(overlayPane.style.marginBottom).toBe('200px'); }); it('should override the left offset of the overlay pane', () => { dialog.open(PizzaMsg, {position: {left: '250px'}}); viewContainerFixture.detectChanges(); let overlayPane = overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement; expect(overlayPane.style.marginLeft).toBe('250px'); }); it('should override the right offset of the overlay pane', () => { dialog.open(PizzaMsg, {position: {right: '125px'}}); viewContainerFixture.detectChanges(); let overlayPane = overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement; expect(overlayPane.style.marginRight).toBe('125px'); }); it('should allow for the position to be updated', () => { let dialogRef = dialog.open(PizzaMsg, {position: {left: '250px'}}); viewContainerFixture.detectChanges(); let overlayPane = overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement; expect(overlayPane.style.marginLeft).toBe('250px'); dialogRef.updatePosition({left: '500px'}); expect(overlayPane.style.marginLeft).toBe('500px'); }); it('should allow for the dimensions to be updated', () => { let dialogRef = dialog.open(PizzaMsg, {width: '100px'}); viewContainerFixture.detectChanges(); let overlayPane = overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement; expect(overlayPane.style.width).toBe('100px'); dialogRef.updateSize('200px'); expect(overlayPane.style.width).toBe('200px'); }); it('should reset the overlay dimensions to their initial size', () => { let dialogRef = dialog.open(PizzaMsg); viewContainerFixture.detectChanges(); let overlayPane = overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement; expect(overlayPane.style.width).toBeFalsy(); expect(overlayPane.style.height).toBeFalsy(); dialogRef.updateSize('200px', '200px'); expect(overlayPane.style.width).toBe('200px'); expect(overlayPane.style.height).toBe('200px'); dialogRef.updateSize(); expect(overlayPane.style.width).toBeFalsy(); expect(overlayPane.style.height).toBeFalsy(); }); it('should allow setting the layout direction', () => { dialog.open(PizzaMsg, {direction: 'rtl'}); viewContainerFixture.detectChanges(); let overlayPane = overlayContainerElement.querySelector('.cdk-global-overlay-wrapper')!; expect(overlayPane.getAttribute('dir')).toBe('rtl'); }); it('should inject the correct layout direction in the component instance', () => { const dialogRef = dialog.open(PizzaMsg, {direction: 'rtl'}); viewContainerFixture.detectChanges(); expect(dialogRef.componentInstance.directionality.value).toBe('rtl'); }); it('should fall back to injecting the global direction if none is passed by the config', () => { const dialogRef = dialog.open(PizzaMsg, {}); viewContainerFixture.detectChanges(); expect(dialogRef.componentInstance.directionality.value).toBe('ltr'); }); it('should use the passed in ViewContainerRef from the config', async () => { const dialogRef = dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); // One view ref is for the container and one more for the component with the content. expect(testViewContainerRef.length).toBe(2); dialogRef.close(); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); expect(testViewContainerRef.length).toBe(0); }); it('should close all of the dialogs', async () => { dialog.open(PizzaMsg); dialog.open(PizzaMsg); dialog.open(PizzaMsg); expect(overlayContainerElement.querySelectorAll('mat-dialog-container').length).toBe(3); dialog.closeAll(); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); expect(overlayContainerElement.querySelectorAll('mat-dialog-container').length).toBe(0); }); it('should close all dialogs when the user goes forwards/backwards in history', async () => { dialog.open(PizzaMsg); dialog.open(PizzaMsg); expect(overlayContainerElement.querySelectorAll('mat-dialog-container').length).toBe(2); mockLocation.simulateUrlPop(''); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); expect(overlayContainerElement.querySelectorAll('mat-dialog-container').length).toBe(0); }); it('should close all open dialogs when the location hash changes', async () => { dialog.open(PizzaMsg); dialog.open(PizzaMsg); expect(overlayContainerElement.querySelectorAll('mat-dialog-container').length).toBe(2); mockLocation.simulateHashChange(''); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); expect(overlayContainerElement.querySelectorAll('mat-dialog-container').length).toBe(0); }); it('should close all of the dialogs when the injectable is destroyed', async () => { dialog.open(PizzaMsg); dialog.open(PizzaMsg); dialog.open(PizzaMsg); expect(overlayContainerElement.querySelectorAll('mat-dialog-container').length).toBe(3); dialog.ngOnDestroy(); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); expect(overlayContainerElement.querySelectorAll('mat-dialog-container').length).toBe(0); }); it('should complete open and close streams when the injectable is destroyed', async () => { const afterOpenedSpy = jasmine.createSpy('after opened spy'); const afterAllClosedSpy = jasmine.createSpy('after all closed spy'); const afterOpenedSubscription = dialog.afterOpened.subscribe({complete: afterOpenedSpy}); const afterAllClosedSubscription = dialog.afterAllClosed.subscribe({ complete: afterAllClosedSpy, }); dialog.ngOnDestroy(); expect(afterOpenedSpy).toHaveBeenCalled(); expect(afterAllClosedSpy).toHaveBeenCalled(); afterOpenedSubscription.unsubscribe(); afterAllClosedSubscription.unsubscribe(); }); it('should allow the consumer to disable closing a dialog on navigation', async () => { dialog.open(PizzaMsg); dialog.open(PizzaMsg, {closeOnNavigation: false}); expect(overlayContainerElement.querySelectorAll('mat-dialog-container').length).toBe(2); mockLocation.simulateUrlPop(''); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); expect(overlayContainerElement.querySelectorAll('mat-dialog-container').length).toBe(1); }); it('should have the componentInstance available in the afterClosed callback', async () => { let dialogRef = dialog.open(PizzaMsg); let spy = jasmine.createSpy('afterClosed spy'); await viewContainerFixture.whenStable(); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); dialogRef.afterClosed().subscribe(() => { spy(); expect(dialogRef.componentInstance) .withContext('Expected component instance to be defined.') .toBeTruthy(); }); dialogRef.close(); await viewContainerFixture.whenStable(); viewContainerFixture.detectChanges(); await wait(100); // Ensure that the callback actually fires. expect(spy).toHaveBeenCalled(); }); it('should be able to attach a custom scroll strategy', async () => { const scrollStrategy: ScrollStrategy = { attach: () => {}, enable: jasmine.createSpy('scroll strategy enable spy'), disable: () => {}, }; dialog.open(PizzaMsg, {scrollStrategy}); await viewContainerFixture.whenStable(); expect(scrollStrategy.enable).toHaveBeenCalled(); }); describe('passing in data', () => { it('should be able to pass in data', () => { let config = {data: {stringParam: 'hello', dateParam: new Date()}}; let instance = dialog.open(DialogWithInjectedData, config).componentInstance; expect(instance.data.stringParam).toBe(config.data.stringParam); expect(instance.data.dateParam).toBe(config.data.dateParam); }); it('should default to null if no data is passed', () => { expect(() => { let dialogRef = dialog.open(DialogWithInjectedData); expect(dialogRef.componentInstance.data).toBeNull(); }).not.toThrow(); }); it('should be able to apply bindings', () => { const dialogRef = dialog.open(PizzaMsg, { bindings: [inputBinding('flavor', () => 'pepperoni')], }); viewContainerFixture.detectChanges(); expect(dialogRef.componentInstance!.flavor).toBe('pepperoni'); }); }); it('should not keep a reference to the component after the dialog is closed', async () => { let dialogRef = dialog.open(PizzaMsg); expect(dialogRef.componentInstance).toBeTruthy(); dialogRef.close(); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); expect(dialogRef.componentInstance) .withContext('Expected reference to have been cleared.') .toBeFalsy(); }); it('should assign a unique id to each dialog', async () => { const one = dialog.open(PizzaMsg); const two = dialog.open(PizzaMsg); await viewContainerFixture.whenStable(); expect(one.id).toBeTruthy(); expect(two.id).toBeTruthy(); expect(one.id).not.toBe(two.id); }); it('should allow for the id to be overwritten', () => { const dialogRef = dialog.open(PizzaMsg, {id: 'pizza'}); expect(dialogRef.id).toBe('pizza'); }); it('should throw when trying to open a dialog with the same id as another dialog', () => { dialog.open(PizzaMsg, {id: 'pizza'}); expect(() => dialog.open(PizzaMsg, {id: 'pizza'})).toThrowError(/must be unique/g); }); it('should be able to find a dialog by id', () => { const dialogRef = dialog.open(PizzaMsg, {id: 'pizza'}); expect(dialog.getDialogById('pizza')).toBe(dialogRef); }); it('should toggle `aria-hidden` on the overlay container siblings', async () => { const sibling = document.createElement('div'); overlayContainerElement.parentNode!.appendChild(sibling); const dialogRef = dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); viewContainerFixture.detectChanges(); expect(sibling.getAttribute('aria-hidden')) .withContext('Expected sibling to be hidden') .toBe('true'); expect(overlayContainerElement.hasAttribute('aria-hidden')) .withContext('Expected overlay container not to be hidden.') .toBe(false); dialogRef.close(); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); expect(sibling.hasAttribute('aria-hidden')) .withContext('Expected sibling to no longer be hidden.') .toBe(false); sibling.remove(); }); it('should restore `aria-hidden` to the overlay container siblings on close', async () => { const sibling = document.createElement('div'); sibling.setAttribute('aria-hidden', 'true'); overlayContainerElement.parentNode!.appendChild(sibling); const dialogRef = dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); expect(sibling.getAttribute('aria-hidden')) .withContext('Expected sibling to be hidden.') .toBe('true'); dialogRef.close(); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); expect(sibling.getAttribute('aria-hidden')) .withContext('Expected sibling to remain hidden.') .toBe('true'); sibling.remove(); }); it('should not set `aria-hidden` on `aria-live` elements', async () => { const sibling = document.createElement('div'); sibling.setAttribute('aria-live', 'polite'); overlayContainerElement.parentNode!.appendChild(sibling); dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); expect(sibling.hasAttribute('aria-hidden')) .withContext('Expected live element not to be hidden.') .toBe(false); sibling.remove(); }); it('should add and remove classes while open', () => { let dialogRef = dialog.open(PizzaMsg, { disableClose: true, viewContainerRef: testViewContainerRef, }); const pane = overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement; expect(pane.classList).not.toContain( 'custom-class-one', 'Expected class to be initially missing', ); dialogRef.addPanelClass('custom-class-one'); expect(pane.classList).withContext('Expected class to be added').toContain('custom-class-one'); dialogRef.removePanelClass('custom-class-one'); expect(pane.classList).not.toContain('custom-class-one', 'Expected class to be removed'); }); it('should not inject the CDK dialog ref into the child component', () => { const dialogRef = dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); viewContainerFixture.detectChanges(); expect(dialogRef.componentInstance.cdkDialogRef).toBe(null); expect(dialogRef.componentInstance.dialogRef).toBeTruthy(); }); describe('disableClose option', () => { it('should prevent closing via clicks on the backdrop', async () => { dialog.open(PizzaMsg, {disableClose: true, viewContainerRef: testViewContainerRef}); viewContainerFixture.detectChanges(); let backdrop = overlayContainerElement.querySelector('.cdk-overlay-backdrop') as HTMLElement; backdrop.click(); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); expect(overlayContainerElement.querySelector('mat-dialog-container')).toBeTruthy(); }); it('should prevent closing via the escape key', async () => { dialog.open(PizzaMsg, {disableClose: true, viewContainerRef: testViewContainerRef}); viewContainerFixture.detectChanges(); dispatchKeyboardEvent(document.body, 'keydown', ESCAPE); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); expect(overlayContainerElement.querySelector('mat-dialog-container')).toBeTruthy(); }); it('should allow for the disableClose option to be updated while open', async () => { let dialogRef = dialog.open(PizzaMsg, { disableClose: true, viewContainerRef: testViewContainerRef, }); viewContainerFixture.detectChanges(); let backdrop = overlayContainerElement.querySelector('.cdk-overlay-backdrop') as HTMLElement; backdrop.click(); expect(overlayContainerElement.querySelector('mat-dialog-container')).toBeTruthy(); dialogRef.disableClose = false; backdrop.click(); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); expect(overlayContainerElement.querySelector('mat-dialog-container')).toBeFalsy(); }); it('should recapture focus when clicking on the backdrop', async () => { dialog.open(PizzaMsg, {disableClose: true, viewContainerRef: testViewContainerRef}); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); let backdrop = overlayContainerElement.querySelector('.cdk-overlay-backdrop') as HTMLElement; let input = overlayContainerElement.querySelector('input') as HTMLInputElement; expect(document.activeElement) .withContext('Expected input to be focused on open') .toBe(input); input.blur(); // Programmatic clicks might not move focus so we simulate it. backdrop.click(); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); expect(document.activeElement) .withContext('Expected input to stay focused after click') .toBe(input); }); it( 'should recapture focus to the first tabbable element when clicking on the backdrop with ' + 'autoFocus set to "first-tabbable" (the default)', async () => { dialog.open(PizzaMsg, { disableClose: true, viewContainerRef: testViewContainerRef, }); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); let backdrop = overlayContainerElement.querySelector( '.cdk-overlay-backdrop', ) as HTMLElement; let input = overlayContainerElement.querySelector('input') as HTMLInputElement; expect(document.activeElement) .withContext('Expected input to be focused on open') .toBe(input); input.blur(); // Programmatic clicks might not move focus so we simulate it. backdrop.click(); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); expect(document.activeElement) .withContext('Expected input to stay focused after click') .toBe(input); }, ); it( 'should recapture focus to the container when clicking on the backdrop with ' + 'autoFocus set to "dialog"', async () => { dialog.open(PizzaMsg, { disableClose: true, viewContainerRef: testViewContainerRef, autoFocus: 'dialog', }); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); viewContainerFixture.detectChanges(); let backdrop = overlayContainerElement.querySelector( '.cdk-overlay-backdrop', ) as HTMLElement; let container = overlayContainerElement.querySelector( '.mat-mdc-dialog-container', ) as HTMLInputElement; expect(document.activeElement) .withContext('Expected container to be focused on open') .toBe(container); container.blur(); // Programmatic clicks might not move focus so we simulate it. backdrop.click(); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); expect(document.activeElement) .withContext('Expected container to stay focused after click') .toBe(container); }, ); }); describe('closePredicate option', () => { function getDialogs() { return overlayContainerElement.querySelectorAll('mat-dialog-container'); } it('should determine whether closing via the backdrop is allowed', async () => { let canClose = false; const closedSpy = jasmine.createSpy('closed spy'); const ref = dialog.open(PizzaMsg, { closePredicate: () => canClose, viewContainerRef: testViewContainerRef, }); ref.afterClosed().subscribe(closedSpy); viewContainerFixture.detectChanges(); expect(getDialogs().length).toBe(1); let backdrop = overlayContainerElement.querySelector('.cdk-overlay-backdrop') as HTMLElement; backdrop.click(); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); expect(getDialogs().length).toBe(1); expect(closedSpy).not.toHaveBeenCalled(); canClose = true; backdrop.click(); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); expect(getDialogs().length).toBe(0); expect(closedSpy).toHaveBeenCalledTimes(1); }); it('should determine whether closing via the escape key is allowed', async () => { let canClose = false; const closedSpy = jasmine.createSpy('closed spy'); const ref = dialog.open(PizzaMsg, { closePredicate: () => canClose, viewContainerRef: testViewContainerRef, }); ref.afterClosed().subscribe(closedSpy); viewContainerFixture.detectChanges(); expect(getDialogs().length).toBe(1); dispatchKeyboardEvent(document.body, 'keydown', ESCAPE); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); expect(getDialogs().length).toBe(1); expect(closedSpy).not.toHaveBeenCalled(); canClose = true; dispatchKeyboardEvent(document.body, 'keydown', ESCAPE); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); expect(getDialogs().length).toBe(0); expect(closedSpy).toHaveBeenCalledTimes(1); }); it('should determine whether closing via the `close` method is allowed', async () => { let canClose = false; const closedSpy = jasmine.createSpy('closed spy'); const ref = dialog.open(PizzaMsg, { closePredicate: () => canClose, viewContainerRef: testViewContainerRef, }); ref.afterClosed().subscribe(closedSpy); viewContainerFixture.detectChanges(); expect(getDialogs().length).toBe(1); ref.close(); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); expect(getDialogs().length).toBe(1); expect(closedSpy).not.toHaveBeenCalled(); canClose = true; ref.close('hello'); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); expect(getDialogs().length).toBe(0); expect(closedSpy).toHaveBeenCalledTimes(1); expect(closedSpy).toHaveBeenCalledWith('hello'); }); it('should not be closed by `closeAll` if not allowed by the predicate', async () => { let canClose = false; const config = {closePredicate: () => canClose}; const spy = jasmine.createSpy('afterAllClosed spy'); dialog.open(PizzaMsg, config); viewContainerFixture.detectChanges(); dialog.open(PizzaMsg, config); viewContainerFixture.detectChanges(); dialog.open(PizzaMsg, config); viewContainerFixture.detectChanges(); const subscription = dialog.afterAllClosed.subscribe(spy); expect(getDialogs().length).toBe(3); expect(dialog.openDialogs.length).toBe(3); dialog.closeAll(); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); expect(getDialogs().length).toBe(3); expect(dialog.openDialogs.length).toBe(3); expect(spy).not.toHaveBeenCalled(); canClose = true; dialog.closeAll(); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); expect(getDialogs().length).toBe(0); expect(dialog.openDialogs.length).toBe(0); expect(spy).toHaveBeenCalledTimes(1); subscription.unsubscribe(); }); it('should recapture focus to the first tabbable element when clicking on the backdrop while the `closePredicate` is blocking the close sequence', async () => { // When testing focus, all of the elements must be in the DOM. document.body.appendChild(overlayContainerElement); dialog.open(PizzaMsg, { closePredicate: () => false, viewContainerRef: testViewContainerRef, }); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); const backdrop = overlayContainerElement.querySelector( '.cdk-overlay-backdrop', ) as HTMLElement; const input = overlayContainerElement.querySelector('input') as HTMLInputElement; expect(document.activeElement) .withContext('Expected input to be focused on open') .toBe(input); input.blur(); // Programmatic clicks might not move focus so we simulate it. backdrop.click(); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); expect(document.activeElement) .withContext('Expected input to stay focused after click') .toBe(input); overlayContainerElement.remove(); }); }); it( 'should recapture focus to the first header when clicking on the backdrop with ' + 'autoFocus set to "first-heading"', async () => { dialog.open(ContentElementDialog, { disableClose: true, viewContainerRef: testViewContainerRef, autoFocus: 'first-heading', }); await viewContainerFixture.whenStable(); viewContainerFixture.detectChanges(); let backdrop = overlayContainerElement.querySelector('.cdk-overlay-backdrop') as HTMLElement; let firstHeader = overlayContainerElement.querySelector( '.mat-mdc-dialog-title[tabindex="-1"]', ) as HTMLInputElement; expect(document.activeElement) .withContext('Expected first header to be focused on open') .toBe(firstHeader); firstHeader.blur(); // Programmatic clicks might not move focus so we simulate it. backdrop.click(); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); expect(document.activeElement) .withContext('Expected first header to stay focused after click') .toBe(firstHeader); }, ); it( 'should recapture focus to the first element that matches the css selector when ' + 'clicking on the backdrop with autoFocus set to a css selector', async () => { dialog.open(ContentElementDialog, { disableClose: true, viewContainerRef: testViewContainerRef, autoFocus: 'button', }); await viewContainerFixture.whenStable(); viewContainerFixture.detectChanges(); let backdrop = overlayContainerElement.querySelector('.cdk-overlay-backdrop') as HTMLElement; let firstButton = overlayContainerElement.querySelector( '[mat-dialog-close]', ) as HTMLInputElement; expect(document.activeElement) .withContext('Expected first button to be focused on open') .toBe(firstButton); firstButton.blur(); // Programmatic clicks might not move focus so we simulate it. backdrop.click(); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); expect(document.activeElement) .withContext('Expected first button to stay focused after click') .toBe(firstButton); }, ); it('should be able to use afterOpened in the template while animations are disabled', async () => { const ref = dialog.open(DialogWithAfterOpenSubscription); await new Promise(resolve => setTimeout(resolve, 10)); expect(ref.componentInstance.animations.animationsDisabled).toBe(true); expect(overlayContainerElement.textContent).toContain('The dialog is now open!'); }); describe('hasBackdrop option', () => { it('should have a backdrop', () => { dialog.open(PizzaMsg, {hasBackdrop: true, viewContainerRef: testViewContainerRef}); viewContainerFixture.detectChanges(); expect(overlayContainerElement.querySelector('.cdk-overlay-backdrop')).toBeTruthy(); }); it('should not have a backdrop', () => { dialog.open(PizzaMsg, {hasBackdrop: false, viewContainerRef: testViewContainerRef}); viewContainerFixture.detectChanges(); expect(overlayContainerElement.querySelector('.cdk-overlay-backdrop')).toBeFalsy(); }); }); describe('panelClass option', () => { it('should have custom panel class', () => { dialog.open(PizzaMsg, { panelClass: 'custom-panel-class', viewContainerRef: testViewContainerRef, }); viewContainerFixture.detectChanges(); expect(overlayContainerElement.querySelector('.custom-panel-class')).toBeTruthy(); }); }); describe('backdropClass option', () => { it('should have default backdrop class', () => { dialog.open(PizzaMsg, {backdropClass: '', viewContainerRef: testViewContainerRef}); viewContainerFixture.detectChanges(); expect(overlayContainerElement.querySelector('.cdk-overlay-dark-backdrop')).toBeTruthy(); }); it('should have custom backdrop class', () => { dialog.open(PizzaMsg, { backdropClass: 'custom-backdrop-class', viewContainerRef: testViewContainerRef, }); viewContainerFixture.detectChanges(); expect(overlayContainerElement.querySelector('.custom-backdrop-class')).toBeTruthy(); }); }); describe('focus management', () => { // When testing focus, all of the elements must be in the DOM. beforeEach(() => document.body.appendChild(overlayContainerElement)); afterEach(() => overlayContainerElement.remove()); it('should focus the first focusable element on open', async () => { dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); viewContainerFixture.detectChanges(); await wait(0); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); expect(document.activeElement!.tagName) .withContext('Expected input to be focused') .toBe('INPUT'); }); it('should focus the first tabbable element of the dialog on open (the default)', async () => { dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); expect(document.activeElement!.tagName) .withContext('Expected first tabbable element (input) in the dialog to be focused.') .toBe('INPUT'); }); it('should focus the dialog element on open', async () => { dialog.open(PizzaMsg, { viewContainerRef: testViewContainerRef, autoFocus: 'dialog', }); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); viewContainerFixture.detectChanges(); let container = overlayContainerElement.querySelector( '.mat-mdc-dialog-container', ) as HTMLInputElement; expect(document.activeElement) .withContext('Expected container to be focused on open') .toBe(container); }); it('should focus the first header element on open', async () => { dialog.open(ContentElementDialog, { viewContainerRef: testViewContainerRef, autoFocus: 'first-heading', }); await viewContainerFixture.whenStable(); viewContainerFixture.detectChanges(); let firstHeader = overlayContainerElement.querySelector( 'h2[tabindex="-1"]', ) as HTMLInputElement; expect(document.activeElement) .withContext('Expected first header to be focused on open') .toBe(firstHeader); }); it('should focus the first element that matches the css selector from autoFocus on open', async () => { dialog.open(PizzaMsg, { viewContainerRef: testViewContainerRef, autoFocus: 'p', }); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); viewContainerFixture.detectChanges(); let firstParagraph = overlayContainerElement.querySelector( 'p[tabindex="-1"]', ) as HTMLInputElement; expect(document.activeElement) .withContext('Expected first paragraph to be focused on open') .toBe(firstParagraph); }); it('should attach the focus trap even if automatic focus is disabled', async () => { dialog.open(PizzaMsg, { viewContainerRef: testViewContainerRef, autoFocus: 'false', }); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); expect( overlayContainerElement.querySelectorAll('.cdk-focus-trap-anchor').length, ).toBeGreaterThan(0); }); it('should re-focus trigger element when dialog closes', async () => { // Create a element that has focus before the dialog is opened. let button = document.createElement('button'); button.id = 'dialog-trigger'; document.body.appendChild(button); button.focus(); const dialogRef = dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); await viewContainerFixture.whenStable(); viewContainerFixture.detectChanges(); expect(document.activeElement!.id).not.toBe( 'dialog-trigger', 'Expected the focus to change when dialog was opened.', ); dialogRef.close(); viewContainerFixture.detectChanges(); await wait(100); expect(document.activeElement!.id) .withContext('Expected focus to be restored to trigger element after dialog is closed.') .toBe('dialog-trigger'); button.remove(); }); it('should re-focus trigger element inside the shadow DOM when dialog closes', async () => { if (!_supportsShadowDom()) { return; } viewContainerFixture.destroy(); const fixture = TestBed.createComponent(ShadowDomComponent); fixture.detectChanges(); await fixture.whenStable(); const button = fixture.debugElement.query(By.css('button'))!.nativeElement; button.focus(); const dialogRef = dialog.open(PizzaMsg); fixture.detectChanges(); await fixture.whenStable(); const spy = spyOn(button, 'focus').and.callThrough(); dialogRef.close(); fixture.detectChanges(); await wait(100); expect(spy).toHaveBeenCalled(); }); it('should re-focus the trigger via keyboard when closed via escape key', async () => { const button = document.createElement('button'); let lastFocusOrigin: FocusOrigin = null; focusMonitor.monitor(button, false).subscribe(focusOrigin => (lastFocusOrigin = focusOrigin)); document.body.appendChild(button); button.focus(); // Patch the element focus after the initial and real focus, because otherwise the // `activeElement` won't be set, and the dialog won't be able to restore focus to an // element. patchElementFocus(button); dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); await wait(100); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); expect(lastFocusOrigin!).withContext('Expected the trigger button to be blurred').toBeNull(); dispatchKeyboardEvent(document.body, 'keydown', ESCAPE); await viewContainerFixture.whenStable(); viewContainerFixture.detectChanges(); await wait(100); expect(lastFocusOrigin!) .withContext('Expected the trigger button to be focused via keyboard') .toBe('keyboard'); focusMonitor.stopMonitoring(button); button.remove(); }); it('should re-focus the trigger via mouse when backdrop has been clicked', async () => { const button = document.createElement('button'); let lastFocusOrigin: FocusOrigin = null; focusMonitor.monitor(button, false).subscribe(focusOrigin => (lastFocusOrigin = focusOrigin)); document.body.appendChild(button); button.focus(); // Patch the element focus after the initial and real focus, because otherwise the // `activeElement` won't be set, and the dialog won't be able to restore focus to an // element. patchElementFocus(button); dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); await wait(100); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); expect(lastFocusOrigin!).withContext('Expected the trigger button to be blurred').toBeNull(); const backdrop = overlayContainerElement.querySelector( '.cdk-overlay-backdrop', ) as HTMLElement; backdrop.click(); viewContainerFixture.detectChanges(); await wait(100); expect(lastFocusOrigin!) .withContext('Expected the trigger button to be focused via mouse') .toBe('mouse'); focusMonitor.stopMonitoring(button); button.remove(); }); it('should re-focus via keyboard if the close button has been triggered through keyboard', async () => { const button = document.createElement('button'); let lastFocusOrigin: FocusOrigin = null; focusMonitor.monitor(button, false).subscribe(focusOrigin => (lastFocusOrigin = focusOrigin)); document.body.appendChild(button); button.focus(); // Patch the element focus after the initial and real focus, because otherwise the // `activeElement` won't be set, and the dialog won't be able to restore focus to an // element. patchElementFocus(button); dialog.open(ContentElementDialog, {viewContainerRef: testViewContainerRef}); await wait(100); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); expect(lastFocusOrigin!).withContext('Expected the trigger button to be blurred').toBeNull(); const closeButton = overlayContainerElement.querySelector( 'button[mat-dialog-close]', ) as HTMLElement; // Fake the behavior of pressing the SPACE key on a button element. Browsers fire a `click` // event with a MouseEvent, which has coordinates that are out of the element boundaries. dispatchMouseEvent(closeButton, 'click', 0, 0); viewContainerFixture.detectChanges(); await wait(100); expect(lastFocusOrigin!) .withContext('Expected the trigger button to be focused via keyboard') .toBe('keyboard'); focusMonitor.stopMonitoring(button); button.remove(); }); it('should re-focus via mouse if the close button has been clicked', async () => { const button = document.createElement('button'); let lastFocusOrigin: FocusOrigin = null; focusMonitor.monitor(button, false).subscribe(focusOrigin => (lastFocusOrigin = focusOrigin)); document.body.appendChild(button); button.focus(); // Patch the element focus after the initial and real focus, because otherwise the // `activeElement` won't be set, and the dialog won't be able to restore focus to an // element. patchElementFocus(button); dialog.open(ContentElementDialog, {viewContainerRef: testViewContainerRef}); await wait(100); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); expect(lastFocusOrigin!).withContext('Expected the trigger button to be blurred').toBeNull(); const closeButton = overlayContainerElement.querySelector( 'button[mat-dialog-close]', ) as HTMLElement; // The dialog close button detects the focus origin by inspecting the click event. If // coordinates of the click are not present, it assumes that the click has been triggered // by keyboard. dispatchMouseEvent(closeButton, 'click', 10, 10); viewContainerFixture.detectChanges(); await wait(100); expect(lastFocusOrigin!) .withContext('Expected the trigger button to be focused via mouse') .toBe('mouse'); focusMonitor.stopMonitoring(button); button.remove(); }); it('should allow the consumer to shift focus in afterClosed', async () => { // Create a element that has focus before the dialog is opened. let button = document.createElement('button'); let input = document.createElement('input'); button.id = 'dialog-trigger'; input.id = 'input-to-be-focused'; document.body.appendChild(button); document.body.appendChild(input); button.focus(); let dialogRef = dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); await wait(100); viewContainerFixture.detectChanges(); dialogRef.afterClosed().subscribe(() => input.focus()); dialogRef.close(); await wait(100); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); expect(document.activeElement!.id) .withContext('Expected that the trigger was refocused after the dialog is closed.') .toBe('input-to-be-focused'); button.remove(); input.remove(); await viewContainerFixture.whenStable(); }); it('should move focus to the container if there are no focusable elements in the dialog', async () => { dialog.open(DialogWithoutFocusableElements); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); expect(document.activeElement!.tagName) .withContext('Expected dialog container to be focused.') .toBe('MAT-DIALOG-CONTAINER'); }); it('should be able to disable focus restoration', async () => { // Create a element that has focus before the dialog is opened. const button = document.createElement('button'); button.id = 'dialog-trigger'; document.body.appendChild(button); button.focus(); const dialogRef = dialog.open(PizzaMsg, { viewContainerRef: testViewContainerRef, restoreFocus: false, }); await viewContainerFixture.whenStable(); viewContainerFixture.detectChanges(); expect(document.activeElement!.id).not.toBe( 'dialog-trigger', 'Expected the focus to change when dialog was opened.', ); dialogRef.close(); viewContainerFixture.detectChanges(); await wait(100); expect(document.activeElement!.id).not.toBe( 'dialog-trigger', 'Expected focus not to have been restored.', ); button.remove(); }); it('should not move focus if it was moved outside the dialog while animating', async () => { // Create a element that has focus before the dialog is opened. const button = document.createElement('button'); const otherButton = document.createElement('button'); const body = document.body; button.id = 'dialog-trigger'; otherButton.id = 'other-button'; body.appendChild(button); body.appendChild(otherButton); button.focus(); const dialogRef = dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); await viewContainerFixture.whenStable(); viewContainerFixture.detectChanges(); expect(document.activeElement!.id).not.toBe( 'dialog-trigger', 'Expected the focus to change when dialog was opened.', ); // Start the closing sequence and move focus out of dialog. dialogRef.close(); otherButton.focus(); expect(document.activeElement!.id) .withContext('Expected focus to be on the alternate button.') .toBe('other-button'); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); expect(document.activeElement!.id) .withContext('Expected focus to stay on the alternate button.') .toBe('other-button'); button.remove(); otherButton.remove(); }); }); describe('dialog content elements', () => { let dialogRef: MatDialogRef<any>; let hostInstance: ContentElementDialog | ComponentWithContentElementTemplateRef; describe('inside component dialog', () => { beforeEach(async () => { dialogRef = dialog.open(ContentElementDialog, {viewContainerRef: testViewContainerRef}); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); hostInstance = dialogRef.componentInstance; }); runContentElementTests(); }); describe('inside template portal', () => { beforeEach(async () => { const fixture = TestBed.createComponent(ComponentWithContentElementTemplateRef); fixture.detectChanges(); dialogRef = dialog.open(fixture.componentInstance.templateRef, { viewContainerRef: testViewContainerRef, }); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); hostInstance = fixture.componentInstance; }); runContentElementTests(); }); it('should set the aria-labelledby attribute to the id of the title under OnPush host', async () => { @Component({ imports: [MatDialogTitle], template: `@if (showTitle()) { <h2 mat-dialog-title>This is the first title</h2> }`, changeDetection: ChangeDetectionStrategy.Eager, }) class DialogCmp { showTitle = signal(true); } @Component({ template: '', selector: 'child', changeDetection: ChangeDetectionStrategy.Eager, }) class Child { readonly viewContainerRef = inject(ViewContainerRef); readonly dialog = inject(MatDialog); dialogRef?: MatDialogRef<DialogCmp>; open() { this.dialogRef = this.dialog.open(DialogCmp, {viewContainerRef: this.viewContainerRef}); } } @Component({ imports: [Child], template: `<child></child>`, }) class OnPushHost { @ViewChild(Child, {static: true}) child!: Child; } const hostFixture = TestBed.createComponent(OnPushHost); hostFixture.componentInstance.child.open(); hostFixture.detectChanges(); await hostFixture.whenStable(); hostFixture.detectChanges(); const overlayContainer = TestBed.inject(OverlayContainer); const title = overlayContainer.getContainerElement().querySelector('[mat-dialog-title]')!; const container = overlayContainerElement.querySelector('mat-dialog-container')!; expect(title.id).withContext('Expected title element to have an id.').toBeTruthy(); expect(container.getAttribute('aria-labelledby')) .withContext('Expected the aria-labelledby to match the title id.') .toBe(title.id); hostFixture.componentInstance.child.dialogRef?.componentInstance.showTitle.set(false); hostFixture.detectChanges(); await hostFixture.whenStable(); hostFixture.detectChanges(); expect(container.getAttribute('aria-labelledby')).toBe(null); }); function runContentElementTests() { it('should close the dialog when clicking on the close button', async () => { expect(overlayContainerElement.querySelectorAll('.mat-mdc-dialog-container').length).toBe( 1, ); (overlayContainerElement.querySelector('button[mat-dialog-close]') as HTMLElement).click(); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); expect(overlayContainerElement.querySelectorAll('.mat-mdc-dialog-container').length).toBe( 0, ); }); it('should not close when clicking on an aria-disabled close button', async () => { expect(overlayContainerElement.querySelectorAll('.mat-mdc-dialog-container').length).toBe( 1, ); const closeButton = overlayContainerElement.querySelector( 'button[mat-dialog-close]', ) as HTMLElement; closeButton.setAttribute('aria-disabled', 'true'); closeButton.click(); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); expect(overlayContainerElement.querySelectorAll('.mat-mdc-dialog-container').length).toBe( 1, ); }); it('should not close if [mat-dialog-close] is applied on a non-button node', () => { expect(overlayContainerElement.querySelectorAll('.mat-mdc-dialog-container').length).toBe( 1, ); (overlayContainerElement.querySelector('div[mat-dialog-close]') as HTMLElement).click(); expect(overlayContainerElement.querySelectorAll('.mat-mdc-dialog-container').length).toBe( 1, ); }); it('should allow for a user-specified aria-label on the close button', async () => { let button = overlayContainerElement.querySelector('.close-with-aria-label')!; expect(button.getAttribute('aria-label')).toBe('Best close button ever'); }); it('should set the "type" attribute of the close button if not set manually', () => { let button = overlayContainerElement.querySelector('button[mat-dialog-close]')!; expect(button.getAttribute('type')).toBe('button'); }); it('should not override type attribute of the close button if set manually', () => { let button = overlayContainerElement.querySelector('button.with-submit')!; expect(button.getAttribute('type')).toBe('submit'); }); it('should return the [mat-dialog-close] result when clicking the close button', async () => { let afterCloseCallback = jasmine.createSpy('afterClose callback'); dialogRef.afterClosed().subscribe(afterCloseCallback); (overlayContainerElement.querySelector('button.close-with-true') as HTMLElement).click(); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); expect(afterCloseCallback).toHaveBeenCalledWith(true); }); it('should set the aria-labelledby attribute to the id of the title', async () => { let title = overlayContainerElement.querySelector('[mat-dialog-title]')!; let container = overlayContainerElement.querySelector('mat-dialog-container')!; await viewContainerFixture.whenStable(); viewContainerFixture.detectChanges(); expect(title.id).withContext('Expected title element to have an id.').toBeTruthy(); expect(container.getAttribute('aria-labelledby')) .withContext('Expected the aria-labelledby to match the title id.') .toBe(title.id); }); it('should update the aria-labelledby attribute if two titles are swapped', async () => { const container = overlayContainerElement.querySelector('mat-dialog-container')!; let title = overlayContainerElement.querySelector('[mat-dialog-title]')!; await viewContainerFixture.whenStable(); viewContainerFixture.detectChanges(); const previousId = title.id; expect(title.id).toBeTruthy(); expect(container.getAttribute('aria-labelledby')).toBe(title.id); hostInstance.shownTitle = 'second'; viewContainerFixture.changeDetectorRef.markForCheck(); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); viewContainerFixture.detectChanges(); title = overlayContainerElement.querySelector('[mat-dialog-title]')!; expect(title.id).toBeTruthy(); expect(title.id).not.toBe(previousId); expect(container.getAttribute('aria-labelledby')).toBe(title.id); }); it('should update the aria-labelledby attribute if multiple titles are present and one is removed', async () => { const container = overlayContainerElement.querySelector('mat-dialog-container')!; hostInstance.shownTitle = 'all'; viewContainerFixture.changeDetectorRef.markForCheck(); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); viewContainerFixture.detectChanges(); const titles = overlayContainerElement.querySelectorAll('[mat-dialog-title]'); expect(titles.length).toBe(3); expect(container.getAttribute('aria-labelledby')).toBe(titles[0].id); hostInstance.shownTitle = 'second'; viewContainerFixture.changeDetectorRef.markForCheck(); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); viewContainerFixture.detectChanges(); expect(container.getAttribute('aria-labelledby')).toBe(titles[1].id); }); it('should add correct css class according to given [align] input in [mat-dialog-actions]', () => { let actions = overlayContainerElement.querySelector('mat-dialog-actions')!; expect(actions) .withContext('Expected action buttons to not have class align-center') .not.toHaveClass('mat-mdc-dialog-actions-align-center'); expect(actions) .withContext('Expected action buttons to have class align-end') .toHaveClass('mat-mdc-dialog-actions-align-end'); }); } }); describe('aria-labelledby', () => { it('should be able to set a custom aria-labelledby', () => { dialog.open(PizzaMsg, { ariaLabelledBy: 'Labelled By', viewContainerRef: testViewContainerRef, }); viewContainerFixture.detectChanges(); const container = overlayContainerElement.querySelector('mat-dialog-container')!; expect(container.getAttribute('aria-labelledby')).toBe('Labelled By'); }); it( 'should not set the aria-labelledby automatically if it has an aria-label ' + 'and an aria-labelledby', async () => { dialog.open(ContentElementDialog, { ariaLabel: 'Hello there', ariaLabelledBy: 'Labelled By', viewContainerRef: testViewContainerRef, }); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); viewContainerFixture.detectChanges(); const container = overlayContainerElement.querySelector('mat-dialog-container')!; expect(container.hasAttribute('aria-labelledby')).toBe(false); }, ); it( 'should set the aria-labelledby attribute to the config provided aria-labelledby ' + 'instead of the mat-dialog-title id', async () => { dialog.open(ContentElementDialog, { ariaLabelledBy: 'Labelled By', viewContainerRef: testViewContainerRef, }); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); let title = overlayContainerElement.querySelector('[mat-dialog-title]')!; let container = overlayContainerElement.querySelector('mat-dialog-container')!; await viewContainerFixture.whenStable(); viewContainerFixture.detectChanges(); expect(title.id).withContext('Expected title element to have an id.').toBeTruthy(); expect(container.getAttribute('aria-labelledby')).toBe('Labelled By'); }, ); }); describe('aria-label', () => { it('should be able to set a custom aria-label', () => { dialog.open(PizzaMsg, {ariaLabel: 'Hello there', viewContainerRef: testViewContainerRef}); viewContainerFixture.detectChanges(); const container = overlayContainerElement.querySelector('mat-dialog-container')!; expect(container.getAttribute('aria-label')).toBe('Hello there'); }); it('should not set the aria-labelledby automatically if it has an aria-label', async () => { dialog.open(ContentElementDialog, { ariaLabel: 'Hello there', viewContainerRef: testViewContainerRef, }); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); viewContainerFixture.detectChanges(); const container = overlayContainerElement.querySelector('mat-dialog-container')!; expect(container.hasAttribute('aria-labelledby')).toBe(false); }); }); it('should dispose backdrop if containing dialog view is destroyed', async () => { const dialogRef = dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); expect(overlayContainerElement.querySelector('.cdk-overlay-backdrop')).toBeDefined(); dialogRef.close(); viewContainerFixture.componentInstance.showChildView = false; viewContainerFixture.changeDetectorRef.markForCheck(); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); expect(overlayContainerElement.querySelector('.cdk-overlay-backdrop')).toBe(null); }); }); describe('MatDialog with a parent MatDialog', () => { let parentDialog: MatDialog; let childDialog: MatDialog; let overlayContainerElement: HTMLElement; let fixture: ComponentFixture<ComponentThatProvidesMatDialog>; beforeEach(() => { TestBed.configureTestingModule({ providers: [ { provide: OverlayContainer, useFactory: () => { overlayContainerElement = document.createElement('div'); return {getContainerElement: () => overlayContainerElement}; }, }, {provide: Location, useClass: SpyLocation}, {provide: MATERIAL_ANIMATIONS, useValue: {animationsDisabled: true}}, ], }); parentDialog = TestBed.inject(MatDialog); fixture = TestBed.createComponent(ComponentThatProvidesMatDialog); childDialog = fixture.componentInstance.dialog; fixture.detectChanges(); }); afterEach(() => { overlayContainerElement.innerHTML = ''; }); it('should close dialogs opened by a parent when calling closeAll on a child MatDialog', async () => { parentDialog.open(PizzaMsg); fixture.detectChanges(); await wait(0); fixture.detectChanges(); expect(overlayContainerElement.textContent) .withContext('Expected a dialog to be opened') .toContain('Pizza'); childDialog.closeAll(); fixture.detectChanges(); await wait(0); fixture.detectChanges(); expect(overlayContainerElement.textContent!.trim()) .withContext('Expected closeAll on child MatDialog to close dialog opened by parent') .toBe(''); }); it('should close dialogs opened by a child when calling closeAll on a parent MatDialog', async () => { childDialog.open(PizzaMsg); fixture.detectChanges(); await wait(0); fixture.detectChanges(); expect(overlayContainerElement.textContent) .withContext('Expected a dialog to be opened') .toContain('Pizza'); parentDialog.closeAll(); fixture.detectChanges(); await wait(0); fixture.detectChanges(); expect(overlayContainerElement.textContent!.trim()) .withContext('Expected closeAll on parent MatDialog to close dialog opened by child') .toBe(''); }); it('should close the top dialog via the escape key', async () => { childDialog.open(PizzaMsg); fixture.detectChanges(); await wait(0); fixture.detectChanges(); dispatchKeyboardEvent(document.body, 'keydown', ESCAPE); fixture.detectChanges(); await wait(0); fixture.detectChanges(); expect(overlayContainerElement.querySelector('mat-dialog-container')).toBeNull(); }); it('should not close the parent dialogs when a child is destroyed', async () => { parentDialog.open(PizzaMsg); fixture.detectChanges(); await wait(0); fixture.detectChanges(); expect(overlayContainerElement.textContent) .withContext('Expected a dialog to be opened') .toContain('Pizza'); childDialog.ngOnDestroy(); fixture.detectChanges(); await wait(0); fixture.detectChanges(); expect(overlayContainerElement.textContent) .withContext('Expected a dialog to be opened') .toContain('Pizza'); }); }); describe('MatDialog with default options', () => { let dialog: MatDialog; let overlayContainerElement: HTMLElement; let testViewContainerRef: ViewContainerRef; let viewContainerFixture: ComponentFixture<ComponentWithChildViewContainer>; beforeEach(() => { const defaultConfig = { hasBackdrop: false, disableClose: true, width: '100px', height: '100px', minWidth: '50px', minHeight: '50px', maxWidth: '150px', maxHeight: '150px', autoFocus: 'dialog', }; TestBed.configureTestingModule({ providers: [ {provide: MAT_DIALOG_DEFAULT_OPTIONS, useValue: defaultConfig}, {provide: MATERIAL_ANIMATIONS, useValue: {animationsDisabled: true}}, ], }); dialog = TestBed.inject(MatDialog); overlayContainerElement = TestBed.inject(OverlayContainer).getContainerElement(); viewContainerFixture = TestBed.createComponent(ComponentWithChildViewContainer); viewContainerFixture.detectChanges(); testViewContainerRef = viewContainerFixture.componentInstance.childViewContainer; }); it('should use the provided defaults', () => { dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); viewContainerFixture.detectChanges(); expect(overlayContainerElement.querySelector('.cdk-overlay-backdrop')).toBeFalsy(); dispatchKeyboardEvent(document.body, 'keydown', ESCAPE); expect(overlayContainerElement.querySelector('mat-dialog-container')).toBeTruthy(); expect(document.activeElement!.tagName).not.toBe('INPUT'); let overlayPane = overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement; expect(overlayPane.style.width).toBe('100px'); expect(overlayPane.style.height).toBe('100px'); expect(overlayPane.style.minWidth).toBe('50px'); expect(overlayPane.style.minHeight).toBe('50px'); expect(overlayPane.style.maxWidth).toBe('150px'); expect(overlayPane.style.maxHeight).toBe('150px'); }); it('should be overridable by open() options', async () => { dialog.open(PizzaMsg, { hasBackdrop: true, disableClose: false, viewContainerRef: testViewContainerRef, }); viewContainerFixture.detectChanges(); await viewContainerFixture.whenStable(); expect(overlayContainerElement.querySelector('.cdk-overlay-backdrop')).toBeTruthy(); dispatchKeyboardEvent(document.body, 'keydown', ESCAPE); viewContainerFixture.detectChanges(); await wait(0); viewContainerFixture.detectChanges(); expect(overlayContainerElement.querySelector('mat-dialog-container')).toBeFalsy(); }); }); describe('MatDialog with animations enabled', () => { let dialog: MatDialog; let testViewContainerRef: ViewContainerRef; let viewContainerFixture: ComponentFixture<ComponentWithChildViewContainer>; beforeEach(() => { dialog = TestBed.inject(MatDialog); viewContainerFixture = TestBed.createComponent(ComponentWithChildViewContainer); viewContainerFixture.detectChanges(); testViewContainerRef = viewContainerFixture.componentInstance.childViewContainer; }); it('should emit when dialog opening animation is complete', async () => { const dialogRef = dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); const spy = jasmine.createSpy('afterOpen spy'); dialogRef.afterOpened().subscribe(spy); viewContainerFixture.detectChanges(); // callback should not be called before animation is complete expect(spy).not.toHaveBeenCalled(); await wait(OPEN_ANIMATION_DURATION); expect(spy).toHaveBeenCalled(); }); it('return the current state of the dialog', async () => { const dialogRef = dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); expect(dialogRef.getState()).toBe(MatDialogState.OPEN); dialogRef.close(); viewContainerFixture.detectChanges(); expect(dialogRef.getState()).toBe(MatDialogState.CLOSING); // Ensure that the closing state is still set if half of the animation has // passed by. The dialog state should be only set to `closed` when the dialog // finished the close animation. await wait(CLOSE_ANIMATION_DURATION / 2); expect(dialogRef.getState()).toBe(MatDialogState.CLOSING); // Flush the remaining duration of the closing animation. We wait for the remaining duration // to avoid pending timer failures. await wait(CLOSE_ANIMATION_DURATION); expect(dialogRef.getState()).toBe(MatDialogState.CLOSED); }); }); describe('MatDialog with explicit injector provided', () => { let overlayContainerElement: HTMLElement; let fixture: ComponentFixture<ModuleBoundDialogParentComponent>; beforeEach(() => { overlayContainerElement = TestBed.inject(OverlayContainer).getContainerElement(); fixture = TestBed.createComponent(ModuleBoundDialogParentComponent); }); it('should use the standalone injector and render the dialog successfully', () => { fixture.componentInstance.openDialog(); fixture.detectChanges(); expect( overlayContainerElement.querySelector('module-bound-dialog-child-component')!.innerHTML, ).toEqual('<p>Pasta</p>'); }); }); function wait(ms: number): Promise<void> { return new Promise(resolve => setTimeout(resolve, ms)); } @Directive({ selector: 'dir-with-view-container', }) class DirectiveWithViewContainer { viewContainerRef = inject(ViewContainerRef); } @Component({ template: 'hello', }) class ComponentWithOnPushViewContainer { viewContainerRef = inject(ViewContainerRef); } @Component({ selector: 'arbitrary-component', template: `@if (showChildView) {<dir-with-view-container></dir-with-view-container>}`, imports: [DirectiveWithViewContainer], changeDetection: ChangeDetectionStrategy.Eager, }) class ComponentWithChildViewContainer { showChildView = true; @ViewChild(DirectiveWithViewContainer) childWithViewContainer!: DirectiveWithViewContainer; get childViewContainer() { return this.childWithViewContainer.viewContainerRef; } } @Component({ selector: 'arbitrary-component-with-template-ref', template: `<ng-template let-data let-dialogRef="dialogRef"> Cheese {{localValue}} {{data?.value}}{{setDialogRef(dialogRef)}}</ng-template>`, changeDetection: ChangeDetectionStrategy.Eager, }) class ComponentWithTemplateRef { localValue!: string; dialogRef!: MatDialogRef<any>; @ViewChild(TemplateRef) templateRef!: TemplateRef<any>; setDialogRef(dialogRef: MatDialogRef<any>): string { this.dialogRef = dialogRef; return ''; } } /** Simple component for testing ComponentPortal. */ @Component({ template: '<p>Pizza</p> <input> <button>Close</button>', changeDetection: ChangeDetectionStrategy.Eager, }) class PizzaMsg { @Input() flavor = 'unknown'; dialogRef = inject<MatDialogRef<PizzaMsg>>(MatDialogRef); dialogInjector = inject(Injector); directionality = inject(Directionality); cdkDialogRef = inject(DialogRef, {optional: true}); } @Component({ template: ` @if (shouldShowTitle('first')) { <h2 mat-dialog-title>This is the first title</h2> } @if (shouldShowTitle('second')) { <h2 mat-dialog-title>This is the second title</h2> } @if (shouldShowTitle('third')) { <h2 mat-dialog-title>This is the third title</h2> } <mat-dialog-content>Lorem ipsum dolor sit amet.</mat-dialog-content> <mat-dialog-actions align="end"> <button mat-dialog-close>Close</button> <button class="close-with-true" [mat-dialog-close]="true">Close and return true</button> <button class="close-with-aria-label" aria-label="Best close button ever" [mat-dialog-close]="true"></button> <div mat-dialog-close>Should not close</div> <button class="with-submit" type="submit" mat-dialog-close>Should have submit</button> </mat-dialog-actions> `, imports: [MatDialogTitle, MatDialogContent, MatDialogActions, MatDialogClose], changeDetection: ChangeDetectionStrategy.Eager, }) class ContentElementDialog { shownTitle: 'first' | 'second' | 'third' | 'all' = 'first'; shouldShowTitle(name: string) { return this.shownTitle === 'all' || this.shownTitle === name; } } @Component({ template: ` <ng-template> @if (shouldShowTitle('first')) { <h2 mat-dialog-title>This is the first title</h2> } @if (shouldShowTitle('second')) { <h2 mat-dialog-title>This is the second title</h2> } @if (shouldShowTitle('third')) { <h2 mat-dialog-title>This is the third title</h2> } <mat-dialog-content>Lorem ipsum dolor sit amet.</mat-dialog-content> <mat-dialog-actions align="end"> <button mat-dialog-close>Close</button> <button class="close-with-true" [mat-dialog-close]="true">Close and return true</button> <button class="close-with-aria-label" aria-label="Best close button ever" [mat-dialog-close]="true"></button> <div mat-dialog-close>Should not close</div> <button class="with-submit" type="submit" mat-dialog-close>Should have submit</button> </mat-dialog-actions> </ng-template> `, imports: [MatDialogTitle, MatDialogContent, MatDialogActions, MatDialogClose], changeDetection: ChangeDetectionStrategy.Eager, }) class ComponentWithContentElementTemplateRef { @ViewChild(TemplateRef) templateRef!: TemplateRef<any>; shownTitle: 'first' | 'second' | 'third' | 'all' = 'first'; shouldShowTitle(name: string) { return this.shownTitle === 'all' || this.shownTitle === name; } } @Component({ template: '', providers: [MatDialog], changeDetection: ChangeDetectionStrategy.Eager, }) class ComponentThatProvidesMatDialog { dialog = inject(MatDialog); } /** Simple component for testing ComponentPortal. */ @Component({ template: '', changeDetection: ChangeDetectionStrategy.Eager, }) class DialogWithInjectedData { data = inject(MAT_DIALOG_DATA); } @Component({ template: '<p>Pasta</p>', changeDetection: ChangeDetectionStrategy.Eager, }) class DialogWithoutFocusableElements {} @Component({ template: `<button>I'm a button</button>`, encapsulation: ViewEncapsulation.ShadowDom, changeDetection: ChangeDetectionStrategy.Eager, }) class ShadowDomComponent {} @Component({ template: '', changeDetection: ChangeDetectionStrategy.Eager, }) class ModuleBoundDialogParentComponent { private _injector = inject(Injector); private _dialog = inject(MatDialog); openDialog(): void { const ngModuleRef = createNgModule( ModuleBoundDialogModule, /* parentInjector */ this._injector, ); this._dialog.open(ModuleBoundDialogComponent, {injector: ngModuleRef.injector}); } } @Service({autoProvided: false}) class ModuleBoundDialogService { name = 'Pasta'; } @Component({ template: '<module-bound-dialog-child-component></module-bound-dialog-child-component>', imports: [forwardRef(() => ModuleBoundDialogChildComponent)], changeDetection: ChangeDetectionStrategy.Eager, }) class ModuleBoundDialogComponent {} @Component({ selector: 'module-bound-dialog-child-component', template: '<p>{{service.name}}</p>', changeDetection: ChangeDetectionStrategy.Eager, }) class ModuleBoundDialogChildComponent { service = inject(ModuleBoundDialogService); } @NgModule({ imports: [ModuleBoundDialogComponent, ModuleBoundDialogChildComponent], providers: [ModuleBoundDialogService], }) class ModuleBoundDialogModule {} @Component({ template: `{{message | async}}`, imports: [AsyncPipe], }) class DialogWithAfterOpenSubscription { dialogRef = inject(MatDialogRef); animations = inject(MATERIAL_ANIMATIONS); protected message = this.dialogRef.afterOpened().pipe(map(() => 'The dialog is now open!')); }