summaryrefslogtreecommitdiffstats
path: root/devtools/client/styleeditor/test/browser_styleeditor_filter.js
blob: c8472b5f40fa81b56780e5d197ed0e9198932585 (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
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
/* Any copyright is dedicated to the Public Domain.
   http://creativecommons.org/publicdomain/zero/1.0/ */

"use strict";

requestLongerTimeout(2);

// Check that the stylesheets list can be filtered

const INITIAL_INLINE_STYLE_SHEETS_COUNT = 100;

const TEST_URI =
  "data:text/html;charset=UTF-8," +
  encodeURIComponent(
    `
      <!DOCTYPE html>
      <html>
        <head>
          <title>Test filter</title>
          <link rel="stylesheet" type="text/css" href="${TEST_BASE_HTTPS}simple.css">
          ${Array.from({ length: INITIAL_INLINE_STYLE_SHEETS_COUNT })
            .map((_, i) => `<style>/* inline ${i} */</style>`)
            .join("\n")}
          <link rel="stylesheet" type="text/css" href="${TEST_BASE_HTTPS}pretty.css">
        </head>
        <body>
        </body>
      </html>
    `
  );

add_task(async function () {
  const { panel, ui } = await openStyleEditorForURL(TEST_URI);
  const { panelWindow } = panel;
  is(
    ui.editors.length,
    INITIAL_INLINE_STYLE_SHEETS_COUNT + 2,
    "correct number of editors"
  );

  const doc = panel.panelWindow.document;

  const filterInput = doc.querySelector(".devtools-filterinput");
  const filterInputClearButton = doc.querySelector(
    ".devtools-searchinput-clear"
  );
  ok(filterInput, "There's a filter input");
  ok(filterInputClearButton, "There's a clear button next to the filter input");
  ok(
    filterInputClearButton.hasAttribute("hidden"),
    "The clear button is hidden by default"
  );

  const setFilterInputValue = value => {
    // The keyboard shortcut focuses the input and select its content, so we should
    // be able to type right-away.
    synthesizeKeyShortcut("CmdOrCtrl+P");
    EventUtils.sendString(value);
  };

  info(
    "Check that the list can be filtered with the stylesheet name, regardless of the casing"
  );
  let onEditorSelected = ui.once("editor-selected");
  setFilterInputValue("PREttY");
  ok(
    !filterInputClearButton.hasAttribute("hidden"),
    "The clear button is visible when the input isn't empty"
  );
  Assert.deepEqual(
    getVisibleStyleSheetsNames(doc),
    ["pretty.css"],
    "Only pretty.css is now displayed"
  );

  await onEditorSelected;
  is(
    ui.selectedEditor,
    ui.editors.at(-1),
    "When the selected stylesheet is filtered out, the first visible one gets selected"
  );
  is(
    filterInput.ownerGlobal.document.activeElement,
    filterInput,
    "Even when a stylesheet was automatically opened, the filter input is still focused"
  );
  ok(!ui.selectedEditor.sourceEditor.hasFocus(), "Editor doesn't have focus.");

  info(
    "Clicking on the clear button should clear the input and unfilter the list"
  );
  EventUtils.synthesizeMouseAtCenter(
    filterInputClearButton,
    {},
    panel.panelWindow
  );
  is(filterInput.value, "", "input was cleared");
  ok(!isListFiltered(doc), "List isn't filtered anymore");
  ok(
    filterInputClearButton.hasAttribute("hidden"),
    "The clear button is hidden after clicking on it"
  );

  info("Check that the list can be filtered with name-less stylesheets");
  onEditorSelected = ui.once("editor-selected");
  setFilterInputValue("#1");
  Assert.deepEqual(
    getVisibleStyleSheetsNames(doc),
    [
      "<inline style sheet #1>",
      "<inline style sheet #10>",
      "<inline style sheet #11>",
      "<inline style sheet #12>",
      "<inline style sheet #13>",
      "<inline style sheet #14>",
      "<inline style sheet #15>",
      "<inline style sheet #16>",
      "<inline style sheet #17>",
      "<inline style sheet #18>",
      "<inline style sheet #19>",
      "<inline style sheet #100>",
    ],
    `List is showing inline stylesheets whose index start with "1"`
  );
  await onEditorSelected;
  is(
    ui.selectedEditor,
    ui.editors[1],
    "The first visible stylesheet got selected"
  );

  info("Check that keyboard navigation still works when the list is filtered");
  // Move focus out of the input
  EventUtils.synthesizeKey("VK_TAB", {}, panelWindow);
  EventUtils.synthesizeKey("VK_DOWN", {}, panelWindow);

  is(
    panelWindow.document.activeElement.childNodes[0].value,
    "<inline style sheet #1>",
    "focus is on first inline stylesheet"
  );

  EventUtils.synthesizeKey("VK_DOWN", {}, panelWindow);
  is(
    panelWindow.document.activeElement.childNodes[0].value,
    "<inline style sheet #10>",
    "focus is on inline stylesheet #10"
  );

  EventUtils.synthesizeKey("VK_DOWN", {}, panelWindow);
  is(
    panelWindow.document.activeElement.childNodes[0].value,
    "<inline style sheet #11>",
    "focus is on inline stylesheet #11"
  );

  info(
    "Check that when stylesheets are added in the page, they respect the filter state"
  );
  let onEditorAdded = ui.once("editor-added");
  // Adding an inline stylesheet that will match the search
  await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
    const document = content.document;
    const style = document.createElement("style");
    style.appendChild(document.createTextNode(`/* inline 101 */`));
    document.head.appendChild(style);
  });
  await onEditorAdded;
  ok(
    getVisibleStyleSheetsNames(doc).includes("<inline style sheet #101>"),
    "New inline stylesheet is visible as it matches the search"
  );

  // Adding a stylesheet that won't match the search
  onEditorAdded = ui.once("editor-added");
  await SpecialPowers.spawn(
    gBrowser.selectedBrowser,
    [TEST_BASE_HTTPS],
    baseUrl => {
      const document = content.document;
      const link = document.createElement("link");
      link.setAttribute("rel", "stylesheet");
      link.setAttribute("type", "text/css");
      link.setAttribute("href", `${baseUrl}doc_short_string.css`);
      document.head.appendChild(link);
    }
  );
  await onEditorAdded;

  ok(
    !getVisibleStyleSheetsNames(doc).includes("doc_short_string.css"),
    "doc_short_string.css is not visible as its name does not match the search"
  );

  info(
    "Check that clicking on the Add New Stylesheet button clears the list and show the stylesheet"
  );
  onEditorAdded = ui.once("editor-added");
  await createNewStyleSheet(ui, panel.panelWindow);
  is(filterInput.value, "", "Filter input was cleared");

  ok(!isListFiltered(doc), "List is not filtered anymore");
  is(ui.selectedEditor, ui.editors.at(-1), "The new stylesheet got selected");

  info(
    "Check that when no stylesheet matches the search, a class is added to the nav"
  );
  setFilterInputValue("sync_with_csp");
  ok(navHasAllFilteredClass(panel), `"splitview-all-filtered" was added`);
  ok(
    filterInput
      .closest(".devtools-searchbox")
      .classList.contains("devtools-searchbox-no-match"),
    `The searchbox has the "devtools-searchbox-no-match" class`
  );

  info(
    "Check that adding a stylesheet matching the search remove the splitview-all-filtered class"
  );
  onEditorAdded = ui.once("editor-added");
  await SpecialPowers.spawn(
    gBrowser.selectedBrowser,
    [TEST_BASE_HTTPS],
    baseUrl => {
      const document = content.document;
      const link = document.createElement("link");
      link.setAttribute("rel", "stylesheet");
      link.setAttribute("type", "text/css");
      link.setAttribute("href", `${baseUrl}sync_with_csp.css`);
      document.head.appendChild(link);
    }
  );
  await onEditorAdded;
  ok(!navHasAllFilteredClass(panel), `"splitview-all-filtered" was removed`);
  ok(
    !filterInput
      .closest(".devtools-searchbox")
      .classList.contains("devtools-searchbox-no-match"),
    `The searchbox does not have the "devtools-searchbox-no-match" class anymore`
  );

  info(
    "Check that reloading the page when the filter don't match anything won't select anything"
  );
  setFilterInputValue("XXXDONTMATCHANYTHING");
  ok(navHasAllFilteredClass(panel), `"splitview-all-filtered" was added`);
  await reloadPageAndWaitForStyleSheets(
    ui,
    INITIAL_INLINE_STYLE_SHEETS_COUNT + 2
  );
  ok(
    navHasAllFilteredClass(panel),
    `"splitview-all-filtered" is still applied`
  );
  is(getVisibleStyleSheets(doc).length, 0, "No stylesheets are displayed");
  is(ui.selectedEditor, null, "No editor was selected");

  info(
    "Check that reloading the page when the filter was matching elements keep the same state"
  );
  onEditorSelected = ui.once("editor-selected");
  setFilterInputValue("pretty");
  await onEditorSelected;
  Assert.deepEqual(
    getVisibleStyleSheetsNames(doc),
    ["pretty.css"],
    "Only pretty.css is now displayed"
  );

  onEditorSelected = ui.once("editor-selected");
  await reloadPageAndWaitForStyleSheets(
    ui,
    INITIAL_INLINE_STYLE_SHEETS_COUNT + 2
  );
  await onEditorSelected;
  Assert.deepEqual(
    getVisibleStyleSheetsNames(doc),
    ["pretty.css"],
    "pretty.css is still the only stylesheet displayed"
  );
  is(
    ui.selectedEditor.friendlyName,
    "pretty.css",
    "pretty.css editor is active"
  );

  info("Check that clearing the input does show all the stylesheets");
  EventUtils.synthesizeMouseAtCenter(
    filterInputClearButton,
    {},
    panel.panelWindow
  );
  ok(!isListFiltered(doc), "List is not filtered anymore");
  is(
    ui.selectedEditor.friendlyName,
    "pretty.css",
    "pretty.css editor is still active"
  );
});

/**
 * @param {StyleEditorPanel} panel
 * @returns Boolean
 */
function navHasAllFilteredClass(panel) {
  return panel.panelWindow.document
    .querySelector(".splitview-nav")
    .classList.contains("splitview-all-filtered");
}

/**
 * Returns true if there's at least one stylesheet filtered out
 *
 * @param {Document} doc: StyleEditor document
 * @returns Boolean
 */
function isListFiltered(doc) {
  return !!doc.querySelectorAll("ol > li.splitview-filtered").length;
}

/**
 * Returns the list of stylesheet list elements.
 *
 * @param {Document} doc: StyleEditor document
 * @returns Array<Element>
 */
function getVisibleStyleSheets(doc) {
  return Array.from(
    doc.querySelectorAll(
      "ol > li:not(.splitview-filtered) .stylesheet-name label"
    )
  );
}

/**
 * Returns the list of stylesheet names visible in the style editor list.
 *
 * @param {Document} doc: StyleEditor document
 * @returns Array<String>
 */
function getVisibleStyleSheetsNames(doc) {
  return getVisibleStyleSheets(doc).map(label => label.value);
}