6
6
* found in the LICENSE file at https://angular.io/license
7
7
*/
8
8
9
- import { ComponentHarness , HarnessPredicate } from '@angular/cdk/testing' ;
9
+ import { ComponentHarness , HarnessPredicate , TestElement , TestKey } from '@angular/cdk/testing' ;
10
10
import { coerceBooleanProperty } from '@angular/cdk/coercion' ;
11
- import {
12
- MenuHarnessFilters ,
13
- MenuItemHarnessFilters
14
- } from '@angular/material/menu/testing' ;
11
+ import { MenuHarnessFilters , MenuItemHarnessFilters } from '@angular/material/menu/testing' ;
15
12
16
- /** Harness for interacting with a MDC-based mat-menu in tests. */
13
+ /** Harness for interacting with an MDC-based mat-menu in tests. */
17
14
export class MatMenuHarness extends ComponentHarness {
15
+ /** The selector for the host element of a `MatMenu` instance. */
18
16
static hostSelector = '.mat-menu-trigger' ;
19
17
18
+ private _documentRootLocator = this . documentRootLocatorFactory ( ) ;
19
+
20
20
// TODO: potentially extend MatButtonHarness
21
21
22
22
/**
23
- * Gets a `HarnessPredicate` that can be used to search for a menu with specific attributes.
24
- * @param options Options for narrowing the search:
25
- * - `selector` finds a menu whose host element matches the given selector.
26
- * - `label` finds a menu with specific label text.
23
+ * Gets a `HarnessPredicate` that can be used to search for a `MatMenuHarness` that meets certain
24
+ * criteria.
25
+ * @param options Options for filtering which menu instances are considered a match.
27
26
* @return a `HarnessPredicate` configured with the given options.
28
27
*/
29
28
static with ( options : MenuHarnessFilters = { } ) : HarnessPredicate < MatMenuHarness > {
@@ -32,26 +31,28 @@ export class MatMenuHarness extends ComponentHarness {
32
31
( harness , text ) => HarnessPredicate . stringMatches ( harness . getTriggerText ( ) , text ) ) ;
33
32
}
34
33
35
- /** Gets a boolean promise indicating if the menu is disabled. */
34
+ /** Whether the menu is disabled. */
36
35
async isDisabled ( ) : Promise < boolean > {
37
36
const disabled = ( await this . host ( ) ) . getAttribute ( 'disabled' ) ;
38
37
return coerceBooleanProperty ( await disabled ) ;
39
38
}
40
39
40
+ /** Whether the menu is open. */
41
41
async isOpen ( ) : Promise < boolean > {
42
- throw Error ( 'not implemented' ) ;
42
+ return ! ! ( await this . _getMenuPanel ( ) ) ;
43
43
}
44
44
45
+ /** Gets the text of the menu's trigger element. */
45
46
async getTriggerText ( ) : Promise < string > {
46
47
return ( await this . host ( ) ) . text ( ) ;
47
48
}
48
49
49
- /** Focuses the menu and returns a void promise that indicates when the action is complete . */
50
+ /** Focuses the menu. */
50
51
async focus ( ) : Promise < void > {
51
52
return ( await this . host ( ) ) . focus ( ) ;
52
53
}
53
54
54
- /** Blurs the menu and returns a void promise that indicates when the action is complete . */
55
+ /** Blurs the menu. */
55
56
async blur ( ) : Promise < void > {
56
57
return ( await this . host ( ) ) . blur ( ) ;
57
58
}
@@ -61,35 +62,86 @@ export class MatMenuHarness extends ComponentHarness {
61
62
return ( await this . host ( ) ) . isFocused ( ) ;
62
63
}
63
64
65
+ /** Opens the menu. */
64
66
async open ( ) : Promise < void > {
65
- throw Error ( 'not implemented' ) ;
67
+ if ( ! await this . isOpen ( ) ) {
68
+ return ( await this . host ( ) ) . click ( ) ;
69
+ }
66
70
}
67
71
72
+ /** Closes the menu. */
68
73
async close ( ) : Promise < void > {
69
- throw Error ( 'not implemented' ) ;
74
+ const panel = await this . _getMenuPanel ( ) ;
75
+ if ( panel ) {
76
+ return panel . sendKeys ( TestKey . ESCAPE ) ;
77
+ }
70
78
}
71
79
80
+ /**
81
+ * Gets a list of `MatMenuItemHarness` representing the items in the menu.
82
+ * @param filters Optionally filters which menu items are included.
83
+ */
72
84
async getItems ( filters : Omit < MenuItemHarnessFilters , 'ancestor' > = { } ) :
73
85
Promise < MatMenuItemHarness [ ] > {
74
- throw Error ( 'not implemented' ) ;
86
+ const panelId = await this . _getPanelId ( ) ;
87
+ if ( panelId ) {
88
+ return this . _documentRootLocator . locatorForAll (
89
+ MatMenuItemHarness . with ( { ...filters , ancestor : `#${ panelId } ` } ) ) ( ) ;
90
+ }
91
+ return [ ] ;
75
92
}
76
93
77
- async clickItem ( filter : Omit < MenuItemHarnessFilters , 'ancestor' > ,
78
- ...filters : Omit < MenuItemHarnessFilters , 'ancestor' > [ ] ) : Promise < void > {
79
- throw Error ( 'not implemented' ) ;
94
+ /**
95
+ * Clicks an item in the menu, and optionally continues clicking items in subsequent sub-menus.
96
+ * @param itemFilter A filter used to represent which item in the menu should be clicked. The
97
+ * first matching menu item will be clicked.
98
+ * @param subItemFilters A list of filters representing the items to click in any subsequent
99
+ * sub-menus. The first item in the sub-menu matching the corresponding filter in
100
+ * `subItemFilters` will be clicked.
101
+ */
102
+ async clickItem (
103
+ itemFilter : Omit < MenuItemHarnessFilters , 'ancestor' > ,
104
+ ...subItemFilters : Omit < MenuItemHarnessFilters , 'ancestor' > [ ] ) : Promise < void > {
105
+ await this . open ( ) ;
106
+ const items = await this . getItems ( itemFilter ) ;
107
+ if ( ! items . length ) {
108
+ throw Error ( `Could not find item matching ${ JSON . stringify ( itemFilter ) } ` ) ;
109
+ }
110
+
111
+ if ( ! subItemFilters . length ) {
112
+ return await items [ 0 ] . click ( ) ;
113
+ }
114
+
115
+ const menu = await items [ 0 ] . getSubmenu ( ) ;
116
+ if ( ! menu ) {
117
+ throw Error ( `Item matching ${ JSON . stringify ( itemFilter ) } does not have a submenu` ) ;
118
+ }
119
+ return menu . clickItem ( ...subItemFilters as [ Omit < MenuItemHarnessFilters , 'ancestor' > ] ) ;
120
+ }
121
+
122
+ /** Gets the menu panel associated with this menu. */
123
+ private async _getMenuPanel ( ) : Promise < TestElement | null > {
124
+ const panelId = await this . _getPanelId ( ) ;
125
+ return panelId ? this . _documentRootLocator . locatorForOptional ( `#${ panelId } ` ) ( ) : null ;
126
+ }
127
+
128
+ /** Gets the id of the menu panel associated with this menu. */
129
+ private async _getPanelId ( ) : Promise < string | null > {
130
+ const panelId = await ( await this . host ( ) ) . getAttribute ( 'aria-controls' ) ;
131
+ return panelId || null ;
80
132
}
81
133
}
82
134
83
135
84
- /** Harness for interacting with a standard mat-menu in tests. */
136
+ /** Harness for interacting with an MDC-based mat-menu-item in tests. */
85
137
export class MatMenuItemHarness extends ComponentHarness {
86
- static hostSelector = '.mat-menu-item' ;
138
+ /** The selector for the host element of a `MatMenuItem` instance. */
139
+ static hostSelector = '.mat-mdc-menu-item' ;
87
140
88
141
/**
89
- * Gets a `HarnessPredicate` that can be used to search for a menu with specific attributes.
90
- * @param options Options for narrowing the search:
91
- * - `selector` finds a menu item whose host element matches the given selector.
92
- * - `label` finds a menu item with specific label text.
142
+ * Gets a `HarnessPredicate` that can be used to search for a `MatMenuItemHarness` that meets
143
+ * certain criteria.
144
+ * @param options Options for filtering which menu item instances are considered a match.
93
145
* @return a `HarnessPredicate` configured with the given options.
94
146
*/
95
147
static with ( options : MenuItemHarnessFilters = { } ) : HarnessPredicate < MatMenuItemHarness > {
@@ -100,24 +152,23 @@ export class MatMenuItemHarness extends ComponentHarness {
100
152
async ( harness , hasSubmenu ) => ( await harness . hasSubmenu ( ) ) === hasSubmenu ) ;
101
153
}
102
154
103
- /** Gets a boolean promise indicating if the menu is disabled. */
155
+ /** Whether the menu is disabled. */
104
156
async isDisabled ( ) : Promise < boolean > {
105
157
const disabled = ( await this . host ( ) ) . getAttribute ( 'disabled' ) ;
106
158
return coerceBooleanProperty ( await disabled ) ;
107
159
}
108
160
161
+ /** Gets the text of the menu item. */
109
162
async getText ( ) : Promise < string > {
110
163
return ( await this . host ( ) ) . text ( ) ;
111
164
}
112
165
113
- /**
114
- * Focuses the menu item and returns a void promise that indicates when the action is complete.
115
- */
166
+ /** Focuses the menu item. */
116
167
async focus ( ) : Promise < void > {
117
168
return ( await this . host ( ) ) . focus ( ) ;
118
169
}
119
170
120
- /** Blurs the menu item and returns a void promise that indicates when the action is complete . */
171
+ /** Blurs the menu item. */
121
172
async blur ( ) : Promise < void > {
122
173
return ( await this . host ( ) ) . blur ( ) ;
123
174
}
@@ -127,15 +178,21 @@ export class MatMenuItemHarness extends ComponentHarness {
127
178
return ( await this . host ( ) ) . isFocused ( ) ;
128
179
}
129
180
181
+ /** Clicks the menu item. */
130
182
async click ( ) : Promise < void > {
131
- throw Error ( 'not implemented' ) ;
183
+ return ( await this . host ( ) ) . click ( ) ;
132
184
}
133
185
186
+ /** Whether this item has a submenu. */
134
187
async hasSubmenu ( ) : Promise < boolean > {
135
- throw Error ( 'not implemented' ) ;
188
+ return ( await this . host ( ) ) . matchesSelector ( MatMenuHarness . hostSelector ) ;
136
189
}
137
190
191
+ /** Gets the submenu associated with this menu item, or null if none. */
138
192
async getSubmenu ( ) : Promise < MatMenuHarness | null > {
139
- throw Error ( 'not implemented' ) ;
193
+ if ( await this . hasSubmenu ( ) ) {
194
+ return new MatMenuHarness ( this . locatorFactory ) ;
195
+ }
196
+ return null ;
140
197
}
141
198
}
0 commit comments