summaryrefslogtreecommitdiffstats
path: root/browser/components/aboutlogins/tests/chrome/test_menu_button.html
blob: 2beede09f1e5bac8f2b2816a70f8bcffe3a7b8db (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
<!DOCTYPE HTML>
<html>
<!--
Test the menu-button component
-->
<head>
  <meta charset="utf-8">
  <title>Test the menu-button component</title>
  <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
  <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
  <script type="module" src="chrome://browser/content/aboutlogins/components/menu-button.mjs"></script>
  <script src="aboutlogins_common.js"></script>

  <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
</head>
<body>
  <p id="display">
  </p>
<div id="content" style="display: none">
  <iframe id="templateFrame" src="chrome://browser/content/aboutlogins/aboutLogins.html"
          sandbox="allow-same-origin"></iframe>
</div>
<pre id="test">
</pre>
<script>
/** Test the menu-button component **/

let gMenuButton;
add_setup(async () => {
  let templateFrame = document.getElementById("templateFrame");
  let displayEl = document.getElementById("display");
  await importDependencies(templateFrame, displayEl);

  gMenuButton = document.createElement("menu-button");
  displayEl.appendChild(gMenuButton);
  gMenuButton.style.marginInlineStart = "100px";

  isnot(document.activeElement, gMenuButton, "menu-button should not be focused by default");
  while (document.activeElement != gMenuButton) {
    sendKey("TAB");
    await new Promise(resolve => requestAnimationFrame(resolve));
  }
});

add_task(async function test_menu_click_button () {
  let menu = gMenuButton.shadowRoot.querySelector(".menu");
  let menuButton = gMenuButton.shadowRoot.querySelector(".menu-button");
  ok(menu.hidden, "menu should be hidden before being clicked");
  await synthesizeMouseAtCenter(menuButton, {});
  await new Promise(resolve => requestAnimationFrame(resolve));
  ok(!menu.hidden, "menu should be visible after clicked");

  let menuListSeparators = gMenuButton.shadowRoot.querySelectorAll(".menuitem-separator");
  await synthesizeMouseAtCenter(menuListSeparators[0], {});
  await new Promise(resolve => requestAnimationFrame(resolve));
  ok(!menu.hidden, "menu should still be visible after menu separator has been clicked");

  let menuListButtons = gMenuButton.shadowRoot.querySelectorAll(".menuitem-button");
  await synthesizeMouseAtCenter(menuListButtons[0], {});
  await new Promise(resolve => requestAnimationFrame(resolve));
  ok(menu.hidden, "menu should be hidden after a button has been clicked");
});

add_task(async function test_menu_click_outside () {
  let menu = gMenuButton.shadowRoot.querySelector(".menu");
  let menuButton = gMenuButton.shadowRoot.querySelector(".menu-button");
  ok(menu.hidden, "menu should be hidden before being clicked");
  await synthesizeMouseAtCenter(menuButton, {});
  await new Promise(resolve => requestAnimationFrame(resolve));
  ok(!menu.hidden, "menu should be visible after clicked");

  let menuListSeparators = gMenuButton.shadowRoot.querySelectorAll(".menuitem-separator");
  await synthesizeMouseAtCenter(menuListSeparators[0], {});
  await new Promise(resolve => requestAnimationFrame(resolve));
  ok(!menu.hidden, "menu should still be visible after menu separator has been clicked");

  let outsideEl = document.getElementById("test");
  await synthesizeMouseAtCenter(outsideEl, {});
  await new Promise(resolve => requestAnimationFrame(resolve));
  ok(menu.hidden, "menu should be hidden after a click outside of the menu has been clicked");

  for (let key of ["KEY_ArrowDown", "KEY_ArrowUp"]) {
    synthesizeKey(key);
    await new Promise(resolve => requestAnimationFrame(resolve));
    ok(menu.hidden, `menu should still be hidden when ${key} is entered`);
  }
});

add_task(async function test_menu_esc_after_click_disabled_item () {
  let menu = gMenuButton.shadowRoot.querySelector(".menu");
  let menuButton = gMenuButton.shadowRoot.querySelector(".menu-button");
  ok(menu.hidden, "menu should be hidden before being clicked");
  await synthesizeMouseAtCenter(menuButton, {});
  await new Promise(resolve => requestAnimationFrame(resolve));
  ok(!menu.hidden, "menu should be visible after clicked");

  let menuListSeparators = gMenuButton.shadowRoot.querySelectorAll(".menuitem-separator");
  await synthesizeMouseAtCenter(menuListSeparators[0], {});
  await new Promise(resolve => requestAnimationFrame(resolve));
  ok(!menu.hidden, "menu should still be visible after menu separator has been clicked");

  sendKey("ESCAPE");
  await new Promise(resolve => requestAnimationFrame(resolve));
  ok(menu.hidden, "menu should be hidden after pressing 'escape'");
});

add_task(async function test_menu_open_close() {
  is(document.activeElement, gMenuButton, "menu-button should be focused to start the test");

  let menu = gMenuButton.shadowRoot.querySelector(".menu");
  is(true, menu.hidden, "menu should be hidden before pressing 'space'");
  sendKey("SPACE");
  await new Promise(resolve => requestAnimationFrame(resolve));
  ok(!menu.hidden, "menu should be visible after pressing 'space'");

  sendKey("ESCAPE");
  await new Promise(resolve => requestAnimationFrame(resolve));
  ok(menu.hidden, "menu should be hidden after pressing 'escape'");
  is(gMenuButton.shadowRoot.activeElement, gMenuButton.shadowRoot.querySelector(".menu-button"),
    "the .menu-button should be focused after closing the menu via keyboard");

  sendKey("RETURN");
  let firstVisibleItem = gMenuButton.shadowRoot.querySelector(".menuitem-button:not([hidden])");
  await SimpleTest.promiseWaitForCondition(() => firstVisibleItem.matches(":focus"),
    "waiting for firstVisibleItem to get focus");

  ok(!menu.hidden, "menu should be visible after pressing 'return'");
  ok(firstVisibleItem.matches(":focus"), "firstVisibleItem should be focused after opening popup");

  synthesizeKey("VK_TAB", { shiftKey: true });
  await SimpleTest.promiseWaitForCondition(() => !firstVisibleItem.matches(":focus"),
    "waiting for firstVisibleItem to lose focus");
  ok(!firstVisibleItem.matches(":focus"), "firstVisibleItem should lose focus after tabbing away from it");
  sendKey("TAB");
  await SimpleTest.promiseWaitForCondition(() => firstVisibleItem.matches(":focus"),
    "waiting for firstVisibleItem to get focus again");
  ok(firstVisibleItem.matches(":focus"), "firstVisibleItem should be focused after tabbing to it again");
  if (SpecialPowers.getBoolPref("signon.management.page.fileImport.enabled")) {
    sendKey("TAB"); // Import from file
  }
  sendKey("TAB"); // Export
  sendKey("TAB"); // Remove All Logins

  if (navigator.platform == "Win32" || navigator.platform == "MacIntel") {
    // The Import menuitem is only visible on Windows/macOS, where we will need another Tab
    // press to get to the Preferences item.
    let preferencesItem = gMenuButton.shadowRoot.querySelector(".menuitem-preferences");
    sendKey("DOWN");
    await SimpleTest.promiseWaitForCondition(() => preferencesItem.matches(":focus"),
      "waiting for preferencesItem to gain focus");
    ok(preferencesItem.matches(":focus"), `.menuitem-preferences should be now be focused (DOWN)`);
    sendKey("UP");
    await SimpleTest.promiseWaitForCondition(() => !preferencesItem.matches(":focus"),
      `waiting for preferencesItem to lose focus (UP)`);
    ok(!preferencesItem.matches(":focus"), `.menuitem-preferences should lose focus after pressing up`);

    sendKey("TAB");
    await SimpleTest.promiseWaitForCondition(() => preferencesItem.matches(":focus"),
      "waiting for preferencesItem to get focus");
    ok(preferencesItem.matches(":focus"), ".menuitem-preferences should be focused after tabbing to it");
  }

  let openPreferencesEvent = null;
  ok(!menu.hidden, "menu should be visible before pressing 'space' on .menuitem-preferences");
  window.addEventListener(
    "AboutLoginsOpenPreferences",
    event => openPreferencesEvent = event,
    {once: true}
  );
  sendKey("SPACE");
  ok(openPreferencesEvent, "AboutLoginsOpenPreferences event should be dispatched after pressing 'space' on .menuitem-preferences");
  ok(menu.hidden, "menu should be hidden after pressing 'space' on .menuitem-preferences");

  // Clean up task
  sendKey("TAB");
  synthesizeKey("VK_TAB", { shiftKey: true });
});

add_task(async function test_menu_keyboard_cycling() {
  function waitForElementFocus(selector) {
    return SimpleTest.promiseWaitForCondition(
      () => gMenuButton.shadowRoot.querySelector(selector).matches(":focus"),
      `waiting for ${selector} to be focused`
    );
  }

  function getFocusedMenuItem() {
    return gMenuButton.shadowRoot.querySelector(".menuitem-button:focus");
  }

  let allItems = [
    "menuitem-export",
    "menuitem-remove-all-logins",
    "menuitem-preferences",
    "menuitem-help",
  ];
  if (SpecialPowers.getBoolPref("signon.management.page.fileImport.enabled")) {
    allItems = ["menuitem-import-file", ...allItems];
  }
  if (navigator.platform == "Win32" || navigator.platform == "MacIntel") {
    allItems = ["menuitem-import-browser", ...allItems];
  }

  let menu = gMenuButton.shadowRoot.querySelector(".menu");

  is(document.activeElement, gMenuButton, "menu-button should be focused to start the test");
  is(true, menu.hidden, "menu should be hidden before pressing 'space'");

  sendKey("RETURN");

  await SimpleTest.promiseWaitForCondition(() => !menu.hidden, "waiting for menu to show");

  ok(!menu.hidden, "menu should be visible after pressing 'enter'");

  for (let item of allItems) {
    await waitForElementFocus("." + item);
    ok(
      getFocusedMenuItem().classList.contains(item),
      `.${item} should be selected after key is pressed`
    );
    sendKey("DOWN");
  }


  await waitForElementFocus("." + allItems[0]);
  ok(
    getFocusedMenuItem().classList.contains(allItems[0]),
    "Focused item should not change if left arrow is pressed"
  )
  sendKey("LEFT");

  await waitForElementFocus("." + allItems[0]);
  ok(
    getFocusedMenuItem().classList.contains(allItems[0]),
    "Focused item should not change if right arrow is pressed"
  )
  sendKey("RIGHT");

  await waitForElementFocus("." + allItems[0]);
  ok(
    getFocusedMenuItem().classList.contains(allItems[0]),
    "Last item should cycle back to first item"
  );

  sendKey("UP");

  let reversedItems = allItems.reverse();
  for (let item of reversedItems) {
    await waitForElementFocus("." + item);
    ok(
      getFocusedMenuItem().classList.contains(item),
      `.${item} should be selected after up key is pressed`
    );
    sendKey("UP");
  }
});
</script>

</body>
</html>