summaryrefslogtreecommitdiffstats
path: root/browser/components/translations
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-15 03:35:49 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-15 03:35:49 +0000
commitd8bbc7858622b6d9c278469aab701ca0b609cddf (patch)
treeeff41dc61d9f714852212739e6b3738b82a2af87 /browser/components/translations
parentReleasing progress-linux version 125.0.3-1~progress7.99u1. (diff)
downloadfirefox-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')
-rw-r--r--browser/components/translations/content/TranslationsPanelShared.sys.mjs93
-rw-r--r--browser/components/translations/content/fullPageTranslationsPanel.js91
-rw-r--r--browser/components/translations/content/selectTranslationsPanel.inc.xhtml172
-rw-r--r--browser/components/translations/content/selectTranslationsPanel.js895
-rw-r--r--browser/components/translations/moz.build2
-rw-r--r--browser/components/translations/tests/browser/browser.toml38
-rw-r--r--browser/components/translations/tests/browser/browser_translations_about_preferences_settings_ui.js201
-rw-r--r--browser/components/translations/tests/browser/browser_translations_full_page_move_tab_to_new_window.js64
-rw-r--r--browser/components/translations/tests/browser/browser_translations_full_page_multiple_windows.js68
-rw-r--r--browser/components/translations/tests/browser/browser_translations_full_page_panel_init_failure.js25
-rw-r--r--browser/components/translations/tests/browser/browser_translations_full_page_panel_retry.js2
-rw-r--r--browser/components/translations/tests/browser/browser_translations_full_page_panel_switch_languages.js10
-rw-r--r--browser/components/translations/tests/browser/browser_translations_full_page_panel_unsupported_lang.js (renamed from browser/components/translations/tests/browser/browser_translations_full_page_panel_engine_unsupported_lang.js)3
-rw-r--r--browser/components/translations/tests/browser/browser_translations_full_page_telemetry_switch_languages.js16
-rw-r--r--browser/components/translations/tests/browser/browser_translations_select_context_menu_feature_disabled.js16
-rw-r--r--browser/components/translations/tests/browser/browser_translations_select_context_menu_with_full_page_translations_active.js22
-rw-r--r--browser/components/translations/tests/browser/browser_translations_select_context_menu_with_hyperlink.js12
-rw-r--r--browser/components/translations/tests/browser/browser_translations_select_context_menu_with_no_text_selected.js6
-rw-r--r--browser/components/translations/tests/browser/browser_translations_select_context_menu_with_text_selected.js14
-rw-r--r--browser/components/translations/tests/browser/browser_translations_select_panel_engine_cache.js59
-rw-r--r--browser/components/translations/tests/browser/browser_translations_select_panel_fallback_to_doc_language.js38
-rw-r--r--browser/components/translations/tests/browser/browser_translations_select_panel_language_selectors.js54
-rw-r--r--browser/components/translations/tests/browser/browser_translations_select_panel_mainview_ui.js36
-rw-r--r--browser/components/translations/tests/browser/browser_translations_select_panel_open_to_idle_state.js61
-rw-r--r--browser/components/translations/tests/browser/browser_translations_select_panel_retranslate_on_change_language_directly.js70
-rw-r--r--browser/components/translations/tests/browser/browser_translations_select_panel_retranslate_on_change_language_from_dropdown_menu.js68
-rw-r--r--browser/components/translations/tests/browser/browser_translations_select_panel_select_current_language_directly.js71
-rw-r--r--browser/components/translations/tests/browser/browser_translations_select_panel_select_current_language_from_dropdown_menu.js71
-rw-r--r--browser/components/translations/tests/browser/browser_translations_select_panel_select_same_from_and_to_languages_directly.js71
-rw-r--r--browser/components/translations/tests/browser/browser_translations_select_panel_select_same_from_and_to_languages_from_dropdown_menu.js71
-rw-r--r--browser/components/translations/tests/browser/browser_translations_select_panel_translate_on_change_language_directly.js71
-rw-r--r--browser/components/translations/tests/browser/browser_translations_select_panel_translate_on_change_language_from_dropdown_menu.js71
-rw-r--r--browser/components/translations/tests/browser/browser_translations_select_panel_translate_on_change_language_multiple_times_directly.js97
-rw-r--r--browser/components/translations/tests/browser/browser_translations_select_panel_translate_on_change_language_multiple_times_from_dropdown_menu.js98
-rw-r--r--browser/components/translations/tests/browser/browser_translations_select_panel_translate_on_open.js86
-rw-r--r--browser/components/translations/tests/browser/head.js1036
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;