/
githubmirror
/
components
Обзор
Документация
Войти
/
githubmirror
/
components
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/cdk/menu/context-menu-trigger.spec.ts
572 строки
18 KB
Kristiyan Kostadinov
build: add explicit change detection to tests (#33010)
30 мар 2026, 10:52
Не верифицирован
30 мар 2026, 10:52
7806398
Код
Авторство
О чём код?
import { Component, ViewChild, ElementRef, ViewChildren, QueryList, ChangeDetectionStrategy, } from '@angular/core'; import {TestBed, ComponentFixture} from '@angular/core/testing'; import {CdkMenu} from './menu'; import {CdkContextMenuTrigger} from './context-menu-trigger'; import {dispatchKeyboardEvent, dispatchMouseEvent} from '../testing/private'; import {By} from '@angular/platform-browser'; import {CdkMenuItem} from './menu-item'; import {CdkMenuTrigger} from './menu-trigger'; import {CdkMenuBar} from './menu-bar'; import {LEFT_ARROW, RIGHT_ARROW} from '../keycodes'; describe('CdkContextMenuTrigger', () => { describe('with simple context menu trigger', () => { let fixture: ComponentFixture<SimpleContextMenu>; beforeEach(() => { fixture = TestBed.createComponent(SimpleContextMenu); fixture.detectChanges(); }); /** Get the menu opened by the context menu trigger. */ function getContextMenu() { return fixture.componentInstance.menu; } /** Return a reference to the context menu native element. */ function getNativeContextMenu() { return fixture.componentInstance.nativeMenu?.nativeElement; } /** Get the context in which the context menu should trigger. */ function getMenuTrigger() { return fixture.componentInstance.triggerElement.nativeElement; } /** Open up the context menu and run change detection. */ function openContextMenu() { // right click triggers a context menu event dispatchMouseEvent(getMenuTrigger(), 'contextmenu'); fixture.detectChanges(); } it('should display context menu on right click inside of context component', () => { expect(getContextMenu()).not.toBeDefined(); openContextMenu(); expect(getContextMenu()).toBeDefined(); }); it('should close out the context menu when clicking in the context', () => { openContextMenu(); getMenuTrigger().click(); fixture.detectChanges(); expect(getContextMenu()).not.toBeDefined(); }); it('should close out the context menu when clicking on element outside of the context', () => { openContextMenu(); fixture.nativeElement.querySelector('#other').click(); fixture.detectChanges(); expect(getContextMenu()).not.toBeDefined(); }); it('should not close the menu on first auxclick after opening via contextmenu event', () => { openContextMenu(); fixture.nativeElement.querySelector('#other').dispatchEvent(new MouseEvent('auxclick')); fixture.detectChanges(); expect(getContextMenu()).toBeDefined(); fixture.nativeElement.querySelector('#other').dispatchEvent(new MouseEvent('auxclick')); fixture.detectChanges(); expect(getContextMenu()).not.toBeDefined(); }); it('should close the menu on first auxclick after opening programmatically', () => { fixture.componentInstance.trigger.open({x: 0, y: 0}); fixture.nativeElement.querySelector('#other').dispatchEvent(new MouseEvent('auxclick')); fixture.detectChanges(); expect(getContextMenu()).not.toBeDefined(); }); it('should close out the context menu when clicking a menu item', () => { openContextMenu(); fixture.debugElement.query(By.directive(CdkMenuItem)).injector.get(CdkMenuItem).trigger(); fixture.detectChanges(); expect(getContextMenu()).not.toBeDefined(); }); it('should re-open the same menu when right clicking twice in the context', () => { openContextMenu(); openContextMenu(); const menus = fixture.componentInstance.menus; expect(menus.length) .withContext('two context menu triggers should result in a single context menu') .toBe(1); }); it('should retain the context menu on right click inside the open menu', () => { openContextMenu(); dispatchMouseEvent(getNativeContextMenu()!, 'contextmenu'); fixture.detectChanges(); expect(getContextMenu()).toBeDefined(); }); it('should focus the first menuitem when the context menu is opened', () => { openContextMenu(); expect(document.activeElement!.id).toEqual('first_menu_item'); }); it('should not close context menu when pressing left arrow', () => { openContextMenu(); expect(document.activeElement!.id).toEqual('first_menu_item'); dispatchKeyboardEvent(document.activeElement!, 'keydown', LEFT_ARROW, 'ArrowLeft'); fixture.detectChanges(); expect(getContextMenu()).toBeDefined(); }); it('should not close context menu when pressing right arrow', () => { openContextMenu(); expect(document.activeElement!.id).toEqual('first_menu_item'); dispatchKeyboardEvent(document.activeElement!, 'keydown', RIGHT_ARROW, 'ArrowRight'); fixture.detectChanges(); expect(getContextMenu()).toBeDefined(); }); it('should toggle aria-controls depending on if the menu is open', () => { const trigger = getMenuTrigger(); expect(trigger.hasAttribute('aria-controls')).toBe(false); expect(getContextMenu()).not.toBeDefined(); openContextMenu(); expect(trigger.getAttribute('aria-controls')).toBeTruthy(); expect(getContextMenu()).toBeDefined(); trigger.click(); fixture.detectChanges(); expect(trigger.hasAttribute('aria-controls')).toBe(false); expect(getContextMenu()).not.toBeDefined(); }); }); describe('nested context menu triggers', () => { let fixture: ComponentFixture<NestedContextMenu>; beforeEach(() => { fixture = TestBed.createComponent(NestedContextMenu); fixture.detectChanges(); }); /** Get the cut context menu. */ function getCutMenu() { return fixture.componentInstance.cutMenu; } /** Get the copy context menu. */ function getCopyMenu() { return fixture.componentInstance.copyMenu; } /** Get the context in which the cut context menu should trigger. */ function getCutMenuContext() { return fixture.componentInstance.cutContext.nativeElement; } /** Get the context in which the copy context menu should trigger. */ function getCopyMenuContext() { return fixture.componentInstance.copyContext.nativeElement; } /** Open up the cut context menu and run change detection. */ function openCutContextMenu() { // right click triggers a context menu event dispatchMouseEvent(getCutMenuContext(), 'contextmenu'); fixture.detectChanges(); } /** Open up the copy context menu and run change detection. */ function openCopyContextMenu() { // right click triggers a context menu event dispatchMouseEvent(getCopyMenuContext(), 'contextmenu'); fixture.detectChanges(); } it('should open the cut context menu only when right clicked in its trigger context', () => { openCutContextMenu(); expect(getCutMenu()).toBeDefined(); expect(getCopyMenu()).not.toBeDefined(); }); it('should open the nested copy context menu only when right clicked in nested context', () => { openCopyContextMenu(); expect(getCopyMenu()).toBeDefined(); expect(getCutMenu()).not.toBeDefined(); }); it( 'should open the parent context menu only when right clicked in nested context and nested' + ' is disabled', () => { fixture.componentInstance.copyMenuDisabled = true; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); openCopyContextMenu(); expect(getCopyMenu()).not.toBeDefined(); expect(getCutMenu()).toBeDefined(); }, ); it('should close nested context menu when parent is opened', () => { openCopyContextMenu(); openCutContextMenu(); expect(getCopyMenu()).not.toBeDefined(); expect(getCutMenu()).toBeDefined(); }); it('should close the parent context menu when nested is open', () => { openCutContextMenu(); openCopyContextMenu(); expect(getCopyMenu()).toBeDefined(); expect(getCutMenu()).not.toBeDefined(); }); it('should close nested context menu when clicking in parent', () => { openCopyContextMenu(); getCutMenuContext().click(); fixture.detectChanges(); expect(getCopyMenu()).not.toBeDefined(); }); it('should close parent context menu when clicking in nested menu', () => { openCutContextMenu(); getCopyMenuContext().click(); fixture.detectChanges(); expect(getCutMenu()).not.toBeDefined(); }); }); describe('with context menu that has submenu', () => { let fixture: ComponentFixture<ContextMenuWithSubmenu>; let instance: ContextMenuWithSubmenu; beforeEach(() => { fixture = TestBed.createComponent(ContextMenuWithSubmenu); fixture.detectChanges(); instance = fixture.componentInstance; }); it('should open context menu submenu without closing context menu', () => { dispatchMouseEvent(instance.context.nativeElement, 'contextmenu'); fixture.detectChanges(); instance.triggerNativeElement.nativeElement.click(); fixture.detectChanges(); expect(instance.cutMenu).toBeDefined(); expect(instance.copyMenu).toBeDefined(); }); }); describe('with menubar and inline menu on page', () => { let fixture: ComponentFixture<ContextMenuWithMenuBarAndInlineMenu>; let nativeMenuBar: HTMLElement; let nativeMenuBarTrigger!: HTMLElement; beforeEach(() => { fixture = TestBed.createComponent(ContextMenuWithMenuBarAndInlineMenu); fixture.detectChanges(); nativeMenuBar = fixture.componentInstance.nativeMenuBar.nativeElement; nativeMenuBarTrigger = fixture.componentInstance.nativeMenuBarTrigger.nativeElement; }); /** Get the menu opened by the context menu trigger. */ function getContextMenu() { return fixture.componentInstance.contextMenu; } /** Get the menu opened by the menu bar item trigger. */ function getFileMenu() { return fixture.componentInstance.fileMenu; } /** Get the context in which the context menu should trigger. */ function getMenuContext() { return fixture.componentInstance.trigger.nativeElement; } /** Get the inline menus trigger element. */ function getInlineMenuTrigger() { return fixture.componentInstance.nativeInlineMenuButton.nativeElement; } /** Return the native element for the inline menu. */ function getInlineMenuElement() { return fixture.componentInstance.nativeInlineMenu.nativeElement; } /** Open up the context menu and run change detection. */ function openContextMenu() { // right click triggers a context menu event dispatchMouseEvent(getMenuContext(), 'contextmenu'); dispatchMouseEvent(getMenuContext(), 'mousedown'); fixture.detectChanges(); } /** Open up the file menu from the menu bar. */ function openFileMenu() { nativeMenuBarTrigger.click(); fixture.detectChanges(); } it('should close the open context menu when clicking on the menubar element', () => { openContextMenu(); dispatchMouseEvent(nativeMenuBar, 'click'); fixture.detectChanges(); expect(getContextMenu()).not.toBeDefined(); }); it('should close the open context menu when clicking on the menubar menu item', () => { openContextMenu(); nativeMenuBarTrigger.click(); fixture.detectChanges(); expect(getContextMenu()).not.toBeDefined(); }); it('should close the open context menu when clicking on the inline menu element', () => { openContextMenu(); getInlineMenuElement().click(); fixture.detectChanges(); expect(getContextMenu()).not.toBeDefined(); }); it('should close the open context menu when clicking on an inline menu item', () => { openContextMenu(); getInlineMenuTrigger().click(); fixture.detectChanges(); expect(getContextMenu()).not.toBeDefined(); }); it('should close the open menu when opening a context menu', () => { openFileMenu(); openContextMenu(); expect(getFileMenu()).not.toBeDefined(); }); }); describe('with shared triggered menu', () => { it('should allow a context menu and menubar trigger share a menu', () => { const fixture = TestBed.createComponent(MenuBarAndContextTriggerShareMenu); fixture.detectChanges(); expect(fixture.componentInstance.menus.length).toBe(0); fixture.componentInstance.menuBarTrigger.toggle(); fixture.detectChanges(); expect(fixture.componentInstance.menus.length).toBe(1); fixture.componentInstance.menuBarTrigger.toggle(); fixture.detectChanges(); expect(fixture.componentInstance.menus.length).toBe(0); fixture.componentInstance.contextTrigger.open({x: 0, y: 0}); fixture.detectChanges(); expect(fixture.componentInstance.menus.length).toBe(1); }); }); it('should be able to pass data to the menu via the template context', () => { const fixture = TestBed.createComponent(ContextTriggerWithData); fixture.componentInstance.menuData = {message: 'Hello!'}; fixture.detectChanges(); dispatchMouseEvent(fixture.componentInstance.triggerElement.nativeElement, 'contextmenu'); fixture.detectChanges(); expect(document.querySelector('.test-menu')?.textContent).toBe('Hello!'); }); }); @Component({ template: ` <div [cdkContextMenuTriggerFor]="context"></div> <div id="other"></div> <ng-template #context> <div cdkMenu> <button id="first_menu_item" cdkMenuItem></button> </div> </ng-template> `, imports: [CdkContextMenuTrigger, CdkMenu, CdkMenuItem], changeDetection: ChangeDetectionStrategy.Eager, }) class SimpleContextMenu { @ViewChild(CdkContextMenuTrigger) trigger!: CdkContextMenuTrigger; @ViewChild(CdkContextMenuTrigger, {read: ElementRef}) triggerElement!: ElementRef<HTMLElement>; @ViewChild(CdkMenu) menu?: CdkMenu; @ViewChild(CdkMenu, {read: ElementRef}) nativeMenu?: ElementRef<HTMLElement>; @ViewChildren(CdkMenu) menus!: QueryList<CdkMenu>; } @Component({ template: ` <div #cut_trigger [cdkContextMenuTriggerFor]="cut"> <div #copy_trigger [cdkContextMenuDisabled]="copyMenuDisabled" [cdkContextMenuTriggerFor]="copy" ></div> </div> <ng-template #cut> <div #cut_menu cdkMenu></div> </ng-template> <ng-template #copy> <div #copy_menu cdkMenu></div> </ng-template> `, imports: [CdkContextMenuTrigger, CdkMenu], changeDetection: ChangeDetectionStrategy.Eager, }) class NestedContextMenu { @ViewChild('cut_trigger', {read: ElementRef}) cutContext!: ElementRef<HTMLElement>; @ViewChild('copy_trigger', {read: ElementRef}) copyContext!: ElementRef<HTMLElement>; @ViewChild('cut_menu', {read: CdkMenu}) cutMenu!: CdkMenu; @ViewChild('copy_menu', {read: CdkMenu}) copyMenu!: CdkMenu; copyMenuDisabled = false; } @Component({ template: ` <div [cdkContextMenuTriggerFor]="cut"></div> <ng-template #cut> <div #cut_menu cdkMenu> <button cdkMenuItem [cdkMenuTriggerFor]="copy"></button> </div> </ng-template> <ng-template #copy> <div #copy_menu cdkMenu></div> </ng-template> `, imports: [CdkContextMenuTrigger, CdkMenuTrigger, CdkMenu, CdkMenuItem], changeDetection: ChangeDetectionStrategy.Eager, }) class ContextMenuWithSubmenu { @ViewChild(CdkContextMenuTrigger, {read: ElementRef}) context!: ElementRef<HTMLElement>; @ViewChild(CdkMenuTrigger, {read: ElementRef}) triggerNativeElement!: ElementRef<HTMLElement>; @ViewChild('cut_menu', {read: CdkMenu}) cutMenu!: CdkMenu; @ViewChild('copy_menu', {read: CdkMenu}) copyMenu!: CdkMenu; } @Component({ template: ` <div cdkMenuBar id="menu_bar"> <button #trigger cdkMenuItem [cdkMenuTriggerFor]="file">File</button> </div> <ng-template #file> <div cdkMenu #file_menu id="file_menu"></div> </ng-template> <div [cdkContextMenuTriggerFor]="context"></div> <ng-template #context> <div cdkMenu #context_menu> <button cdkMenuItem></button> </div> </ng-template> <div #inline_menu cdkMenu> <button #inline_menu_button cdkMenuItem></button> </div> `, imports: [CdkContextMenuTrigger, CdkMenuTrigger, CdkMenu, CdkMenuItem, CdkMenuBar], changeDetection: ChangeDetectionStrategy.Eager, }) class ContextMenuWithMenuBarAndInlineMenu { @ViewChild(CdkMenuBar, {read: ElementRef}) nativeMenuBar!: ElementRef; @ViewChild('trigger', {read: ElementRef}) nativeMenuBarTrigger!: ElementRef; @ViewChild('context_menu') contextMenu?: CdkMenu; @ViewChild(CdkContextMenuTrigger, {read: ElementRef}) trigger!: ElementRef<HTMLElement>; @ViewChild('file_menu') fileMenu?: CdkMenu; @ViewChild('inline_menu') nativeInlineMenu!: ElementRef; @ViewChild('inline_menu_button') nativeInlineMenuButton!: ElementRef; } @Component({ template: ` <div cdkMenuBar> <button cdkMenuItem [cdkMenuTriggerFor]="menu">First</button> </div> <div [cdkContextMenuTriggerFor]="menu"></div> <ng-template #menu> <div cdkMenu> <button cdkMenuItem></button> </div> </ng-template> `, imports: [CdkMenuBar, CdkContextMenuTrigger, CdkMenu, CdkMenuItem, CdkMenuTrigger], changeDetection: ChangeDetectionStrategy.Eager, }) class MenuBarAndContextTriggerShareMenu { @ViewChild(CdkMenuTrigger) menuBarTrigger!: CdkMenuTrigger; @ViewChild(CdkContextMenuTrigger) contextTrigger!: CdkContextMenuTrigger; @ViewChildren(CdkMenu) menus!: QueryList<CdkMenu>; } @Component({ template: ` <div [cdkContextMenuTriggerFor]="context" [cdkContextMenuTriggerData]="menuData"></div> <ng-template #context let-message="message"> <div cdkMenu class="test-menu">{{message}}</div> </ng-template> `, imports: [CdkContextMenuTrigger, CdkMenu], changeDetection: ChangeDetectionStrategy.Eager, }) class ContextTriggerWithData { @ViewChild(CdkContextMenuTrigger, {read: ElementRef}) triggerElement!: ElementRef<HTMLElement>; menuData: unknown; }