summaryrefslogtreecommitdiffstats
path: root/browser/base/content/test/static/browser_title_case_menus.js
blob: 9251db057bc51f13c75b43c27cafa0e75b6c3c1c (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
/* Any copyright is dedicated to the Public Domain.
   http://creativecommons.org/publicdomain/zero/1.0/ */

"use strict";

/**
 * This test file checks that our en-US builds use APA-style Title Case strings
 * where appropriate.
 */

// MINOR_WORDS are words that are okay to not be capitalized when they're
// mid-string.
//
// Source: https://apastyle.apa.org/style-grammar-guidelines/capitalization/title-case
const MINOR_WORDS = [
  "a",
  "an",
  "and",
  "as",
  "at",
  "but",
  "by",
  "for",
  "if",
  "in",
  "nor",
  "of",
  "off",
  "on",
  "or",
  "per",
  "so",
  "the",
  "to",
  "up",
  "via",
  "yet",
];

/**
 * Returns a generator that will yield all of the <xul:menupopups>
 * beneath <xul:menu> elements within a given <xul:menubar>. Each
 * <xul:menupopup> will have the "popupshowing" and "popupshown"
 * event fired on them to give them an opportunity to fully populate
 * themselves before being yielded.
 *
 * @generator
 * @param {<xul:menubar>} menubar The <xul:menubar> to get <xul:menupopup>s
 *        for.
 * @yields {<xul:menupopup>} The next <xul:menupopup> under the <xul:menubar>.
 */
async function* iterateMenuPopups(menubar) {
  let menus = menubar.querySelectorAll("menu");

  for (let menu of menus) {
    for (let menupopup of menu.querySelectorAll("menupopup")) {
      // We fake the popupshowing and popupshown events to give the menupopups
      // an opportunity to fully populate themselves. We don't actually open
      // the menupopups because this is not possible on macOS.
      menupopup.dispatchEvent(
        new MouseEvent("popupshowing", { bubbles: true })
      );
      menupopup.dispatchEvent(new MouseEvent("popupshown", { bubbles: true }));

      yield menupopup;

      // Just for good measure, we'll fire the popuphiding/popuphidden events
      // after we close the menupopups.
      menupopup.dispatchEvent(new MouseEvent("popuphiding", { bubbles: true }));
      menupopup.dispatchEvent(new MouseEvent("popuphidden", { bubbles: true }));
    }
  }
}

/**
 * Given a <xul:menupopup>, checks all of the child elements with label
 * properties to see if those labels are Title Cased. Skips any elements that
 * have an empty or undefined label property.
 *
 * @param {<xul:menupopup>} menupopup The <xul:menupopup> to check.
 */
function checkMenuItems(menupopup) {
  info("Checking menupopup with id " + menupopup.id);
  for (let child of menupopup.children) {
    if (child.label) {
      info("Checking menupopup child with id " + child.id);
      checkTitleCase(child.label, child.id);
    }
  }
}

/**
 * Given a string, checks that the string is in Title Case.
 *
 * @param {String} string The string to check.
 * @param {String} elementID The ID of the element associated with the string.
 *        This is included in the assertion message.
 */
function checkTitleCase(string, elementID) {
  if (!string || !elementID /* document this */) {
    return;
  }

  let words = string.trim().split(/\s+/);

  // We extract the first word, and always expect it to be capitalized,
  // even if it's a short word like one of MINOR_WORDS.
  let firstWord = words.shift();
  let result = hasExpectedCapitalization(firstWord, true);
  if (result) {
    for (let word of words) {
      if (word) {
        let expectCapitalized = !MINOR_WORDS.includes(word);
        result = hasExpectedCapitalization(word, expectCapitalized);
        if (!result) {
          break;
        }
      }
    }
  }

  Assert.ok(result, `${string} for ${elementID} should have Title Casing.`);
}

/**
 * On Windows, macOS and GTK/KDE Linux, menubars are expected to be in Title
 * Case in order to feel native. This test iterates the menuitem labels of the
 * main menubar to ensure the en-US strings are all in Title Case.
 *
 * We use APA-style Title Case for the menubar, rather than Photon-style Title
 * Case (https://design.firefox.com/photon/copy/capitalization.html) to match
 * the native platform conventions.
 */
add_task(async function apa_test_title_case_menubar() {
  let newWin = await BrowserTestUtils.openNewBrowserWindow();
  let menuToolbar = newWin.document.getElementById("main-menubar");

  for await (const menupopup of iterateMenuPopups(menuToolbar)) {
    checkMenuItems(menupopup);
  }

  await BrowserTestUtils.closeWindow(newWin);
});

/**
 * This test iterates the menuitem labels of the macOS dock menu for the
 * application to ensure the en-US strings are all in Title Case.
 */
add_task(async function apa_test_title_case_macos_dock_menu() {
  if (AppConstants.platform != "macosx") {
    return;
  }

  let hiddenWindow = Services.appShell.hiddenDOMWindow;
  Assert.ok(hiddenWindow, "Could get at hidden window");
  let menupopup = hiddenWindow.document.getElementById("menu_mac_dockmenu");
  checkMenuItems(menupopup);
});