summaryrefslogtreecommitdiffstats
path: root/browser/base/content/test/static/browser_sentence_case_strings.js
diff options
context:
space:
mode:
Diffstat (limited to 'browser/base/content/test/static/browser_sentence_case_strings.js')
-rw-r--r--browser/base/content/test/static/browser_sentence_case_strings.js279
1 files changed, 279 insertions, 0 deletions
diff --git a/browser/base/content/test/static/browser_sentence_case_strings.js b/browser/base/content/test/static/browser_sentence_case_strings.js
new file mode 100644
index 0000000000..e995f76b1a
--- /dev/null
+++ b/browser/base/content/test/static/browser_sentence_case_strings.js
@@ -0,0 +1,279 @@
+/* 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 sentence case strings
+ * where appropriate. It's not exhaustive - some panels will show different
+ * items in different states, and this test doesn't iterate all of them.
+ */
+
+/* global PanelUI */
+
+const { CustomizableUITestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/CustomizableUITestUtils.sys.mjs"
+);
+
+const { AppMenuNotifications } = ChromeUtils.importESModule(
+ "resource://gre/modules/AppMenuNotifications.sys.mjs"
+);
+
+// These are brand names, proper names, or other things that we expect to
+// not abide exactly to sentence case. NAMES is for single words, and PHRASES
+// is for words in a specific order.
+const NAMES = new Set(["Mozilla", "Nightly", "Firefox"]);
+const PHRASES = new Set(["Troubleshoot Modeā€¦"]);
+
+let gCUITestUtils = new CustomizableUITestUtils(window);
+let gLocalization = new Localization(["browser/newtab/asrouter.ftl"], true);
+
+/**
+ * This recursive function will take the current main or subview, find all of
+ * the buttons that navigate to subviews inside it, and click each one
+ * individually. Upon entering the new view, we recurse. When the subviews
+ * within a view have been exhausted, we go back up a level.
+ *
+ * @generator
+ * @param {<xul:panelview>} parentView The view to start scanning for
+ * subviews.
+ * @yields {<xul:panelview>} Each found <xul:panelview>, in depth-first search
+ * order.
+ */
+async function* iterateSubviews(parentView) {
+ let navButtons = Array.from(
+ // Ensure that only enabled buttons are tested
+ parentView.querySelectorAll(".subviewbutton-nav:not([disabled])")
+ );
+ if (!navButtons) {
+ return;
+ }
+
+ for (let button of navButtons) {
+ info("Click " + button.id);
+ let panel = parentView.closest("panel");
+ let panelmultiview = parentView.closest("panelmultiview");
+ let promiseViewShown = BrowserTestUtils.waitForEvent(panel, "ViewShown");
+ button.click();
+ let viewShownEvent = await promiseViewShown;
+
+ yield viewShownEvent.originalTarget;
+
+ info("Shown " + viewShownEvent.originalTarget.id);
+ yield* iterateSubviews(viewShownEvent.originalTarget);
+ promiseViewShown = BrowserTestUtils.waitForEvent(parentView, "ViewShown");
+ panelmultiview.goBack();
+ await promiseViewShown;
+ }
+}
+
+/**
+ * Given a <xul:panelview>, look for <xul:toolbarbutton> descendants, extract
+ * any relevant strings from them, and check to see if they are in sentence
+ * case. By default, labels, textContent, and toolTipText (including dynamic
+ * toolTipText) are checked.
+ *
+ * @param {<xul:panelview>} view The <xul:panelview> to check.
+ */
+function checkToolbarButtons(view) {
+ let toolbarbuttons = view.querySelectorAll("toolbarbutton");
+ info("Checking toolbarbuttons in subview with id " + view.id);
+
+ for (let toolbarbutton of toolbarbuttons) {
+ let strings = [
+ toolbarbutton.label,
+ toolbarbutton.textContent,
+ toolbarbutton.toolTipText,
+ GetDynamicShortcutTooltipText(toolbarbutton.id),
+ ];
+ info("Checking toolbarbutton " + toolbarbutton.id);
+ for (let string of strings) {
+ checkSentenceCase(string, toolbarbutton.id);
+ }
+ }
+}
+
+function checkSubheaders(view) {
+ let subheaders = view.querySelectorAll("h2");
+ info("Checking subheaders in subview with id " + view.id);
+
+ for (let subheader of subheaders) {
+ checkSentenceCase(subheader.textContent, subheader.id);
+ }
+}
+
+async function checkUpdateBanner(view) {
+ let banner = view.querySelector("#appMenu-proton-update-banner");
+
+ const notifications = [
+ "update-downloading",
+ "update-available",
+ "update-manual",
+ "update-unsupported",
+ "update-restart",
+ ];
+
+ for (const notification of notifications) {
+ // Forcibly remove the label in order to wait for the new label.
+ banner.removeAttribute("label");
+
+ let labelPromise = BrowserTestUtils.waitForMutationCondition(
+ banner,
+ { attributes: true, attributeFilter: ["label"] },
+ () => !!banner.getAttribute("label")
+ );
+
+ AppMenuNotifications.showNotification(notification);
+
+ await labelPromise;
+
+ checkSentenceCase(banner.label, banner.id);
+
+ AppMenuNotifications.removeNotification(/.*/);
+ }
+}
+
+/**
+ * Asserts whether or not a string matches sentence case.
+ *
+ * @param {String} string The string to check for sentence case.
+ * @param {String} elementID The ID of the element being tested. This is
+ * mainly used for the assertion message to make it easier to debug
+ * failures, but items without IDs will not be checked (as these are
+ * likely using dynamic strings, like bookmarked page titles).
+ */
+function checkSentenceCase(string, elementID) {
+ if (!string || !elementID) {
+ return;
+ }
+
+ info("Testing string: " + string);
+
+ let words = string.trim().split(/\s+/);
+
+ // We expect that the first word is always capitalized. If it isn't,
+ // there's no need to keep checking the rest of the string, since we're
+ // going to fail the assertion.
+ let result = hasExpectedCapitalization(words[0], true);
+ if (result) {
+ for (let wordIndex = 1; wordIndex < words.length; ++wordIndex) {
+ let word = words[wordIndex];
+
+ if (word) {
+ if (isPartOfPhrase(words, wordIndex)) {
+ result = hasExpectedCapitalization(word, true);
+ } else {
+ let isName = NAMES.has(word);
+ result = hasExpectedCapitalization(word, isName);
+ }
+ if (!result) {
+ break;
+ }
+ }
+ }
+ }
+
+ Assert.ok(result, `${string} for ${elementID} should have sentence casing.`);
+}
+
+/**
+ * Returns true if a word is part of a phrase defined in the PHRASES set.
+ * The function will see if the word is contained within any of the defined
+ * PHRASES, and will then scan back and forward within the words array to
+ * to see if the word is indeed part of the phrase in context.
+ *
+ * @param {Array} words The full array of words being checked by the caller.
+ * @param {Number} wordIndex The index of the word being checked within the
+ * words array.
+ * @return {Boolean}
+ */
+function isPartOfPhrase(words, wordIndex) {
+ let word = words[wordIndex];
+
+ info(`Checking if ${word} is part of a phrase`);
+
+ for (let phrase of PHRASES) {
+ let phraseFragments = phrase.split(" ");
+ let fragmentIndex = phraseFragments.indexOf(word);
+
+ // If we didn't find the word within this phrase, the candidate phrase
+ // has more words than what we're analyzing, or the word doesn't have
+ // enough words before it to match the candidate phrase, then move on.
+ if (
+ fragmentIndex == -1 ||
+ words.length - phraseFragments.length < 0 ||
+ fragmentIndex > wordIndex
+ ) {
+ continue;
+ }
+
+ let wordsSlice = words.slice(
+ wordIndex - fragmentIndex,
+ wordIndex + phraseFragments.length
+ );
+ let matches = wordsSlice.every((w, index) => {
+ return phraseFragments[index] === w;
+ });
+
+ if (matches) {
+ info(`${word} is part of phrase ${phrase}`);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/**
+ * Tests that the strings under the AppMenu are in sentence case.
+ */
+add_task(async function test_sentence_case_appmenu() {
+ // Some of these panels are lazy, so it's necessary to open them in
+ // order for them to be inserted into the DOM.
+ await gCUITestUtils.openMainMenu();
+ registerCleanupFunction(async () => {
+ await gCUITestUtils.hideMainMenu();
+ });
+
+ checkToolbarButtons(PanelUI.mainView);
+ checkSubheaders(PanelUI.mainView);
+
+ for await (const view of iterateSubviews(PanelUI.mainView)) {
+ checkToolbarButtons(view);
+ checkSubheaders(view);
+ }
+
+ await checkUpdateBanner(PanelUI.mainView);
+});
+
+/**
+ * Tests that the strings under the All Tabs panel are in sentence case.
+ */
+add_task(async function test_sentence_case_all_tabs_panel() {
+ gTabsPanel.init();
+
+ const allTabsView = document.getElementById("allTabsMenu-allTabsView");
+ let allTabsPopupShownPromise = BrowserTestUtils.waitForEvent(
+ allTabsView,
+ "ViewShown"
+ );
+ gTabsPanel.showAllTabsPanel();
+ await allTabsPopupShownPromise;
+
+ registerCleanupFunction(async () => {
+ let allTabsPopupHiddenPromise = BrowserTestUtils.waitForEvent(
+ allTabsView.panelMultiView,
+ "PanelMultiViewHidden"
+ );
+ gTabsPanel.hideAllTabsPanel();
+ await allTabsPopupHiddenPromise;
+ });
+
+ checkToolbarButtons(gTabsPanel.allTabsView);
+ checkSubheaders(gTabsPanel.allTabsView);
+
+ for await (const view of iterateSubviews(gTabsPanel.allTabsView)) {
+ checkToolbarButtons(view);
+ checkSubheaders(view);
+ }
+});