blob: ec340ed9947a73467a5942d821d301210d2b9cf3 [file] [log] [blame]
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +01001// Copyright 2019 The Chromium Authors
Copybara854996b2021-09-07 19:36:02 +00002// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import {assert} from 'chai';
6import {MrDropdown, SCREENREADER_ATTRIBUTE_ERROR} from './mr-dropdown.js';
7import sinon from 'sinon';
8
9let element;
10let randomButton;
11
12describe('mr-dropdown', () => {
13 beforeEach(() => {
14 element = document.createElement('mr-dropdown');
15 document.body.appendChild(element);
16 element.label = 'new dropdown';
17
18 randomButton = document.createElement('button');
19 document.body.appendChild(randomButton);
20 });
21
22 afterEach(() => {
23 document.body.removeChild(element);
24 document.body.removeChild(randomButton);
25 });
26
27 it('initializes', () => {
28 assert.instanceOf(element, MrDropdown);
29 });
30
31 it('warns users about accessibility when no label or text', async () => {
32 element.label = 'ok';
33 sinon.spy(console, 'error');
34
35 await element.updateComplete;
36 sinon.assert.notCalled(console.error);
37
38 element.label = undefined;
39
40 await element.updateComplete;
41 sinon.assert.calledWith(console.error, SCREENREADER_ATTRIBUTE_ERROR);
42
43 console.error.restore();
44 });
45
46 it('toggle changes opened state', () => {
47 element.open();
48 assert.isTrue(element.opened);
49
50 element.close();
51 assert.isFalse(element.opened);
52
53 element.toggle();
54 assert.isTrue(element.opened);
55
56 element.toggle();
57 assert.isFalse(element.opened);
58
59 element.toggle();
60 element.toggle();
61 assert.isFalse(element.opened);
62 });
63
64 it('clicking outside element closes menu', () => {
65 element.open();
66 assert.isTrue(element.opened);
67
68 randomButton.click();
69
70 assert.isFalse(element.opened);
71 });
72
73 it('escape while focusing the anchor closes menu', async () => {
74 element.open();
75 await element.updateComplete;
76
77 assert.isTrue(element.opened);
78
79 const anchor = element.shadowRoot.querySelector('.anchor');
80 anchor.dispatchEvent(new KeyboardEvent('keydown', {key: 'Escape'}));
81
82 assert.isFalse(element.opened);
83 });
84
85 it('other key while focusing the anchor does not close menu', async () => {
86 element.open();
87 await element.updateComplete;
88
89 assert.isTrue(element.opened);
90
91 const anchor = element.shadowRoot.querySelector('.anchor');
92 anchor.dispatchEvent(new KeyboardEvent('keydown', {key: 'Enter'}));
93
94 assert.isTrue(element.opened);
95 });
96
97 it('escape while focusing an item closes the menu', async () => {
98 element.items = [{text: 'An item'}];
99 element.open();
100 await element.updateComplete;
101
102 assert.isTrue(element.opened);
103
104 const item = element.shadowRoot.querySelector('.menu-item');
105 item.dispatchEvent(new KeyboardEvent('keydown', {key: 'Escape'}));
106
107 assert.isFalse(element.opened);
108 });
109
110 it('icon hidden when undefined', async () => {
111 element.items = [
112 {text: 'test'},
113 ];
114
115 await element.updateComplete;
116
117 const icon = element.shadowRoot.querySelector(
118 '.menu-item > .material-icons');
119
120 assert.isTrue(icon.hidden);
121 });
122
123 it('icon shown when defined, even as empty string', async () => {
124 element.items = [
125 {text: 'test', icon: ''},
126 ];
127
128 await element.updateComplete;
129
130 const icon = element.shadowRoot.querySelector(
131 '.menu-item > .material-icons');
132
133 assert.isFalse(icon.hidden);
134 assert.equal(icon.textContent.trim(), '');
135 });
136
137 it('icon shown when set to material icon', async () => {
138 element.items = [
139 {text: 'test', icon: 'check'},
140 ];
141
142 await element.updateComplete;
143
144 const icon = element.shadowRoot.querySelector(
145 '.menu-item > .material-icons');
146
147 assert.isFalse(icon.hidden);
148 assert.equal(icon.textContent.trim(), 'check');
149 });
150
151 it('items with handlers are handled', async () => {
152 const handler1 = sinon.spy();
153 const handler2 = sinon.spy();
154 const handler3 = sinon.spy();
155
156 element.items = [
157 {
158 url: '#',
159 text: 'blah',
160 handler: handler1,
161 },
162 {
163 url: '#',
164 text: 'rutabaga noop',
165 handler: handler2,
166 },
167 {
168 url: '#',
169 text: 'click me please',
170 handler: handler3,
171 },
172 ];
173
174 element.open();
175
176 await element.updateComplete;
177
178 element.clickItem(0);
179
180 assert.isTrue(handler1.calledOnce);
181 assert.isFalse(handler2.called);
182 assert.isFalse(handler3.called);
183
184 element.clickItem(2);
185
186 assert.isTrue(handler1.calledOnce);
187 assert.isFalse(handler2.called);
188 assert.isTrue(handler3.calledOnce);
189 });
190
191 describe('nested dropdown menus', () => {
192 beforeEach(() => {
193 element.items = [
194 {
195 text: 'test',
196 items: [
197 {text: 'item 1'},
198 {text: 'item 2'},
199 {text: 'item 3'},
200 ],
201 },
202 ];
203
204 element.open();
205 });
206
207 it('nested dropdown menu renders', async () => {
208 await element.updateComplete;
209
210 const nestedDropdown = element.shadowRoot.querySelector('mr-dropdown');
211
212 assert.equal(nestedDropdown.text, 'test');
213 assert.deepEqual(nestedDropdown.items, [
214 {text: 'item 1'},
215 {text: 'item 2'},
216 {text: 'item 3'},
217 ]);
218 });
219
220 it('clicking nested item with handler calls handler', async () => {
221 const handler = sinon.stub();
222 element.items = [{
223 text: 'test',
224 items: [
225 {text: 'item 1'},
226 {
227 text: 'item with handler',
228 handler,
229 },
230 ],
231 }];
232
233 await element.updateComplete;
234
235 const nestedDropdown = element.shadowRoot.querySelector('mr-dropdown');
236
237 nestedDropdown.open();
238 await element.updateComplete;
239
240 // Clicking an unrelated nested item shouldn't call the handler.
241 nestedDropdown.clickItem(0);
242 // Nor should clicking the parent item call the handler.
243 element.clickItem(0);
244 sinon.assert.notCalled(handler);
245
246 element.open();
247 nestedDropdown.open();
248 await element.updateComplete;
249
250 nestedDropdown.clickItem(1);
251 sinon.assert.calledOnce(handler);
252 });
253
254 it('clicking nested dropdown menu toggles nested menu', async () => {
255 await element.updateComplete;
256
257 const nestedDropdown = element.shadowRoot.querySelector('mr-dropdown');
258 const nestedAnchor = nestedDropdown.shadowRoot.querySelector('.anchor');
259
260 assert.isTrue(element.opened);
261 assert.isFalse(nestedDropdown.opened);
262
263 nestedAnchor.click();
264 await element.updateComplete;
265
266 assert.isTrue(element.opened);
267 assert.isTrue(nestedDropdown.opened);
268
269 nestedAnchor.click();
270 await element.updateComplete;
271
272 assert.isTrue(element.opened);
273 assert.isFalse(nestedDropdown.opened);
274 });
275 });
276});