From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- .../test/static/browser_sentence_case_strings.js | 279 +++++++++++++++++++++ 1 file changed, 279 insertions(+) create mode 100644 browser/base/content/test/static/browser_sentence_case_strings.js (limited to 'browser/base/content/test/static/browser_sentence_case_strings.js') 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 {} parentView The view to start scanning for + * subviews. + * @yields {} Each found , 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 , look for 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 {} view The 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); + } +}); -- cgit v1.2.3