66 * found in the LICENSE file at https://angular.io/license
77 */
88
9- import { ComponentHarness , HarnessPredicate } from '@angular/cdk/testing' ;
9+ import { ComponentHarness , HarnessPredicate , TestElement , TestKey } from '@angular/cdk/testing' ;
1010import { coerceBooleanProperty } from '@angular/cdk/coercion' ;
11- import { MenuHarnessFilters } from './menu-harness-filters' ;
12- import { MatMenuItemHarness } from './menu-item-harness' ;
11+ import { MenuHarnessFilters , MenuItemHarnessFilters } from './menu-harness-filters' ;
1312
1413/**
1514 * Harness for interacting with a standard mat-menu in tests.
@@ -18,6 +17,8 @@ import {MatMenuItemHarness} from './menu-item-harness';
1817export class MatMenuHarness extends ComponentHarness {
1918 static hostSelector = '.mat-menu-trigger' ;
2019
20+ private _documentRootLocator = this . documentRootLocatorFactory ( ) ;
21+
2122 // TODO: potentially extend MatButtonHarness
2223
2324 /**
@@ -29,7 +30,7 @@ export class MatMenuHarness extends ComponentHarness {
2930 */
3031 static with ( options : MenuHarnessFilters = { } ) : HarnessPredicate < MatMenuHarness > {
3132 return new HarnessPredicate ( MatMenuHarness , options )
32- . addOption ( 'text ' , options . triggerText ,
33+ . addOption ( 'triggerText ' , options . triggerText ,
3334 ( harness , text ) => HarnessPredicate . stringMatches ( harness . getTriggerText ( ) , text ) ) ;
3435 }
3536
@@ -39,8 +40,9 @@ export class MatMenuHarness extends ComponentHarness {
3940 return coerceBooleanProperty ( await disabled ) ;
4041 }
4142
43+ /** Whether the menu is open. */
4244 async isOpen ( ) : Promise < boolean > {
43- throw Error ( 'not implemented' ) ;
45+ return ! ! ( await this . _getMenuPanel ( ) ) ;
4446 }
4547
4648 async getTriggerText ( ) : Promise < string > {
@@ -58,30 +60,116 @@ export class MatMenuHarness extends ComponentHarness {
5860 }
5961
6062 async open ( ) : Promise < void > {
61- throw Error ( 'not implemented' ) ;
63+ if ( ! await this . isOpen ( ) ) {
64+ return ( await this . host ( ) ) . click ( ) ;
65+ }
6266 }
6367
6468 async close ( ) : Promise < void > {
65- throw Error ( 'not implemented' ) ;
69+ const panel = await this . _getMenuPanel ( ) ;
70+ if ( panel ) {
71+ return panel . sendKeys ( TestKey . ESCAPE ) ;
72+ }
73+ }
74+
75+ async getItems ( filters : Omit < MenuItemHarnessFilters , 'ancestor' > = { } ) :
76+ Promise < MatMenuItemHarness [ ] > {
77+ const panelId = await this . _getPanelId ( ) ;
78+ if ( panelId ) {
79+ return this . _documentRootLocator . locatorForAll (
80+ MatMenuItemHarness . with ( { ...filters , ancestor : `#${ panelId } ` } ) ) ( ) ;
81+ }
82+ return [ ] ;
83+ }
84+
85+ async clickItem ( filter : Omit < MenuItemHarnessFilters , 'ancestor' > ,
86+ ...filters : Omit < MenuItemHarnessFilters , 'ancestor' > [ ] ) : Promise < void > {
87+ await this . open ( ) ;
88+ const items = await this . getItems ( filter ) ;
89+ if ( ! items . length ) {
90+ throw Error ( `Could not find item matching ${ JSON . stringify ( filter ) } ` ) ;
91+ }
92+
93+ if ( ! filters . length ) {
94+ return await items [ 0 ] . click ( ) ;
95+ }
96+
97+ const menu = await items [ 0 ] . getSubmenu ( ) ;
98+ if ( ! menu ) {
99+ throw Error ( `Item matching ${ JSON . stringify ( filter ) } does not have a submenu` ) ;
100+ }
101+ return menu . clickItem ( ...filters as [ Omit < MenuItemHarnessFilters , 'ancestor' > ] ) ;
66102 }
67103
68- async getItems ( ) : Promise < MatMenuItemHarness [ ] > {
69- throw Error ( 'not implemented' ) ;
104+ private async _getMenuPanel ( ) : Promise < TestElement | null > {
105+ const panelId = await this . _getPanelId ( ) ;
106+ return panelId ? this . _documentRootLocator . locatorForOptional ( `#${ panelId } ` ) ( ) : null ;
70107 }
71108
72- async getItemLabels ( ) : Promise < string [ ] > {
73- throw Error ( 'not implemented' ) ;
109+ private async _getPanelId ( ) : Promise < string | null > {
110+ const panelId = await ( await this . host ( ) ) . getAttribute ( 'aria-controls' ) ;
111+ return panelId || null ;
112+ }
113+ }
114+
115+
116+ /**
117+ * Harness for interacting with a standard mat-menu-item in tests.
118+ * @dynamic
119+ */
120+ export class MatMenuItemHarness extends ComponentHarness {
121+ static hostSelector = '.mat-menu-item' ;
122+
123+ /**
124+ * Gets a `HarnessPredicate` that can be used to search for a menu with specific attributes.
125+ * @param options Options for narrowing the search:
126+ * - `selector` finds a menu item whose host element matches the given selector.
127+ * - `label` finds a menu item with specific label text.
128+ * @return a `HarnessPredicate` configured with the given options.
129+ */
130+ static with ( options : MenuItemHarnessFilters = { } ) : HarnessPredicate < MatMenuItemHarness > {
131+ return new HarnessPredicate ( MatMenuItemHarness , options )
132+ . addOption ( 'text' , options . text ,
133+ ( harness , text ) => HarnessPredicate . stringMatches ( harness . getText ( ) , text ) )
134+ . addOption ( 'hasSubmenu' , options . hasSubmenu ,
135+ async ( harness , hasSubmenu ) => ( await harness . hasSubmenu ( ) ) === hasSubmenu ) ;
136+ }
137+
138+ /** Gets a boolean promise indicating if the menu is disabled. */
139+ async isDisabled ( ) : Promise < boolean > {
140+ const disabled = ( await this . host ( ) ) . getAttribute ( 'disabled' ) ;
141+ return coerceBooleanProperty ( await disabled ) ;
142+ }
143+
144+ async getText ( ) : Promise < string > {
145+ return ( await this . host ( ) ) . text ( ) ;
146+ }
147+
148+ /** Focuses the menu and returns a void promise that indicates when the action is complete. */
149+ async focus ( ) : Promise < void > {
150+ return ( await this . host ( ) ) . focus ( ) ;
151+ }
152+
153+ /** Blurs the menu and returns a void promise that indicates when the action is complete. */
154+ async blur ( ) : Promise < void > {
155+ return ( await this . host ( ) ) . blur ( ) ;
74156 }
75157
76- async getItemByLabel ( ) : Promise < MatMenuItemHarness > {
77- throw Error ( 'not implemented' ) ;
158+ /** Clicks the menu item. */
159+ async click ( ) : Promise < void > {
160+ return ( await this . host ( ) ) . click ( ) ;
78161 }
79162
80- async getItemByIndex ( ) : Promise < MatMenuItemHarness > {
81- throw Error ( 'not implemented' ) ;
163+ /** Whether this item has a submenu. */
164+ async hasSubmenu ( ) : Promise < boolean > {
165+ return ( await this . host ( ) ) . matchesSelector ( MatMenuHarness . hostSelector ) ;
82166 }
83167
84- async getFocusedItem ( ) : Promise < MatMenuItemHarness > {
85- throw Error ( 'not implemented' ) ;
168+ /** Gets the submenu associated with this menu item, or null if none. */
169+ async getSubmenu ( ) : Promise < MatMenuHarness | null > {
170+ if ( await this . hasSubmenu ( ) ) {
171+ return new MatMenuHarness ( this . locatorFactory ) ;
172+ }
173+ return null ;
86174 }
87175}
0 commit comments