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,116 @@ 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 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' > ] ) ;
66
102
}
67
103
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 ;
70
107
}
71
108
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 ( ) ;
74
156
}
75
157
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 ( ) ;
78
161
}
79
162
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 ) ;
82
166
}
83
167
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 ;
86
174
}
87
175
}
0 commit comments