commit bd005d4ae09f1e8021b55dec464fec033238d890
parent 75259352201f05beaca3f1d8dc56c0b582d60a21
Author: Erik Nordin <enordin@mozilla.com>
Date: Tue, 23 Dec 2025 16:05:29 +0000
Bug 1992233 - Part 2/3: Implement Clear Butotn Visibility r=translations-reviewers,gregtatum
This commit introduces the logic for when the source-section clear button
should be visible or hidden on the about:translations page.
At this point, the button has no functionality when clicked.
This will be added in the next commit.
Differential Revision: https://phabricator.services.mozilla.com/D277056
Diffstat:
5 files changed, 233 insertions(+), 0 deletions(-)
diff --git a/toolkit/components/translations/content/about-translations.html b/toolkit/components/translations/content/about-translations.html
@@ -126,6 +126,7 @@
type="ghost icon"
data-l10n-id="about-translations-clear-button"
tabindex="-1"
+ hidden
></moz-button>
</div>
</div>
diff --git a/toolkit/components/translations/content/about-translations.mjs b/toolkit/components/translations/content/about-translations.mjs
@@ -320,6 +320,7 @@ class AboutTranslations {
this.#updateTargetScriptDirection();
this.#showMainUserInterface();
+ this.#updateSourceSectionClearButtonVisibility();
this.#ensureSectionHeightsMatch({ scheduleCallback: false });
this.#setInitialFocus();
}
@@ -457,6 +458,7 @@ class AboutTranslations {
* Handles input events on the source textarea.
*/
#onSourceTextInput = () => {
+ this.#updateSourceSectionClearButtonVisibility();
this.#maybeRequestTranslation();
};
@@ -968,6 +970,7 @@ class AboutTranslations {
this.#updateSourceScriptDirection();
this.#updateTargetScriptDirection();
this.#ensureSectionHeightsMatch({ scheduleCallback: false });
+ this.#updateSourceSectionClearButtonVisibility();
if (sourceSectionTextArea.value) {
this.#displayTranslatingPlaceholder();
@@ -1016,6 +1019,40 @@ class AboutTranslations {
}
/**
+ * Returns true if the source textarea contains any text, otherwise false.
+ *
+ * @returns {boolean}
+ */
+ #sourceTextAreaHasValue() {
+ return Boolean(this.elements.sourceSectionTextArea.value);
+ }
+
+ /**
+ * Shows or hides the source clear button based on whether the textarea has text.
+ */
+ #updateSourceSectionClearButtonVisibility() {
+ const { sourceSectionClearButton } = this.elements;
+
+ const shouldShow = this.#sourceTextAreaHasValue();
+ const isHidden = sourceSectionClearButton.hidden;
+
+ const shouldHide = !shouldShow;
+ const isShown = !isHidden;
+
+ if (shouldShow && isHidden) {
+ sourceSectionClearButton.hidden = false;
+ document.dispatchEvent(
+ new CustomEvent("AboutTranslationsTest:SourceTextClearButtonShown")
+ );
+ } else if (shouldHide && isShown) {
+ sourceSectionClearButton.hidden = true;
+ document.dispatchEvent(
+ new CustomEvent("AboutTranslationsTest:SourceTextClearButtonHidden")
+ );
+ }
+ }
+
+ /**
* Sets the value of the source <textarea> and dispatches an input event.
*
* @param {string} value
diff --git a/toolkit/components/translations/tests/browser/browser.toml b/toolkit/components/translations/tests/browser/browser.toml
@@ -41,6 +41,8 @@ skip-if = [
["browser_about_translations_flip_lexical_shortlist.js"]
+["browser_about_translations_source_clear_button_visibility.js"]
+
["browser_about_translations_swap_languages_button_detected.js"]
["browser_about_translations_swap_languages_button_explicit.js"]
diff --git a/toolkit/components/translations/tests/browser/browser_about_translations_source_clear_button_visibility.js b/toolkit/components/translations/tests/browser/browser_about_translations_source_clear_button_visibility.js
@@ -0,0 +1,96 @@
+/* Any copyright is dedicated to the Public Domain.
+ https://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function test_source_clear_button_shows_and_hides_with_input() {
+ const { aboutTranslationsTestUtils, cleanup } = await openAboutTranslations({
+ languagePairs: LANGUAGE_PAIRS,
+ });
+
+ await aboutTranslationsTestUtils.assertSourceClearButton({
+ visible: false,
+ });
+
+ await aboutTranslationsTestUtils.assertEvents(
+ {
+ expected: [
+ [AboutTranslationsTestUtils.Events.SourceTextClearButtonShown],
+ ],
+ unexpected: [
+ AboutTranslationsTestUtils.Events.SourceTextClearButtonHidden,
+ ],
+ },
+ async () => {
+ await aboutTranslationsTestUtils.setSourceTextAreaValue("Hello world");
+ }
+ );
+
+ await aboutTranslationsTestUtils.assertSourceClearButton({
+ visible: true,
+ });
+
+ await aboutTranslationsTestUtils.assertEvents(
+ {
+ expected: [
+ [AboutTranslationsTestUtils.Events.SourceTextClearButtonHidden],
+ ],
+ unexpected: [
+ AboutTranslationsTestUtils.Events.SourceTextClearButtonShown,
+ ],
+ },
+ async () => {
+ await aboutTranslationsTestUtils.setSourceTextAreaValue("");
+ }
+ );
+
+ await aboutTranslationsTestUtils.assertSourceClearButton({
+ visible: false,
+ });
+
+ await cleanup();
+});
+
+add_task(
+ async function test_source_clear_button_remains_visible_for_whitespace_only_values() {
+ const { aboutTranslationsTestUtils, cleanup } = await openAboutTranslations(
+ { languagePairs: LANGUAGE_PAIRS }
+ );
+
+ await aboutTranslationsTestUtils.assertSourceClearButton({
+ visible: false,
+ });
+
+ await aboutTranslationsTestUtils.assertEvents(
+ {
+ expected: [
+ [AboutTranslationsTestUtils.Events.SourceTextClearButtonShown],
+ ],
+ },
+ async () => {
+ await aboutTranslationsTestUtils.setSourceTextAreaValue("Text");
+ }
+ );
+
+ await aboutTranslationsTestUtils.assertSourceClearButton({
+ visible: true,
+ });
+
+ await aboutTranslationsTestUtils.assertEvents(
+ {
+ unexpected: [
+ AboutTranslationsTestUtils.Events.SourceTextClearButtonHidden,
+ ],
+ },
+ async () => {
+ await aboutTranslationsTestUtils.setSourceTextAreaValue(" ");
+ }
+ );
+
+ await aboutTranslationsTestUtils.assertSourceClearButton({
+ visible: true,
+ });
+
+ await cleanup();
+ }
+);
diff --git a/toolkit/components/translations/tests/browser/shared-head.js b/toolkit/components/translations/tests/browser/shared-head.js
@@ -210,6 +210,7 @@ async function openAboutTranslations({
swapLanguagesButton: "moz-button#about-translations-swap-languages-button",
sourceSectionTextArea: "textarea#about-translations-source-textarea",
targetSectionTextArea: "textarea#about-translations-target-textarea",
+ clearButton: "moz-button#about-translations-clear-button",
copyButton: "moz-button#about-translations-copy-button",
unsupportedInfoMessage:
"moz-message-bar#about-translations-unsupported-info-message",
@@ -4187,6 +4188,22 @@ class AboutTranslationsTestUtils {
static CopyButtonReset = "AboutTranslationsTest:CopyButtonReset";
/**
+ * Event fired when the source clear button becomes visible.
+ *
+ * @type {string}
+ */
+ static SourceTextClearButtonShown =
+ "AboutTranslationsTest:SourceTextClearButtonShown";
+
+ /**
+ * Event fired when the source clear button becomes hidden.
+ *
+ * @type {string}
+ */
+ static SourceTextClearButtonHidden =
+ "AboutTranslationsTest:SourceTextClearButtonHidden";
+
+ /**
* Event fired when the page layout changes.
*
* @type {string}
@@ -5179,6 +5196,66 @@ class AboutTranslationsTestUtils {
}
/**
+ * Retrieves the state of the source clear button.
+ *
+ * @returns {Promise<{exists: boolean, hidden: boolean, tabIndex: string | null}>}
+ */
+ async getSourceClearButtonState() {
+ await doubleRaf(document);
+
+ try {
+ return await this.#runInPage(selectors => {
+ const button = content.document.querySelector(selectors.clearButton);
+ return {
+ exists: !!button,
+ hidden: button?.hasAttribute("hidden") ?? true,
+ tabIndex: button?.getAttribute("tabindex") ?? null,
+ };
+ });
+ } catch (error) {
+ AboutTranslationsTestUtils.#reportTestFailure(error);
+ }
+
+ return {
+ exists: false,
+ hidden: true,
+ tabIndex: null,
+ };
+ }
+
+ /**
+ * Asserts properties of the source clear button.
+ *
+ * @param {object} options
+ * @param {boolean} [options.visible=false]
+ * @param {string} [options.tabIndex="-1"]
+ * @returns {Promise<void>}
+ */
+ async assertSourceClearButton({ visible = false, tabIndex = "-1" } = {}) {
+ const {
+ exists,
+ hidden,
+ tabIndex: actualTabIndex,
+ } = await this.getSourceClearButtonState();
+
+ ok(exists, "Expected source clear button to be present.");
+
+ if (visible) {
+ ok(!hidden, "Expected source clear button to be visible.");
+ } else {
+ ok(hidden, "Expected source clear button to be hidden.");
+ }
+
+ if (tabIndex !== undefined) {
+ is(
+ actualTabIndex,
+ tabIndex,
+ `Expected source clear button tabindex to be "${tabIndex}".`
+ );
+ }
+ }
+
+ /**
* Retrieves the current value of the target textarea.
*
* @returns {Promise<string>}
@@ -5380,6 +5457,8 @@ class AboutTranslationsTestUtils {
* @param {boolean} [options.mainUserInterface=false]
* @param {boolean} [options.sourceLanguageSelector=false]
* @param {boolean} [options.targetLanguageSelector=false]
+ * @param {boolean} [options.clearButton=undefined]
+ * The clear button visibility is automatically determined from source text if undefined.
* @param {boolean} [options.copyButton=false]
* @param {boolean} [options.swapLanguagesButton=false]
* @param {boolean} [options.sourceSectionTextArea=false]
@@ -5393,6 +5472,7 @@ class AboutTranslationsTestUtils {
mainUserInterface = false,
sourceLanguageSelector = false,
targetLanguageSelector = false,
+ clearButton = undefined,
copyButton = false,
swapLanguagesButton = false,
sourceSectionTextArea = false,
@@ -5404,6 +5484,16 @@ class AboutTranslationsTestUtils {
await doubleRaf(document);
try {
+ if (clearButton === undefined) {
+ const sourceTextAreaValue = await this.#runInPage(selectors => {
+ const sourceTextArea = content.document.querySelector(
+ selectors.sourceSectionTextArea
+ );
+ return sourceTextArea?.value ?? "";
+ });
+ clearButton = Boolean(sourceTextAreaValue.trim());
+ }
+
const visibilityMap = await this.#runInPage(selectors => {
const { document, window } = content;
const isElementVisible = selector => {
@@ -5420,6 +5510,7 @@ class AboutTranslationsTestUtils {
const { display, visibility } = computedStyle;
return !(display === "none" || visibility === "hidden");
};
+
return {
pageHeader: isElementVisible(selectors.pageHeader),
mainUserInterface: isElementVisible(selectors.mainUserInterface),
@@ -5429,6 +5520,7 @@ class AboutTranslationsTestUtils {
targetLanguageSelector: isElementVisible(
selectors.targetLanguageSelector
),
+ clearButton: isElementVisible(selectors.clearButton),
copyButton: isElementVisible(selectors.copyButton),
swapLanguagesButton: isElementVisible(selectors.swapLanguagesButton),
sourceSectionTextArea: isElementVisible(
@@ -5484,6 +5576,11 @@ class AboutTranslationsTestUtils {
"source textarea"
);
assertVisibility(
+ clearButton,
+ visibilityMap.clearButton,
+ "source clear button"
+ );
+ assertVisibility(
targetSectionTextArea,
visibilityMap.targetSectionTextArea,
"target textarea"