import { OverlayContainer , OverlayRef } from '@angular/cdk/overlay' ; import { Component , ViewChild } from '@angular/core' ; import { async , ComponentFixture , inject , TestBed } from '@angular/core/testing' ; import { NoopAnimationsModule } from '@angular/platform-browser/animations' ; import { OverlayPlacementService } from '../../services/overlay-placement' ; import { MenuTriggerDirective } from './menu-trigger/menu-trigger.directive' ; import { MenuModule } from './menu.module' ; @ Component ( { selector : 'app-menu-test' , template : `
        <ux-menu #menu [alignment]="alignment">
            <button
                type="button"
                id="menu-item-1"
                uxMenuItem
                (click)="onClick()"
                [disabled]="disabled"
                (activate)="onActivate($event)">
                Item One
            </button>
            <ux-menu-divider></ux-menu-divider>
            <button
                uxMenuItem
                [disabled]="disabled"
                #subMenuTrigger="ux-menu-trigger"
                [uxMenuTriggerFor]="subMenu">
                Item Two
            </button>
            <button uxMenuItem>Item Three</button>
        </ux-menu>

        <ux-menu #subMenu>
            <button uxMenuItem id="submenu-item-1"></button>
            <button uxMenuItem>Sub Item Two</button>
        </ux-menu>

        <div>
            <button
                id="trigger"
                #menuTrigger="ux-menu-trigger"
                [uxMenuTriggerFor]="menu">
                Open Menu
            </button>
        </div>
    ` } ) export class MenuTestComponent { alignment = 'start' ; disabled : boolean = false ; @ ViewChild ( 'menuTrigger' , { static : true } ) trigger : MenuTriggerDirective ; @ ViewChild ( 'subMenuTrigger' , { static : true } ) subMenuTrigger : MenuTriggerDirective ; onActivate ( _ ) { } onClick ( _ ) { } } describe ( 'MenuComponent' , ( ) => { let component ; let fixture ; let nativeElement ; let triggerElement ; let overlayContainer ; let overlayContainerElement ; let overlayPlacement ; beforeEach ( async ( ( ) => { TestBed . configureTestingModule ( { imports : [ MenuModule , NoopAnimationsModule ] , declarations : [ MenuTestComponent ] } ) . compileComponents ( ) ; inject ( [ OverlayContainer ] , ( oc ) => { overlayContainer = oc ; overlayContainerElement = oc . getContainerElement ( ) ; } ) ( ) ; } ) ) ; afterEach ( ( ) => { overlayContainer . ngOnDestroy ( ) ; } ) ; beforeEach ( ( ) => { fixture = TestBed . createComponent ( MenuTestComponent ) ; component = fixture . componentInstance ; nativeElement = fixture . nativeElement ; triggerElement = nativeElement . querySelector ( '#trigger' ) ; overlayPlacement = TestBed . inject ( OverlayPlacementService ) ; fixture . detectChanges ( ) ; } ) ; it ( 'should create not initially show the menus' , ( ) => { expect ( document . querySelectorAll ( '.ux-menu' ) . length ) . toBe ( 0 ) ; } ) ; it ( 'should show the menu on trigger click' , ( ) => { triggerElement . click ( ) ; fixture . detectChanges ( ) ; expect ( document . querySelectorAll ( '.ux-menu' ) . length ) . toBe ( 1 ) ; } ) ; it ( 'should hide when the trigger is clicked again' , async ( ) => { triggerElement . click ( ) ; fixture . detectChanges ( ) ; expect ( document . querySelectorAll ( '.ux-menu' ) . length ) . toBe ( 1 ) ; triggerElement . click ( ) ; fixture . detectChanges ( ) ; await fixture . whenStable ( ) ; expect ( document . querySelectorAll ( '.ux-menu' ) . length ) . toBe ( 0 ) ; } ) ; it ( 'should hide when the outside the menu is clicked' , async ( ) => { triggerElement . click ( ) ; fixture . detectChanges ( ) ; expect ( document . querySelectorAll ( '.ux-menu' ) . length ) . toBe ( 1 ) ; const backdrop = overlayContainerElement . querySelector ( '.cdk-overlay-backdrop' ) as HTMLDivElement ; expect ( backdrop ) . toBeTruthy ( ) ; backdrop . click ( ) ; fixture . detectChanges ( ) ; await fixture . whenStable ( ) ; expect ( document . querySelectorAll ( '.ux-menu' ) . length ) . toBe ( 0 ) ; } ) ; it ( 'should show the menu when the trigger is programmatically called' , ( ) => { component . trigger . openMenu ( ) ; fixture . detectChanges ( ) ; expect ( document . querySelectorAll ( '.ux-menu' ) . length ) . toBe ( 1 ) ; } ) ; it ( 'should close the menu when the trigger is programmatically called' , async ( ) => { component . trigger . openMenu ( ) ; fixture . detectChanges ( ) ; await fixture . whenStable ( ) ; expect ( document . querySelectorAll ( '.ux-menu' ) . length ) . toBe ( 1 ) ; component . trigger . closeMenu ( ) ; fixture . detectChanges ( ) ; await fixture . whenStable ( ) ; expect ( document . querySelectorAll ( '.ux-menu' ) . length ) . toBe ( 0 ) ; } ) ; it ( 'should toggle the menu when the trigger is programmatically called' , async ( ) => { component . trigger . toggleMenu ( ) ; fixture . detectChanges ( ) ; await fixture . whenStable ( ) ; expect ( document . querySelectorAll ( '.ux-menu' ) . length ) . toBe ( 1 ) ; component . trigger . toggleMenu ( ) ; fixture . detectChanges ( ) ; await fixture . whenStable ( ) ; expect ( document . querySelectorAll ( '.ux-menu' ) . length ) . toBe ( 0 ) ; } ) ; it ( 'should have the correct number of menu items' , ( ) => { component . trigger . openMenu ( ) ; fixture . detectChanges ( ) ; expect ( document . querySelectorAll ( 'button[uxmenuitem]' ) . length ) . toBe ( 3 ) ; } ) ; it ( 'should display the menu divider' , ( ) => { component . trigger . openMenu ( ) ; fixture . detectChanges ( ) ; expect ( document . querySelectorAll ( 'ux-menu-divider' ) . length ) . toBe ( 1 ) ; } ) ; it ( 'should update the aria-expanded state on the trigger' , ( ) => { expect ( triggerElement . getAttribute ( 'aria-haspopup' ) ) . toBe ( 'true' ) ; expect ( triggerElement . getAttribute ( 'aria-expanded' ) ) . toBe ( 'false' ) ; component . trigger . openMenu ( ) ; fixture . detectChanges ( ) ; expect ( triggerElement . getAttribute ( 'aria-expanded' ) ) . toBe ( 'true' ) ; } ) ; it ( 'should make only the first menu item tabbable' , async ( ) => { component . trigger . openMenu ( ) ; fixture . detectChanges ( ) ; await fixture . whenStable ( ) ; const items = document . querySelectorAll ( 'button[uxmenuitem]' ) ; expect ( items . item ( 0 ) . getAttribute ( 'tabindex' ) ) . toBe ( '0' ) ; expect ( items . item ( 1 ) . getAttribute ( 'tabindex' ) ) . toBe ( '-1' ) ; expect ( items . item ( 2 ) . getAttribute ( 'tabindex' ) ) . toBe ( '-1' ) ; } ) ; it ( 'should focus the menu trigger when the menu is closed' , async ( ) => { component . trigger . openMenu ( ) ; fixture . detectChanges ( ) ; await fixture . whenStable ( ) ; const items = document . querySelectorAll < HTMLButtonElement > ( 'button[uxmenuitem]' ) ; items . item ( 0 ) . click ( ) ; fixture . detectChanges ( ) ; await fixture . whenStable ( ) ; expect ( document . activeElement ) . toBe ( triggerElement ) ; } ) ; it ( 'should show the submenu when the menu item is hovered' , async ( ) => { component . trigger . openMenu ( ) ; fixture . detectChanges ( ) ; await fixture . whenStable ( ) ; component . subMenuTrigger . _onMouseEnter ( ) ; fixture . detectChanges ( ) ; await fixture . whenStable ( ) ; expect ( document . querySelectorAll ( '.ux-menu' ) . length ) . toBe ( 2 ) ; } ) ; it ( 'should close all menus when backdrop is clicked' , async ( ) => { component . trigger . openMenu ( ) ; fixture . detectChanges ( ) ; await fixture . whenStable ( ) ; component . subMenuTrigger . _onMouseEnter ( ) ; fixture . detectChanges ( ) ; await fixture . whenStable ( ) ; const backdrop = overlayContainerElement . querySelector ( '.cdk-overlay-backdrop' ) as HTMLDivElement ; expect ( backdrop ) . toBeTruthy ( ) ; backdrop . click ( ) ; fixture . detectChanges ( ) ; await fixture . whenStable ( ) ; expect ( document . querySelectorAll ( '.ux-menu' ) . length ) . toBe ( 0 ) ; } ) ; it ( 'should close the menu when an item is clicked' , async ( ) => { component . trigger . openMenu ( ) ; fixture . detectChanges ( ) ; await fixture . whenStable ( ) ; const items = document . querySelectorAll < HTMLButtonElement > ( 'button[uxmenuitem]' ) ; items . item ( 0 ) . click ( ) ; fixture . detectChanges ( ) ; await fixture . whenStable ( ) ; expect ( document . querySelectorAll ( '.ux-menu' ) . length ) . toBe ( 0 ) ; } ) ; it ( 'should close both menus when a menu item is clicked' , async ( ) => { component . trigger . openMenu ( ) ; fixture . detectChanges ( ) ; await fixture . whenStable ( ) ; component . subMenuTrigger . _onMouseEnter ( ) ; fixture . detectChanges ( ) ; await fixture . whenStable ( ) ; const items = document . querySelectorAll < HTMLButtonElement > ( 'button[uxmenuitem]' ) ; items . item ( 0 ) . click ( ) ; fixture . detectChanges ( ) ; await fixture . whenStable ( ) ; expect ( document . querySelectorAll ( '.ux-menu' ) . length ) . toBe ( 0 ) ; } ) ; /**
     * Test case covering issue: https://portal.digitalsafe.net/browse/EL-3644
     *
     * Opening a menu, expanding a submenu and selecting an item would close
     * the menu, hover when the top level menu is re-opened the internal hover
     * and focus states were not being reset correctly
     */ it ( 'should correctly reset menu state when closed' , async ( ) => { component . trigger . openMenu ( ) ; fixture . detectChanges ( ) ; await fixture . whenStable ( ) ; component . subMenuTrigger . openMenu ( ) ; fixture . detectChanges ( ) ; await fixture . whenStable ( ) ; const submenuItem = document . querySelector < HTMLButtonElement > ( '#submenu-item-1' ) ; expect ( submenuItem ) . toBeTruthy ( ) ; submenuItem . click ( ) ; fixture . detectChanges ( ) ; await fixture . whenStable ( ) ; expect ( component . trigger . menu . _isHovering$ . value ) . toBeFalsy ( ) ; expect ( component . trigger . menu . _isFocused$ . value ) . toBeFalsy ( ) ; expect ( component . subMenuTrigger . menu . _isHovering$ . value ) . toBeFalsy ( ) ; expect ( component . subMenuTrigger . menu . _isFocused$ . value ) . toBeFalsy ( ) ; } ) ; it ( 'should emit an activated event when clicking on menu item' , async ( ) => { component . trigger . openMenu ( ) ; fixture . detectChanges ( ) ; await fixture . whenStable ( ) ; spyOn ( component , 'onActivate' ) ; const item1Element = overlayContainerElement . querySelector ( '#menu-item-1' ) as HTMLButtonElement ; expect ( item1Element ) . toBeTruthy ( ) ; item1Element . click ( ) ; expect ( component . onActivate ) . toHaveBeenCalledTimes ( 1 ) ; } ) ; it ( 'should emit an activated event when pressing enter key on a menu item' , async ( ) => { component . trigger . openMenu ( ) ; fixture . detectChanges ( ) ; await fixture . whenStable ( ) ; spyOn ( component , 'onActivate' ) ; const item1Element = overlayContainerElement . querySelector ( '#menu-item-1' ) as HTMLButtonElement ; expect ( item1Element ) . toBeTruthy ( ) ; item1Element . dispatchEvent ( new KeyboardEvent ( 'keydown' , { key : 'Enter' } ) ) ; expect ( component . onActivate ) . toHaveBeenCalledTimes ( 1 ) ; } ) ; it ( 'should not emit a click or activated event when disabled is true.' , async ( ) => { component . disabled = true ; fixture . detectChanges ( ) ; await fixture . whenStable ( ) ; component . trigger . openMenu ( ) ; fixture . detectChanges ( ) ; await fixture . whenStable ( ) ; const menuItem = overlayContainerElement . querySelector ( '#menu-item-1' ) as HTMLButtonElement ; expect ( menuItem ) . toBeTruthy ( ) ; spyOn ( component , 'onActivate' ) ; spyOn ( component , 'onClick' ) ; menuItem . click ( ) ; fixture . detectChanges ( ) ; await fixture . whenStable ( ) ; expect ( component . onActivate ) . not . toHaveBeenCalled ( ) ; expect ( component . onClick ) . not . toHaveBeenCalled ( ) ; } ) ; it ( 'should not open submenu when menu item is disabled ' , async ( ) => { component . disabled = true ; fixture . detectChanges ( ) ; await fixture . whenStable ( ) ; component . trigger . openMenu ( ) ; fixture . detectChanges ( ) ; await fixture . whenStable ( ) ; component . subMenuTrigger . openMenu ( ) ; fixture . detectChanges ( ) ; await fixture . whenStable ( ) ; const subItemMenu = overlayContainerElement . querySelector ( '#submenu-item-1' ) as HTMLButtonElement ; expect ( subItemMenu ) . toBeFalsy ( ) ; } ) ; it ( 'should contain the role menu in the overlay dropdown' , async ( ) => { await triggerElement . click ( ) ; await fixture . detectChanges ( ) ; expect ( document . querySelectorAll ( '.ux-menu' ) . length ) . toBe ( 1 ) ; expect ( document . querySelector ( '.ux-menu' ) . getAttribute ( 'role' ) ) . toBe ( 'menu' ) ; } ) ; it ( 'should change update the alignment when the alignment input is changed' , async ( ) => { spyOn ( overlayPlacement , 'updatePosition' ) . and . callThrough ( ) ; component . alignment = 'end' ; fixture . detectChanges ( ) ; await fixture . whenStable ( ) ; expect ( await overlayPlacement . updatePosition ) . toHaveBeenCalledWith ( jasmine . any ( OverlayRef ) , 'bottom' , 'end' ) ; } ) ; } ) ; @ Component ( { selector : 'app-menu-trigger-test' , template : `
        <div class="btn-group" *ngIf="showTrigger">
            <button
                type="button"
                class="btn button-secondary dropdown-toggle"
                [uxMenuTriggerFor]="menu">
                Actions
            </button>
        </div>

        <ux-menu id="ux-menu-1" #menu>
            <button type="button" uxMenuItem>
                <span class="dropdown-menu"></span>
                <span class="dropdown-menu-text">Export</span>
            </button>

            <button type="button" id="menu-item-2" (activate)="onActivate($event)" [closeOnSelect]="closeOnSelect" uxMenuItem>
                <span class="dropdown-menu"></span>
                <span class="dropdown-menu-text">Annotate</span>
            </button>

            <button type="button" uxMenuItem>
                <span class="dropdown-menu"></span>
                <span class="dropdown-menu-text">Save List</span>
            </button>
        </ux-menu>
    ` } ) export class MenuTriggerDestroyTestComponent { @ ViewChild ( MenuTriggerDirective , { static : false } ) trigger : MenuTriggerDirective ; onActivate ( _ ) { } showTrigger : boolean = true ; closeOnSelect : boolean = true ; } describe ( 'MenuTriggerDestroyTestComponent' , ( ) => { let component ; let fixture ; beforeEach ( async ( ( ) => { TestBed . configureTestingModule ( { imports : [ MenuModule , NoopAnimationsModule ] , declarations : [ MenuTriggerDestroyTestComponent ] } ) . compileComponents ( ) ; } ) ) ; beforeEach ( ( ) => { fixture = TestBed . createComponent ( MenuTriggerDestroyTestComponent ) ; component = fixture . componentInstance ; fixture . detectChanges ( ) ; } ) ; it ( 'should hide the menu when the trigger element is destroyed.' , async ( ) => { component . trigger . openMenu ( ) ; fixture . detectChanges ( ) ; await fixture . whenStable ( ) ; expect ( document . querySelectorAll ( '.ux-menu' ) . length ) . toBe ( 1 ) ; component . showTrigger = false ; fixture . detectChanges ( ) ; await fixture . whenStable ( ) ; expect ( component . trigger ) . toBeFalsy ( ) ; expect ( document . querySelectorAll ( '.ux-menu' ) . length ) . toBe ( 0 ) ; } ) ; it ( 'should not close the menu when an item is clicked when closeOnSelect is false' , async ( ) => { component . closeOnSelect = false ; component . trigger . openMenu ( ) ; fixture . detectChanges ( ) ; await fixture . whenStable ( ) ; const items = document . querySelectorAll < HTMLButtonElement > ( 'button[uxmenuitem]' ) ; items . item ( 1 ) . click ( ) ; fixture . detectChanges ( ) ; await fixture . whenStable ( ) ; expect ( document . querySelectorAll ( '.ux-menu' ) . length ) . toBe ( 1 ) ; } ) ; it ( 'should not close the menu item when pressing enter key when closeOnSelect is false' , async ( ) => { component . closeOnSelect = false ; component . trigger . openMenu ( ) ; fixture . detectChanges ( ) ; await fixture . whenStable ( ) ; const item2Element = document . querySelector ( '#menu-item-2' ) as HTMLButtonElement ; item2Element . dispatchEvent ( new KeyboardEvent ( 'keydown' , { key : 'Enter' } ) ) ; fixture . detectChanges ( ) ; await fixture . whenStable ( ) ; expect ( document . querySelectorAll ( '.ux-menu' ) . length ) . toBe ( 1 ) ; } ) ; it ( 'should contain the aria-controls attribute when the menu is opened' , async ( ) => { expect ( document . querySelector ( '.btn' ) . getAttribute ( 'aria-controls' ) ) . toBe ( null ) ; component . trigger . openMenu ( ) ; fixture . detectChanges ( ) ; await fixture . whenStable ( ) ; expect ( document . querySelector ( '.btn' ) . getAttribute ( 'aria-controls' ) ) . toBe ( 'ux-menu-1-menu' ) ; } ) ; it ( 'should contain the correct id for the ux-menu and the overlay' , async ( ) => { component . trigger . openMenu ( ) ; fixture . detectChanges ( ) ; await fixture . whenStable ( ) ; expect ( document . querySelector ( '.ux-menu' ) . getAttribute ( 'id' ) ) . toBe ( 'ux-menu-1-menu' ) ; expect ( document . querySelector ( 'ux-menu' ) . getAttribute ( 'id' ) ) . toBe ( 'ux-menu-1' ) ; } ) ; } ) ;