summaryrefslogtreecommitdiffstats
path: root/toolkit/content/tests/chrome/test_named_deck.html
blob: 4f1ba2272f0d2398d989f039f6e2537a54baea0a (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
<!DOCTYPE HTML>
<!-- Any copyright is dedicated to the Public Domain.
   - http://creativecommons.org/publicdomain/zero/1.0/ -->
<html>
<head>
  <meta charset="utf-8">
  <title><!-- Shadow Parts issue with xul/xbl domparser --></title>
  <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
  <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
  <link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
  <script>
const { BrowserTestUtils } = ChromeUtils.import("resource://testing-common/BrowserTestUtils.jsm");
const DEFAULT_SECTION_NAMES = ["one", "two", "three"];

function makeButton({ name, deckId }) {
  let button = document.createElement("button", { is: "named-deck-button" });
  button.setAttribute("name", name);
  button.deckId = deckId;
  button.textContent = name.toUpperCase();
  return button;
}

function makeSection({ name }) {
  let view = document.createElement("section");
  view.setAttribute("name", name);
  view.textContent = name + name;
  return view;
}

function addSection({ name, deck, buttons }) {
  let button = makeButton({ name, deckId: deck.id });
  buttons.appendChild(button);
  let view = makeSection({ name });
  deck.appendChild(view);
  return { button, view };
}

async function runTests({ deck, buttons }) {
  const selectedSlot = deck.shadowRoot.querySelector('slot[name="selected"]');
  const getButtonByName = name => buttons.querySelector(`[name="${name}"]`);

  function checkState(name, count, empty = false) {
    // Check that the right view is selected.
    is(deck.selectedViewName, name, "The right view is selected");

    // Verify there's one element in the slot.
    let slottedEls = selectedSlot.assignedElements();
    if (empty) {
      is(slottedEls.length, 0, "The deck is empty");
    } else {
      is(slottedEls.length, 1, "There's one visible view");
      is(
        slottedEls[0].getAttribute("name"),
        name,
        "The correct view is in the slot"
      );
    }

    // Check that the hidden properties are set.
    let sections = deck.querySelectorAll("section");
    is(sections.length, count, "There are the right number of sections");
    for (let section of sections) {
      let sectionName = section.getAttribute("name");
      if (sectionName == name) {
        is(section.slot, "selected", `${sectionName} is visible`);
      } else {
        is(section.slot, "", `${sectionName} is hidden`);
      }
    }

    // Check the right button is selected.
    is(buttons.children.length, count, "There are the right number of buttons");
    for (let button of buttons.children) {
      let buttonName = button.getAttribute("name");
      let selected = buttonName == name;
      is(
        button.hasAttribute("selected"),
        selected,
        `${buttonName} is ${selected ? "selected" : "not selected"}`
      );
    }
  }

  // Check that the first view is selected by default.
  checkState("one", 3);

  // Switch to the third view.
  info("Switch to section three");
  getButtonByName("three").click();
  checkState("three", 3);

  // Add a new section, nothing changes.
  info("Add section last");
  let last = addSection({ name: "last", deck, buttons });
  checkState("three", 4);

  // We can switch to the new section.
  last.button.click();
  info("Switch to section last");
  checkState("last", 4);

  info("Switch view with selectedViewName");
  let shown = BrowserTestUtils.waitForEvent(deck, "view-changed");
  deck.selectedViewName = "two";
  await shown;
  checkState("two", 4);

  info("Switch back to the last view to test removing selected view");
  shown = BrowserTestUtils.waitForEvent(deck, "view-changed");
  deck.setAttribute("selected-view", "last");
  await shown;
  checkState("last", 4);

  // Removing the selected section leaves the selected slot empty.
  info("Remove section last");
  last.button.remove();
  last.view.remove();

  info("Should not have any selected views");
  checkState("last", 3, true);

  // Setting a missing view will give a "view-changed" event.
  info("Set view to a missing name");
  let hidden = BrowserTestUtils.waitForEvent(deck, "view-changed");
  deck.selectedViewName = "missing";
  await hidden;
  checkState("missing", 3, true);

  // Adding the view won't trigger "view-changed", but the view will slotted.
  info("Add the missing view, it should be shown");
  shown = BrowserTestUtils.waitForEvent(selectedSlot, "slotchange");
  let viewChangedEvent = false;
  let viewChangedFn = () => {
    viewChangedEvent = true;
  };
  deck.addEventListener("view-changed", viewChangedFn);
  addSection({ name: "missing", deck, buttons });
  await shown;
  deck.removeEventListener("view-changed", viewChangedFn);
  ok(!viewChangedEvent, "The view-changed event didn't fire");
  checkState("missing", 4);
}

async function setup({ beAsync, first, deckId }) {
  // Make the deck and buttons.
  const deck = document.createElement("named-deck");
  deck.id = deckId;
  for (let name of DEFAULT_SECTION_NAMES) {
    deck.appendChild(makeSection({ name }));
  }
  const buttons = document.createElement("button-group");
  for (let name of DEFAULT_SECTION_NAMES) {
    buttons.appendChild(makeButton({ name, deckId }));
  }

  let ordered;
  if (first == "deck") {
    ordered = [deck, buttons];
  } else if (first == "buttons") {
    ordered = [buttons, deck];
  } else {
    throw new Error("Invalid order");
  }

  // Insert them in the specified order, possibly async.
  document.body.appendChild(ordered.shift());
  if (beAsync) {
    await new Promise(resolve => requestAnimationFrame(resolve));
  }
  document.body.appendChild(ordered.shift());

  return { deck, buttons };
}

add_task(async function testNamedDeckAndButtons() {
  // Check adding the deck first.
  dump("Running deck first tests synchronously");
  await runTests(await setup({ beAsync: false, first: "deck", deckId: "deck-sync" }));
  dump("Running deck first tests asynchronously");
  await runTests(await setup({ beAsync: true, first: "deck", deckId: "deck-async" }));

  // Check adding the buttons first.
  dump("Running buttons first tests synchronously");
  await runTests(await setup({ beAsync: false, first: "buttons", deckId: "buttons-sync" }));
  dump("Running buttons first tests asynchronously");
  await runTests(await setup({ beAsync: true, first: "buttons", deckId: "buttons-async" }));
});

add_task(async function testFocusAndClickMixing() {
  const waitForAnimationFrame = () =>
    new Promise(r => requestAnimationFrame(r));
  const sendTab = (e = {}) => {
    synthesizeKey("VK_TAB", e);
    return waitForAnimationFrame();
  };

  const firstButton = document.createElement("button");
  document.body.append(firstButton);

  const { deck, buttons: buttonGroup } = await setup({
    beAsync: false,
    first: "buttons",
    deckId: "focus-click-mixing",
  });
  const buttons = buttonGroup.children;
  firstButton.focus();
  const secondButton = document.createElement("button");
  document.body.append(secondButton);

  await sendTab();
  is(document.activeElement, buttons[0], "first deck button is focused");
  is(deck.selectedViewName, "one", "first view is shown");

  await sendTab();
  is(document.activeElement, secondButton, "focus moves out of group");

  await sendTab({ shiftKey: true });
  is(document.activeElement, buttons[0], "focus moves back to first button");

  // Click on another tab button, this should make it the focusable button.
  synthesizeMouseAtCenter(buttons[1], {});
  await waitForAnimationFrame();

  is(deck.selectedViewName, "two", "second view is shown");

  if (document.activeElement != buttons[1]) {
    // On Mac the button isn't focused on click, but it is on Windows/Linux.
    await sendTab();
  }
  is(document.activeElement, buttons[1], "second deck button is focusable");

  await sendTab();
  is(document.activeElement, secondButton, "focus moved to second plain button");

  await sendTab({ shiftKey: true });
  is(document.activeElement, buttons[1], "second deck button is focusable");

  await sendTab({ shiftKey: true });
  is(
    document.activeElement,
    firstButton,
    "next shift-tab moves out of button group"
  );
});
  </script>
</head>
<body>
</body>
</html>