diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-15 03:35:49 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-15 03:35:49 +0000 |
commit | d8bbc7858622b6d9c278469aab701ca0b609cddf (patch) | |
tree | eff41dc61d9f714852212739e6b3738b82a2af87 /browser/components/translations | |
parent | Releasing progress-linux version 125.0.3-1~progress7.99u1. (diff) | |
download | firefox-d8bbc7858622b6d9c278469aab701ca0b609cddf.tar.xz firefox-d8bbc7858622b6d9c278469aab701ca0b609cddf.zip |
Merging upstream version 126.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'browser/components/translations')
36 files changed, 3250 insertions, 629 deletions
diff --git a/browser/components/translations/content/TranslationsPanelShared.sys.mjs b/browser/components/translations/content/TranslationsPanelShared.sys.mjs index 570528df3f..f5045f57e0 100644 --- a/browser/components/translations/content/TranslationsPanelShared.sys.mjs +++ b/browser/components/translations/content/TranslationsPanelShared.sys.mjs @@ -11,9 +11,53 @@ ChromeUtils.defineESModuleGetters(lazy, { /** * A class containing static functionality that is shared by both * the FullPageTranslationsPanel and SelectTranslationsPanel classes. + * + * It is recommended to read the documentation above the TranslationsParent class + * definition to understand the scope of the Translations architecture throughout + * Firefox. + * + * @see TranslationsParent + * + * The static instance of this class is a singleton in the parent process, and is + * available throughout all windows and tabs, just like the static instance of + * the TranslationsParent class. + * + * Unlike the TranslationsParent, this class is never instantiated as an actor + * outside of the static-context functionality defined below. */ export class TranslationsPanelShared { - static #langListsInitState = new Map(); + /** + * A map from Translations Panel instances to their initialized states. + * There is one instance of each panel per top ChromeWindow in Firefox. + * + * See the documentation above the TranslationsParent class for a detailed + * explanation of the translations architecture throughout Firefox. + * + * @see TranslationsParent + * + * @type {Map<FullPageTranslationsPanel | SelectTranslationsPanel, string>} + */ + static #langListsInitState = new WeakMap(); + + /** + * True if the next language-list initialization to fail for testing. + * + * @see TranslationsPanelShared.ensureLangListsBuilt + * + * @type {boolean} + */ + static #simulateLangListError = false; + + /** + * Clears cached data regarding the initialization state of the + * FullPageTranslationsPanel or the SelectTranslationsPanel. + * + * This is only needed for test runners to ensure that each test + * starts from a clean slate. + */ + static clearCache() { + this.#langListsInitState = new WeakMap(); + } /** * Defines lazy getters for accessing elements in the document based on provided entries. @@ -46,13 +90,25 @@ export class TranslationsPanelShared { } /** + * Ensures that the next call to ensureLangListBuilt wil fail + * for the purpose of testing the error state. + * + * @see TranslationsPanelShared.ensureLangListsBuilt + * + * @type {boolean} + */ + static simulateLangListError() { + this.#simulateLangListError = true; + } + + /** * Retrieves the initialization state of language lists for the specified panel. * * @param {FullPageTranslationsPanel | SelectTranslationsPanel} panel * - The panel for which to look up the state. */ static getLangListsInitState(panel) { - return TranslationsPanelShared.#langListsInitState.get(panel.id); + return TranslationsPanelShared.#langListsInitState.get(panel); } /** @@ -64,17 +120,17 @@ export class TranslationsPanelShared { * @param {FullPageTranslationsPanel | SelectTranslationsPanel} panel * - The panel for which to ensure language lists are built. */ - static async ensureLangListsBuilt(document, panel, innerWindowId) { - const { id } = panel; - switch ( - TranslationsPanelShared.#langListsInitState.get(`${id}-${innerWindowId}`) - ) { + static async ensureLangListsBuilt(document, panel) { + const { panel: panelElement } = panel.elements; + switch (TranslationsPanelShared.#langListsInitState.get(panel)) { case "initialized": // This has already been initialized. return; case "error": case undefined: - // attempt to initialize + // Set the error state in case there is an early exit at any point. + // This will be set to "initialized" if everything succeeds. + TranslationsPanelShared.#langListsInitState.set(panel, "error"); break; default: throw new Error( @@ -88,18 +144,28 @@ export class TranslationsPanelShared { await lazy.TranslationsParent.getSupportedLanguages(); // Verify that we are in a proper state. - if (languagePairs.length === 0) { + if (languagePairs.length === 0 || this.#simulateLangListError) { + this.#simulateLangListError = false; throw new Error("No translation languages were retrieved."); } - const fromPopups = panel.querySelectorAll( + const fromPopups = panelElement.querySelectorAll( ".translations-panel-language-menupopup-from" ); - const toPopups = panel.querySelectorAll( + const toPopups = panelElement.querySelectorAll( ".translations-panel-language-menupopup-to" ); for (const popup of fromPopups) { + // For the moment, the FullPageTranslationsPanel includes its own + // menu item for "Choose another language" as the first item in the list + // with an empty-string for its value. The SelectTranslationsPanel has + // only languages in its list with BCP-47 tags for values. As such, + // this loop works for both panels, to remove all of the languages + // from the list, but ensuring that any empty-string items are retained. + while (popup.lastChild?.value) { + popup.lastChild.remove(); + } for (const { langTag, displayName } of fromLanguages) { const fromMenuItem = document.createXULElement("menuitem"); fromMenuItem.setAttribute("value", langTag); @@ -109,6 +175,9 @@ export class TranslationsPanelShared { } for (const popup of toPopups) { + while (popup.lastChild?.value) { + popup.lastChild.remove(); + } for (const { langTag, displayName } of toLanguages) { const toMenuItem = document.createXULElement("menuitem"); toMenuItem.setAttribute("value", langTag); @@ -117,6 +186,6 @@ export class TranslationsPanelShared { } } - TranslationsPanelShared.#langListsInitState.set(id, "initialized"); + TranslationsPanelShared.#langListsInitState.set(panel, "initialized"); } } diff --git a/browser/components/translations/content/fullPageTranslationsPanel.js b/browser/components/translations/content/fullPageTranslationsPanel.js index 2e35440160..eddd3566f1 100644 --- a/browser/components/translations/content/fullPageTranslationsPanel.js +++ b/browser/components/translations/content/fullPageTranslationsPanel.js @@ -188,12 +188,19 @@ class CheckboxPageAction { } /** - * This singleton class controls the Translations popup panel. + * This singleton class controls the FullPageTranslations panel. * * This component is a `/browser` component, and the actor is a `/toolkit` actor, so care * must be taken to keep the presentation (this component) from the state management * (the Translations actor). This class reacts to state changes coming from the * Translations actor. + * + * A global instance of this class is created once per top ChromeWindow and is initialized + * when the new window is created. + * + * See the comment above TranslationsParent for more details. + * + * @see TranslationsParent */ var FullPageTranslationsPanel = new (class { /** @type {Console?} */ @@ -374,21 +381,6 @@ var FullPageTranslationsPanel = new (class { } /** - * @returns {TranslationsParent} - */ - #getTranslationsActor() { - const actor = - gBrowser.selectedBrowser.browsingContext.currentWindowGlobal.getActor( - "Translations" - ); - - if (!actor) { - throw new Error("Unable to get the TranslationsParent"); - } - return actor; - } - - /** * Fetches the language tags for the document and the user and caches the results * Use `#getCachedDetectedLanguages` when the lang tags do not need to be re-fetched. * This requires a bit of work to do, so prefer the cached version when possible. @@ -396,8 +388,9 @@ var FullPageTranslationsPanel = new (class { * @returns {Promise<LangTags>} */ async #fetchDetectedLanguages() { - this.detectedLanguages = - await this.#getTranslationsActor().getDetectedLanguages(); + this.detectedLanguages = await TranslationsParent.getTranslationsActor( + gBrowser.selectedBrowser + ).getDetectedLanguages(); return this.detectedLanguages; } @@ -421,11 +414,7 @@ var FullPageTranslationsPanel = new (class { */ async #ensureLangListsBuilt() { try { - await TranslationsPanelShared.ensureLangListsBuilt( - document, - this.elements.panel, - gBrowser.selectedBrowser.innerWindowID - ); + await TranslationsPanelShared.ensureLangListsBuilt(document, this); } catch (error) { this.console?.error(error); } @@ -438,7 +427,9 @@ var FullPageTranslationsPanel = new (class { * @param {TranslationsLanguageState} languageState */ #updateViewFromTranslationStatus( - languageState = this.#getTranslationsActor().languageState + languageState = TranslationsParent.getTranslationsActor( + gBrowser.selectedBrowser + ).languageState ) { const { translateButton, toMenuList, fromMenuList, header, cancelButton } = this.elements; @@ -553,7 +544,7 @@ var FullPageTranslationsPanel = new (class { // Unconditionally hide the intro text in case the panel is re-shown. intro.hidden = true; - if (TranslationsPanelShared.getLangListsInitState(panel) === "error") { + if (TranslationsPanelShared.getLangListsInitState(this) === "error") { // There was an error, display it in the view rather than the language // dropdowns. const { cancelButton, errorHintAction } = this.elements; @@ -722,8 +713,9 @@ var FullPageTranslationsPanel = new (class { const neverTranslateSiteMenuItems = panel.ownerDocument.querySelectorAll( ".never-translate-site-menuitem" ); - const neverTranslateSite = - await this.#getTranslationsActor().shouldNeverTranslateSite(); + const neverTranslateSite = await TranslationsParent.getTranslationsActor( + gBrowser.selectedBrowser + ).shouldNeverTranslateSite(); for (const menuitem of neverTranslateSiteMenuItems) { menuitem.setAttribute("checked", neverTranslateSite ? "true" : "false"); @@ -801,7 +793,9 @@ var FullPageTranslationsPanel = new (class { async #showRevisitView({ fromLanguage, toLanguage }) { const { fromMenuList, toMenuList, intro } = this.elements; if (!this.#isShowingDefaultView()) { - await this.#showDefaultView(this.#getTranslationsActor()); + await this.#showDefaultView( + TranslationsParent.getTranslationsActor(gBrowser.selectedBrowser) + ); } intro.hidden = true; fromMenuList.value = fromLanguage; @@ -897,7 +891,7 @@ var FullPageTranslationsPanel = new (class { PanelMultiView.hidePopup(panel); await this.#showDefaultView( - this.#getTranslationsActor(), + TranslationsParent.getTranslationsActor(gBrowser.selectedBrowser), true /* force this view to be shown */ ); @@ -1119,8 +1113,10 @@ var FullPageTranslationsPanel = new (class { const { button } = this.buttonElements; - const { requestedTranslationPair, locationChangeId } = - this.#getTranslationsActor().languageState; + const { requestedTranslationPair } = + TranslationsParent.getTranslationsActor( + gBrowser.selectedBrowser + ).languageState; // Store this value because it gets modified when #showDefaultView is called below. const isFirstUserInteraction = !this._hasShownPanel; @@ -1132,7 +1128,9 @@ var FullPageTranslationsPanel = new (class { this.console?.error(error); }); } else { - await this.#showDefaultView(this.#getTranslationsActor()).catch(error => { + await this.#showDefaultView( + TranslationsParent.getTranslationsActor(gBrowser.selectedBrowser) + ).catch(error => { this.console?.error(error); }); } @@ -1145,16 +1143,6 @@ var FullPageTranslationsPanel = new (class { ? button : this.elements.appMenuButton; - if (!TranslationsParent.isActiveLocation(locationChangeId)) { - this.console?.log(`A translation panel open request was stale.`, { - locationChangeId, - newlocationChangeId: - this.#getTranslationsActor().languageState.locationChangeId, - currentURISpec: gBrowser.currentURI.spec, - }); - return; - } - this.console?.log(`Showing a translation panel`, gBrowser.currentURI.spec); await this.#openPanelPopup(targetButton, { @@ -1173,7 +1161,9 @@ var FullPageTranslationsPanel = new (class { */ #isTranslationsActive() { const { requestedTranslationPair } = - this.#getTranslationsActor().languageState; + TranslationsParent.getTranslationsActor( + gBrowser.selectedBrowser + ).languageState; return requestedTranslationPair !== null; } @@ -1183,7 +1173,9 @@ var FullPageTranslationsPanel = new (class { async onTranslate() { PanelMultiView.hidePopup(this.elements.panel); - const actor = this.#getTranslationsActor(); + const actor = TranslationsParent.getTranslationsActor( + gBrowser.selectedBrowser + ); actor.translate( this.elements.fromMenuList.value, this.elements.toMenuList.value, @@ -1205,7 +1197,7 @@ var FullPageTranslationsPanel = new (class { this.#updateSettingsMenuLanguageCheckboxStates(); this.#updateSettingsMenuSiteCheckboxStates(); const popup = button.ownerDocument.getElementById( - "translations-panel-settings-menupopup" + "full-page-translations-panel-settings-menupopup" ); popup.openPopup(button, "after_end"); } @@ -1331,8 +1323,9 @@ var FullPageTranslationsPanel = new (class { */ async onNeverTranslateSite() { const pageAction = this.getCheckboxPageActionFor().neverTranslateSite(); - const toggledOn = - await this.#getTranslationsActor().toggleNeverTranslateSitePermissions(); + const toggledOn = await TranslationsParent.getTranslationsActor( + gBrowser.selectedBrowser + ).toggleNeverTranslateSitePermissions(); TranslationsParent.telemetry().panel().onNeverTranslateSite(toggledOn); this.#updateSettingsMenuSiteCheckboxStates(); await this.#doPageAction(pageAction); @@ -1349,7 +1342,9 @@ var FullPageTranslationsPanel = new (class { throw new Error("Expected to have a document language tag."); } - this.#getTranslationsActor().restorePage(docLangTag); + TranslationsParent.getTranslationsActor( + gBrowser.selectedBrowser + ).restorePage(docLangTag); } /** diff --git a/browser/components/translations/content/selectTranslationsPanel.inc.xhtml b/browser/components/translations/content/selectTranslationsPanel.inc.xhtml index 72e2bd7095..8c643ea3f6 100644 --- a/browser/components/translations/content/selectTranslationsPanel.inc.xhtml +++ b/browser/components/translations/content/selectTranslationsPanel.inc.xhtml @@ -4,99 +4,99 @@ <html:template id="template-select-translations-panel"> <panel id="select-translations-panel" - class="panel-no-padding translations-panel" + class="panel-no-padding translations-panel translations-panel-view" type="arrow" role="alertdialog" noautofocus="true" aria-labelledby="translations-panel-header" - orient="vertical"> - <panelmultiview id="select-translations-panel-multiview" mainViewId="select-translations-panel-view-default"> - <panelview id="select-translations-panel-view-default" - class="PanelUI-subView translations-panel-view" - role="document" - mainview-with-header="true" - has-custom-header="true"> - <hbox class="panel-header select-translations-panel-header"> - <html:h1 class="translations-panel-header-wrapper"> - <html:span id="select-translations-panel-header" data-l10n-id="select-translations-panel-header"> - </html:span> - </html:h1> - <hbox class="translations-panel-beta"> - <image id="select-translations-panel-beta-icon" - class="translations-panel-beta-icon"> - </image> - </hbox> - <toolbarbutton id="select-translations-panel-settings" - class="panel-info-button translations-panel-settings-gear-icon" - data-l10n-id="translations-panel-settings-button" - closemenu="none" /> - </hbox> - <vbox class="select-translations-panel-content"> - <hbox id="select-translations-panel-lang-selection"> - <vbox flex="1"> - <label id="select-translations-panel-from-label" - class="select-translations-panel-label" - data-l10n-id="select-translations-panel-from-label"> - </label> - <menulist id="select-translations-panel-from" - flex="1" - value="detect" - size="large" - data-l10n-id="translations-panel-choose-language" - aria-labelledby="translations-panel-from-label"> - <menupopup id="select-translations-panel-from-menupopup" - class="translations-panel-language-menupopup-from"> - <!-- The list of <menuitem> will be dynamically inserted. --> - </menupopup> - </menulist> - </vbox> - <vbox flex="1"> - <label id="select-translations-panel-to-label" - class="select-translations-panel-label" - data-l10n-id="select-translations-panel-to-label"> - </label> - <menulist id="select-translations-panel-to" - flex="1" - value="detect" - size="large" - data-l10n-id="translations-panel-choose-language" - aria-labelledby="translations-panel-to-label"> - <menupopup id="select-translations-panel-to-menupopup" - class="translations-panel-language-menupopup-to"> - <!-- The list of <menuitem> will be dynamically inserted. --> - </menupopup> - </menulist> - </vbox> - </hbox> + orient="vertical" + onpopupshown="SelectTranslationsPanel.handlePanelPopupShownEvent(event)" + onpopuphidden="SelectTranslationsPanel.handlePanelPopupHiddenEvent(event)"> + <hbox class="panel-header select-translations-panel-header"> + <html:h1 class="translations-panel-header-wrapper"> + <html:span id="select-translations-panel-header" data-l10n-id="select-translations-panel-header"> + </html:span> + </html:h1> + <hbox class="translations-panel-beta"> + <image id="select-translations-panel-beta-icon" + class="translations-panel-beta-icon"> + </image> + </hbox> + <toolbarbutton id="select-translations-panel-settings" + class="panel-info-button translations-panel-settings-gear-icon" + data-l10n-id="translations-panel-settings-button" + closemenu="none" /> + </hbox> + <vbox class="select-translations-panel-content"> + <hbox id="select-translations-panel-lang-selection"> + <vbox flex="1"> + <label id="select-translations-panel-from-label" + class="select-translations-panel-label" + data-l10n-id="select-translations-panel-from-label"> + </label> + <menulist id="select-translations-panel-from" + flex="1" + value="" + size="large" + data-l10n-id="translations-panel-choose-language" + aria-labelledby="select-translations-panel-from-label" + noinitialselection="true" + oncommand="SelectTranslationsPanel.onChangeFromLanguage(event)"> + <menupopup id="select-translations-panel-from-menupopup" + onpopupshown="SelectTranslationsPanel.handlePanelPopupShownEvent(event)" + class="translations-panel-language-menupopup-from"> + <!-- The list of <menuitem> will be dynamically inserted. --> + </menupopup> + </menulist> </vbox> - <vbox class="select-translations-panel-content"> - <html:textarea id="select-translations-panel-translation-area" - data-l10n-id="select-translations-panel-placeholder-text" - readonly="true" - tabindex="0"> - </html:textarea> + <vbox flex="1"> + <label id="select-translations-panel-to-label" + class="select-translations-panel-label" + data-l10n-id="select-translations-panel-to-label"> + </label> + <menulist id="select-translations-panel-to" + flex="1" + value="" + size="large" + data-l10n-id="translations-panel-choose-language" + aria-labelledby="select-translations-panel-to-label" + noinitialselection="true" + oncommand="SelectTranslationsPanel.onChangeToLanguage(event)"> + <menupopup id="select-translations-panel-to-menupopup" + onpopupshown="SelectTranslationsPanel.handlePanelPopupShownEvent(event)" + class="translations-panel-language-menupopup-to"> + <!-- The list of <menuitem> will be dynamically inserted. --> + </menupopup> + </menulist> </vbox> + </hbox> + </vbox> + <vbox class="select-translations-panel-content"> + <html:textarea id="select-translations-panel-text-area" + class="select-translations-panel-text-area" + readonly="true" + tabindex="0"> + </html:textarea> + </vbox> - <hbox class="select-translations-panel-content"> - <button id="select-translations-panel-copy-button" - class="footer-button select-translations-panel-button select-translations-panel-copy-button" - data-l10n-id="select-translations-panel-copy-button"> - </button> - </hbox> + <hbox class="select-translations-panel-content"> + <button id="select-translations-panel-copy-button" + class="footer-button select-translations-panel-button select-translations-panel-copy-button" + data-l10n-id="select-translations-panel-copy-button"> + </button> + </hbox> - <html:moz-button-group class="panel-footer translations-panel-footer"> - <button id="select-translations-panel-translate-full-page-button" - class="footer-button select-translations-panel-button" - data-l10n-id="select-translations-panel-translate-full-page-button"> - </button> - <button id="select-translations-panel-done-button" - class="footer-button select-translations-panel-button" - data-l10n-id="select-translations-panel-done-button" - default="true" - oncommand = "SelectTranslationsPanel.close()"> - </button> - </html:moz-button-group> - </panelview> - </panelmultiview> + <html:moz-button-group class="panel-footer translations-panel-footer"> + <button id="select-translations-panel-translate-full-page-button" + class="footer-button select-translations-panel-button" + data-l10n-id="select-translations-panel-translate-full-page-button"> + </button> + <button id="select-translations-panel-done-button" + class="footer-button select-translations-panel-button" + data-l10n-id="select-translations-panel-done-button" + default="true" + oncommand = "SelectTranslationsPanel.close()"> + </button> + </html:moz-button-group> </panel> </html:template> diff --git a/browser/components/translations/content/selectTranslationsPanel.js b/browser/components/translations/content/selectTranslationsPanel.js index b4fe3e9735..bb825eaefa 100644 --- a/browser/components/translations/content/selectTranslationsPanel.js +++ b/browser/components/translations/content/selectTranslationsPanel.js @@ -4,15 +4,27 @@ /* eslint-env mozilla/browser-window */ +/** + * @typedef {import("../../../../toolkit/components/translations/translations").SelectTranslationsPanelState} SelectTranslationsPanelState + */ + ChromeUtils.defineESModuleGetters(this, { LanguageDetector: "resource://gre/modules/translation/LanguageDetector.sys.mjs", TranslationsPanelShared: "chrome://browser/content/translations/TranslationsPanelShared.sys.mjs", + Translator: "chrome://global/content/translations/Translator.mjs", }); /** - * This singleton class controls the Translations popup panel. + * This singleton class controls the SelectTranslations panel. + * + * A global instance of this class is created once per top ChromeWindow and is initialized + * when the context menu is opened in that window. + * + * See the comment above TranslationsParent for more details. + * + * @see TranslationsParent */ var SelectTranslationsPanel = new (class { /** @type {Console?} */ @@ -40,6 +52,69 @@ var SelectTranslationsPanel = new (class { } /** + * The textarea height for shorter text. + * + * @type {string} + */ + #shortTextHeight = "8em"; + + /** + * Retrieves the read-only textarea height for shorter text. + * + * @see #shortTextHeight + */ + get shortTextHeight() { + return this.#shortTextHeight; + } + + /** + * The textarea height for shorter text. + * + * @type {string} + */ + #longTextHeight = "16em"; + + /** + * Retrieves the read-only textarea height for longer text. + * + * @see #longTextHeight + */ + get longTextHeight() { + return this.#longTextHeight; + } + + /** + * The threshold used to determine when the panel should + * use the short text-height vs. the long-text height. + * + * @type {number} + */ + #textLengthThreshold = 800; + + /** + * Retrieves the read-only text-length threshold. + * + * @see #textLengthThreshold + */ + get textLengthThreshold() { + return this.#textLengthThreshold; + } + + /** + * The localized placeholder text to display when idle. + * + * @type {string} + */ + #idlePlaceholderText; + + /** + * The localized placeholder text to display when translating. + * + * @type {string} + */ + #translatingPlaceholderText; + + /** * Where the lazy elements are stored. * * @type {Record<string, Element>?} @@ -47,6 +122,29 @@ var SelectTranslationsPanel = new (class { #lazyElements; /** + * The internal state of the SelectTranslationsPanel. + * + * @type {SelectTranslationsPanelState} + */ + #translationState = { phase: "closed" }; + + /** + * The Translator for the current language pair. + * + * @type {Translator} + */ + #translator; + + /** + * An Id that increments with each translation, used to help keep track + * of whether an active translation request continue its progression or + * stop due to the existence of a newer translation request. + * + * @type {number} + */ + #translationId = 0; + + /** * Lazily creates the dom elements, and lazily selects them. * * @returns {Record<string, Element>} @@ -77,11 +175,12 @@ var SelectTranslationsPanel = new (class { doneButton: "select-translations-panel-done-button", fromLabel: "select-translations-panel-from-label", fromMenuList: "select-translations-panel-from", + fromMenuPopup: "select-translations-panel-from-menupopup", header: "select-translations-panel-header", - multiview: "select-translations-panel-multiview", - textArea: "select-translations-panel-translation-area", + textArea: "select-translations-panel-text-area", toLabel: "select-translations-panel-to-label", toMenuList: "select-translations-panel-to", + toMenuPopup: "select-translations-panel-to-menupopup", translateFullPageButton: "select-translations-panel-translate-full-page-button", }); @@ -91,6 +190,43 @@ var SelectTranslationsPanel = new (class { } /** + * Attempts to determine the best language tag to use as the source language for translation. + * If the detected language is not supported, attempts to fallback to the document's language tag. + * + * @param {string} textToTranslate - The text for which the language detection and target language retrieval are performed. + * + * @returns {Promise<string>} - The code of a supported language, a supported document language, or the top detected language. + */ + async getTopSupportedDetectedLanguage(textToTranslate) { + // First see if any of the detected languages are supported and return it if so. + const { language, languages } = await LanguageDetector.detectLanguage( + textToTranslate + ); + for (const { languageCode } of languages) { + const isSupported = await TranslationsParent.isSupportedAsFromLang( + languageCode + ); + if (isSupported) { + return languageCode; + } + } + + // Since none of the detected languages were supported, check to see if the + // document has a specified language tag that is supported. + const actor = TranslationsParent.getTranslationsActor( + gBrowser.selectedBrowser + ); + const detectedLanguages = actor.languageState.detectedLanguages; + if (detectedLanguages?.isDocLangTagSupported) { + return detectedLanguages.docLangTag; + } + + // No supported language was found, so return the top detected language + // to inform the panel's unsupported language state. + return language; + } + + /** * Detects the language of the provided text and retrieves a language pair for translation * based on user settings. * @@ -101,9 +237,7 @@ var SelectTranslationsPanel = new (class { */ async getLangPairPromise(textToTranslate) { const [fromLang, toLang] = await Promise.all([ - LanguageDetector.detectLanguage(textToTranslate).then( - ({ language }) => language - ), + SelectTranslationsPanel.getTopSupportedDetectedLanguage(textToTranslate), TranslationsParent.getTopPreferredSupportedToLang(), ]); @@ -122,99 +256,740 @@ var SelectTranslationsPanel = new (class { } /** - * Builds the <menulist> of languages for both the "from" and "to". This can be - * called every time the popup is shown, as it will retry when there is an error - * (such as a network error) or be a noop if it's already initialized. + * Ensures that the from-language and to-language dropdowns are built. + * + * This can be called every time the popup is shown, since it will retry + * when there is an error (such as a network error) or be a no-op if the + * dropdowns have already been initialized. */ async #ensureLangListsBuilt() { - try { - await TranslationsPanelShared.ensureLangListsBuilt( - document, - this.elements.panel - ); - } catch (error) { - this.console?.error(error); - } + await TranslationsPanelShared.ensureLangListsBuilt(document, this); } /** - * Updates the language dropdown based on the provided language tag. + * Initializes the selected value of the given language dropdown based on the language tag. * * @param {string} langTag - A BCP-47 language tag. - * @param {Element} menuList - The dropdown menu element that will be updated based on language support. + * @param {Element} menuList - The menu list element to update. + * * @returns {Promise<void>} */ - async #updateLanguageDropdown(langTag, menuList) { - const langTagIsSupported = + async #initializeLanguageMenuList(langTag, menuList) { + const isLangTagSupported = menuList.id === this.elements.fromMenuList.id ? await TranslationsParent.isSupportedAsFromLang(langTag) : await TranslationsParent.isSupportedAsToLang(langTag); - if (langTagIsSupported) { + if (isLangTagSupported) { // Remove the data-l10n-id because the menulist label will // be populated from the supported language's display name. - menuList.value = langTag; menuList.removeAttribute("data-l10n-id"); + menuList.value = langTag; } else { - // Set the data-l10n-id placeholder because no valid - // language will be selected when the panel opens. - menuList.value = undefined; - document.l10n.setAttributes( - menuList, - "translations-panel-choose-language" - ); - await document.l10n.translateElements([menuList]); + await this.#deselectLanguage(menuList); } } /** - * Updates the language selection dropdowns based on the given langPairPromise. + * Initializes the selected values of the from-language and to-language menu + * lists based on the result of the given language pair promise. * * @param {Promise<{fromLang?: string, toLang?: string}>} langPairPromise + * * @returns {Promise<void>} */ - async #updateLanguageDropdowns(langPairPromise) { + async #initializeLanguageMenuLists(langPairPromise) { const { fromLang, toLang } = await langPairPromise; - - this.console?.debug(`fromLang(${fromLang})`); - this.console?.debug(`toLang(${toLang})`); - const { fromMenuList, toMenuList } = this.elements; - await Promise.all([ - this.#updateLanguageDropdown(fromLang, fromMenuList), - this.#updateLanguageDropdown(toLang, toMenuList), + this.#initializeLanguageMenuList(fromLang, fromMenuList), + this.#initializeLanguageMenuList(toLang, toMenuList), ]); } /** - * Opens the panel and populates the currently selected fromLang and toLang based - * on the result of the langPairPromise. + * Opens the panel, ensuring the panel's UI and state are initialized correctly. * * @param {Event} event - The triggering event for opening the panel. + * @param {number} screenX - The x-axis location of the screen at which to open the popup. + * @param {number} screenY - The y-axis location of the screen at which to open the popup. + * @param {string} sourceText - The text to translate. * @param {Promise} langPairPromise - Promise resolving to language pair data for initializing dropdowns. + * * @returns {Promise<void>} */ - async open(event, langPairPromise) { - this.console?.log("Showing a translation panel."); + async open(event, screenX, screenY, sourceText, langPairPromise) { + if (this.#isOpen()) { + return; + } + this.#registerSourceText(sourceText); await this.#ensureLangListsBuilt(); - await this.#updateLanguageDropdowns(langPairPromise); - - // TODO(Bug 1878721) Rework the logic of where to open the panel. - // - // For the moment, the Select Translations panel opens at the - // AppMenu Button, but it will eventually need to open near - // to the selected content. - const appMenuButton = document.getElementById("PanelUI-menu-button"); - const { panel, textArea } = this.elements; - - panel.addEventListener("popupshown", () => textArea.focus(), { - once: true, + + await Promise.all([ + this.#cachePlaceholderText(), + this.#initializeLanguageMenuLists(langPairPromise), + ]); + + this.#displayIdlePlaceholder(); + this.#maybeRequestTranslation(); + await this.#openPopup(event, screenX, screenY); + } + + /** + * Opens a the panel popup at a location on the screen. + * + * @param {Event} event - The event that triggers the popup opening. + * @param {number} screenX - The x-axis location of the screen at which to open the popup. + * @param {number} screenY - The y-axis location of the screen at which to open the popup. + */ + async #openPopup(event, screenX, screenY) { + await window.ensureCustomElements("moz-button-group"); + + this.console?.log("Showing SelectTranslationsPanel"); + const { panel } = this.elements; + panel.openPopupAtScreen(screenX, screenY, /* isContextMenu */ false, event); + } + + /** + * Adds the source text to the translation state and adapts the size of the text area based + * on the length of the text. + * + * @param {string} sourceText - The text to translate. + * + * @returns {Promise<void>} + */ + #registerSourceText(sourceText) { + const { textArea } = this.elements; + this.#changeStateTo("idle", /* retainEntries */ false, { + sourceText, }); - await PanelMultiView.openPopup(panel, appMenuButton, { - position: "bottomright topright", - triggerEvent: event, - }).catch(error => this.console?.error(error)); + + if (sourceText.length < SelectTranslationsPanel.textLengthThreshold) { + textArea.style.height = SelectTranslationsPanel.shortTextHeight; + } else { + textArea.style.height = SelectTranslationsPanel.longTextHeight; + } + } + + /** + * Caches the localized text to use as placeholders. + */ + async #cachePlaceholderText() { + const [idleText, translatingText] = await document.l10n.formatValues([ + { id: "select-translations-panel-idle-placeholder-text" }, + { id: "select-translations-panel-translating-placeholder-text" }, + ]); + this.#idlePlaceholderText = idleText; + this.#translatingPlaceholderText = translatingText; + } + + /** + * Handles events when a popup is shown within the panel, including showing + * the panel itself. + * + * @param {Event} event - The event that triggered the popup to show. + */ + handlePanelPopupShownEvent(event) { + const { panel, fromMenuPopup, toMenuPopup } = this.elements; + switch (event.target.id) { + case panel.id: { + this.#updatePanelUIFromState(); + break; + } + case fromMenuPopup.id: { + this.#maybeTranslateOnEvents(["popuphidden"], fromMenuPopup); + break; + } + case toMenuPopup.id: { + this.#maybeTranslateOnEvents(["popuphidden"], toMenuPopup); + break; + } + } + } + + /** + * Handles events when a popup is closed within the panel, including closing + * the panel itself. + * + * @param {Event} event - The event that triggered the popup to close. + */ + handlePanelPopupHiddenEvent(event) { + const { panel } = this.elements; + switch (event.target.id) { + case panel.id: { + this.#changeStateToClosed(); + break; + } + } + } + + /** + * Handles events when the panels select from-language is changed. + */ + onChangeFromLanguage() { + const { fromMenuList, toMenuList } = this.elements; + this.#maybeTranslateOnEvents(["blur", "keypress"], fromMenuList); + this.#maybeStealLanguageFrom(toMenuList); + } + + /** + * Handles events when the panels select to-language is changed. + */ + onChangeToLanguage() { + const { toMenuList, fromMenuList } = this.elements; + this.#maybeTranslateOnEvents(["blur", "keypress"], toMenuList); + this.#maybeStealLanguageFrom(fromMenuList); + } + + /** + * Clears the selected language and ensures that the menu list displays + * the proper placeholder text. + * + * @param {Element} menuList - The target menu list element to update. + */ + async #deselectLanguage(menuList) { + menuList.value = ""; + document.l10n.setAttributes(menuList, "translations-panel-choose-language"); + await document.l10n.translateElements([menuList]); + } + + /** + * Deselects the language from the target menu list if both menu lists + * have the same language selected, simulating the effect of one menu + * list stealing the selected language value from the other. + * + * @param {Element} menuList - The target menu list element to update. + */ + async #maybeStealLanguageFrom(menuList) { + const { fromLanguage, toLanguage } = this.#getSelectedLanguagePair(); + if (fromLanguage === toLanguage) { + await this.#deselectLanguage(menuList); + this.#maybeFocusMenuList(menuList); + } + } + + /** + * Focuses on the given menu list if provided and empty, or defaults to focusing one + * of the from-menu or to-menu lists if either is empty. + * + * @param {Element} [menuList] - The menu list to focus if specified. + */ + #maybeFocusMenuList(menuList) { + if (menuList && !menuList.value) { + menuList.focus({ focusVisible: true }); + return; + } + + const { fromMenuList, toMenuList } = this.elements; + if (!fromMenuList.value) { + fromMenuList.focus({ focusVisible: true }); + } else if (!toMenuList.value) { + toMenuList.focus({ focusVisible: true }); + } + } + + /** + * Focuses the translated-text area and sets its overflow to auto post-animation. + */ + #indicateTranslatedTextArea({ overflow }) { + const { textArea } = this.elements; + textArea.focus({ focusVisible: true }); + requestAnimationFrame(() => { + // We want to set overflow to auto as the final animation, because if it is + // set before the translated text is displayed, then the scrollTop will + // move to the bottom as the text is populated. + // + // Setting scrollTop = 0 on its own works, but it sometimes causes an animation + // of the text jumping from the bottom to the top. It looks a lot cleaner to + // disable overflow before rendering the text, then re-enable it after it renders. + requestAnimationFrame(() => { + textArea.style.overflow = overflow; + textArea.scrollTop = 0; + }); + }); + } + + /** + * Checks if the given language pair matches the panel's currently selected language pair. + * + * @param {string} fromLanguage - The from-language to compare. + * @param {string} toLanguage - The to-language to compare. + * + * @returns {boolean} - True if the given language pair matches the selected languages in the panel UI, otherwise false. + */ + #isSelectedLangPair(fromLanguage, toLanguage) { + const { fromLanguage: selectedFromLang, toLanguage: selectedToLang } = + this.#getSelectedLanguagePair(); + return fromLanguage === selectedFromLang && toLanguage === selectedToLang; + } + + /** + * Checks if the translator's language configuration matches the given language pair. + * + * @param {string} fromLanguage - The from-language to compare. + * @param {string} toLanguage - The to-language to compare. + * + * @returns {boolean} - True if the translator's languages match the given pair, otherwise false. + */ + #translatorMatchesLangPair(fromLanguage, toLanguage) { + return ( + this.#translator?.fromLanguage === fromLanguage && + this.#translator?.toLanguage === toLanguage + ); + } + + /** + * Retrieves the currently selected language pair from the menu lists. + * + * @returns {{fromLanguage: string, toLanguage: string}} An object containing the selected languages. + */ + #getSelectedLanguagePair() { + const { fromMenuList, toMenuList } = this.elements; + return { + fromLanguage: fromMenuList.value, + toLanguage: toMenuList.value, + }; + } + + /** + * Retrieves the source text from the translation state. + * This value is not available when the panel is closed. + * + * @returns {string | undefined} The source text. + */ + getSourceText() { + return this.#translationState?.sourceText; + } + + /** + * Retrieves the source text from the translation state. + * This value is only available in the translated phase. + * + * @returns {string | undefined} The translated text. + */ + getTranslatedText() { + return this.#translationState?.translatedText; + } + + /** + * Retrieves the current phase of the translation state. + * + * @returns {SelectTranslationsPanelState} + */ + #phase() { + return this.#translationState.phase; + } + + /** + * @returns {boolean} True if the panel is open, otherwise false. + */ + #isOpen() { + return this.#phase() !== "closed"; + } + + /** + * @returns {boolean} True if the panel is closed, otherwise false. + */ + #isClosed() { + return this.#phase() === "closed"; + } + + /** + * Changes the translation state to a new phase with options to retain or overwrite existing entries. + * + * @param {SelectTranslationsPanelState} phase - The new phase to transition to. + * @param {boolean} [retainEntries] - Whether to retain existing state entries that are not overwritten. + * @param {object | null} [data=null] - Additional data to merge into the state. + * @throws {Error} If an invalid phase is specified. + */ + #changeStateTo(phase, retainEntries, data = null) { + const { textArea } = this.elements; + switch (phase) { + case "translating": { + textArea.classList.add("translating"); + break; + } + case "closed": + case "idle": + case "translatable": + case "translated": { + textArea.classList.remove("translating"); + break; + } + default: { + throw new Error(`Invalid state change to '${phase}'`); + } + } + + const previousPhase = this.#phase(); + if (data && retainEntries) { + // Change the phase and apply new entries from data, but retain non-overwritten entries from previous state. + this.#translationState = { ...this.#translationState, phase, ...data }; + } else if (data) { + // Change the phase and apply new entries from data, but drop any entries that are not overwritten by data. + this.#translationState = { phase, ...data }; + } else if (retainEntries) { + // Change only the phase and retain all entries from previous data. + this.#translationState.phase = phase; + } else { + // Change the phase and delete all entries from previous data. + this.#translationState = { phase }; + } + + if (previousPhase === this.#phase()) { + // Do not continue on to update the UI because the phase didn't change. + return; + } + + const { fromLanguage, toLanguage } = this.#translationState; + this.console?.debug( + `SelectTranslationsPanel (${fromLanguage ? fromLanguage : "??"}-${ + toLanguage ? toLanguage : "??" + }) state change (${previousPhase} => ${phase})` + ); + + this.#updatePanelUIFromState(); + } + + /** + * Changes the phase to closed, discarding any entries in the translation state. + */ + #changeStateToClosed() { + this.#changeStateTo("closed", /* retainEntries */ false); + } + + /** + * Changes the phase from "translatable" to "translating". + * + * @throws {Error} If the current state is not "translatable". + */ + #changeStateToTranslating() { + const phase = this.#phase(); + if (phase !== "translatable") { + throw new Error(`Invalid state change (${phase} => translating)`); + } + this.#changeStateTo("translating", /* retainEntries */ true); + } + + /** + * Changes the phase from "translating" to "translated". + * + * @throws {Error} If the current state is not "translating". + */ + #changeStateToTranslated(translatedText) { + const phase = this.#phase(); + if (phase !== "translating") { + throw new Error(`Invalid state change (${phase} => translated)`); + } + this.#changeStateTo("translated", /* retainEntries */ true, { + translatedText, + }); + } + + /** + * Transitions the phase of the state based on the given language pair. + * + * @param {string} fromLanguage - The BCP-47 from-language tag. + * @param {string} toLanguage - The BCP-47 to-language tag. + * + * @returns {SelectTranslationsPanelState} The new phase of the translation state. + */ + #changeStateByLanguagePair(fromLanguage, toLanguage) { + const { + phase: previousPhase, + fromLanguage: previousFromLanguage, + toLanguage: previousToLanguage, + } = this.#translationState; + + let nextPhase = "translatable"; + + if ( + // No from-language is selected, so we cannot translate. + !fromLanguage || + // No to-language is selected, so we cannot translate. + !toLanguage || + // The same language has been selected, so we cannot translate. + fromLanguage === toLanguage + ) { + nextPhase = "idle"; + } else if ( + // The languages have not changed, so there is nothing to do. + previousFromLanguage === fromLanguage && + previousToLanguage === toLanguage + ) { + nextPhase = previousPhase; + } + + this.#changeStateTo(nextPhase, /* retainEntries */ true, { + fromLanguage, + toLanguage, + }); + + return nextPhase; + } + + /** + * Determines whether translation should continue based on panel state and language pair. + * + * @param {number} translationId - The id of the translation request to match. + * @param {string} fromLanguage - The from-language to analyze. + * @param {string} toLanguage - The to-language to analyze. + * + * @returns {boolean} True if translation should continue with the given pair, otherwise false. + */ + #shouldContinueTranslation(translationId, fromLanguage, toLanguage) { + return ( + // Continue only if the panel is still open. + this.#isOpen() && + // Continue only if the current translationId matches. + translationId === this.#translationId && + // Continue only if the given language pair is still the actively selected pair. + this.#isSelectedLangPair(fromLanguage, toLanguage) && + // Continue only if the given language pair matches the current translator. + this.#translatorMatchesLangPair(fromLanguage, toLanguage) + ); + } + + /** + * Displays the placeholder text for the translation state's "idle" phase. + */ + #displayIdlePlaceholder() { + const { textArea } = SelectTranslationsPanel.elements; + textArea.value = this.#idlePlaceholderText; + this.#updateTextDirection(); + this.#updateConditionalUIEnabledState(); + this.#maybeFocusMenuList(); + } + + /** + * Displays the placeholder text for the translation state's "translating" phase. + */ + #displayTranslatingPlaceholder() { + const { textArea } = SelectTranslationsPanel.elements; + textArea.value = this.#translatingPlaceholderText; + this.#updateTextDirection(); + this.#updateConditionalUIEnabledState(); + this.#indicateTranslatedTextArea({ overflow: "hidden" }); + } + + /** + * Displays the translated text for the translation state's "translated" phase. + */ + #displayTranslatedText() { + const { toLanguage } = this.#getSelectedLanguagePair(); + const { textArea } = SelectTranslationsPanel.elements; + textArea.value = this.getTranslatedText(); + this.#updateTextDirection(toLanguage); + this.#updateConditionalUIEnabledState(); + this.#indicateTranslatedTextArea({ overflow: "auto" }); + } + + /** + * Enables or disables UI components that are conditional on a valid language pair being selected. + */ + #updateConditionalUIEnabledState() { + const { fromLanguage, toLanguage } = this.#getSelectedLanguagePair(); + const { copyButton, translateFullPageButton, textArea } = this.elements; + + const invalidLangPairSelected = !fromLanguage || !toLanguage; + const isTranslating = this.#phase() === "translating"; + + textArea.disabled = invalidLangPairSelected; + translateFullPageButton.disabled = invalidLangPairSelected; + copyButton.disabled = invalidLangPairSelected || isTranslating; + } + + /** + * Updates the panel UI based on the current phase of the translation state. + */ + #updatePanelUIFromState() { + switch (this.#phase()) { + case "idle": { + this.#displayIdlePlaceholder(); + break; + } + case "translating": { + this.#displayTranslatingPlaceholder(); + break; + } + case "translated": { + this.#displayTranslatedText(); + break; + } + } + } + + /** + * Sets the text direction attribute in the text areas based on the specified language. + * Uses the given language tag if provided, otherwise uses the current app locale. + * + * @param {string} [langTag] - The language tag to determine text direction. + */ + #updateTextDirection(langTag) { + const { textArea } = this.elements; + if (langTag) { + const scriptDirection = Services.intl.getScriptDirection(langTag); + textArea.setAttribute("dir", scriptDirection); + } else { + textArea.removeAttribute("dir"); + } + } + + /** + * Requests a translations port for a given language pair. + * + * @param {string} fromLanguage - The from-language. + * @param {string} toLanguage - The to-language. + * + * @returns {Promise<MessagePort | undefined>} The message port promise. + */ + async #requestTranslationsPort(fromLanguage, toLanguage) { + const innerWindowId = + gBrowser.selectedBrowser.browsingContext.top.embedderElement + .innerWindowID; + if (!innerWindowId) { + return undefined; + } + const port = await TranslationsParent.requestTranslationsPort( + innerWindowId, + fromLanguage, + toLanguage + ); + return port; + } + + /** + * Retrieves the existing translator for the specified language pair if it matches, + * otherwise creates a new translator. + * + * @param {string} fromLanguage - The source language code. + * @param {string} toLanguage - The target language code. + * + * @returns {Promise<Translator>} A promise that resolves to a `Translator` instance for the given language pair. + */ + async #getOrCreateTranslator(fromLanguage, toLanguage) { + if (this.#translatorMatchesLangPair(fromLanguage, toLanguage)) { + return this.#translator; + } + + this.console?.log( + `Creating new Translator (${fromLanguage}-${toLanguage})` + ); + if (this.#translator) { + this.#translator.destroy(); + this.#translator = null; + } + + this.#translator = await Translator.create( + fromLanguage, + toLanguage, + this.#requestTranslationsPort + ); + return this.#translator; + } + + /** + * Initiates the translation process if the panel state and selected languages + * meet the conditions for translation. + */ + #maybeRequestTranslation() { + if (this.#isClosed()) { + return; + } + const { fromLanguage, toLanguage } = this.#getSelectedLanguagePair(); + const nextState = this.#changeStateByLanguagePair(fromLanguage, toLanguage); + if (nextState !== "translatable") { + return; + } + + const translationId = ++this.#translationId; + this.#getOrCreateTranslator(fromLanguage, toLanguage) + .then(translator => { + if ( + this.#shouldContinueTranslation( + translationId, + fromLanguage, + toLanguage + ) + ) { + this.#changeStateToTranslating(); + return translator.translate(this.getSourceText()); + } + return null; + }) + .then(translatedText => { + if ( + translatedText && + this.#shouldContinueTranslation( + translationId, + fromLanguage, + toLanguage + ) + ) { + this.#changeStateToTranslated(translatedText); + } else if (this.#isOpen()) { + this.#changeStateTo("idle", /* retainEntires */ false, { + sourceText: this.getSourceText(), + }); + } + }) + .catch(error => this.console?.error(error)); + } + + /** + * Attaches event listeners to the target element for initiating translation on specified event types. + * + * @param {string[]} eventTypes - An array of event types to listen for. + * @param {object} target - The target element to attach event listeners to. + * @throws {Error} If an unrecognized event type is provided. + */ + #maybeTranslateOnEvents(eventTypes, target) { + if (!target.translationListenerCallbacks) { + target.translationListenerCallbacks = []; + } + if (target.translationListenerCallbacks.length === 0) { + for (const eventType of eventTypes) { + let callback; + switch (eventType) { + case "blur": + case "popuphidden": { + callback = () => { + this.#maybeRequestTranslation(); + this.#removeTranslationListeners(target); + }; + break; + } + case "keypress": { + callback = event => { + if (event.key === "Enter") { + this.#maybeRequestTranslation(); + } + this.#removeTranslationListeners(target); + }; + break; + } + default: { + throw new Error( + `Invalid translation event type given: '${eventType}` + ); + } + } + target.addEventListener(eventType, callback, { once: true }); + target.translationListenerCallbacks.push({ eventType, callback }); + } + } + } + + /** + * Removes all translation event listeners from the target element. + * + * @param {Element} target - The element from which event listeners are to be removed. + */ + #removeTranslationListeners(target) { + for (const { eventType, callback } of target.translationListenerCallbacks) { + target.removeEventListener(eventType, callback); + } + target.translationListenerCallbacks = []; } })(); diff --git a/browser/components/translations/moz.build b/browser/components/translations/moz.build index 212b93e509..49f3afc632 100644 --- a/browser/components/translations/moz.build +++ b/browser/components/translations/moz.build @@ -3,7 +3,7 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. with Files("**"): - BUG_COMPONENT = ("Firefox", "Translation") + BUG_COMPONENT = ("Firefox", "Translations") BROWSER_CHROME_MANIFESTS += ["tests/browser/browser.toml"] diff --git a/browser/components/translations/tests/browser/browser.toml b/browser/components/translations/tests/browser/browser.toml index a9d36363da..472ae28866 100644 --- a/browser/components/translations/tests/browser/browser.toml +++ b/browser/components/translations/tests/browser/browser.toml @@ -15,6 +15,10 @@ support-files = [ ["browser_translations_about_preferences_settings_ui.js"] +["browser_translations_full_page_move_tab_to_new_window.js"] + +["browser_translations_full_page_multiple_windows.js"] + ["browser_translations_full_page_panel_a11y_focus.js"] ["browser_translations_full_page_panel_always_translate_language_bad_data.js"] @@ -51,8 +55,6 @@ support-files = [ ["browser_translations_full_page_panel_engine_unsupported.js"] -["browser_translations_full_page_panel_engine_unsupported_lang.js"] - ["browser_translations_full_page_panel_firstrun.js"] ["browser_translations_full_page_panel_firstrun_revisit.js"] @@ -62,6 +64,8 @@ skip-if = ["true"] ["browser_translations_full_page_panel_gear.js"] +["browser_translations_full_page_panel_init_failure.js"] + ["browser_translations_full_page_panel_never_translate_language.js"] ["browser_translations_full_page_panel_never_translate_site_auto.js"] @@ -77,6 +81,8 @@ skip-if = ["os == 'linux' && !debug"] # Bug 1863227 ["browser_translations_full_page_panel_switch_languages.js"] +["browser_translations_full_page_panel_unsupported_lang.js"] + ["browser_translations_full_page_reader_mode.js"] ["browser_translations_full_page_telemetry_firstrun_auto_translate.js"] @@ -109,6 +115,30 @@ skip-if = ["os == 'linux' && !debug"] # Bug 1863227 ["browser_translations_select_context_menu_with_text_selected.js"] -["browser_translations_select_panel_language_selectors.js"] +["browser_translations_select_panel_engine_cache.js"] + +["browser_translations_select_panel_fallback_to_doc_language.js"] + +["browser_translations_select_panel_open_to_idle_state.js"] + +["browser_translations_select_panel_retranslate_on_change_language_directly.js"] + +["browser_translations_select_panel_retranslate_on_change_language_from_dropdown_menu.js"] + +["browser_translations_select_panel_select_current_language_directly.js"] + +["browser_translations_select_panel_select_current_language_from_dropdown_menu.js"] + +["browser_translations_select_panel_select_same_from_and_to_languages_directly.js"] + +["browser_translations_select_panel_select_same_from_and_to_languages_from_dropdown_menu.js"] + +["browser_translations_select_panel_translate_on_change_language_directly.js"] + +["browser_translations_select_panel_translate_on_change_language_from_dropdown_menu.js"] + +["browser_translations_select_panel_translate_on_change_language_multiple_times_directly.js"] + +["browser_translations_select_panel_translate_on_change_language_multiple_times_from_dropdown_menu.js"] -["browser_translations_select_panel_mainview_ui.js"] +["browser_translations_select_panel_translate_on_open.js"] diff --git a/browser/components/translations/tests/browser/browser_translations_about_preferences_settings_ui.js b/browser/components/translations/tests/browser/browser_translations_about_preferences_settings_ui.js index ee81b84a36..f618b27814 100644 --- a/browser/components/translations/tests/browser/browser_translations_about_preferences_settings_ui.js +++ b/browser/components/translations/tests/browser/browser_translations_about_preferences_settings_ui.js @@ -22,8 +22,8 @@ add_task(async function test_translations_settings_pane_elements() { translationsSettingsDescription, translateAlwaysHeader, translateNeverHeader, - translateAlwaysAddButton, - translateNeverAddButton, + translateAlwaysMenuList, + translateNeverMenuList, translateNeverSiteHeader, translateNeverSiteDesc, translateDownloadLanguagesHeader, @@ -41,8 +41,8 @@ add_task(async function test_translations_settings_pane_elements() { translationsSettingsDescription, translateAlwaysHeader, translateNeverHeader, - translateAlwaysAddButton, - translateNeverAddButton, + translateAlwaysMenuList, + translateNeverMenuList, translateNeverSiteHeader, translateNeverSiteDesc, translateDownloadLanguagesHeader, @@ -74,14 +74,203 @@ add_task(async function test_translations_settings_pane_elements() { translationsSettingsDescription, translateAlwaysHeader, translateNeverHeader, - translateAlwaysAddButton, - translateNeverAddButton, + translateAlwaysMenuList, + translateNeverMenuList, translateNeverSiteHeader, translateNeverSiteDesc, translateDownloadLanguagesHeader, translateDownloadLanguagesLearnMore, }, }); + await cleanup(); +}); + +add_task(async function test_translations_settings_always_translate() { + const { + cleanup, + elements: { settingsButton }, + } = await setupAboutPreferences(LANGUAGE_PAIRS, { + prefs: [["browser.translations.newSettingsUI.enable", true]], + }); + + const document = gBrowser.selectedBrowser.contentDocument; + + assertVisibility({ + message: "Expect paneGeneral elements to be visible.", + visible: { settingsButton }, + }); + + const { translateAlwaysMenuList } = + await TranslationsSettingsTestUtils.openAboutPreferencesTranslationsSettingsPane( + settingsButton + ); + let alwaysTranslateSection = document.getElementById( + "translations-settings-always-translate-section" + ); + await testLanguageList(alwaysTranslateSection, translateAlwaysMenuList); + + await cleanup(); +}); + +async function testLanguageList(translateSection, menuList) { + const sectionName = + translateSection.id === "translations-settings-always-translate-section" + ? "Always" + : "Never"; + + is( + translateSection.querySelector(".translations-settings-languages-card"), + null, + `Language list not present in ${sectionName} Translate list` + ); + + for (let i = 0; i < menuList.children[0].children.length; i++) { + menuList.value = menuList.children[0].children[i].value; + + let clickMenu = BrowserTestUtils.waitForEvent(menuList, "command"); + menuList.dispatchEvent(new Event("command")); + await clickMenu; + + /** Languages are always added on the top, so check the firstChild + * for newly added languages. + * the firstChild.lastChild.innerText is the language display name + * which is compared with the menulist display name that is selected + */ + is( + translateSection.querySelector(".translations-settings-language-list") + .firstChild.lastChild.innerText, + getIntlDisplayName(menuList.children[0].children[i].value), + `Language list has element ${getIntlDisplayName( + menuList.children[0].children[i].value + )}` + ); + } + /** The test cases has 4 languages, so check if 4 languages are added to the list */ + let langNum = translateSection.querySelector( + ".translations-settings-language-list" + ).childElementCount; + is(langNum, 4, "Number of languages added is 4"); + + const languagelist = translateSection.querySelector( + ".translations-settings-language-list" + ); + + for (let i = 0; i < langNum; i++) { + // Delete the first language in the list + let langName = languagelist.children[0].lastChild.innerText; + let langButton = languagelist.children[0].querySelector("moz-button"); + + let clickButton = BrowserTestUtils.waitForEvent(langButton, "click"); + langButton.dispatchEvent(new Event("click")); + await clickButton; + + if (i < langNum - 1) { + is( + languagelist.childElementCount, + langNum - i - 1, + `${langName} removed from ${sectionName} Translate` + ); + } else { + /** Check if the language list card is removed after removing the last language */ + is( + translateSection.querySelector(".translations-settings-languages-card"), + null, + `${langName} removed from ${sectionName} Translate` + ); + } + } +} + +add_task(async function test_translations_settings_never_translate() { + const { + cleanup, + elements: { settingsButton }, + } = await setupAboutPreferences(LANGUAGE_PAIRS, { + prefs: [["browser.translations.newSettingsUI.enable", true]], + }); + + const document = gBrowser.selectedBrowser.contentDocument; + + assertVisibility({ + message: "Expect paneGeneral elements to be visible.", + visible: { settingsButton }, + }); + + const { translateNeverMenuList } = + await TranslationsSettingsTestUtils.openAboutPreferencesTranslationsSettingsPane( + settingsButton + ); + let neverTranslateSection = document.getElementById( + "translations-settings-never-translate-section" + ); + await testLanguageList(neverTranslateSection, translateNeverMenuList); + await cleanup(); +}); + +add_task(async function test_translations_settings_download_languages() { + const { + cleanup, + elements: { settingsButton }, + } = await setupAboutPreferences(LANGUAGE_PAIRS, { + prefs: [["browser.translations.newSettingsUI.enable", true]], + }); + assertVisibility({ + message: "Expect paneGeneral elements to be visible.", + visible: { settingsButton }, + }); + + const { translateDownloadLanguagesList } = + await TranslationsSettingsTestUtils.openAboutPreferencesTranslationsSettingsPane( + settingsButton + ); + + let langList = translateDownloadLanguagesList.querySelector( + ".translations-settings-language-list" + ); + + for (let i = 0; i < langList.children.length; i++) { + is( + langList.children[i] + .querySelector("moz-button") + .classList.contains("translations-settings-download-icon"), + true, + "Download icon is visible" + ); + + let clickButton = BrowserTestUtils.waitForEvent( + langList.children[i].querySelector("moz-button"), + "click" + ); + langList.children[i] + .querySelector("moz-button") + .dispatchEvent(new Event("click")); + await clickButton; + + is( + langList.children[i] + .querySelector("moz-button") + .classList.contains("translations-settings-delete-icon"), + true, + "Delete icon is visible" + ); + + clickButton = BrowserTestUtils.waitForEvent( + langList.children[i].querySelector("moz-button"), + "click" + ); + langList.children[i] + .querySelector("moz-button") + .dispatchEvent(new Event("click")); + await clickButton; + + is( + langList.children[i] + .querySelector("moz-button") + .classList.contains("translations-settings-download-icon"), + true, + "Download icon is visible" + ); + } await cleanup(); }); diff --git a/browser/components/translations/tests/browser/browser_translations_full_page_move_tab_to_new_window.js b/browser/components/translations/tests/browser/browser_translations_full_page_move_tab_to_new_window.js new file mode 100644 index 0000000000..f384fc59c8 --- /dev/null +++ b/browser/components/translations/tests/browser/browser_translations_full_page_move_tab_to_new_window.js @@ -0,0 +1,64 @@ +/* Any copyright is dedicated to the Public Domain. + https://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * This test case tests a specific situation described in Bug 1893776 + * where the Translations panels were not initializing correctly after + * dragging a tab to become its own new window after opening the panel + * in the previous window. + */ +add_task(async function test_browser_translations_full_page_multiple_windows() { + const window1 = window; + const testPage = await loadTestPage({ + page: SPANISH_PAGE_URL, + languagePairs: LANGUAGE_PAIRS, + }); + + await FullPageTranslationsTestUtils.assertTranslationsButton( + { button: true, circleArrows: false, locale: false, icon: true }, + "The translations button is visible.", + window1 + ); + + info("Opening FullPageTranslationsPanel in window1"); + await FullPageTranslationsTestUtils.openPanel({ + onOpenPanel: FullPageTranslationsTestUtils.assertPanelViewDefault, + }); + + await FullPageTranslationsTestUtils.clickCancelButton(); + + info("Moving the tab to a new window of its own"); + const window2 = await window1.gBrowser.replaceTabWithWindow(testPage.tab); + const swapDocShellPromise = BrowserTestUtils.waitForEvent( + testPage.tab.linkedBrowser, + "SwapDocShells" + ); + await swapDocShellPromise; + + await FullPageTranslationsTestUtils.assertTranslationsButton( + { button: true, circleArrows: false, locale: false, icon: true }, + "The translations button is visible.", + window2 + ); + + info("Opening FullPageTranslationsPanel in window2"); + await FullPageTranslationsTestUtils.openPanel({ + win: window2, + }); + + info("Translating the same page in window2"); + await FullPageTranslationsTestUtils.clickTranslateButton({ + win: window2, + downloadHandler: testPage.resolveDownloads, + }); + await FullPageTranslationsTestUtils.assertLangTagIsShownOnTranslationsButton( + "es", + "en", + window2 + ); + + await testPage.cleanup(); + await BrowserTestUtils.closeWindow(window2); +}); diff --git a/browser/components/translations/tests/browser/browser_translations_full_page_multiple_windows.js b/browser/components/translations/tests/browser/browser_translations_full_page_multiple_windows.js new file mode 100644 index 0000000000..9bdee2c406 --- /dev/null +++ b/browser/components/translations/tests/browser/browser_translations_full_page_multiple_windows.js @@ -0,0 +1,68 @@ +/* Any copyright is dedicated to the Public Domain. + https://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * @param {Window} win + */ +function focusWindow(win) { + const promise = BrowserTestUtils.waitForEvent(win, "focus"); + win.focus(); + return promise; +} + +/** + * Test that the full page translation panel works when multiple windows are used. + */ +add_task(async function test_browser_translations_full_page_multiple_windows() { + const window1 = window; + const testPage1 = await loadTestPage({ + page: SPANISH_PAGE_URL, + languagePairs: LANGUAGE_PAIRS, + }); + + const window2 = await BrowserTestUtils.openNewBrowserWindow(); + + const testPage2 = await loadTestPage({ + win: window2, + page: SPANISH_PAGE_URL, + languagePairs: LANGUAGE_PAIRS, + }); + + // Focus back to the original window first. This ensures coverage for invalid caching + // logic involving multiple windows. + await focusWindow(window1); + + info("Testing window 1"); + await FullPageTranslationsTestUtils.openPanel({ + onOpenPanel: FullPageTranslationsTestUtils.assertPanelViewDefault, + }); + await FullPageTranslationsTestUtils.clickTranslateButton({ + downloadHandler: testPage1.resolveDownloads, + }); + await FullPageTranslationsTestUtils.assertPageIsTranslated( + "es", + "en", + testPage1.runInPage, + "Window 1 gets translated", + window1 + ); + + await focusWindow(window2); + + info("Testing window 2"); + await FullPageTranslationsTestUtils.openPanel({ win: window2 }); + await FullPageTranslationsTestUtils.clickTranslateButton({ win: window2 }); + await FullPageTranslationsTestUtils.assertPageIsTranslated( + "es", + "en", + testPage2.runInPage, + "Window 2 gets translated", + window2 + ); + + await testPage2.cleanup(); + await BrowserTestUtils.closeWindow(window2); + await testPage1.cleanup(); +}); diff --git a/browser/components/translations/tests/browser/browser_translations_full_page_panel_init_failure.js b/browser/components/translations/tests/browser/browser_translations_full_page_panel_init_failure.js new file mode 100644 index 0000000000..34986726b8 --- /dev/null +++ b/browser/components/translations/tests/browser/browser_translations_full_page_panel_init_failure.js @@ -0,0 +1,25 @@ +/* Any copyright is dedicated to the Public Domain. + https://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * This test case verifies that the proper error message is displayed in + * the FullPageTranslationsPanel if the panel tries to open, but the language + * dropdown menus fail to initialize. + */ +add_task(async function test_full_page_translations_panel_init_failure() { + const { cleanup } = await loadTestPage({ + page: SPANISH_PAGE_URL, + languagePairs: LANGUAGE_PAIRS, + }); + + TranslationsPanelShared.simulateLangListError(); + await FullPageTranslationsTestUtils.openPanel({ + onOpenPanel: FullPageTranslationsTestUtils.assertPanelViewInitFailure, + }); + + await FullPageTranslationsTestUtils.clickCancelButton(); + + await cleanup(); +}); diff --git a/browser/components/translations/tests/browser/browser_translations_full_page_panel_retry.js b/browser/components/translations/tests/browser/browser_translations_full_page_panel_retry.js index 74d92381b9..01af5cbd8d 100644 --- a/browser/components/translations/tests/browser/browser_translations_full_page_panel_retry.js +++ b/browser/components/translations/tests/browser/browser_translations_full_page_panel_retry.js @@ -37,7 +37,7 @@ add_task(async function test_translations_panel_retry() { onOpenPanel: FullPageTranslationsTestUtils.assertPanelViewRevisit, }); - FullPageTranslationsTestUtils.switchSelectedToLanguage("fr"); + FullPageTranslationsTestUtils.changeSelectedToLanguage("fr"); await FullPageTranslationsTestUtils.clickTranslateButton({ downloadHandler: resolveDownloads, diff --git a/browser/components/translations/tests/browser/browser_translations_full_page_panel_switch_languages.js b/browser/components/translations/tests/browser/browser_translations_full_page_panel_switch_languages.js index 0c5db67b20..6ab70e634f 100644 --- a/browser/components/translations/tests/browser/browser_translations_full_page_panel_switch_languages.js +++ b/browser/components/translations/tests/browser/browser_translations_full_page_panel_switch_languages.js @@ -30,29 +30,29 @@ add_task(async function test_translations_panel_switch_language() { FullPageTranslationsTestUtils.assertSelectedFromLanguage({ langTag: "es" }); FullPageTranslationsTestUtils.assertSelectedToLanguage({ langTag: "en" }); - FullPageTranslationsTestUtils.switchSelectedFromLanguage("en"); + FullPageTranslationsTestUtils.changeSelectedFromLanguage("en"); ok( translateButton.disabled, "The translate button is disabled when the languages are the same" ); - FullPageTranslationsTestUtils.switchSelectedFromLanguage("es"); + FullPageTranslationsTestUtils.changeSelectedFromLanguage("es"); ok( !translateButton.disabled, "When the languages are different it can be translated" ); - FullPageTranslationsTestUtils.switchSelectedFromLanguage(""); + FullPageTranslationsTestUtils.changeSelectedFromLanguage(""); ok( translateButton.disabled, "The translate button is disabled nothing is selected." ); - FullPageTranslationsTestUtils.switchSelectedFromLanguage("en"); - FullPageTranslationsTestUtils.switchSelectedToLanguage("fr"); + FullPageTranslationsTestUtils.changeSelectedFromLanguage("en"); + FullPageTranslationsTestUtils.changeSelectedToLanguage("fr"); ok(!translateButton.disabled, "The translate button can now be used"); diff --git a/browser/components/translations/tests/browser/browser_translations_full_page_panel_engine_unsupported_lang.js b/browser/components/translations/tests/browser/browser_translations_full_page_panel_unsupported_lang.js index 21f7e8fdb7..59be1e329b 100644 --- a/browser/components/translations/tests/browser/browser_translations_full_page_panel_engine_unsupported_lang.js +++ b/browser/components/translations/tests/browser/browser_translations_full_page_panel_unsupported_lang.js @@ -23,6 +23,9 @@ add_task(async function test_unsupported_lang() { }); await FullPageTranslationsTestUtils.clickChangeSourceLanguageButton(); + FullPageTranslationsTestUtils.assertPanelViewDefault(); + FullPageTranslationsTestUtils.assertSelectedFromLanguage({ langTag: "" }); + FullPageTranslationsTestUtils.assertSelectedToLanguage({ langTag: "en" }); await cleanup(); }); diff --git a/browser/components/translations/tests/browser/browser_translations_full_page_telemetry_switch_languages.js b/browser/components/translations/tests/browser/browser_translations_full_page_telemetry_switch_languages.js index ef13940b3f..41183cc9cf 100644 --- a/browser/components/translations/tests/browser/browser_translations_full_page_telemetry_switch_languages.js +++ b/browser/components/translations/tests/browser/browser_translations_full_page_telemetry_switch_languages.js @@ -24,7 +24,7 @@ add_task(async function test_translations_telemetry_switch_from_language() { }); FullPageTranslationsTestUtils.assertSelectedFromLanguage({ langTag: "es" }); - FullPageTranslationsTestUtils.switchSelectedFromLanguage("en"); + FullPageTranslationsTestUtils.changeSelectedFromLanguage("en"); await TestTranslationsTelemetry.assertEvent(Glean.translationsPanel.open, { expectedEventCount: 1, @@ -45,7 +45,7 @@ add_task(async function test_translations_telemetry_switch_from_language() { } ); - FullPageTranslationsTestUtils.switchSelectedFromLanguage("es"); + FullPageTranslationsTestUtils.changeSelectedFromLanguage("es"); await TestTranslationsTelemetry.assertEvent( Glean.translationsPanel.changeFromLanguage, @@ -56,7 +56,7 @@ add_task(async function test_translations_telemetry_switch_from_language() { } ); - FullPageTranslationsTestUtils.switchSelectedFromLanguage(""); + FullPageTranslationsTestUtils.changeSelectedFromLanguage(""); await TestTranslationsTelemetry.assertEvent( Glean.translationsPanel.changeFromLanguage, @@ -65,7 +65,7 @@ add_task(async function test_translations_telemetry_switch_from_language() { } ); - FullPageTranslationsTestUtils.switchSelectedFromLanguage("en"); + FullPageTranslationsTestUtils.changeSelectedFromLanguage("en"); await TestTranslationsTelemetry.assertEvent( Glean.translationsPanel.changeFromLanguage, @@ -100,7 +100,7 @@ add_task(async function test_translations_telemetry_switch_to_language() { }); FullPageTranslationsTestUtils.assertSelectedToLanguage({ langTag: "en" }); - FullPageTranslationsTestUtils.switchSelectedToLanguage("fr"); + FullPageTranslationsTestUtils.changeSelectedToLanguage("fr"); await TestTranslationsTelemetry.assertEvent(Glean.translationsPanel.open, { expectedEventCount: 1, @@ -121,7 +121,7 @@ add_task(async function test_translations_telemetry_switch_to_language() { } ); - FullPageTranslationsTestUtils.switchSelectedToLanguage("en"); + FullPageTranslationsTestUtils.changeSelectedToLanguage("en"); await TestTranslationsTelemetry.assertEvent( Glean.translationsPanel.changeToLanguage, @@ -132,7 +132,7 @@ add_task(async function test_translations_telemetry_switch_to_language() { } ); - FullPageTranslationsTestUtils.switchSelectedToLanguage(""); + FullPageTranslationsTestUtils.changeSelectedToLanguage(""); await TestTranslationsTelemetry.assertEvent( Glean.translationsPanel.changeToLanguage, @@ -141,7 +141,7 @@ add_task(async function test_translations_telemetry_switch_to_language() { } ); - FullPageTranslationsTestUtils.switchSelectedToLanguage("en"); + FullPageTranslationsTestUtils.changeSelectedToLanguage("en"); await TestTranslationsTelemetry.assertEvent( Glean.translationsPanel.changeToLanguage, diff --git a/browser/components/translations/tests/browser/browser_translations_select_context_menu_feature_disabled.js b/browser/components/translations/tests/browser/browser_translations_select_context_menu_feature_disabled.js index a6b3f71924..c3ed228ecc 100644 --- a/browser/components/translations/tests/browser/browser_translations_select_context_menu_feature_disabled.js +++ b/browser/components/translations/tests/browser/browser_translations_select_context_menu_feature_disabled.js @@ -19,7 +19,7 @@ add_task( async function test_translate_selection_menuitem_is_unavailable_with_feature_disabled_and_no_text_selected() { const { cleanup, runInPage } = await loadTestPage({ - page: SPANISH_PAGE_URL, + page: SELECT_TEST_PAGE_URL, languagePairs: LANGUAGE_PAIRS, prefs: [["browser.translations.select.enable", false]], }); @@ -34,8 +34,8 @@ add_task( await SelectTranslationsTestUtils.assertContextMenuTranslateSelectionItem( runInPage, { - selectSpanishParagraph: false, - openAtSpanishParagraph: true, + selectSpanishSentence: false, + openAtSpanishSentence: true, expectMenuItemVisible: false, }, "The translate-selection context menu item should be unavailable when the feature is disabled." @@ -54,7 +54,7 @@ add_task( add_task( async function test_translate_selection_menuitem_is_unavailable_with_feature_disabled_and_text_selected() { const { cleanup, runInPage } = await loadTestPage({ - page: SPANISH_PAGE_URL, + page: SELECT_TEST_PAGE_URL, languagePairs: LANGUAGE_PAIRS, prefs: [["browser.translations.select.enable", false]], }); @@ -69,8 +69,8 @@ add_task( await SelectTranslationsTestUtils.assertContextMenuTranslateSelectionItem( runInPage, { - selectSpanishParagraph: true, - openAtSpanishParagraph: true, + selectSpanishSentence: true, + openAtSpanishSentence: true, expectMenuItemVisible: false, }, "The translate-selection context menu item should be unavailable when the feature is disabled." @@ -89,7 +89,7 @@ add_task( add_task( async function test_translate_selection_menuitem_is_unavailable_with_feature_disabled_and_clicking_a_hyperlink() { const { cleanup, runInPage } = await loadTestPage({ - page: SPANISH_PAGE_URL, + page: SELECT_TEST_PAGE_URL, languagePairs: LANGUAGE_PAIRS, prefs: [["browser.translations.select.enable", false]], }); @@ -102,7 +102,7 @@ add_task( await SelectTranslationsTestUtils.assertContextMenuTranslateSelectionItem( runInPage, { - selectSpanishParagraph: false, + selectSpanishSentence: false, openAtSpanishHyperlink: true, expectMenuItemVisible: false, }, diff --git a/browser/components/translations/tests/browser/browser_translations_select_context_menu_with_full_page_translations_active.js b/browser/components/translations/tests/browser/browser_translations_select_context_menu_with_full_page_translations_active.js index 99cff2b4ec..788ca7de63 100644 --- a/browser/components/translations/tests/browser/browser_translations_select_context_menu_with_full_page_translations_active.js +++ b/browser/components/translations/tests/browser/browser_translations_select_context_menu_with_full_page_translations_active.js @@ -12,7 +12,7 @@ add_task( async function test_translate_selection_menuitem_with_text_selected_and_full_page_translations_active() { const { cleanup, resolveDownloads, runInPage } = await loadTestPage({ - page: SPANISH_PAGE_URL, + page: SELECT_TEST_PAGE_URL, languagePairs: LANGUAGE_PAIRS, prefs: [["browser.translations.select.enable", true]], }); @@ -27,8 +27,8 @@ add_task( await SelectTranslationsTestUtils.assertContextMenuTranslateSelectionItem( runInPage, { - selectSpanishParagraph: true, - openAtSpanishParagraph: true, + selectSpanishSentence: true, + openAtSpanishSentence: true, expectMenuItemVisible: true, expectedTargetLanguage: "en", }, @@ -52,8 +52,8 @@ add_task( await SelectTranslationsTestUtils.assertContextMenuTranslateSelectionItem( runInPage, { - selectSpanishParagraph: true, - openAtSpanishParagraph: true, + selectSpanishSentence: true, + openAtSpanishSentence: true, expectMenuItemVisible: false, }, "The translate-selection context menu item should be unavailable while full-page translations is active." @@ -70,8 +70,8 @@ add_task( await SelectTranslationsTestUtils.assertContextMenuTranslateSelectionItem( runInPage, { - selectSpanishParagraph: true, - openAtSpanishParagraph: true, + selectSpanishSentence: true, + openAtSpanishSentence: true, expectMenuItemVisible: true, expectedTargetLanguage: "en", }, @@ -91,7 +91,7 @@ add_task( add_task( async function test_translate_selection_menuitem_with_link_clicked_and_full_page_translations_active() { const { cleanup, resolveDownloads, runInPage } = await loadTestPage({ - page: SPANISH_PAGE_URL, + page: SELECT_TEST_PAGE_URL, languagePairs: LANGUAGE_PAIRS, prefs: [["browser.translations.select.enable", true]], }); @@ -106,7 +106,7 @@ add_task( await SelectTranslationsTestUtils.assertContextMenuTranslateSelectionItem( runInPage, { - selectSpanishParagraph: false, + selectSpanishSentence: false, openAtSpanishHyperlink: true, expectMenuItemVisible: true, expectedTargetLanguage: "en", @@ -131,7 +131,7 @@ add_task( await SelectTranslationsTestUtils.assertContextMenuTranslateSelectionItem( runInPage, { - selectSpanishParagraph: false, + selectSpanishSentence: false, openAtSpanishHyperlink: true, expectMenuItemVisible: false, }, @@ -149,7 +149,7 @@ add_task( await SelectTranslationsTestUtils.assertContextMenuTranslateSelectionItem( runInPage, { - selectSpanishParagraph: false, + selectSpanishSentence: false, openAtSpanishHyperlink: true, expectMenuItemVisible: true, expectedTargetLanguage: "en", diff --git a/browser/components/translations/tests/browser/browser_translations_select_context_menu_with_hyperlink.js b/browser/components/translations/tests/browser/browser_translations_select_context_menu_with_hyperlink.js index cefd83f046..83e836489f 100644 --- a/browser/components/translations/tests/browser/browser_translations_select_context_menu_with_hyperlink.js +++ b/browser/components/translations/tests/browser/browser_translations_select_context_menu_with_hyperlink.js @@ -12,7 +12,7 @@ add_task( async function test_translate_selection_menuitem_translate_link_text_to_target_language() { const { cleanup, runInPage } = await loadTestPage({ - page: SPANISH_PAGE_URL, + page: SELECT_TEST_PAGE_URL, languagePairs: LANGUAGE_PAIRS, prefs: [["browser.translations.select.enable", true]], }); @@ -25,7 +25,7 @@ add_task( await SelectTranslationsTestUtils.assertContextMenuTranslateSelectionItem( runInPage, { - selectSpanishParagraph: false, + selectSpanishSentence: false, openAtSpanishHyperlink: true, expectMenuItemVisible: true, expectedTargetLanguage: "en", @@ -47,7 +47,7 @@ add_task( add_task( async function test_translate_selection_menuitem_translate_link_text_in_preferred_language() { const { cleanup, runInPage } = await loadTestPage({ - page: SPANISH_PAGE_URL, + page: SELECT_TEST_PAGE_URL, languagePairs: LANGUAGE_PAIRS, prefs: [["browser.translations.select.enable", true]], }); @@ -60,7 +60,7 @@ add_task( await SelectTranslationsTestUtils.assertContextMenuTranslateSelectionItem( runInPage, { - selectSpanishParagraph: false, + selectSpanishSentence: false, openAtEnglishHyperlink: true, expectMenuItemVisible: true, expectedTargetLanguage: null, @@ -82,7 +82,7 @@ add_task( add_task( async function test_translate_selection_menuitem_selected_text_takes_precedence_over_link_text() { const { cleanup, runInPage } = await loadTestPage({ - page: SPANISH_PAGE_URL, + page: SELECT_TEST_PAGE_URL, languagePairs: LANGUAGE_PAIRS, prefs: [["browser.translations.select.enable", true]], }); @@ -95,7 +95,7 @@ add_task( await SelectTranslationsTestUtils.assertContextMenuTranslateSelectionItem( runInPage, { - selectSpanishParagraph: true, + selectSpanishSentence: true, openAtEnglishHyperlink: true, expectMenuItemVisible: true, expectedTargetLanguage: "en", diff --git a/browser/components/translations/tests/browser/browser_translations_select_context_menu_with_no_text_selected.js b/browser/components/translations/tests/browser/browser_translations_select_context_menu_with_no_text_selected.js index 82e5d3ba63..5e7d482441 100644 --- a/browser/components/translations/tests/browser/browser_translations_select_context_menu_with_no_text_selected.js +++ b/browser/components/translations/tests/browser/browser_translations_select_context_menu_with_no_text_selected.js @@ -10,7 +10,7 @@ add_task( async function test_translate_selection_menuitem_is_unavailable_when_no_text_is_selected() { const { cleanup, runInPage } = await loadTestPage({ - page: SPANISH_PAGE_URL, + page: SELECT_TEST_PAGE_URL, languagePairs: LANGUAGE_PAIRS, prefs: [["browser.translations.select.enable", true]], }); @@ -25,8 +25,8 @@ add_task( await SelectTranslationsTestUtils.assertContextMenuTranslateSelectionItem( runInPage, { - selectSpanishParagraph: false, - openAtSpanishParagraph: true, + selectSpanishSentence: false, + openAtSpanishSentence: true, expectMenuItemVisible: false, }, "The translate-selection context menu item should be unavailable when no text is selected." diff --git a/browser/components/translations/tests/browser/browser_translations_select_context_menu_with_text_selected.js b/browser/components/translations/tests/browser/browser_translations_select_context_menu_with_text_selected.js index deb5911a37..6b44f2ca1f 100644 --- a/browser/components/translations/tests/browser/browser_translations_select_context_menu_with_text_selected.js +++ b/browser/components/translations/tests/browser/browser_translations_select_context_menu_with_text_selected.js @@ -12,7 +12,7 @@ add_task( async function test_translate_selection_menuitem_when_selected_text_is_not_preferred_language() { const { cleanup, runInPage } = await loadTestPage({ - page: SPANISH_PAGE_URL, + page: SELECT_TEST_PAGE_URL, languagePairs: LANGUAGE_PAIRS, prefs: [["browser.translations.select.enable", true]], }); @@ -27,8 +27,8 @@ add_task( await SelectTranslationsTestUtils.assertContextMenuTranslateSelectionItem( runInPage, { - selectSpanishParagraph: true, - openAtSpanishParagraph: true, + selectSpanishSentence: true, + openAtSpanishSentence: true, expectMenuItemVisible: true, expectedTargetLanguage: "en", }, @@ -49,21 +49,21 @@ add_task( add_task( async function test_translate_selection_menuitem_when_selected_text_is_preferred_language() { const { cleanup, runInPage } = await loadTestPage({ - page: ENGLISH_PAGE_URL, + page: SELECT_TEST_PAGE_URL, languagePairs: LANGUAGE_PAIRS, prefs: [["browser.translations.select.enable", true]], }); await FullPageTranslationsTestUtils.assertTranslationsButton( - { button: false }, + { button: true, circleArrows: false, locale: false, icon: true }, "The button is available." ); await SelectTranslationsTestUtils.assertContextMenuTranslateSelectionItem( runInPage, { - selectFirstParagraph: true, - openAtFirstParagraph: true, + selectEnglishSentence: true, + openAtEnglishSentence: true, expectMenuItemVisible: true, expectedTargetLanguage: null, }, diff --git a/browser/components/translations/tests/browser/browser_translations_select_panel_engine_cache.js b/browser/components/translations/tests/browser/browser_translations_select_panel_engine_cache.js new file mode 100644 index 0000000000..a0ef58c694 --- /dev/null +++ b/browser/components/translations/tests/browser/browser_translations_select_panel_engine_cache.js @@ -0,0 +1,59 @@ +/* Any copyright is dedicated to the Public Domain. + https://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * This test case tests that the SelectTranslationsPanel successfully + * caches the engine within the Translator for the given language pair, + * and if that engine is destroyed, the Translator will correctly reinitialize + * the engine, even for the same language pair. + */ +add_task( + async function test_select_translations_panel_translate_sentence_on_open() { + const { cleanup, runInPage, resolveDownloads } = await loadTestPage({ + page: SELECT_TEST_PAGE_URL, + languagePairs: LANGUAGE_PAIRS, + prefs: [["browser.translations.select.enable", true]], + }); + + await SelectTranslationsTestUtils.openPanel(runInPage, { + selectFrenchSection: true, + openAtFrenchSection: true, + expectedFromLanguage: "fr", + expectedToLanguage: "en", + expectedDownloads: 1, + downloadHandler: resolveDownloads, + onOpenPanel: SelectTranslationsTestUtils.assertPanelViewTranslated, + }); + + await SelectTranslationsTestUtils.clickDoneButton(); + + await SelectTranslationsTestUtils.openPanel(runInPage, { + selectFrenchSentence: true, + openAtFrenchSentence: true, + expectedFromLanguage: "fr", + expectedToLanguage: "en", + // No downloads because the engine is cached for this language pair. + onOpenPanel: SelectTranslationsTestUtils.assertPanelViewTranslated, + }); + + await SelectTranslationsTestUtils.clickDoneButton(); + + info("Explicitly destroying the Translations Engine."); + await destroyTranslationsEngine(); + + await SelectTranslationsTestUtils.openPanel(runInPage, { + openAtFrenchHyperlink: true, + expectedFromLanguage: "fr", + expectedToLanguage: "en", + // Expect downloads again since the engine was destroyed. + downloadHandler: resolveDownloads, + onOpenPanel: SelectTranslationsTestUtils.assertPanelViewTranslated, + }); + + await SelectTranslationsTestUtils.clickDoneButton(); + + await cleanup(); + } +); diff --git a/browser/components/translations/tests/browser/browser_translations_select_panel_fallback_to_doc_language.js b/browser/components/translations/tests/browser/browser_translations_select_panel_fallback_to_doc_language.js new file mode 100644 index 0000000000..d2c6f42486 --- /dev/null +++ b/browser/components/translations/tests/browser/browser_translations_select_panel_fallback_to_doc_language.js @@ -0,0 +1,38 @@ +/* Any copyright is dedicated to the Public Domain. + https://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * This test case tests the case of opening the SelectTranslationsPanel when the + * detected language is unsupported, but the page language is known to be a supported + * language. The panel should automatically fall back to the page language in an + * effort to combat falsely identified selections. + */ +add_task( + async function test_select_translations_panel_translate_sentence_on_open() { + const { cleanup, runInPage, resolveDownloads } = await loadTestPage({ + page: SELECT_TEST_PAGE_URL, + languagePairs: [ + // Do not include French. + { fromLang: "es", toLang: "en" }, + { fromLang: "en", toLang: "es" }, + ], + prefs: [["browser.translations.select.enable", true]], + }); + + await SelectTranslationsTestUtils.openPanel(runInPage, { + selectFrenchSentence: true, + openAtFrenchSentence: true, + // French is not supported, but the page is in Spanish, so expect Spanish. + expectedFromLanguage: "es", + expectedToLanguage: "en", + downloadHandler: resolveDownloads, + onOpenPanel: SelectTranslationsTestUtils.assertPanelViewTranslated, + }); + + await SelectTranslationsTestUtils.clickDoneButton(); + + await cleanup(); + } +); diff --git a/browser/components/translations/tests/browser/browser_translations_select_panel_language_selectors.js b/browser/components/translations/tests/browser/browser_translations_select_panel_language_selectors.js deleted file mode 100644 index 1dcc76450f..0000000000 --- a/browser/components/translations/tests/browser/browser_translations_select_panel_language_selectors.js +++ /dev/null @@ -1,54 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - http://creativecommons.org/publicdomain/zero/1.0/ */ - -"use strict"; - -add_task( - async function test_select_translations_panel_open_spanish_language_selectors() { - const { cleanup, runInPage } = await loadTestPage({ - page: SPANISH_PAGE_URL, - languagePairs: LANGUAGE_PAIRS, - prefs: [["browser.translations.select.enable", true]], - }); - - await SelectTranslationsTestUtils.openPanel(runInPage, { - selectSpanishParagraph: true, - openAtSpanishParagraph: true, - expectedTargetLanguage: "en", - onOpenPanel: SelectTranslationsTestUtils.assertPanelViewDefault, - }); - - SelectTranslationsTestUtils.assertSelectedFromLanguage({ langTag: "es" }); - SelectTranslationsTestUtils.assertSelectedToLanguage({ langTag: "en" }); - - await SelectTranslationsTestUtils.clickDoneButton(); - - await cleanup(); - } -); - -add_task( - async function test_select_translations_panel_open_english_language_selectors() { - const { cleanup, runInPage } = await loadTestPage({ - page: ENGLISH_PAGE_URL, - languagePairs: LANGUAGE_PAIRS, - prefs: [["browser.translations.select.enable", true]], - }); - - await SelectTranslationsTestUtils.openPanel(runInPage, { - selectFirstParagraph: true, - openAtFirstParagraph: true, - expectedTargetLanguage: "en", - onOpenPanel: SelectTranslationsTestUtils.assertPanelViewDefault, - }); - - SelectTranslationsTestUtils.assertSelectedFromLanguage({ langTag: "en" }); - SelectTranslationsTestUtils.assertSelectedToLanguage({ - l10nId: "translations-panel-choose-language", - }); - - await SelectTranslationsTestUtils.clickDoneButton(); - - await cleanup(); - } -); diff --git a/browser/components/translations/tests/browser/browser_translations_select_panel_mainview_ui.js b/browser/components/translations/tests/browser/browser_translations_select_panel_mainview_ui.js deleted file mode 100644 index 79d21e57d0..0000000000 --- a/browser/components/translations/tests/browser/browser_translations_select_panel_mainview_ui.js +++ /dev/null @@ -1,36 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - http://creativecommons.org/publicdomain/zero/1.0/ */ - -"use strict"; - -/** - * This test case verifies the visibility and initial state of UI elements within the - * Select Translations Panel's main-view UI. - */ -add_task( - async function test_select_translations_panel_mainview_ui_element_visibility() { - const { cleanup, runInPage } = await loadTestPage({ - page: SPANISH_PAGE_URL, - languagePairs: LANGUAGE_PAIRS, - prefs: [["browser.translations.select.enable", true]], - }); - - await FullPageTranslationsTestUtils.assertTranslationsButton( - { button: true, circleArrows: false, locale: false, icon: true }, - "The button is available." - ); - - await FullPageTranslationsTestUtils.assertPageIsUntranslated(runInPage); - - await SelectTranslationsTestUtils.openPanel(runInPage, { - selectSpanishParagraph: true, - openAtSpanishParagraph: true, - expectedTargetLanguage: "es", - onOpenPanel: SelectTranslationsTestUtils.assertPanelViewDefault, - }); - - await SelectTranslationsTestUtils.clickDoneButton(); - - await cleanup(); - } -); diff --git a/browser/components/translations/tests/browser/browser_translations_select_panel_open_to_idle_state.js b/browser/components/translations/tests/browser/browser_translations_select_panel_open_to_idle_state.js new file mode 100644 index 0000000000..d5a1096e70 --- /dev/null +++ b/browser/components/translations/tests/browser/browser_translations_select_panel_open_to_idle_state.js @@ -0,0 +1,61 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * This test case verifies the select translations panel's functionality when opened with an unsupported + * from-language, ensuring it opens with the correct view with no from-language selected. + */ +add_task( + async function test_select_translations_panel_open_no_selected_from_lang() { + const { cleanup, runInPage } = await loadTestPage({ + page: SELECT_TEST_PAGE_URL, + languagePairs: [ + // Do not include Spanish. + { fromLang: "fr", toLang: "en" }, + { fromLang: "en", toLang: "fr" }, + ], + prefs: [["browser.translations.select.enable", true]], + }); + + await SelectTranslationsTestUtils.openPanel(runInPage, { + selectSpanishSentence: true, + openAtSpanishSentence: true, + expectedFromLanguage: null, + expectedToLanguage: "en", + onOpenPanel: + SelectTranslationsTestUtils.assertPanelViewNoFromLangSelected, + }); + + await SelectTranslationsTestUtils.clickDoneButton(); + + await cleanup(); + } +); + +/** + * This test case verifies the select translations panel's functionality when opened with an undetermined + * to-language, ensuring it opens with the correct view with no to-language selected. + */ +add_task( + async function test_select_translations_panel_open_no_selected_to_lang() { + const { cleanup, runInPage } = await loadTestPage({ + page: SELECT_TEST_PAGE_URL, + languagePairs: LANGUAGE_PAIRS, + prefs: [["browser.translations.select.enable", true]], + }); + + await SelectTranslationsTestUtils.openPanel(runInPage, { + selectEnglishSentence: true, + openAtEnglishSentence: true, + expectedFromLanguage: "en", + expectedToLanguage: null, + onOpenPanel: SelectTranslationsTestUtils.assertPanelViewNoToLangSelected, + }); + + await SelectTranslationsTestUtils.clickDoneButton(); + + await cleanup(); + } +); diff --git a/browser/components/translations/tests/browser/browser_translations_select_panel_retranslate_on_change_language_directly.js b/browser/components/translations/tests/browser/browser_translations_select_panel_retranslate_on_change_language_directly.js new file mode 100644 index 0000000000..fff0326f75 --- /dev/null +++ b/browser/components/translations/tests/browser/browser_translations_select_panel_retranslate_on_change_language_directly.js @@ -0,0 +1,70 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * This test case verifies the behavior of triggering a translation by directly switching + * the from-language when the panel is already in the "translated" state from a previous + * language pair. + */ +add_task( + async function test_select_translations_panel_retranslate_on_change_from_language_directly() { + const { cleanup, runInPage, resolveDownloads } = await loadTestPage({ + page: SELECT_TEST_PAGE_URL, + languagePairs: LANGUAGE_PAIRS, + prefs: [["browser.translations.select.enable", true]], + }); + + await SelectTranslationsTestUtils.openPanel(runInPage, { + selectFrenchSentence: true, + openAtFrenchSentence: true, + expectedFromLanguage: "fr", + expectedToLanguage: "en", + downloadHandler: resolveDownloads, + onOpenPanel: SelectTranslationsTestUtils.assertPanelViewTranslated, + }); + + await SelectTranslationsTestUtils.changeSelectedFromLanguage(["es"], { + openDropdownMenu: false, + pivotTranslation: true, + downloadHandler: resolveDownloads, + onChangeLanguage: SelectTranslationsTestUtils.assertPanelViewTranslated, + }); + + await cleanup(); + } +); + +/** + * This test case verifies the behavior of triggering a translation by directly switching + * the to-language when the panel is already in the "translated" state from a previous + * language pair. + */ +add_task( + async function test_select_translations_panel_retranslate_on_change_to_language_directly() { + const { cleanup, runInPage, resolveDownloads } = await loadTestPage({ + page: SELECT_TEST_PAGE_URL, + languagePairs: LANGUAGE_PAIRS, + prefs: [["browser.translations.select.enable", true]], + }); + + await SelectTranslationsTestUtils.openPanel(runInPage, { + selectFrenchSentence: true, + openAtFrenchSentence: true, + expectedFromLanguage: "fr", + expectedToLanguage: "en", + downloadHandler: resolveDownloads, + onOpenPanel: SelectTranslationsTestUtils.assertPanelViewTranslated, + }); + + await SelectTranslationsTestUtils.changeSelectedToLanguage(["es"], { + openDropdownMenu: false, + pivotTranslation: true, + downloadHandler: resolveDownloads, + onChangeLanguage: SelectTranslationsTestUtils.assertPanelViewTranslated, + }); + + await cleanup(); + } +); diff --git a/browser/components/translations/tests/browser/browser_translations_select_panel_retranslate_on_change_language_from_dropdown_menu.js b/browser/components/translations/tests/browser/browser_translations_select_panel_retranslate_on_change_language_from_dropdown_menu.js new file mode 100644 index 0000000000..16f2cb39f7 --- /dev/null +++ b/browser/components/translations/tests/browser/browser_translations_select_panel_retranslate_on_change_language_from_dropdown_menu.js @@ -0,0 +1,68 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * This test case verifies the behavior of triggering a translation by switching the + * from-language by opening the language dropdown menu when the panel is already in + * the "translated" state from a previous language pair. + */ +add_task( + async function test_select_translations_panel_retranslate_on_change_from_language_via_popup() { + const { cleanup, runInPage, resolveDownloads } = await loadTestPage({ + page: SELECT_TEST_PAGE_URL, + languagePairs: LANGUAGE_PAIRS, + prefs: [["browser.translations.select.enable", true]], + }); + + await SelectTranslationsTestUtils.openPanel(runInPage, { + openAtSpanishHyperlink: true, + expectedFromLanguage: "es", + expectedToLanguage: "en", + downloadHandler: resolveDownloads, + onOpenPanel: SelectTranslationsTestUtils.assertPanelViewTranslated, + }); + + await SelectTranslationsTestUtils.changeSelectedFromLanguage(["uk"], { + openDropdownMenu: true, + pivotTranslation: true, + downloadHandler: resolveDownloads, + onChangeLanguage: SelectTranslationsTestUtils.assertPanelViewTranslated, + }); + + await cleanup(); + } +); + +/** + * This test case verifies the behavior of triggering a translation by switching the + * to-language by opening the language dropdown menu when the panel is already in + * the "translated" state from a previous language pair. + */ +add_task( + async function test_select_translations_panel_retranslate_on_change_to_language_via_popup() { + const { cleanup, runInPage, resolveDownloads } = await loadTestPage({ + page: SELECT_TEST_PAGE_URL, + languagePairs: LANGUAGE_PAIRS, + prefs: [["browser.translations.select.enable", true]], + }); + + await SelectTranslationsTestUtils.openPanel(runInPage, { + openAtSpanishHyperlink: true, + expectedFromLanguage: "es", + expectedToLanguage: "en", + downloadHandler: resolveDownloads, + onOpenPanel: SelectTranslationsTestUtils.assertPanelViewTranslated, + }); + + await SelectTranslationsTestUtils.changeSelectedToLanguage(["uk"], { + openDropdownMenu: true, + pivotTranslation: true, + downloadHandler: resolveDownloads, + onChangeLanguage: SelectTranslationsTestUtils.assertPanelViewTranslated, + }); + + await cleanup(); + } +); diff --git a/browser/components/translations/tests/browser/browser_translations_select_panel_select_current_language_directly.js b/browser/components/translations/tests/browser/browser_translations_select_panel_select_current_language_directly.js new file mode 100644 index 0000000000..f45326800f --- /dev/null +++ b/browser/components/translations/tests/browser/browser_translations_select_panel_select_current_language_directly.js @@ -0,0 +1,71 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * This test case verifies the behavior of directly switching the from-language to the same + * from-language that is already selected, ensuring no change occurs to the translation state, + * and that no re-translation is triggered. + */ +add_task( + async function test_select_translations_panel_select_current_from_language_directly() { + const { cleanup, runInPage, resolveDownloads } = await loadTestPage({ + page: SELECT_TEST_PAGE_URL, + languagePairs: LANGUAGE_PAIRS, + prefs: [["browser.translations.select.enable", true]], + }); + + await SelectTranslationsTestUtils.openPanel(runInPage, { + selectSpanishSentence: true, + openAtSpanishSentence: true, + expectedFromLanguage: "es", + expectedToLanguage: "en", + downloadHandler: resolveDownloads, + onOpenPanel: SelectTranslationsTestUtils.assertPanelViewTranslated, + }); + + await SelectTranslationsTestUtils.changeSelectedFromLanguage(["es"], { + openDropdownMenu: false, + // No downloads are resolved, because no re-translation is triggered. + onChangeLanguage: SelectTranslationsTestUtils.assertPanelViewTranslated, + }); + + await SelectTranslationsTestUtils.clickDoneButton(); + + await cleanup(); + } +); + +/** + * This test case verifies the behavior of directly switching the to-language to the same + * to-language that is already selected, ensuring no change occurs to the translation state, + * and that no re-translation is triggered. + */ +add_task( + async function test_select_translations_panel_select_current_from_language_directly() { + const { cleanup, runInPage, resolveDownloads } = await loadTestPage({ + page: SELECT_TEST_PAGE_URL, + languagePairs: LANGUAGE_PAIRS, + prefs: [["browser.translations.select.enable", true]], + }); + + await SelectTranslationsTestUtils.openPanel(runInPage, { + openAtFrenchHyperlink: true, + expectedFromLanguage: "fr", + expectedToLanguage: "en", + downloadHandler: resolveDownloads, + onOpenPanel: SelectTranslationsTestUtils.assertPanelViewTranslated, + }); + + await SelectTranslationsTestUtils.changeSelectedToLanguage(["en"], { + openDropdownMenu: false, + // No downloads are resolved, because no re-translation is triggered. + onChangeLanguage: SelectTranslationsTestUtils.assertPanelViewTranslated, + }); + + await SelectTranslationsTestUtils.clickDoneButton(); + + await cleanup(); + } +); diff --git a/browser/components/translations/tests/browser/browser_translations_select_panel_select_current_language_from_dropdown_menu.js b/browser/components/translations/tests/browser/browser_translations_select_panel_select_current_language_from_dropdown_menu.js new file mode 100644 index 0000000000..04aa731cf2 --- /dev/null +++ b/browser/components/translations/tests/browser/browser_translations_select_panel_select_current_language_from_dropdown_menu.js @@ -0,0 +1,71 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * This test case verifies the behavior of directly switching the from-language to the same + * from-language that is already selected by opening the language dropdown menu, + * ensuring no change occurs to the translation state, and that no re-translation is triggered. + */ +add_task( + async function test_select_translations_panel_select_current_from_language_directly() { + const { cleanup, runInPage, resolveDownloads } = await loadTestPage({ + page: SELECT_TEST_PAGE_URL, + languagePairs: LANGUAGE_PAIRS, + prefs: [["browser.translations.select.enable", true]], + }); + + await SelectTranslationsTestUtils.openPanel(runInPage, { + selectSpanishSentence: true, + openAtSpanishSentence: true, + expectedFromLanguage: "es", + expectedToLanguage: "en", + downloadHandler: resolveDownloads, + onOpenPanel: SelectTranslationsTestUtils.assertPanelViewTranslated, + }); + + await SelectTranslationsTestUtils.changeSelectedFromLanguage(["es"], { + openDropdownMenu: true, + // No downloads are resolved, because no re-translation is triggered. + onChangeLanguage: SelectTranslationsTestUtils.assertPanelViewTranslated, + }); + + await SelectTranslationsTestUtils.clickDoneButton(); + + await cleanup(); + } +); + +/** + * This test case verifies the behavior of directly switching the to-language to the same + * to-language that is already selected by opening the language dropdown menu, + * ensuring no change occurs to the translation state, and that no re-translation is triggered. + */ +add_task( + async function test_select_translations_panel_select_current_from_language_directly() { + const { cleanup, runInPage, resolveDownloads } = await loadTestPage({ + page: SELECT_TEST_PAGE_URL, + languagePairs: LANGUAGE_PAIRS, + prefs: [["browser.translations.select.enable", true]], + }); + + await SelectTranslationsTestUtils.openPanel(runInPage, { + openAtFrenchHyperlink: true, + expectedFromLanguage: "fr", + expectedToLanguage: "en", + downloadHandler: resolveDownloads, + onOpenPanel: SelectTranslationsTestUtils.assertPanelViewTranslated, + }); + + await SelectTranslationsTestUtils.changeSelectedToLanguage(["en"], { + openDropdownMenu: true, + // No downloads are resolved, because no re-translation is triggered. + onChangeLanguage: SelectTranslationsTestUtils.assertPanelViewTranslated, + }); + + await SelectTranslationsTestUtils.clickDoneButton(); + + await cleanup(); + } +); diff --git a/browser/components/translations/tests/browser/browser_translations_select_panel_select_same_from_and_to_languages_directly.js b/browser/components/translations/tests/browser/browser_translations_select_panel_select_same_from_and_to_languages_directly.js new file mode 100644 index 0000000000..95feac6708 --- /dev/null +++ b/browser/components/translations/tests/browser/browser_translations_select_panel_select_same_from_and_to_languages_directly.js @@ -0,0 +1,71 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * This test case verifies the behavior of switching the from-language to the same value + * that is currently selected in the to-language, effectively stealing the to-language's + * value, leaving it unselected and focused. + */ +add_task( + async function test_select_translations_panel_select_same_from_language_directly() { + const { cleanup, runInPage } = await loadTestPage({ + page: SELECT_TEST_PAGE_URL, + languagePairs: [ + // Do not include Spanish. + { fromLang: "fr", toLang: "en" }, + { fromLang: "en", toLang: "fr" }, + ], + prefs: [["browser.translations.select.enable", true]], + }); + + await SelectTranslationsTestUtils.openPanel(runInPage, { + selectSpanishSection: true, + openAtSpanishSection: true, + expectedFromLanguage: null, + expectedToLanguage: "en", + onOpenPanel: + SelectTranslationsTestUtils.assertPanelViewNoFromLangSelected, + }); + + await SelectTranslationsTestUtils.changeSelectedFromLanguage(["en"], { + openDropdownMenu: false, + onChangeLanguage: + SelectTranslationsTestUtils.assertPanelViewNoFromToSelected, + }); + + await cleanup(); + } +); + +/** + * This test case verifies the behavior of switching the to-language to the same value + * that is currently selected in the from-language, effectively stealing the from-language's + * value, leaving it unselected and focused. + */ +add_task( + async function test_select_translations_panel_select_same_to_language_directly() { + const { cleanup, runInPage } = await loadTestPage({ + page: SELECT_TEST_PAGE_URL, + languagePairs: LANGUAGE_PAIRS, + prefs: [["browser.translations.select.enable", true]], + }); + + await SelectTranslationsTestUtils.openPanel(runInPage, { + selectEnglishSection: true, + openAtEnglishSection: true, + expectedFromLanguage: "en", + expectedToLanguage: null, + onOpenPanel: SelectTranslationsTestUtils.assertPanelViewNoToLangSelected, + }); + + await SelectTranslationsTestUtils.changeSelectedToLanguage(["en"], { + openDropdownMenu: false, + onChangeLanguage: + SelectTranslationsTestUtils.assertPanelViewNoFromLangSelected, + }); + + await cleanup(); + } +); diff --git a/browser/components/translations/tests/browser/browser_translations_select_panel_select_same_from_and_to_languages_from_dropdown_menu.js b/browser/components/translations/tests/browser/browser_translations_select_panel_select_same_from_and_to_languages_from_dropdown_menu.js new file mode 100644 index 0000000000..5c27be411f --- /dev/null +++ b/browser/components/translations/tests/browser/browser_translations_select_panel_select_same_from_and_to_languages_from_dropdown_menu.js @@ -0,0 +1,71 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * This test case verifies the behavior of switching the from-language to the same value + * that is currently selected in the to-language by opening the language dropdown menu, + * effectively stealing the to-language's value, leaving it unselected and focused. + */ +add_task( + async function test_select_translations_panel_select_same_from_language_via_popup() { + const { cleanup, runInPage } = await loadTestPage({ + page: SELECT_TEST_PAGE_URL, + languagePairs: [ + // Do not include Spanish. + { fromLang: "fr", toLang: "en" }, + { fromLang: "en", toLang: "fr" }, + ], + prefs: [["browser.translations.select.enable", true]], + }); + + await SelectTranslationsTestUtils.openPanel(runInPage, { + selectSpanishSection: true, + openAtSpanishSection: true, + expectedFromLanguage: null, + expectedToLanguage: "en", + onOpenPanel: + SelectTranslationsTestUtils.assertPanelViewNoFromLangSelected, + }); + + await SelectTranslationsTestUtils.changeSelectedFromLanguage(["en"], { + openDropdownMenu: true, + onChangeLanguage: + SelectTranslationsTestUtils.assertPanelViewNoFromToSelected, + }); + + await cleanup(); + } +); + +/** + * This test case verifies the behavior of switching the to-language to the same value + * that is currently selected in the from-language by opening the language dropdown menu, + * effectively stealing the from-language's value, leaving it unselected and focused. + */ +add_task( + async function test_select_translations_panel_select_same_to_language_via_popup() { + const { cleanup, runInPage } = await loadTestPage({ + page: SELECT_TEST_PAGE_URL, + languagePairs: LANGUAGE_PAIRS, + prefs: [["browser.translations.select.enable", true]], + }); + + await SelectTranslationsTestUtils.openPanel(runInPage, { + selectEnglishSection: true, + openAtEnglishSection: true, + expectedFromLanguage: "en", + expectedToLanguage: null, + onOpenPanel: SelectTranslationsTestUtils.assertPanelViewNoToLangSelected, + }); + + await SelectTranslationsTestUtils.changeSelectedToLanguage(["en"], { + openDropdownMenu: true, + onChangeLanguage: + SelectTranslationsTestUtils.assertPanelViewNoFromLangSelected, + }); + + await cleanup(); + } +); diff --git a/browser/components/translations/tests/browser/browser_translations_select_panel_translate_on_change_language_directly.js b/browser/components/translations/tests/browser/browser_translations_select_panel_translate_on_change_language_directly.js new file mode 100644 index 0000000000..64d067d1f4 --- /dev/null +++ b/browser/components/translations/tests/browser/browser_translations_select_panel_translate_on_change_language_directly.js @@ -0,0 +1,71 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * This test case verifies the behavior of triggering a translation by directly switching + * the from-language to a valid selection when the panel is in the "idle" state without + * valid language pair. + */ +add_task( + async function test_select_translations_panel_translate_on_change_from_language_directly() { + const { cleanup, runInPage, resolveDownloads } = await loadTestPage({ + page: SELECT_TEST_PAGE_URL, + languagePairs: [ + // Do not include Spanish. + { fromLang: "fr", toLang: "en" }, + { fromLang: "en", toLang: "fr" }, + ], + prefs: [["browser.translations.select.enable", true]], + }); + + await SelectTranslationsTestUtils.openPanel(runInPage, { + selectSpanishSection: true, + openAtSpanishSection: true, + expectedFromLanguage: null, + expectedToLanguage: "en", + onOpenPanel: + SelectTranslationsTestUtils.assertPanelViewNoFromLangSelected, + }); + + await SelectTranslationsTestUtils.changeSelectedFromLanguage(["fr"], { + openDropdownMenu: false, + downloadHandler: resolveDownloads, + onChangeLanguage: SelectTranslationsTestUtils.assertPanelViewTranslated, + }); + + await cleanup(); + } +); + +/** + * This test case verifies the behavior of triggering a translation by directly switching + * the to-language to a valid selection when the panel is in the "idle" state without + * valid language pair. + */ +add_task( + async function test_select_translations_panel_translate_on_change_to_language_directly() { + const { cleanup, runInPage, resolveDownloads } = await loadTestPage({ + page: SELECT_TEST_PAGE_URL, + languagePairs: LANGUAGE_PAIRS, + prefs: [["browser.translations.select.enable", true]], + }); + + await SelectTranslationsTestUtils.openPanel(runInPage, { + selectEnglishSection: true, + openAtEnglishSection: true, + expectedFromLanguage: "en", + expectedToLanguage: null, + onOpenPanel: SelectTranslationsTestUtils.assertPanelViewNoToLangSelected, + }); + + await SelectTranslationsTestUtils.changeSelectedToLanguage(["es"], { + openDropdownMenu: false, + downloadHandler: resolveDownloads, + onChangeLanguage: SelectTranslationsTestUtils.assertPanelViewTranslated, + }); + + await cleanup(); + } +); diff --git a/browser/components/translations/tests/browser/browser_translations_select_panel_translate_on_change_language_from_dropdown_menu.js b/browser/components/translations/tests/browser/browser_translations_select_panel_translate_on_change_language_from_dropdown_menu.js new file mode 100644 index 0000000000..0cd205d721 --- /dev/null +++ b/browser/components/translations/tests/browser/browser_translations_select_panel_translate_on_change_language_from_dropdown_menu.js @@ -0,0 +1,71 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * This test case verifies the behavior of triggering a translation by switching the + * from-language to a valid selection by opening the language dropdown when the panel + * is in the "idle" state without valid language pair. + */ +add_task( + async function test_select_translations_panel_translate_on_change_from_language() { + const { cleanup, runInPage, resolveDownloads } = await loadTestPage({ + page: SELECT_TEST_PAGE_URL, + languagePairs: [ + // Do not include Spanish. + { fromLang: "fr", toLang: "en" }, + { fromLang: "en", toLang: "fr" }, + ], + prefs: [["browser.translations.select.enable", true]], + }); + + await SelectTranslationsTestUtils.openPanel(runInPage, { + selectSpanishSection: true, + openAtSpanishSection: true, + expectedFromLanguage: null, + expectedToLanguage: "en", + onOpenPanel: + SelectTranslationsTestUtils.assertPanelViewNoFromLangSelected, + }); + + await SelectTranslationsTestUtils.changeSelectedFromLanguage(["fr"], { + openDropdownMenu: true, + downloadHandler: resolveDownloads, + onChangeLanguage: SelectTranslationsTestUtils.assertPanelViewTranslated, + }); + + await cleanup(); + } +); + +/** + * This test case verifies the behavior of triggering a translation by switching the + * to-language to a valid selection by opening the language dropdown when the panel + * is in the "idle" state without valid language pair. + */ +add_task( + async function test_select_translations_panel_translate_on_change_to_language() { + const { cleanup, runInPage, resolveDownloads } = await loadTestPage({ + page: SELECT_TEST_PAGE_URL, + languagePairs: LANGUAGE_PAIRS, + prefs: [["browser.translations.select.enable", true]], + }); + + await SelectTranslationsTestUtils.openPanel(runInPage, { + selectEnglishSection: true, + openAtEnglishSection: true, + expectedFromLanguage: "en", + expectedToLanguage: null, + onOpenPanel: SelectTranslationsTestUtils.assertPanelViewNoToLangSelected, + }); + + await SelectTranslationsTestUtils.changeSelectedToLanguage(["es"], { + openDropdownMenu: true, + downloadHandler: resolveDownloads, + onChangeLanguage: SelectTranslationsTestUtils.assertPanelViewTranslated, + }); + + await cleanup(); + } +); diff --git a/browser/components/translations/tests/browser/browser_translations_select_panel_translate_on_change_language_multiple_times_directly.js b/browser/components/translations/tests/browser/browser_translations_select_panel_translate_on_change_language_multiple_times_directly.js new file mode 100644 index 0000000000..b3c02a96f6 --- /dev/null +++ b/browser/components/translations/tests/browser/browser_translations_select_panel_translate_on_change_language_multiple_times_directly.js @@ -0,0 +1,97 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * This test case verifies the behavior of directly changing the from-language in rapid succession, + * ensuring that any triggered translations are resolved/dropped in order, and that the final translated + * state matches the final selected language. + */ +add_task( + async function test_select_translations_panel_translate_on_change_from_language_multiple_times_directly() { + const { cleanup, runInPage, resolveDownloads } = await loadTestPage({ + page: SELECT_TEST_PAGE_URL, + languagePairs: [ + // Do not include Spanish. + { fromLang: "fa", toLang: "en" }, + { fromLang: "en", toLang: "fa" }, + { fromLang: "fi", toLang: "en" }, + { fromLang: "en", toLang: "fi" }, + { fromLang: "fr", toLang: "en" }, + { fromLang: "en", toLang: "fr" }, + { fromLang: "sl", toLang: "en" }, + { fromLang: "en", toLang: "sl" }, + { fromLang: "uk", toLang: "en" }, + { fromLang: "en", toLang: "uk" }, + ], + prefs: [["browser.translations.select.enable", true]], + }); + + await SelectTranslationsTestUtils.openPanel(runInPage, { + selectSpanishSentence: true, + openAtSpanishSentence: true, + expectedFromLanguage: null, + expectedToLanguage: "en", + onOpenPanel: + SelectTranslationsTestUtils.assertPanelViewNoFromLangSelected, + }); + + await SelectTranslationsTestUtils.changeSelectedFromLanguage( + ["fa", "fi", "fr", "sl", "uk"], + { + openDropdownMenu: false, + downloadHandler: resolveDownloads, + onChangeLanguage: SelectTranslationsTestUtils.assertPanelViewTranslated, + } + ); + + await cleanup(); + } +); + +/** + * This test case verifies the behavior of directly changing the to-language in rapid succession, + * ensuring that any triggered translations are resolved/dropped in order, and that the final translated + * state matches the final selected language. + */ +add_task( + async function test_select_translations_panel_translate_on_change_to_language_multiple_times_directly() { + const { cleanup, runInPage, resolveDownloads } = await loadTestPage({ + page: SELECT_TEST_PAGE_URL, + languagePairs: [ + { fromLang: "es", toLang: "en" }, + { fromLang: "en", toLang: "es" }, + { fromLang: "fa", toLang: "en" }, + { fromLang: "en", toLang: "fa" }, + { fromLang: "fi", toLang: "en" }, + { fromLang: "en", toLang: "fi" }, + { fromLang: "fr", toLang: "en" }, + { fromLang: "en", toLang: "fr" }, + { fromLang: "sl", toLang: "en" }, + { fromLang: "en", toLang: "sl" }, + { fromLang: "uk", toLang: "en" }, + { fromLang: "en", toLang: "uk" }, + ], + prefs: [["browser.translations.select.enable", true]], + }); + + await SelectTranslationsTestUtils.openPanel(runInPage, { + openAtEnglishHyperlink: true, + expectedFromLanguage: "en", + expectedToLanguage: null, + onOpenPanel: SelectTranslationsTestUtils.assertPanelViewNoToLangSelected, + }); + + await SelectTranslationsTestUtils.changeSelectedToLanguage( + ["es", "fa", "fi", "fr", "sl", "uk", "fa"], + { + openDropdownMenu: false, + downloadHandler: resolveDownloads, + onChangeLanguage: SelectTranslationsTestUtils.assertPanelViewTranslated, + } + ); + + await cleanup(); + } +); diff --git a/browser/components/translations/tests/browser/browser_translations_select_panel_translate_on_change_language_multiple_times_from_dropdown_menu.js b/browser/components/translations/tests/browser/browser_translations_select_panel_translate_on_change_language_multiple_times_from_dropdown_menu.js new file mode 100644 index 0000000000..50c877cfbc --- /dev/null +++ b/browser/components/translations/tests/browser/browser_translations_select_panel_translate_on_change_language_multiple_times_from_dropdown_menu.js @@ -0,0 +1,98 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * This test case verifies the behavior of directly changing the from-language in rapid succession + * by opening the language dropdown menu, ensuring that any triggered translations are resolved/dropped + * in order, and that the final translated state matches the final selected language. + */ +add_task( + async function test_select_translations_panel_translate_on_change_from_language_multiple_times_via_popup() { + const { cleanup, runInPage, resolveDownloads } = await loadTestPage({ + page: SELECT_TEST_PAGE_URL, + languagePairs: [ + // Do not include Spanish. + { fromLang: "fa", toLang: "en" }, + { fromLang: "en", toLang: "fa" }, + { fromLang: "fi", toLang: "en" }, + { fromLang: "en", toLang: "fi" }, + { fromLang: "fr", toLang: "en" }, + { fromLang: "en", toLang: "fr" }, + { fromLang: "sl", toLang: "en" }, + { fromLang: "en", toLang: "sl" }, + { fromLang: "uk", toLang: "en" }, + { fromLang: "en", toLang: "uk" }, + ], + prefs: [["browser.translations.select.enable", true]], + }); + + await SelectTranslationsTestUtils.openPanel(runInPage, { + selectSpanishSentence: true, + openAtSpanishSentence: true, + expectedFromLanguage: null, + expectedToLanguage: "en", + onOpenPanel: + SelectTranslationsTestUtils.assertPanelViewNoFromLangSelected, + }); + + await SelectTranslationsTestUtils.changeSelectedFromLanguage( + ["fa", "fi", "fr", "sl", "uk"], + { + openDropdownMenu: true, + downloadHandler: resolveDownloads, + onChangeLanguage: SelectTranslationsTestUtils.assertPanelViewTranslated, + } + ); + + await cleanup(); + } +); + +/** + * This test case verifies the behavior of directly changing the to-language in rapid succession + * by opening the language dropdown menu, ensuring that any triggered translations are resolved/dropped + * in order, and that the final translated state matches the final selected language. + */ +add_task( + async function test_select_translations_panel_translate_on_change_to_language_multiple_times_via_popup() { + const { cleanup, runInPage, resolveDownloads } = await loadTestPage({ + page: SELECT_TEST_PAGE_URL, + languagePairs: [ + { fromLang: "es", toLang: "en" }, + { fromLang: "en", toLang: "es" }, + { fromLang: "fa", toLang: "en" }, + { fromLang: "en", toLang: "fa" }, + { fromLang: "fi", toLang: "en" }, + { fromLang: "en", toLang: "fi" }, + { fromLang: "fr", toLang: "en" }, + { fromLang: "en", toLang: "fr" }, + { fromLang: "sl", toLang: "en" }, + { fromLang: "en", toLang: "sl" }, + { fromLang: "uk", toLang: "en" }, + { fromLang: "en", toLang: "uk" }, + ], + prefs: [["browser.translations.select.enable", true]], + }); + + await SelectTranslationsTestUtils.openPanel(runInPage, { + selectEnglishSentence: true, + openAtEnglishSentence: true, + expectedFromLanguage: "en", + expectedToLanguage: null, + onOpenPanel: SelectTranslationsTestUtils.assertPanelViewNoToLangSelected, + }); + + await SelectTranslationsTestUtils.changeSelectedToLanguage( + ["es", "fa", "fi", "fr", "sl", "uk"], + { + openDropdownMenu: true, + downloadHandler: resolveDownloads, + onChangeLanguage: SelectTranslationsTestUtils.assertPanelViewTranslated, + } + ); + + await cleanup(); + } +); diff --git a/browser/components/translations/tests/browser/browser_translations_select_panel_translate_on_open.js b/browser/components/translations/tests/browser/browser_translations_select_panel_translate_on_open.js new file mode 100644 index 0000000000..7c7d6d88c9 --- /dev/null +++ b/browser/components/translations/tests/browser/browser_translations_select_panel_translate_on_open.js @@ -0,0 +1,86 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * This test case tests the case of opening the SelectTranslationsPanel to a valid + * language pair from a short selection of text, which should trigger a translation + * on panel open. + */ +add_task( + async function test_select_translations_panel_translate_sentence_on_open() { + const { cleanup, runInPage, resolveDownloads } = await loadTestPage({ + page: SELECT_TEST_PAGE_URL, + languagePairs: LANGUAGE_PAIRS, + prefs: [["browser.translations.select.enable", true]], + }); + + await SelectTranslationsTestUtils.openPanel(runInPage, { + selectFrenchSentence: true, + openAtFrenchSentence: true, + expectedFromLanguage: "fr", + expectedToLanguage: "en", + downloadHandler: resolveDownloads, + onOpenPanel: SelectTranslationsTestUtils.assertPanelViewTranslated, + }); + + await SelectTranslationsTestUtils.clickDoneButton(); + + await cleanup(); + } +); + +/** + * This test case tests the case of opening the SelectTranslationsPanel to a valid + * language pair from hyperlink text, which should trigger a translation on panel open. + */ +add_task( + async function test_select_translations_panel_translate_link_text_on_open() { + const { cleanup, runInPage, resolveDownloads } = await loadTestPage({ + page: SELECT_TEST_PAGE_URL, + languagePairs: LANGUAGE_PAIRS, + prefs: [["browser.translations.select.enable", true]], + }); + + await SelectTranslationsTestUtils.openPanel(runInPage, { + openAtSpanishHyperlink: true, + expectedFromLanguage: "es", + expectedToLanguage: "en", + downloadHandler: resolveDownloads, + onOpenPanel: SelectTranslationsTestUtils.assertPanelViewTranslated, + }); + + await SelectTranslationsTestUtils.clickDoneButton(); + + await cleanup(); + } +); + +/** + * This test case tests the case of opening the SelectTranslationsPanel to a valid + * language pair from a long selection of text, which should trigger a translation + * on panel open. + */ +add_task( + async function test_select_translations_panel_translate_long_text_on_open() { + const { cleanup, runInPage, resolveDownloads } = await loadTestPage({ + page: SELECT_TEST_PAGE_URL, + languagePairs: LANGUAGE_PAIRS, + prefs: [["browser.translations.select.enable", true]], + }); + + await SelectTranslationsTestUtils.openPanel(runInPage, { + selectFrenchSection: true, + openAtFrenchSection: true, + expectedFromLanguage: "fr", + expectedToLanguage: "en", + downloadHandler: resolveDownloads, + onOpenPanel: SelectTranslationsTestUtils.assertPanelViewTranslated, + }); + + await SelectTranslationsTestUtils.clickDoneButton(); + + await cleanup(); + } +); diff --git a/browser/components/translations/tests/browser/head.js b/browser/components/translations/tests/browser/head.js index 200ed08719..454de9146b 100644 --- a/browser/components/translations/tests/browser/head.js +++ b/browser/components/translations/tests/browser/head.js @@ -18,7 +18,7 @@ async function addTab(url) { const tab = await BrowserTestUtils.openNewForegroundTab( gBrowser, url, - true // Wait for laod + true // Wait for load ); return { tab, @@ -65,7 +65,6 @@ function click(element, message) { */ function getAllByL10nId(l10nId, doc = document) { const elements = doc.querySelectorAll(`[data-l10n-id="${l10nId}"]`); - console.log(doc); if (elements.length === 0) { throw new Error("Could not find the element by l10n id: " + l10nId); } @@ -285,6 +284,19 @@ async function toggleReaderMode() { */ class SharedTranslationsTestUtils { /** + * Asserts that the specified element currently has focus. + * + * @param {Element} element - The element to check for focus. + */ + static _assertHasFocus(element) { + is( + document.activeElement, + element, + `The element '${element.id}' should have focus.` + ); + } + + /** * Asserts that the mainViewId of the panel matches the given string. * * @param {FullPageTranslationsPanel | SelectTranslationsPanel} panel @@ -300,53 +312,30 @@ class SharedTranslationsTestUtils { } /** - * Asserts that the selected from-language matches the provided arguments. + * Asserts that the selected language in the menu matches the langTag or l10nId. * - * @param {FullPageTranslationsPanel | SelectTranslationsPanel} panel - * - The UI component or panel whose selected from-language is being asserted. - * @param {object} options - An object containing assertion parameters. - * @param {string} [options.langTag] - A BCP-47 language tag. - * @param {string} [options.l10nId] - A localization identifier. + * @param {Element} menuList - The menu list element to check. + * @param {object} options - Options containing 'langTag' and 'l10nId' to assert against. + * @param {string} [options.langTag] - The BCP-47 language tag to match. + * @param {string} [options.l10nId] - The localization Id to match. */ - static _assertSelectedFromLanguage(panel, { langTag, l10nId }) { - const { fromMenuList } = panel.elements; - is( - fromMenuList.value, - langTag, - "Expected selected from-language to match the given language tag" + static _assertSelectedLanguage(menuList, { langTag, l10nId }) { + ok( + menuList.label, + `The label for the menulist ${menuList.id} should not be empty.` ); - if (l10nId) { - is( - fromMenuList.getAttribute("data-l10n-id"), - l10nId, - "Expected selected from-language to match the given l10n id" - ); - } - } - - /** - * Asserts that the selected to-language matches the provided arguments. - * - * @param {FullPageTranslationsPanel | SelectTranslationsPanel} panel - * - The UI component or panel whose selected from-language is being asserted. - * @param {object} options - An object containing assertion parameters. - * @param {string} [options.langTag] - A BCP-47 language tag. - * @param {string} [options.l10nId] - A localization identifier. - */ - static _assertSelectedToLanguage(panel, { langTag, l10nId }) { - const { toMenuList } = panel.elements; if (langTag) { is( - toMenuList.value, + menuList.value, langTag, - "Expected selected to-language to match the given language tag" + `Expected ${menuList.id} selection to match '${langTag}'` ); } if (l10nId) { is( - toMenuList.getAttribute("data-l10n-id"), + menuList.getAttribute("data-l10n-id"), l10nId, - "Expected selected to-language to match the given l10n id" + `Expected ${menuList.id} l10nId to match '${l10nId}'` ); } } @@ -391,6 +380,7 @@ class SharedTranslationsTestUtils { * This is often used to trigger the event on the expected element. * @param {Function|null} [postEventAssertion=null] - An optional callback function to execute after * the event has occurred. + * @param {ChromeWindow} [win] * @throws Throws if the element with the specified `elementId` does not exist. * @returns {Promise<void>} */ @@ -398,18 +388,21 @@ class SharedTranslationsTestUtils { elementId, eventName, callback, - postEventAssertion = null + postEventAssertion = null, + win = window ) { - const element = document.getElementById(elementId); + const element = win.document.getElementById(elementId); if (!element) { - throw new Error("Unable to find the translations panel element."); + throw new Error( + `Unable to find the ${elementId} element in the document.` + ); } const promise = BrowserTestUtils.waitForEvent(element, eventName); await callback(); - info("Waiting for the translations panel popup to be shown"); + info(`Waiting for the ${elementId} ${eventName} event`); await promise; if (postEventAssertion) { - postEventAssertion(); + await postEventAssertion(); } // Wait a single tick on the event loop. await new Promise(resolve => setTimeout(resolve, 0)); @@ -568,10 +561,12 @@ class FullPageTranslationsTestUtils { * * @param {string} fromLanguage - The BCP-47 language tag being translated from. * @param {string} toLanguage - The BCP-47 language tag being translated into. + * @param {ChromeWindow} win */ - static async #assertLangTagIsShownOnTranslationsButton( + static async assertLangTagIsShownOnTranslationsButton( fromLanguage, - toLanguage + toLanguage, + win = window ) { info( `Ensuring that the translations button displays the language tag "${toLanguage}"` @@ -579,7 +574,8 @@ class FullPageTranslationsTestUtils { const { button, locale } = await FullPageTranslationsTestUtils.assertTranslationsButton( { button: true, circleArrows: false, locale: true, icon: true }, - "The icon presents the locale." + "The icon presents the locale.", + win ); is( locale.innerText, @@ -605,12 +601,14 @@ class FullPageTranslationsTestUtils { * @param {string} toLanguage - The BCP-47 language tag being translated into. * @param {Function} runInPage - Allows running a closure in the content page. * @param {string} message - An optional message to log to info. + * @param {ChromeWindow} [win] */ static async assertPageIsTranslated( fromLanguage, toLanguage, runInPage, - message = null + message = null, + win = window ) { if (message) { info(message); @@ -625,9 +623,10 @@ class FullPageTranslationsTestUtils { ); }; await runInPage(callback, { fromLang: fromLanguage, toLang: toLanguage }); - await FullPageTranslationsTestUtils.#assertLangTagIsShownOnTranslationsButton( + await FullPageTranslationsTestUtils.assertLangTagIsShownOnTranslationsButton( fromLanguage, - toLanguage + toLanguage, + win ); } @@ -668,6 +667,9 @@ class FullPageTranslationsTestUtils { changeSourceLanguageButton: false, dismissErrorButton: false, error: false, + errorMessage: false, + errorMessageHint: false, + errorHintAction: false, fromMenuList: false, fromLabel: false, header: false, @@ -744,6 +746,34 @@ class FullPageTranslationsTestUtils { } /** + * Asserts that panel element visibility matches the initialization-failure view. + */ + static assertPanelViewInitFailure() { + info("Checking that the panel shows the default view"); + const { translateButton } = FullPageTranslationsPanel.elements; + FullPageTranslationsTestUtils.#assertPanelMainViewId( + "full-page-translations-panel-view-default" + ); + FullPageTranslationsTestUtils.#assertPanelElementVisibility({ + cancelButton: true, + error: true, + errorMessage: true, + errorMessageHint: true, + errorHintAction: true, + header: true, + translateButton: true, + }); + is( + translateButton.disabled, + true, + "The translate button should be disabled." + ); + FullPageTranslationsTestUtils.#assertPanelHeaderL10nId( + "translations-panel-header" + ); + } + + /** * Asserts that panel element visibility matches the panel error view. */ static assertPanelViewError() { @@ -753,6 +783,7 @@ class FullPageTranslationsTestUtils { ); FullPageTranslationsTestUtils.#assertPanelElementVisibility({ error: true, + errorMessage: true, ...FullPageTranslationsTestUtils.#defaultViewVisibilityExpectations, }); FullPageTranslationsTestUtils.#assertPanelHeaderL10nId( @@ -854,25 +885,31 @@ class FullPageTranslationsTestUtils { /** * Asserts that the selected from-language matches the provided language tag. * - * @param {string} langTag - A BCP-47 language tag. + * @param {object} options - Options containing 'langTag' and 'l10nId' to assert against. + * @param {string} [options.langTag] - The BCP-47 language tag to match. + * @param {string} [options.l10nId] - The localization Id to match. */ static assertSelectedFromLanguage({ langTag, l10nId }) { - SharedTranslationsTestUtils._assertSelectedFromLanguage( - FullPageTranslationsPanel, - { langTag, l10nId } - ); + const { fromMenuList } = FullPageTranslationsPanel.elements; + SharedTranslationsTestUtils._assertSelectedLanguage(fromMenuList, { + langTag, + l10nId, + }); } /** * Asserts that the selected to-language matches the provided language tag. * - * @param {string} langTag - A BCP-47 language tag. + * @param {object} options - Options containing 'langTag' and 'l10nId' to assert against. + * @param {string} [options.langTag] - The BCP-47 language tag to match. + * @param {string} [options.l10nId] - The localization Id to match. */ static assertSelectedToLanguage({ langTag, l10nId }) { - SharedTranslationsTestUtils._assertSelectedToLanguage( - FullPageTranslationsPanel, - { langTag, l10nId } - ); + const { toMenuList } = FullPageTranslationsPanel.elements; + SharedTranslationsTestUtils._assertSelectedLanguage(toMenuList, { + langTag, + l10nId, + }); } /** @@ -880,16 +917,21 @@ class FullPageTranslationsTestUtils { * * @param {Record<string, boolean>} visibleAssertions * @param {string} message The message for the assertion. + * @param {ChromeWindow} [win] * @returns {HTMLElement} */ - static async assertTranslationsButton(visibleAssertions, message) { + static async assertTranslationsButton( + visibleAssertions, + message, + win = window + ) { const elements = { - button: document.getElementById("translations-button"), - icon: document.getElementById("translations-button-icon"), - circleArrows: document.getElementById( + button: win.document.getElementById("translations-button"), + icon: win.document.getElementById("translations-button-icon"), + circleArrows: win.document.getElementById( "translations-button-circle-arrows" ), - locale: document.getElementById("translations-button-locale"), + locale: win.document.getElementById("translations-button-locale"), }; for (const [name, element] of Object.entries(elements)) { @@ -1083,25 +1125,31 @@ class FullPageTranslationsTestUtils { * @param {boolean} config.pivotTranslation * - True if the expected translation is a pivot translation, otherwise false. * Affects the number of expected downloads. + * @param {ChromeWindow} [config.win] + * - An optional ChromeWindow, for multi-window tests. */ static async clickTranslateButton({ downloadHandler = null, pivotTranslation = false, + win = window, } = {}) { logAction(); - const { translateButton } = FullPageTranslationsPanel.elements; + const { translateButton } = win.FullPageTranslationsPanel.elements; assertVisibility({ visible: { translateButton } }); await FullPageTranslationsTestUtils.waitForPanelPopupEvent( "popuphidden", () => { click(translateButton); - } + }, + null /* postEventAssertion */, + win ); if (downloadHandler) { await FullPageTranslationsTestUtils.assertTranslationsButton( { button: true, circleArrows: true, locale: false, icon: true }, - "The icon presents the loading indicator." + "The icon presents the loading indicator.", + win ); await downloadHandler(pivotTranslation ? 2 : 1); } @@ -1117,21 +1165,26 @@ class FullPageTranslationsTestUtils { * - Open the panel from the app menu. If false, uses the translations button. * @param {boolean} config.openWithKeyboard * - Open the panel by synthesizing the keyboard. If false, synthesizes the mouse. + * @param {ChromeWindow} [config.win] + * - An optional window for multi-window tests. */ static async openPanel({ onOpenPanel = null, openFromAppMenu = false, openWithKeyboard = false, + win = window, }) { logAction(); - await closeAllOpenPanelsAndMenus(); + await closeAllOpenPanelsAndMenus(win); if (openFromAppMenu) { await FullPageTranslationsTestUtils.#openPanelViaAppMenu({ + win, onOpenPanel, openWithKeyboard, }); } else { await FullPageTranslationsTestUtils.#openPanelViaTranslationsButton({ + win, onOpenPanel, openWithKeyboard, }); @@ -1146,21 +1199,26 @@ class FullPageTranslationsTestUtils { * - A function to run as soon as the panel opens. * @param {boolean} config.openWithKeyboard * - Open the panel by synthesizing the keyboard. If false, synthesizes the mouse. + * @param {ChromeWindow} [config.win] */ static async #openPanelViaAppMenu({ onOpenPanel = null, openWithKeyboard = false, + win = window, }) { logAction(); - const appMenuButton = getById("PanelUI-menu-button"); + const appMenuButton = getById("PanelUI-menu-button", win.document); if (openWithKeyboard) { hitEnterKey(appMenuButton, "Opening the app-menu button with keyboard"); } else { click(appMenuButton, "Opening the app-menu button"); } - await BrowserTestUtils.waitForEvent(window.PanelUI.mainView, "ViewShown"); + await BrowserTestUtils.waitForEvent(win.PanelUI.mainView, "ViewShown"); - const translateSiteButton = getById("appMenu-translate-button"); + const translateSiteButton = getById( + "appMenu-translate-button", + win.document + ); is( translateSiteButton.disabled, @@ -1189,16 +1247,19 @@ class FullPageTranslationsTestUtils { * - A function to run as soon as the panel opens. * @param {boolean} config.openWithKeyboard * - Open the panel by synthesizing the keyboard. If false, synthesizes the mouse. + * @param {ChromeWindow} [config.win] */ static async #openPanelViaTranslationsButton({ onOpenPanel = null, openWithKeyboard = false, + win = window, }) { logAction(); const { button } = await FullPageTranslationsTestUtils.assertTranslationsButton( { button: true }, - "The translations button is visible." + "The translations button is visible.", + win ); await FullPageTranslationsTestUtils.waitForPanelPopupEvent( "popupshown", @@ -1209,7 +1270,8 @@ class FullPageTranslationsTestUtils { click(button, "Opening the popup"); } }, - onOpenPanel + onOpenPanel, + win ); } @@ -1242,7 +1304,7 @@ class FullPageTranslationsTestUtils { * * @param {string} langTag - A BCP-47 language tag. */ - static switchSelectedFromLanguage(langTag) { + static changeSelectedFromLanguage(langTag) { logAction(langTag); const { fromMenuList } = FullPageTranslationsPanel.elements; fromMenuList.value = langTag; @@ -1254,7 +1316,7 @@ class FullPageTranslationsTestUtils { * * @param {string} langTag - A BCP-47 language tag. */ - static switchSelectedToLanguage(langTag) { + static changeSelectedToLanguage(langTag) { logAction(langTag); const { toMenuList } = FullPageTranslationsPanel.elements; toMenuList.value = langTag; @@ -1270,20 +1332,23 @@ class FullPageTranslationsTestUtils { * @param {Function} callback * @param {Function} postEventAssertion * An optional assertion to be made immediately after the event occurs. + * @param {ChromeWindow} [win] * @returns {Promise<void>} */ static async waitForPanelPopupEvent( eventName, callback, - postEventAssertion = null + postEventAssertion = null, + win = window ) { // De-lazify the panel elements. - FullPageTranslationsPanel.elements; + win.FullPageTranslationsPanel.elements; await SharedTranslationsTestUtils._waitForPopupEvent( "full-page-translations-panel", eventName, callback, - postEventAssertion + postEventAssertion, + win ); } } @@ -1297,31 +1362,47 @@ class SelectTranslationsTestUtils { * * @param {Function} runInPage - A content-exposed function to run within the context of the page. * @param {object} options - Options for how to open the context menu and what properties to assert about the translate-selection item. - * @param {boolean} options.selectFirstParagraph - Selects the first paragraph before opening the context menu. - * @param {boolean} options.selectSpanishParagraph - Selects the Spanish paragraph before opening the context menu. - * This is only available in SPANISH_TEST_PAGE. - * @param {boolean} options.expectMenuItemIsVisible - Whether the translate-selection item is expected to be visible. - * Does not assert visibility if left undefined. - * @param {string} options.expectedTargetLanguage - The target language for translation. - * @param {boolean} options.openAtFirstParagraph - Opens the context menu at the first paragraph in the test page. - * @param {boolean} options.openAtSpanishParagraph - Opens the context menu at the Spanish paragraph in the test page. - * This is only available in SPANISH_TEST_PAGE. - * @param {boolean} options.openAtEnglishHyperlink - Opens the context menu at the English hyperlink in the test page. - * This is only available in SPANISH_TEST_PAGE. - * @param {boolean} options.openAtSpanishHyperlink - Opens the context menu at the Spanish hyperlink in the test page. - * This is only available in SPANISH_TEST_PAGE. + * + * The following options will only work when testing SELECT_TEST_PAGE_URL. + * + * @param {boolean} options.expectMenuItemVisible - Whether the select-translations menu item should be present in the context menu. + * @param {boolean} options.expectedTargetLanguage - The expected target language to be shown in the context menu. + * @param {boolean} options.selectFrenchSection - Selects the section of French text. + * @param {boolean} options.selectEnglishSection - Selects the section of English text. + * @param {boolean} options.selectSpanishSection - Selects the section of Spanish text. + * @param {boolean} options.selectFrenchSentence - Selects a French sentence. + * @param {boolean} options.selectEnglishSentence - Selects an English sentence. + * @param {boolean} options.selectSpanishSentence - Selects a Spanish sentence. + * @param {boolean} options.openAtFrenchSection - Opens the context menu at the section of French text. + * @param {boolean} options.openAtEnglishSection - Opens the context menu at the section of English text. + * @param {boolean} options.openAtSpanishSection - Opens the context menu at the section of Spanish text. + * @param {boolean} options.openAtFrenchSentence - Opens the context menu at a French sentence. + * @param {boolean} options.openAtEnglishSentence - Opens the context menu at an English sentence. + * @param {boolean} options.openAtSpanishSentence - Opens the context menu at a Spanish sentence. + * @param {boolean} options.openAtFrenchHyperlink - Opens the context menu at a hyperlinked French text. + * @param {boolean} options.openAtEnglishHyperlink - Opens the context menu at an hyperlinked English text. + * @param {boolean} options.openAtSpanishHyperlink - Opens the context menu at a hyperlinked Spanish text. * @param {string} [message] - A message to log to info. * @throws Throws an error if the properties of the translate-selection item do not match the expected options. */ static async assertContextMenuTranslateSelectionItem( runInPage, { - selectFirstParagraph, - selectSpanishParagraph, - expectMenuItemIsVisible, + expectMenuItemVisible, expectedTargetLanguage, - openAtFirstParagraph, - openAtSpanishParagraph, + selectFrenchSection, + selectEnglishSection, + selectSpanishSection, + selectFrenchSentence, + selectEnglishSentence, + selectSpanishSentence, + openAtFrenchSection, + openAtEnglishSection, + openAtSpanishSection, + openAtFrenchSentence, + openAtEnglishSentence, + openAtSpanishSentence, + openAtFrenchHyperlink, openAtEnglishHyperlink, openAtSpanishHyperlink, }, @@ -1336,10 +1417,21 @@ class SelectTranslationsTestUtils { await closeAllOpenPanelsAndMenus(); await SelectTranslationsTestUtils.openContextMenu(runInPage, { - selectFirstParagraph, - selectSpanishParagraph, - openAtFirstParagraph, - openAtSpanishParagraph, + expectMenuItemVisible, + expectedTargetLanguage, + selectFrenchSection, + selectEnglishSection, + selectSpanishSection, + selectFrenchSentence, + selectEnglishSentence, + selectSpanishSentence, + openAtFrenchSection, + openAtEnglishSection, + openAtSpanishSection, + openAtFrenchSentence, + openAtEnglishSentence, + openAtSpanishSentence, + openAtFrenchHyperlink, openAtEnglishHyperlink, openAtSpanishHyperlink, }); @@ -1349,16 +1441,21 @@ class SelectTranslationsTestUtils { /* ensureIsVisible */ false ); - if (expectMenuItemIsVisible !== undefined) { - const visibility = expectMenuItemIsVisible ? "visible" : "hidden"; - assertVisibility({ [visibility]: menuItem }); + if (expectMenuItemVisible !== undefined) { + const visibility = expectMenuItemVisible ? "visible" : "hidden"; + assertVisibility({ [visibility]: { menuItem } }); } - if (expectMenuItemIsVisible === true) { + if (expectMenuItemVisible === true) { if (expectedTargetLanguage) { // Target language expected, check for the data-l10n-id with a `{$language}` argument. const expectedL10nId = - selectFirstParagraph === true || selectSpanishParagraph === true + selectFrenchSection || + selectEnglishSection || + selectSpanishSection || + selectFrenchSentence || + selectEnglishSentence || + selectSpanishSentence ? "main-context-menu-translate-selection-to-language" : "main-context-menu-translate-link-text-to-language"; await waitForCondition( @@ -1381,7 +1478,12 @@ class SelectTranslationsTestUtils { } else { // No target language expected, check for the data-l10n-id that has no `{$language}` argument. const expectedL10nId = - selectFirstParagraph === true || selectSpanishParagraph === true + selectFrenchSection || + selectEnglishSection || + selectSpanishSection || + selectFrenchSentence || + selectEnglishSentence || + selectSpanishSentence ? "main-context-menu-translate-selection" : "main-context-menu-translate-link-text"; await waitForCondition( @@ -1399,6 +1501,21 @@ class SelectTranslationsTestUtils { } /** + * Elements that should always be visible in the SelectTranslationsPanel. + */ + static #alwaysPresentElements = { + betaIcon: true, + copyButton: true, + doneButton: true, + fromLabel: true, + fromMenuList: true, + header: true, + toLabel: true, + toMenuList: true, + textArea: true, + }; + + /** * Asserts that for each provided expectation, the visible state of the corresponding * element in FullPageTranslationsPanel.elements both exists and matches the visibility expectation. * @@ -1409,16 +1526,7 @@ class SelectTranslationsTestUtils { SharedTranslationsTestUtils._assertPanelElementVisibility( SelectTranslationsPanel.elements, { - betaIcon: false, - copyButton: false, - doneButton: false, - fromLabel: false, - fromMenuList: false, - header: false, - textArea: false, - toLabel: false, - toMenuList: false, - translateFullPageButton: false, + ...SelectTranslationsTestUtils.#alwaysPresentElements, // Overwrite any of the above defaults with the passed in expectations. ...expectations, } @@ -1426,49 +1534,255 @@ class SelectTranslationsTestUtils { } /** - * Asserts that the mainViewId of the panel matches the given string. - * - * @param {string} expectedId + * Asserts that the SelectTranslationsPanel UI matches the expected + * state when the panel has completed its translation. */ - static #assertPanelMainViewId(expectedId) { - SharedTranslationsTestUtils._assertPanelMainViewId( - SelectTranslationsPanel, - expectedId + static assertPanelViewTranslated() { + const { textArea } = SelectTranslationsPanel.elements; + ok( + !textArea.classList.contains("translating"), + "The textarea should not have the translating class." + ); + SelectTranslationsTestUtils.#assertPanelElementVisibility({ + ...SelectTranslationsTestUtils.#alwaysPresentElements, + }); + SelectTranslationsTestUtils.#assertConditionalUIEnabled({ + textArea: true, + copyButton: true, + translateFullPageButton: true, + }); + SelectTranslationsTestUtils.#assertPanelHasTranslatedText(); + SelectTranslationsTestUtils.#assertPanelTextAreaHeight(); + SelectTranslationsTestUtils.#assertPanelTextAreaOverflow(); + } + + static #assertPanelTextAreaDirection(langTag = null) { + const expectedTextDirection = langTag + ? Services.intl.getScriptDirection(langTag) + : null; + const { textArea } = SelectTranslationsPanel.elements; + const actualTextDirection = textArea.getAttribute("dir"); + + is( + actualTextDirection, + expectedTextDirection, + `The text direction should be ${expectedTextDirection}` ); } /** - * Asserts that panel element visibility matches the default panel view. + * Asserts that the SelectTranslationsPanel translated text area is + * both scrollable and scrolled to the top. */ - static assertPanelViewDefault() { - info("Checking that the select-translations panel shows the default view"); - SelectTranslationsTestUtils.#assertPanelMainViewId( - "select-translations-panel-view-default" + static #assertPanelTextAreaOverflow() { + const { textArea } = SelectTranslationsPanel.elements; + is( + textArea.style.overflow, + "auto", + "The translated-text area should be scrollable." + ); + if (textArea.scrollHeight > textArea.clientHeight) { + is( + textArea.scrollTop, + 0, + "The translated-text area should be scrolled to the top." + ); + } + } + + /** + * Asserts that the SelectTranslationsPanel translated text area is + * the correct height for the length of the translated text. + */ + static #assertPanelTextAreaHeight() { + const { textArea } = SelectTranslationsPanel.elements; + + if ( + SelectTranslationsPanel.getSourceText().length < + SelectTranslationsPanel.textLengthThreshold + ) { + is( + textArea.style.height, + SelectTranslationsPanel.shortTextHeight, + "The panel text area should have the short-text height" + ); + } else { + is( + textArea.style.height, + SelectTranslationsPanel.longTextHeight, + "The panel text area should have the long-text height" + ); + } + } + + /** + * Asserts that the SelectTranslationsPanel UI matches the expected + * state when the panel is actively translating text. + */ + static assertPanelViewActivelyTranslating() { + const { textArea } = SelectTranslationsPanel.elements; + ok( + textArea.classList.contains("translating"), + "The textarea should have the translating class." ); SelectTranslationsTestUtils.#assertPanelElementVisibility({ - betaIcon: true, - fromLabel: true, - fromMenuList: true, - header: true, + ...SelectTranslationsTestUtils.#alwaysPresentElements, + }); + SelectTranslationsTestUtils.#assertPanelHasTranslatingPlaceholder(); + } + + /** + * Asserts that the SelectTranslationsPanel UI matches the expected + * state when no from-language is selected in the panel. + */ + static async assertPanelViewNoFromLangSelected() { + const { textArea } = SelectTranslationsPanel.elements; + ok( + !textArea.classList.contains("translating"), + "The textarea should not have the translating class." + ); + SelectTranslationsTestUtils.#assertPanelElementVisibility({ + ...SelectTranslationsTestUtils.#alwaysPresentElements, + }); + await SelectTranslationsTestUtils.#assertPanelHasIdlePlaceholder(); + SelectTranslationsTestUtils.#assertConditionalUIEnabled({ + textArea: false, + copyButton: false, + translateFullPageButton: false, + }); + SelectTranslationsTestUtils.assertSelectedFromLanguage(null); + } + + /** + * Asserts that the SelectTranslationsPanel UI matches the expected + * state when no to-language is selected in the panel. + */ + static async assertPanelViewNoToLangSelected() { + const { textArea } = SelectTranslationsPanel.elements; + ok( + !textArea.classList.contains("translating"), + "The textarea should not have the translating class." + ); + SelectTranslationsTestUtils.#assertPanelElementVisibility({ + ...SelectTranslationsTestUtils.#alwaysPresentElements, + }); + SelectTranslationsTestUtils.assertSelectedToLanguage(null); + SelectTranslationsTestUtils.#assertConditionalUIEnabled({ + textArea: false, + copyButton: false, + translateFullPageButton: false, + }); + await SelectTranslationsTestUtils.#assertPanelHasIdlePlaceholder(); + } + + /** + * Asserts that the SelectTranslationsPanel UI contains the + * idle placeholder text. + */ + static async #assertPanelHasIdlePlaceholder() { + const { textArea } = SelectTranslationsPanel.elements; + const expected = await document.l10n.formatValue( + "select-translations-panel-idle-placeholder-text" + ); + is( + textArea.value, + expected, + "Translated text area should be the idle placeholder." + ); + SelectTranslationsTestUtils.#assertPanelTextAreaDirection(); + } + + /** + * Asserts that the SelectTranslationsPanel UI contains the + * translating placeholder text. + */ + static async #assertPanelHasTranslatingPlaceholder() { + const { textArea } = SelectTranslationsPanel.elements; + const expected = await document.l10n.formatValue( + "select-translations-panel-translating-placeholder-text" + ); + is( + textArea.value, + expected, + "Active translation text area should have the translating placeholder." + ); + SelectTranslationsTestUtils.#assertPanelTextAreaDirection(); + SelectTranslationsTestUtils.#assertConditionalUIEnabled({ + textArea: true, + copyButton: false, + translateFullPageButton: true, + }); + } + + /** + * Asserts that the SelectTranslationsPanel UI contains the + * translated text. + */ + static #assertPanelHasTranslatedText() { + const { textArea, fromMenuList, toMenuList } = + SelectTranslationsPanel.elements; + const fromLanguage = fromMenuList.value; + const toLanguage = toMenuList.value; + const translatedSuffix = ` [${fromLanguage} to ${toLanguage}]`; + ok( + textArea.value.endsWith(translatedSuffix), + `Translated text should match ${fromLanguage} to ${toLanguage}` + ); + is( + SelectTranslationsPanel.getSourceText().length, + SelectTranslationsPanel.getTranslatedText().length - + translatedSuffix.length, + "Expected translated text length to correspond to the source text length." + ); + SelectTranslationsTestUtils.#assertPanelTextAreaDirection(toLanguage); + SelectTranslationsTestUtils.#assertConditionalUIEnabled({ textArea: true, - toLabel: true, - toMenuList: true, copyButton: true, - doneButton: true, translateFullPageButton: true, }); } /** + * Asserts the enabled state of action buttons in the SelectTranslationsPanel. + * + * @param {boolean} expectEnabled - Whether the buttons should be enabled (true) or not (false). + */ + static #assertConditionalUIEnabled({ + copyButton: copyButtonEnabled, + translateFullPageButton: translateFullPageButtonEnabled, + textArea: textAreaEnabled, + }) { + const { copyButton, translateFullPageButton, textArea } = + SelectTranslationsPanel.elements; + is( + copyButton.disabled, + !copyButtonEnabled, + `The copy button should be ${copyButtonEnabled ? "enabled" : "disabled"}.` + ); + is( + translateFullPageButton.disabled, + !translateFullPageButtonEnabled, + `The translate-full-page button should be ${ + translateFullPageButtonEnabled ? "enabled" : "disabled" + }.` + ); + is( + textArea.disabled, + !textAreaEnabled, + `The translated-text area should be ${ + textAreaEnabled ? "enabled" : "disabled" + }.` + ); + } + + /** * Asserts that the selected from-language matches the provided language tag. * * @param {string} langTag - A BCP-47 language tag. */ - static assertSelectedFromLanguage({ langTag, l10nId }) { - SharedTranslationsTestUtils._assertSelectedFromLanguage( - SelectTranslationsPanel, - { langTag, l10nId } - ); + static assertSelectedFromLanguage(langTag = null) { + const { fromMenuList } = SelectTranslationsPanel.elements; + SelectTranslationsTestUtils.#assertSelectedLanguage(fromMenuList, langTag); } /** @@ -1476,11 +1790,29 @@ class SelectTranslationsTestUtils { * * @param {string} langTag - A BCP-47 language tag. */ - static assertSelectedToLanguage({ langTag, l10nId }) { - SharedTranslationsTestUtils._assertSelectedToLanguage( - SelectTranslationsPanel, - { langTag, l10nId } - ); + static assertSelectedToLanguage(langTag = null) { + const { toMenuList } = SelectTranslationsPanel.elements; + SelectTranslationsTestUtils.#assertSelectedLanguage(toMenuList, langTag); + } + + /** + * Asserts the selected language in the given menu list if a langTag is provided. + * If no langTag is given, asserts that the menulist displays the localized placeholder. + * + * @param {object} menuList - The menu list object to check. + * @param {string} [langTag] - The optional language tag to assert against. + */ + static #assertSelectedLanguage(menuList, langTag) { + if (langTag) { + SharedTranslationsTestUtils._assertSelectedLanguage(menuList, { + langTag, + }); + } else { + SharedTranslationsTestUtils._assertSelectedLanguage(menuList, { + l10nId: "translations-panel-choose-language", + }); + SharedTranslationsTestUtils._assertHasFocus(menuList); + } } /** @@ -1503,110 +1835,258 @@ class SelectTranslationsTestUtils { * * @param {Function} runInPage - A content-exposed function to run within the context of the page. * @param {object} options - Options for opening the context menu. - * @param {boolean} options.selectFirstParagraph - Selects the first paragraph before opening the context menu. - * @param {boolean} options.selectSpanishParagraph - Selects the Spanish paragraph before opening the context menu. - * This is only available in SPANISH_TEST_PAGE. - * @param {boolean} options.openAtFirstParagraph - Opens the context menu at the first paragraph in the test page. - * @param {boolean} options.openAtSpanishParagraph - Opens the context menu at the Spanish paragraph in the test page. - * This is only available in SPANISH_TEST_PAGE. - * @param {boolean} options.openAtEnglishHyperlink - Opens the context menu at the English hyperlink in the test page. - * This is only available in SPANISH_TEST_PAGE. - * @param {boolean} options.openAtSpanishHyperlink - Opens the context menu at the Spanish hyperlink in the test page. - * This is only available in SPANISH_TEST_PAGE. + * + * The following options will only work when testing SELECT_TEST_PAGE_URL. + * + * @param {boolean} options.selectFrenchSection - Selects the section of French text. + * @param {boolean} options.selectEnglishSection - Selects the section of English text. + * @param {boolean} options.selectSpanishSection - Selects the section of Spanish text. + * @param {boolean} options.selectFrenchSentence - Selects a French sentence. + * @param {boolean} options.selectEnglishSentence - Selects an English sentence. + * @param {boolean} options.selectSpanishSentence - Selects a Spanish sentence. + * @param {boolean} options.openAtFrenchSection - Opens the context menu at the section of French text. + * @param {boolean} options.openAtEnglishSection - Opens the context menu at the section of English text. + * @param {boolean} options.openAtSpanishSection - Opens the context menu at the section of Spanish text. + * @param {boolean} options.openAtFrenchSentence - Opens the context menu at a French sentence. + * @param {boolean} options.openAtEnglishSentence - Opens the context menu at an English sentence. + * @param {boolean} options.openAtSpanishSentence - Opens the context menu at a Spanish sentence. + * @param {boolean} options.openAtFrenchHyperlink - Opens the context menu at a hyperlinked French text. + * @param {boolean} options.openAtEnglishHyperlink - Opens the context menu at an hyperlinked English text. + * @param {boolean} options.openAtSpanishHyperlink - Opens the context menu at a hyperlinked Spanish text. * @throws Throws an error if no valid option was provided for opening the menu. */ - static async openContextMenu( - runInPage, - { - selectFirstParagraph, - selectSpanishParagraph, - openAtFirstParagraph, - openAtSpanishParagraph, - openAtEnglishHyperlink, - openAtSpanishHyperlink, - } - ) { + static async openContextMenu(runInPage, options) { logAction(); - if (selectFirstParagraph === true) { - await runInPage(async TranslationsTest => { - const { getFirstParagraph } = TranslationsTest.getSelectors(); - const paragraph = getFirstParagraph(); - TranslationsTest.selectContentElement(paragraph); - }); - } + const maybeSelectContentFrom = async keyword => { + const conditionVariableName = `select${keyword}`; + const selectorFunctionName = `get${keyword}`; + + if (options[conditionVariableName]) { + await runInPage( + async (TranslationsTest, data) => { + const selectorFunction = + TranslationsTest.getSelectors()[data.selectorFunctionName]; + if (typeof selectorFunction === "function") { + const paragraph = selectorFunction(); + TranslationsTest.selectContentElement(paragraph); + } + }, + { selectorFunctionName } + ); + } + }; - if (selectSpanishParagraph === true) { - await runInPage(async TranslationsTest => { - const { getSpanishParagraph } = TranslationsTest.getSelectors(); - const paragraph = getSpanishParagraph(); - TranslationsTest.selectContentElement(paragraph); - }); + await maybeSelectContentFrom("FrenchSection"); + await maybeSelectContentFrom("EnglishSection"); + await maybeSelectContentFrom("SpanishSection"); + await maybeSelectContentFrom("FrenchSentence"); + await maybeSelectContentFrom("EnglishSentence"); + await maybeSelectContentFrom("SpanishSentence"); + + const maybeOpenContextMenuAt = async keyword => { + const optionVariableName = `openAt${keyword}`; + const selectorFunctionName = `get${keyword}`; + + if (options[optionVariableName]) { + await SharedTranslationsTestUtils._waitForPopupEvent( + "contentAreaContextMenu", + "popupshown", + async () => { + await runInPage( + async (TranslationsTest, data) => { + const selectorFunction = + TranslationsTest.getSelectors()[data.selectorFunctionName]; + if (typeof selectorFunction === "function") { + const element = selectorFunction(); + await TranslationsTest.rightClickContentElement(element); + } + }, + { selectorFunctionName } + ); + } + ); + } + }; + + await maybeOpenContextMenuAt("FrenchSection"); + await maybeOpenContextMenuAt("EnglishSection"); + await maybeOpenContextMenuAt("SpanishSection"); + await maybeOpenContextMenuAt("FrenchSentence"); + await maybeOpenContextMenuAt("EnglishSentence"); + await maybeOpenContextMenuAt("SpanishSentence"); + await maybeOpenContextMenuAt("FrenchHyperlink"); + await maybeOpenContextMenuAt("EnglishHyperlink"); + await maybeOpenContextMenuAt("SpanishHyperlink"); + } + + /** + * Handles language-model downloads for the SelectTranslationsPanel, ensuring that expected + * UI states match based on the resolved download state. + * + * @param {object} options - Configuration options for downloads. + * @param {function(number): Promise<void>} options.downloadHandler - The function to resolve or reject the downloads. + * @param {boolean} [options.pivotTranslation] - Whether to expect a pivot translation. + * + * @returns {Promise<void>} + */ + static async handleDownloads({ downloadHandler, pivotTranslation }) { + const { textArea } = SelectTranslationsPanel.elements; + + if (downloadHandler) { + if (textArea.style.overflow !== "hidden") { + await BrowserTestUtils.waitForMutationCondition( + textArea, + { attributes: true, attributeFilter: ["style"] }, + () => textArea.style.overflow === "hidden" + ); + } + + await SelectTranslationsTestUtils.assertPanelViewActivelyTranslating(); + await downloadHandler(pivotTranslation ? 2 : 1); } - if (openAtFirstParagraph === true) { - await SharedTranslationsTestUtils._waitForPopupEvent( - "contentAreaContextMenu", - "popupshown", - async () => { - await runInPage(async TranslationsTest => { - const { getFirstParagraph } = TranslationsTest.getSelectors(); - const paragraph = getFirstParagraph(); - await TranslationsTest.rightClickContentElement(paragraph); - }); - } + if (textArea.style.overflow === "hidden") { + await BrowserTestUtils.waitForMutationCondition( + textArea, + { attributes: true, attributeFilter: ["style"] }, + () => textArea.style.overflow === "auto" ); - return; } + } - if (openAtSpanishParagraph === true) { - await SharedTranslationsTestUtils._waitForPopupEvent( - "contentAreaContextMenu", - "popupshown", - async () => { - await runInPage(async TranslationsTest => { - const { getSpanishParagraph } = TranslationsTest.getSelectors(); - const paragraph = getSpanishParagraph(); - await TranslationsTest.rightClickContentElement(paragraph); - }); - } + /** + * Switches the selected from-language to the provided language tags + * + * @param {string[]} langTags - An array of BCP-47 language tags. + * @param {object} options - Configuration options for the language change. + * @param {boolean} options.openDropdownMenu - Determines whether the language change should be made via a dropdown menu or directly. + * + * @returns {Promise<void>} + */ + static async changeSelectedFromLanguage(langTags, options) { + const { fromMenuList, fromMenuPopup } = SelectTranslationsPanel.elements; + const { openDropdownMenu } = options; + + const switchFn = openDropdownMenu + ? SelectTranslationsTestUtils.#changeSelectedLanguageViaDropdownMenu + : SelectTranslationsTestUtils.#changeSelectedLanguageDirectly; + + await switchFn( + langTags, + { menuList: fromMenuList, menuPopup: fromMenuPopup }, + options + ); + } + + /** + * Switches the selected to-language to the provided language tag. + * + * @param {string[]} langTags - An array of BCP-47 language tags. + * @param {object} options - Options for selecting paragraphs and opening the context menu. + * @param {boolean} options.openDropdownMenu - Determines whether the language change should be made via a dropdown menu or directly. + * @param {Function} options.downloadHandler - Handler for initiating downloads post language change, if applicable. + * @param {Function} options.onChangeLanguage - Callback function to be executed after the language change. + * + * @returns {Promise<void>} + */ + static async changeSelectedToLanguage(langTags, options) { + const { toMenuList, toMenuPopup } = SelectTranslationsPanel.elements; + const { openDropdownMenu } = options; + + const switchFn = openDropdownMenu + ? SelectTranslationsTestUtils.#changeSelectedLanguageViaDropdownMenu + : SelectTranslationsTestUtils.#changeSelectedLanguageDirectly; + + await switchFn( + langTags, + { menuList: toMenuList, menuPopup: toMenuPopup }, + options + ); + } + + /** + * Directly changes the selected language to each provided language tag without using a dropdown menu. + * + * @param {string[]} langTags - An array of BCP-47 language tags for direct selection. + * @param {object} elements - Elements required for changing the selected language. + * @param {Element} elements.menuList - The menu list element where languages are directly changed. + * @param {object} options - Configuration options for language change and additional actions. + * @param {Function} options.downloadHandler - Handler for initiating downloads post language change, if applicable. + * @param {Function} options.onChangeLanguage - Callback function to be executed after the language change. + * + * @returns {Promise<void>} + */ + static async #changeSelectedLanguageDirectly(langTags, elements, options) { + const { menuList } = elements; + const { onChangeLanguage, downloadHandler } = options; + + for (const langTag of langTags) { + const menuListUpdated = BrowserTestUtils.waitForMutationCondition( + menuList, + { attributes: true, attributeFilter: ["value"] }, + () => menuList.value === langTag ); - return; + + menuList.value = langTag; + menuList.dispatchEvent(new Event("command")); + await menuListUpdated; } - if (openAtEnglishHyperlink === true) { - await SharedTranslationsTestUtils._waitForPopupEvent( - "contentAreaContextMenu", - "popupshown", - async () => { - await runInPage(async TranslationsTest => { - const { getEnglishHyperlink } = TranslationsTest.getSelectors(); - const hyperlink = getEnglishHyperlink(); - await TranslationsTest.rightClickContentElement(hyperlink); - }); - } - ); - return; + if (downloadHandler) { + menuList.focus(); + EventUtils.synthesizeKey("KEY_Enter"); + await SelectTranslationsTestUtils.handleDownloads(options); } - if (openAtSpanishHyperlink === true) { - await SharedTranslationsTestUtils._waitForPopupEvent( - "contentAreaContextMenu", + if (onChangeLanguage) { + await onChangeLanguage(); + } + } + + /** + * Changes the selected language by opening the dropdown menu for each provided language tag. + * + * @param {string[]} langTags - An array of BCP-47 language tags for selection via dropdown. + * @param {object} elements - Elements involved in the dropdown language selection process. + * @param {Element} elements.menuList - The element that triggers the dropdown menu. + * @param {Element} elements.menuPopup - The dropdown menu element containing selectable languages. + * @param {object} options - Configuration options for language change and additional actions. + * @param {Function} options.downloadHandler - Handler for initiating downloads post language change, if applicable. + * @param {Function} options.onChangeLanguage - Callback function to be executed after the language change. + * + * @returns {Promise<void>} + */ + static async #changeSelectedLanguageViaDropdownMenu( + langTags, + elements, + options + ) { + const { menuList, menuPopup } = elements; + const { onChangeLanguage } = options; + for (const langTag of langTags) { + await SelectTranslationsTestUtils.waitForPanelPopupEvent( "popupshown", - async () => { - await runInPage(async TranslationsTest => { - const { getSpanishHyperlink } = TranslationsTest.getSelectors(); - const hyperlink = getSpanishHyperlink(); - await TranslationsTest.rightClickContentElement(hyperlink); - }); + () => click(menuList) + ); + + const menuItem = menuPopup.querySelector(`[value="${langTag}"]`); + await SelectTranslationsTestUtils.waitForPanelPopupEvent( + "popuphidden", + () => { + click(menuItem); + // Synthesizing a click on the menuitem isn't closing the popup + // as a click normally would, so this tab keypress is added to + // ensure the popup closes. + EventUtils.synthesizeKey("KEY_Tab"); } ); - return; - } - throw new Error( - "openContextMenu() was not provided a declaration for which element to open the menu at." - ); + await SelectTranslationsTestUtils.handleDownloads(options); + if (onChangeLanguage) { + await onChangeLanguage(); + } + } } /** @@ -1614,36 +2094,32 @@ class SelectTranslationsTestUtils { * * @param {Function} runInPage - A content-exposed function to run within the context of the page. * @param {object} options - Options for selecting paragraphs and opening the context menu. - * @param {boolean} options.selectFirstParagraph - Selects the first paragraph before opening the context menu. - * @param {boolean} options.selectSpanishParagraph - Selects the Spanish paragraph before opening the context menu. - * This is only available in SPANISH_TEST_PAGE. - * @param {string} options.expectedTargetLanguage - The target language for translation. - * @param {boolean} options.openAtFirstParagraph - Opens the context menu at the first paragraph. - * @param {boolean} options.openAtSpanishParagraph - Opens at the Spanish paragraph. - * This is only available in SPANISH_TEST_PAGE. - * @param {boolean} options.openAtEnglishHyperlink - Opens at the English hyperlink. - * This is only available in SPANISH_TEST_PAGE. - * @param {boolean} options.openAtSpanishHyperlink - Opens at the Spanish hyperlink. - * This is only available in SPANISH_TEST_PAGE. - * @param {Function|null} [options.onOpenPanel=null] - An optional callback function to execute after the panel opens. - * @param {string|null} [message=null] - An optional message to log to info. + * + * The following options will only work when testing SELECT_TEST_PAGE_URL. + * + * @param {string} options.expectedFromLanguage - The expected from-language tag. + * @param {string} options.expectedToLanguage - The expected to-language tag. + * @param {boolean} options.selectFrenchSection - Selects the section of French text. + * @param {boolean} options.selectEnglishSection - Selects the section of English text. + * @param {boolean} options.selectSpanishSection - Selects the section of Spanish text. + * @param {boolean} options.selectFrenchSentence - Selects a French sentence. + * @param {boolean} options.selectEnglishSentence - Selects an English sentence. + * @param {boolean} options.selectSpanishSentence - Selects a Spanish sentence. + * @param {boolean} options.openAtFrenchSection - Opens the context menu at the section of French text. + * @param {boolean} options.openAtEnglishSection - Opens the context menu at the section of English text. + * @param {boolean} options.openAtSpanishSection - Opens the context menu at the section of Spanish text. + * @param {boolean} options.openAtFrenchSentence - Opens the context menu at a French sentence. + * @param {boolean} options.openAtEnglishSentence - Opens the context menu at an English sentence. + * @param {boolean} options.openAtSpanishSentence - Opens the context menu at a Spanish sentence. + * @param {boolean} options.openAtFrenchHyperlink - Opens the context menu at a hyperlinked French text. + * @param {boolean} options.openAtEnglishHyperlink - Opens the context menu at an hyperlinked English text. + * @param {boolean} options.openAtSpanishHyperlink - Opens the context menu at a hyperlinked Spanish text. + * @param {Function} [options.onOpenPanel] - An optional callback function to execute after the panel opens. + * @param {string|null} [message] - An optional message to log to info. * @throws Throws an error if the context menu could not be opened with the provided options. * @returns {Promise<void>} */ - static async openPanel( - runInPage, - { - selectFirstParagraph, - selectSpanishParagraph, - expectedTargetLanguage, - openAtFirstParagraph, - openAtSpanishParagraph, - openAtEnglishHyperlink, - openAtSpanishHyperlink, - onOpenPanel, - }, - message - ) { + static async openPanel(runInPage, options, message) { logAction(); if (message) { @@ -1652,15 +2128,7 @@ class SelectTranslationsTestUtils { await SelectTranslationsTestUtils.assertContextMenuTranslateSelectionItem( runInPage, - { - selectFirstParagraph, - selectSpanishParagraph, - expectedTargetLanguage, - openAtFirstParagraph, - openAtSpanishParagraph, - openAtEnglishHyperlink, - openAtSpanishHyperlink, - }, + options, message ); @@ -1668,9 +2136,28 @@ class SelectTranslationsTestUtils { await SelectTranslationsTestUtils.waitForPanelPopupEvent( "popupshown", - () => click(menuItem), - onOpenPanel + async () => { + click(menuItem); + await closeContextMenuIfOpen(); + }, + async () => { + const { onOpenPanel } = options; + await SelectTranslationsTestUtils.handleDownloads(options); + if (onOpenPanel) { + await onOpenPanel(); + } + } ); + + const { expectedFromLanguage, expectedToLanguage } = options; + if (expectedFromLanguage !== undefined) { + SelectTranslationsTestUtils.assertSelectedFromLanguage( + expectedFromLanguage + ); + } + if (expectedToLanguage !== undefined) { + SelectTranslationsTestUtils.assertSelectedToLanguage(expectedToLanguage); + } } /** @@ -1732,10 +2219,10 @@ class TranslationsSettingsTestUtils { translateNeverHeader: document.getElementById( "translations-settings-never-translate" ), - translateAlwaysAddButton: document.getElementById( + translateAlwaysMenuList: document.getElementById( "translations-settings-always-translate-list" ), - translateNeverAddButton: document.getElementById( + translateNeverMenuList: document.getElementById( "translations-settings-never-translate-list" ), translateNeverSiteHeader: document.getElementById( @@ -1744,12 +2231,15 @@ class TranslationsSettingsTestUtils { translateNeverSiteDesc: document.getElementById( "translations-settings-never-sites" ), - translateDownloadLanguagesHeader: document.getElementById( - "translations-settings-download-languages" - ), + translateDownloadLanguagesHeader: document + .getElementById("translations-settings-download-section") + .querySelector("h2"), translateDownloadLanguagesLearnMore: document.getElementById( "download-languages-learn-more" ), + translateDownloadLanguagesList: document.getElementById( + "translations-settings-download-section" + ), }; return elements; |