tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

browser_rules_class_panel_autocomplete.js (9613B)


      1 /* Any copyright is dedicated to the Public Domain.
      2 http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict";
      5 
      6 // Test that the autocomplete for the class panel input behaves as expected. The test also
      7 // checks that we're using the cache to retrieve the data when we can do so, and that the
      8 // cache gets cleared, and we're getting data from the server, when there's mutation on
      9 // the page.
     10 
     11 const TEST_URI = `${URL_ROOT}doc_class_panel_autocomplete.html`;
     12 
     13 add_task(async function () {
     14  await addTab(TEST_URI);
     15  const { inspector, view } = await openRuleView();
     16  const { addEl: textInput } = view.classListPreviewer;
     17  await selectNode("#auto-div-id-3", inspector);
     18 
     19  info("Open the class panel");
     20  view.showClassPanel();
     21 
     22  textInput.focus();
     23 
     24  info("Type a letter and check that the popup has the expected items");
     25  const allClasses = [
     26    "auto-body-class-1",
     27    "auto-body-class-2",
     28    "auto-bold",
     29    "auto-cssom-primary-color",
     30    "auto-div-class-1",
     31    "auto-div-class-2",
     32    "auto-html-class-1",
     33    "auto-html-class-2",
     34    "auto-inline-class-1",
     35    "auto-inline-class-2",
     36    "auto-inline-class-3",
     37    "auto-inline-class-4",
     38    "auto-inline-class-5",
     39    "auto-inline-nested-class-1",
     40    "auto-inline-nested-class-2",
     41    "auto-inline-nested-class-3",
     42    "auto-inline-nested-class-4",
     43    "auto-inline-nested-class-5",
     44    "auto-inline-nested-class-6",
     45    "auto-stylesheet-class-1",
     46    "auto-stylesheet-class-2",
     47    "auto-stylesheet-class-3",
     48    "auto-stylesheet-class-4",
     49    "auto-stylesheet-class-5",
     50    "auto-stylesheet-nested-class-1",
     51    "auto-stylesheet-nested-class-2",
     52    "auto-stylesheet-nested-class-3",
     53    "auto-stylesheet-nested-class-4",
     54    "auto-stylesheet-nested-class-5",
     55    "auto-stylesheet-nested-class-6",
     56  ];
     57 
     58  const { autocompletePopup } = view.classListPreviewer;
     59  let onPopupOpened = autocompletePopup.once("popup-opened");
     60  EventUtils.synthesizeKey("a", {}, view.styleWindow);
     61  await waitForClassApplied("auto-body-class-1", "#auto-div-id-3");
     62  await onPopupOpened;
     63  await checkAutocompleteItems(
     64    autocompletePopup,
     65    allClasses,
     66    "The autocomplete popup has all the classes used in the DOM and in stylesheets"
     67  );
     68 
     69  info(
     70    "Test that typing more letters filters the autocomplete popup and uses the cache mechanism"
     71  );
     72  EventUtils.sendString("uto-b", view.styleWindow);
     73  await waitForClassApplied("auto-body-class-1", "#auto-div-id-3");
     74 
     75  await checkAutocompleteItems(
     76    autocompletePopup,
     77    allClasses.filter(cls => cls.startsWith("auto-b")),
     78    "The autocomplete popup was filtered with the content of the input"
     79  );
     80  ok(true, "The results were retrieved from the cache mechanism");
     81 
     82  info("Test that autocomplete shows up-to-date results");
     83  // Modify the content page and assert that the new class is displayed in the
     84  // autocomplete if the user types a new letter.
     85  const onNewMutation = inspector.inspectorFront.walker.once("new-mutations");
     86  await ContentTask.spawn(gBrowser.selectedBrowser, null, async function () {
     87    content.document.body.classList.add("auto-body-added-by-script");
     88  });
     89  await onNewMutation;
     90  await waitForClassApplied("auto-body-added-by-script", "body");
     91 
     92  // close & reopen the autocomplete so it picks up the added to another element while autocomplete was opened
     93  let onPopupClosed = autocompletePopup.once("popup-closed");
     94  EventUtils.synthesizeKey("KEY_Escape", {}, view.styleWindow);
     95  await onPopupClosed;
     96 
     97  // input is now auto-body
     98  onPopupOpened = autocompletePopup.once("popup-opened");
     99  EventUtils.sendString("ody", view.styleWindow);
    100  await onPopupOpened;
    101  await checkAutocompleteItems(
    102    autocompletePopup,
    103    [
    104      ...allClasses.filter(cls => cls.startsWith("auto-body")),
    105      "auto-body-added-by-script",
    106    ].sort(),
    107    "The autocomplete popup was filtered with the content of the input"
    108  );
    109 
    110  info(
    111    "Test that typing a letter that won't match any of the item closes the popup"
    112  );
    113  // input is now auto-bodyy
    114  onPopupClosed = autocompletePopup.once("popup-closed");
    115  EventUtils.synthesizeKey("y", {}, view.styleWindow);
    116  await waitForClassApplied("auto-bodyy", "#auto-div-id-3");
    117  await onPopupClosed;
    118  ok(true, "The popup was closed as expected");
    119  await checkAutocompleteItems(autocompletePopup, [], "The popup was cleared");
    120 
    121  info("Clear the input and try to autocomplete again");
    122  textInput.select();
    123  EventUtils.synthesizeKey("KEY_Backspace", {}, view.styleWindow);
    124  // Wait a bit so the debounced function can be executed
    125  await wait(200);
    126 
    127  onPopupOpened = autocompletePopup.once("popup-opened");
    128  EventUtils.synthesizeKey("a", {}, view.styleWindow);
    129  await onPopupOpened;
    130 
    131  await checkAutocompleteItems(
    132    autocompletePopup,
    133    [...allClasses, "auto-body-added-by-script"].sort(),
    134    "The autocomplete popup was updated with the new class added to the DOM"
    135  );
    136 
    137  info("Test keyboard shortcut when the popup is displayed");
    138  // Escape to hide
    139  onPopupClosed = autocompletePopup.once("popup-closed");
    140  EventUtils.synthesizeKey("KEY_Escape", {}, view.styleWindow);
    141  await onPopupClosed;
    142  ok(true, "The popup was closed when hitting escape");
    143 
    144  // Ctrl + space to show again
    145  onPopupOpened = autocompletePopup.once("popup-opened");
    146  EventUtils.synthesizeKey(" ", { ctrlKey: true }, view.styleWindow);
    147  await onPopupOpened;
    148  ok(true, "Popup was opened again with Ctrl+Space");
    149  await checkAutocompleteItems(
    150    autocompletePopup,
    151    [...allClasses, "auto-body-added-by-script"].sort()
    152  );
    153 
    154  // Arrow left to hide
    155  onPopupClosed = autocompletePopup.once("popup-closed");
    156  EventUtils.synthesizeKey("KEY_ArrowLeft", {}, view.styleWindow);
    157  await onPopupClosed;
    158  ok(true, "The popup was closed as when hitting ArrowLeft");
    159 
    160  // Arrow right and Ctrl + space to show again, and Arrow Right to accept
    161  onPopupOpened = autocompletePopup.once("popup-opened");
    162  EventUtils.synthesizeKey("KEY_ArrowRight", {}, view.styleWindow);
    163  EventUtils.synthesizeKey(" ", { ctrlKey: true }, view.styleWindow);
    164  await onPopupOpened;
    165 
    166  onPopupClosed = autocompletePopup.once("popup-closed");
    167  EventUtils.synthesizeKey("KEY_ArrowRight", {}, view.styleWindow);
    168  await waitForClassApplied("auto-body-added-by-script", "#auto-div-id-3");
    169  await onPopupClosed;
    170  is(
    171    textInput.value,
    172    "auto-body-added-by-script",
    173    "ArrowRight puts the selected item in the input and closes the popup"
    174  );
    175 
    176  // Backspace to show the list again
    177  onPopupOpened = autocompletePopup.once("popup-opened");
    178  EventUtils.synthesizeKey("KEY_Backspace", {}, view.styleWindow);
    179  await waitForClassApplied("auto-body-added-by-script", "#auto-div-id-3");
    180  await onPopupOpened;
    181  is(
    182    textInput.value,
    183    "auto-body-added-by-scrip",
    184    "ArrowRight puts the selected item in the input and closes the popup"
    185  );
    186  await checkAutocompleteItems(
    187    autocompletePopup,
    188    ["auto-body-added-by-script"],
    189    "The autocomplete does show the matching items after hitting backspace"
    190  );
    191 
    192  // Enter to accept
    193  onPopupClosed = autocompletePopup.once("popup-closed");
    194  EventUtils.synthesizeKey("KEY_Enter", {}, view.styleWindow);
    195  await waitForClassRemoved("auto-body-added-by-scrip");
    196  await onPopupClosed;
    197  is(
    198    textInput.value,
    199    "auto-body-added-by-script",
    200    "Enter puts the selected item in the input and closes the popup"
    201  );
    202 
    203  // Backspace to show again
    204  onPopupOpened = autocompletePopup.once("popup-opened");
    205  EventUtils.synthesizeKey("KEY_Backspace", {}, view.styleWindow);
    206  await waitForClassApplied("auto-body-added-by-script", "#auto-div-id-3");
    207  await onPopupOpened;
    208  is(
    209    textInput.value,
    210    "auto-body-added-by-scrip",
    211    "ArrowRight puts the selected item in the input and closes the popup"
    212  );
    213  await checkAutocompleteItems(
    214    autocompletePopup,
    215    ["auto-body-added-by-script"],
    216    "The autocomplete does show the matching items after hitting backspace"
    217  );
    218 
    219  // Tab to accept
    220  onPopupClosed = autocompletePopup.once("popup-closed");
    221  EventUtils.synthesizeKey("KEY_Tab", {}, view.styleWindow);
    222  await onPopupClosed;
    223  is(
    224    textInput.value,
    225    "auto-body-added-by-script",
    226    "Tab puts the selected item in the input and closes the popup"
    227  );
    228  await waitForClassRemoved("auto-body-added-by-scrip");
    229 });
    230 
    231 async function checkAutocompleteItems(
    232  autocompletePopup,
    233  expectedItems,
    234  assertionMessage
    235 ) {
    236  await waitForSuccess(
    237    () =>
    238      getAutocompleteItems(autocompletePopup).length === expectedItems.length
    239  );
    240  const items = getAutocompleteItems(autocompletePopup);
    241  Assert.deepEqual(items, expectedItems, assertionMessage);
    242 }
    243 
    244 function getAutocompleteItems(autocompletePopup) {
    245  return Array.from(autocompletePopup.tooltip.panel.querySelectorAll("li")).map(
    246    el => el.textContent
    247  );
    248 }
    249 
    250 async function waitForClassApplied(cls, selector) {
    251  info("Wait for class to be applied: " + cls);
    252  await SpecialPowers.spawn(
    253    gBrowser.selectedBrowser,
    254    [cls, selector],
    255    async (_cls, _selector) => {
    256      return ContentTaskUtils.waitForCondition(() =>
    257        content.document.querySelector(_selector).classList.contains(_cls)
    258      );
    259    }
    260  );
    261  // Wait for debounced functions to be executed
    262  await wait(200);
    263 }
    264 
    265 async function waitForClassRemoved(cls) {
    266  info("Wait for class to be removed: " + cls);
    267  await SpecialPowers.spawn(gBrowser.selectedBrowser, [cls], async _cls => {
    268    return ContentTaskUtils.waitForCondition(
    269      () =>
    270        !content.document
    271          .querySelector("#auto-div-id-3")
    272          .classList.contains(_cls)
    273    );
    274  });
    275  // Wait for debounced functions to be executed
    276  await wait(200);
    277 }