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 { MenuHarnessFilters } from './menu-harness-filters' ;
12
- import { MatMenuItemHarness } from './menu-item-harness' ;
11
+ import { MenuHarnessFilters , MenuItemHarnessFilters } from './menu-harness-filters' ;
13
12
14
13
/**
15
14
* Harness for interacting with a standard mat-menu in tests.
@@ -18,6 +17,8 @@ import {MatMenuItemHarness} from './menu-item-harness';
18
17
export class MatMenuHarness extends ComponentHarness {
19
18
static hostSelector = '.mat-menu-trigger' ;
20
19
20
+ private _documentRootLocator = this . documentRootLocatorFactory ( ) ;
21
+
21
22
// TODO: potentially extend MatButtonHarness
22
23
23
24
/**
@@ -29,7 +30,7 @@ export class MatMenuHarness extends ComponentHarness {
29
30
*/
30
31
static with ( options : MenuHarnessFilters = { } ) : HarnessPredicate < MatMenuHarness > {
31
32
return new HarnessPredicate ( MatMenuHarness , options )
32
- . addOption ( 'text ' , options . triggerText ,
33
+ . addOption ( 'triggerText ' , options . triggerText ,
33
34
( harness , text ) => HarnessPredicate . stringMatches ( harness . getTriggerText ( ) , text ) ) ;
34
35
}
35
36
@@ -39,8 +40,9 @@ export class MatMenuHarness extends ComponentHarness {
39
40
return coerceBooleanProperty ( await disabled ) ;
40
41
}
41
42
43
+ /** Whether the menu is open. */
42
44
async isOpen ( ) : Promise < boolean > {
43
- throw Error ( 'not implemented' ) ;
45
+ return ! ! ( await this . _getMenuPanel ( ) ) ;
44
46
}
45
47
46
48
async getTriggerText ( ) : Promise < string > {
@@ -58,30 +60,117 @@ export class MatMenuHarness extends ComponentHarness {
58
60
}
59
61
60
62
async open ( ) : Promise < void > {
61
- throw Error ( 'not implemented' ) ;
63
+ if ( ! await this . isOpen ( ) ) {
64
+ return ( await this . host ( ) ) . click ( ) ;
65
+ }
62
66
}
63
67
64
68
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 selectItem ( ...itemFilters : MenuItemHarnessFilters [ ] ) :
86
+ Promise < void > {
87
+ itemFilters = itemFilters . length ? itemFilters : [ { } ] ;
88
+ await this . open ( ) ;
89
+ const items = await this . getItems ( itemFilters [ 0 ] ) ;
90
+ if ( ! items . length ) {
91
+ throw Error ( `Could not find item matching ${ JSON . stringify ( itemFilters [ 0 ] ) } ` ) ;
92
+ }
93
+
94
+ if ( itemFilters . length === 1 ) {
95
+ return await items [ 0 ] . click ( ) ;
96
+ }
97
+
98
+ const menu = await items [ 0 ] . getSubmenu ( ) ;
99
+ if ( ! menu ) {
100
+ throw Error ( `Item matching ${ JSON . stringify ( itemFilters [ 0 ] ) } does not have a submenu` ) ;
101
+ }
102
+ return menu . selectItem ( ...itemFilters . slice ( 1 ) ) ;
103
+ }
104
+
105
+ private async _getMenuPanel ( ) : Promise < TestElement | null > {
106
+ const panelId = await this . _getPanelId ( ) ;
107
+ return panelId ? this . _documentRootLocator . locatorForOptional ( `#${ panelId } ` ) ( ) : null ;
108
+ }
109
+
110
+ private async _getPanelId ( ) : Promise < string | null > {
111
+ const panelId = await ( await this . host ( ) ) . getAttribute ( 'aria-controls' ) ;
112
+ return panelId || null ;
113
+ }
114
+ }
115
+
116
+
117
+ /**
118
+ * Harness for interacting with a standard mat-menu in tests.
119
+ * @dynamic
120
+ */
121
+ export class MatMenuItemHarness extends ComponentHarness {
122
+ static hostSelector = '.mat-menu-item' ;
123
+
124
+ /**
125
+ * Gets a `HarnessPredicate` that can be used to search for a menu with specific attributes.
126
+ * @param options Options for narrowing the search:
127
+ * - `selector` finds a menu item whose host element matches the given selector.
128
+ * - `label` finds a menu item with specific label text.
129
+ * @return a `HarnessPredicate` configured with the given options.
130
+ */
131
+ static with ( options : MenuItemHarnessFilters = { } ) : HarnessPredicate < MatMenuItemHarness > {
132
+ return new HarnessPredicate ( MatMenuItemHarness , options )
133
+ . addOption ( 'text' , options . text ,
134
+ ( harness , text ) => HarnessPredicate . stringMatches ( harness . getText ( ) , text ) )
135
+ . addOption ( 'hasSubmenu' , options . hasSubmenu ,
136
+ async ( harness , hasSubmenu ) => ( await harness . hasSubmenu ( ) ) === hasSubmenu ) ;
66
137
}
67
138
68
- async getItems ( ) : Promise < MatMenuItemHarness [ ] > {
69
- throw Error ( 'not implemented' ) ;
139
+ /** Gets a boolean promise indicating if the menu is disabled. */
140
+ async isDisabled ( ) : Promise < boolean > {
141
+ const disabled = ( await this . host ( ) ) . getAttribute ( 'disabled' ) ;
142
+ return coerceBooleanProperty ( await disabled ) ;
70
143
}
71
144
72
- async getItemLabels ( ) : Promise < string [ ] > {
73
- throw Error ( 'not implemented' ) ;
145
+ async getText ( ) : Promise < string > {
146
+ return ( await this . host ( ) ) . text ( ) ;
147
+ }
148
+
149
+ /** Focuses the menu and returns a void promise that indicates when the action is complete. */
150
+ async focus ( ) : Promise < void > {
151
+ return ( await this . host ( ) ) . focus ( ) ;
152
+ }
153
+
154
+ /** Blurs the menu and returns a void promise that indicates when the action is complete. */
155
+ async blur ( ) : Promise < void > {
156
+ return ( await this . host ( ) ) . blur ( ) ;
74
157
}
75
158
76
- async getItemByLabel ( ) : Promise < MatMenuItemHarness > {
77
- throw Error ( 'not implemented' ) ;
159
+ /** Clicks the menu item. */
160
+ async click ( ) : Promise < void > {
161
+ return ( await this . host ( ) ) . click ( ) ;
78
162
}
79
163
80
- async getItemByIndex ( ) : Promise < MatMenuItemHarness > {
81
- throw Error ( 'not implemented' ) ;
164
+ /** Whether this item has a submenu. */
165
+ async hasSubmenu ( ) : Promise < boolean > {
166
+ return ( await this . host ( ) ) . matchesSelector ( MatMenuHarness . hostSelector ) ;
82
167
}
83
168
84
- async getFocusedItem ( ) : Promise < MatMenuItemHarness > {
85
- throw Error ( 'not implemented' ) ;
169
+ /** Gets the submenu associated with this menu item, or null if none. */
170
+ async getSubmenu ( ) : Promise < MatMenuHarness | null > {
171
+ if ( await this . hasSubmenu ( ) ) {
172
+ return new MatMenuHarness ( this . locatorFactory ) ;
173
+ }
174
+ return null ;
86
175
}
87
176
}
0 commit comments