From 086c044dc34dfc0f74fbe41f4ecb402b2cd34884 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 03:13:33 +0200 Subject: Merging upstream version 125.0.1. Signed-off-by: Daniel Baumann --- .../aboutwelcome/tests/browser/browser.toml | 5 + .../browser/browser_aboutwelcome_attribution.js | 2 - .../browser_aboutwelcome_configurable_ui.js | 104 +++++---- ...rowser_aboutwelcome_multistage_experimentAPI.js | 208 ------------------ .../browser_aboutwelcome_multistage_transitions.js | 219 +++++++++++++++++++ .../tests/browser/browser_aboutwelcome_observer.js | 5 + .../browser/browser_aboutwelcome_toolbar_button.js | 64 ++++++ .../tests/unit/MultiStageAWProton.test.jsx | 103 +++++++++ .../tests/xpcshell/test_AboutWelcomeAttribution.js | 69 ++++++ .../tests/xpcshell/test_AboutWelcomeTelemetry.js | 90 ++++++++ .../xpcshell/test_AboutWelcomeTelemetry_glean.js | 238 +++++++++++++++++++++ .../aboutwelcome/tests/xpcshell/xpcshell.toml | 9 + 12 files changed, 866 insertions(+), 250 deletions(-) create mode 100644 browser/components/aboutwelcome/tests/browser/browser_aboutwelcome_multistage_transitions.js create mode 100644 browser/components/aboutwelcome/tests/browser/browser_aboutwelcome_toolbar_button.js create mode 100644 browser/components/aboutwelcome/tests/xpcshell/test_AboutWelcomeAttribution.js create mode 100644 browser/components/aboutwelcome/tests/xpcshell/test_AboutWelcomeTelemetry.js create mode 100644 browser/components/aboutwelcome/tests/xpcshell/test_AboutWelcomeTelemetry_glean.js create mode 100644 browser/components/aboutwelcome/tests/xpcshell/xpcshell.toml (limited to 'browser/components/aboutwelcome/tests') diff --git a/browser/components/aboutwelcome/tests/browser/browser.toml b/browser/components/aboutwelcome/tests/browser/browser.toml index 22d95272e8..96d1070645 100644 --- a/browser/components/aboutwelcome/tests/browser/browser.toml +++ b/browser/components/aboutwelcome/tests/browser/browser.toml @@ -37,6 +37,9 @@ skip-if = ["os == 'linux' && bits == 64"] # Bug 1757875 ["browser_aboutwelcome_multistage_mr.js"] skip-if = ["os == 'linux' && bits == 64 && debug"] # Bug 1812050 +["browser_aboutwelcome_multistage_transitions.js"] +skip-if = ["debug"] # Bug 1875203 + ["browser_aboutwelcome_multistage_video.js"] ["browser_aboutwelcome_observer.js"] @@ -50,6 +53,8 @@ skip-if = [ ["browser_aboutwelcome_screen_targeting.js"] +["browser_aboutwelcome_toolbar_button.js"] + ["browser_aboutwelcome_upgrade_multistage_mr.js"] skip-if = [ "win11_2009 && debug", diff --git a/browser/components/aboutwelcome/tests/browser/browser_aboutwelcome_attribution.js b/browser/components/aboutwelcome/tests/browser/browser_aboutwelcome_attribution.js index f0727c9b6f..d7076bd7c5 100644 --- a/browser/components/aboutwelcome/tests/browser/browser_aboutwelcome_attribution.js +++ b/browser/components/aboutwelcome/tests/browser/browser_aboutwelcome_attribution.js @@ -42,7 +42,6 @@ const TEST_PROTON_CONTENT = [ navigate: true, }, }, - has_noodles: true, }, }, { @@ -58,7 +57,6 @@ const TEST_PROTON_CONTENT = [ data: {}, }, }, - has_noodles: true, }, }, ]; diff --git a/browser/components/aboutwelcome/tests/browser/browser_aboutwelcome_configurable_ui.js b/browser/components/aboutwelcome/tests/browser/browser_aboutwelcome_configurable_ui.js index d53b5acc14..3081688a0c 100644 --- a/browser/components/aboutwelcome/tests/browser/browser_aboutwelcome_configurable_ui.js +++ b/browser/components/aboutwelcome/tests/browser/browser_aboutwelcome_configurable_ui.js @@ -334,46 +334,46 @@ add_task(async function test_aboutwelcome_dismiss_button() { /** * Test rendering a screen with the "split" position */ -add_task(async function test_aboutwelcome_split_position() { - const TEST_SPLIT_STEP = makeTestContent("TEST_SPLIT_STEP", { - position: "split", - hero_text: "hero test", - }); - - const TEST_SPLIT_JSON = JSON.stringify([TEST_SPLIT_STEP]); - let browser = await openAboutWelcome(TEST_SPLIT_JSON); - - await test_screen_content( - browser, - "renders screen secondary section containing hero text", - // Expected selectors: - [`main.screen[pos="split"]`, `.section-secondary`, `.message-text h1`] - ); - - // Ensure secondary section has split template styling - await test_element_styles( - browser, - "main.screen .section-secondary", - // Expected styles: - { - display: "flex", - margin: "auto 0px auto auto", - } - ); - - // Ensure secondary action has button styling - await test_element_styles( - browser, - ".action-buttons .secondary-cta .secondary", - // Expected styles: - { - // Override default text-link styles - "background-color": "color(srgb 0.0823529 0.0784314 0.101961 / 0.07)", - color: "rgb(21, 20, 26)", - } - ); - browser.closeBrowser(); -}); +// add_task(async function test_aboutwelcome_split_position() { +// const TEST_SPLIT_STEP = makeTestContent("TEST_SPLIT_STEP", { +// position: "split", +// hero_text: "hero test", +// }); + +// const TEST_SPLIT_JSON = JSON.stringify([TEST_SPLIT_STEP]); +// let browser = await openAboutWelcome(TEST_SPLIT_JSON); + +// await test_screen_content( +// browser, +// "renders screen secondary section containing hero text", +// // Expected selectors: +// [`main.screen[pos="split"]`, `.section-secondary`, `.message-text h1`] +// ); + +// // Ensure secondary section has split template styling +// await test_element_styles( +// browser, +// "main.screen .section-secondary", +// // Expected styles: +// { +// display: "flex", +// margin: "auto 0px auto auto", +// } +// ); + +// // Ensure secondary action has button styling +// await test_element_styles( +// browser, +// ".action-buttons .secondary-cta .secondary", +// // Expected styles: +// { +// // Override default text-link styles +// "background-color": "color(srgb 0.0823529 0.0784314 0.101961 / 0.07)", +// color: "rgb(21, 20, 26)", +// } +// ); +// browser.closeBrowser(); +// }); /** * Test rendering a screen with a URL value and default color for backdrop @@ -722,3 +722,27 @@ add_task(async function test_aboutwelcome_start_screen_configured() { browser.closeBrowser(); sandbox.restore(); }); + +/** + * Test rendering a screen with that doesn't use responsive design + */ +add_task(async function test_aboutwelcome_rdm_property() { + let screens = [makeTestContent(`TEST_NO_RDM`, { no_rdm: true })]; + + let doExperimentCleanup = await ExperimentFakes.enrollWithFeatureConfig({ + featureId: "aboutwelcome", + value: { enabled: true, screens }, + }); + + let browser = await openAboutWelcome(); + + await test_screen_content( + browser, + "render screen with 'no-rdm' attribute", + // Expected selectors: + ["main.TEST_NO_RDM[no-rdm]"] + ); + + await doExperimentCleanup(); + browser.closeBrowser(); +}); diff --git a/browser/components/aboutwelcome/tests/browser/browser_aboutwelcome_multistage_experimentAPI.js b/browser/components/aboutwelcome/tests/browser/browser_aboutwelcome_multistage_experimentAPI.js index 960d42a1f8..1e29e230ab 100644 --- a/browser/components/aboutwelcome/tests/browser/browser_aboutwelcome_multistage_experimentAPI.js +++ b/browser/components/aboutwelcome/tests/browser/browser_aboutwelcome_multistage_experimentAPI.js @@ -10,101 +10,6 @@ const { TelemetryTestUtils } = ChromeUtils.importESModule( "resource://testing-common/TelemetryTestUtils.sys.mjs" ); -const TEST_PROTON_CONTENT = [ - { - id: "AW_STEP1", - content: { - title: "Step 1", - primary_button: { - label: "Next", - action: { - navigate: true, - }, - }, - secondary_button: { - label: "link", - }, - secondary_button_top: { - label: "link top", - action: { - type: "SHOW_FIREFOX_ACCOUNTS", - data: { entrypoint: "test" }, - }, - }, - has_noodles: true, - }, - }, - { - id: "AW_STEP2", - content: { - title: "Step 2", - primary_button: { - label: "Next", - action: { - navigate: true, - }, - }, - secondary_button: { - label: "link", - }, - has_noodles: true, - }, - }, - { - id: "AW_STEP3", - content: { - title: "Step 3", - tiles: { - type: "theme", - action: { - theme: "", - }, - data: [ - { - theme: "automatic", - label: "theme-1", - tooltip: "test-tooltip", - }, - { - theme: "dark", - label: "theme-2", - }, - ], - }, - primary_button: { - label: "Next", - action: { - navigate: true, - }, - }, - secondary_button: { - label: "Import", - action: { - type: "SHOW_MIGRATION_WIZARD", - data: { source: "chrome" }, - }, - }, - has_noodles: true, - }, - }, - { - id: "AW_STEP4", - content: { - title: "Step 4", - primary_button: { - label: "Next", - action: { - navigate: true, - }, - }, - secondary_button: { - label: "link", - }, - has_noodles: true, - }, - }, -]; - /** * Test the zero onboarding using ExperimentAPI */ @@ -354,119 +259,6 @@ add_task(async function test_multistage_aboutwelcome_experimentAPI() { await doExperimentCleanup(); }); -/** - * Test the multistage proton welcome UI using ExperimentAPI with transitions - */ -add_task(async function test_multistage_aboutwelcome_transitions() { - const sandbox = sinon.createSandbox(); - await setAboutWelcomePref(true); - await ExperimentAPI.ready(); - - let doExperimentCleanup = await ExperimentFakes.enrollWithFeatureConfig({ - featureId: "aboutwelcome", - value: { - id: "my-mochitest-experiment", - enabled: true, - screens: TEST_PROTON_CONTENT, - transitions: true, - }, - }); - - let tab = await BrowserTestUtils.openNewForegroundTab( - gBrowser, - "about:welcome", - true - ); - - const browser = tab.linkedBrowser; - - let aboutWelcomeActor = await getAboutWelcomeParent(browser); - // Stub AboutWelcomeParent Content Message Handler - sandbox.spy(aboutWelcomeActor, "onContentMessage"); - registerCleanupFunction(() => { - BrowserTestUtils.removeTab(tab); - sandbox.restore(); - }); - - await test_screen_content( - browser, - "multistage proton step 1", - // Expected selectors: - ["div.proton.transition- .screen"], - // Unexpected selectors: - ["div.proton.transition-out"] - ); - - // Double click should still only transition once. - await onButtonClick(browser, "button.primary"); - await onButtonClick(browser, "button.primary"); - - await test_screen_content( - browser, - "multistage proton step 1 transition to 2", - // Expected selectors: - ["div.proton.transition-out .screen", "div.proton.transition- .screen-1"] - ); - - await doExperimentCleanup(); -}); - -/** - * Test the multistage proton welcome UI using ExperimentAPI without transitions - */ -add_task(async function test_multistage_aboutwelcome_transitions_off() { - const sandbox = sinon.createSandbox(); - await setAboutWelcomePref(true); - await ExperimentAPI.ready(); - - let doExperimentCleanup = await ExperimentFakes.enrollWithFeatureConfig({ - featureId: "aboutwelcome", - value: { - id: "my-mochitest-experiment", - enabled: true, - screens: TEST_PROTON_CONTENT, - transitions: false, - }, - }); - - let tab = await BrowserTestUtils.openNewForegroundTab( - gBrowser, - "about:welcome", - true - ); - - const browser = tab.linkedBrowser; - - let aboutWelcomeActor = await getAboutWelcomeParent(browser); - // Stub AboutWelcomeParent Content Message Handler - sandbox.spy(aboutWelcomeActor, "onContentMessage"); - registerCleanupFunction(() => { - BrowserTestUtils.removeTab(tab); - sandbox.restore(); - }); - - await test_screen_content( - browser, - "multistage proton step 1", - // Expected selectors: - ["div.proton.transition- .screen"], - // Unexpected selectors: - ["div.proton.transition-out"] - ); - - await onButtonClick(browser, "button.primary"); - await test_screen_content( - browser, - "multistage proton step 1 no transition to 2", - // Expected selectors: - [], - // Unexpected selectors: - ["div.proton.transition-out .screen-0"] - ); - - await doExperimentCleanup(); -}); - /* Test multistage custom backdrop */ add_task(async function test_multistage_aboutwelcome_backdrop() { diff --git a/browser/components/aboutwelcome/tests/browser/browser_aboutwelcome_multistage_transitions.js b/browser/components/aboutwelcome/tests/browser/browser_aboutwelcome_multistage_transitions.js new file mode 100644 index 0000000000..d27674ef6c --- /dev/null +++ b/browser/components/aboutwelcome/tests/browser/browser_aboutwelcome_multistage_transitions.js @@ -0,0 +1,219 @@ +"use strict"; + +const { ExperimentAPI } = ChromeUtils.importESModule( + "resource://nimbus/ExperimentAPI.sys.mjs" +); +const { ExperimentFakes } = ChromeUtils.importESModule( + "resource://testing-common/NimbusTestUtils.sys.mjs" +); +const { TelemetryTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/TelemetryTestUtils.sys.mjs" +); + +const TEST_PROTON_CONTENT = [ + { + id: "AW_STEP1", + content: { + title: "Step 1", + primary_button: { + label: "Next", + action: { + navigate: true, + }, + }, + secondary_button: { + label: "link", + }, + secondary_button_top: { + label: "link top", + action: { + type: "SHOW_FIREFOX_ACCOUNTS", + data: { entrypoint: "test" }, + }, + }, + has_noodles: true, + }, + }, + { + id: "AW_STEP2", + content: { + title: "Step 2", + primary_button: { + label: "Next", + action: { + navigate: true, + }, + }, + secondary_button: { + label: "link", + }, + has_noodles: true, + }, + }, + { + id: "AW_STEP3", + content: { + title: "Step 3", + tiles: { + type: "theme", + action: { + theme: "", + }, + data: [ + { + theme: "automatic", + label: "theme-1", + tooltip: "test-tooltip", + }, + { + theme: "dark", + label: "theme-2", + }, + ], + }, + primary_button: { + label: "Next", + action: { + navigate: true, + }, + }, + secondary_button: { + label: "Import", + action: { + type: "SHOW_MIGRATION_WIZARD", + data: { source: "chrome" }, + }, + }, + has_noodles: true, + }, + }, + { + id: "AW_STEP4", + content: { + title: "Step 4", + primary_button: { + label: "Next", + action: { + navigate: true, + }, + }, + secondary_button: { + label: "link", + }, + has_noodles: true, + }, + }, +]; + +/** + * Test the multistage proton welcome UI using ExperimentAPI with transitions + */ +add_task(async function test_multistage_aboutwelcome_transitions() { + const sandbox = sinon.createSandbox(); + await setAboutWelcomePref(true); + await ExperimentAPI.ready(); + + let doExperimentCleanup = await ExperimentFakes.enrollWithFeatureConfig({ + featureId: "aboutwelcome", + value: { + id: "my-mochitest-experiment", + enabled: true, + screens: TEST_PROTON_CONTENT, + transitions: true, + }, + }); + + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + "about:welcome", + true + ); + + const browser = tab.linkedBrowser; + + let aboutWelcomeActor = await getAboutWelcomeParent(browser); + // Stub AboutWelcomeParent Content Message Handler + sandbox.spy(aboutWelcomeActor, "onContentMessage"); + registerCleanupFunction(() => { + BrowserTestUtils.removeTab(tab); + sandbox.restore(); + }); + + await test_screen_content( + browser, + "multistage proton step 1", + // Expected selectors: + ["div.proton.transition- .screen"], + // Unexpected selectors: + ["div.proton.transition-out"] + ); + + // Double click should still only transition once. + await onButtonClick(browser, "button.primary"); + await onButtonClick(browser, "button.primary"); + + await test_screen_content( + browser, + "multistage proton step 1 transition to 2", + // Expected selectors: + ["div.proton.transition-out .screen", "div.proton.transition- .screen-1"] + ); + + await doExperimentCleanup(); +}); + +/** + * Test the multistage proton welcome UI using ExperimentAPI without transitions + */ +add_task(async function test_multistage_aboutwelcome_transitions_off() { + const sandbox = sinon.createSandbox(); + await setAboutWelcomePref(true); + await ExperimentAPI.ready(); + + let doExperimentCleanup = await ExperimentFakes.enrollWithFeatureConfig({ + featureId: "aboutwelcome", + value: { + id: "my-mochitest-experiment", + enabled: true, + screens: TEST_PROTON_CONTENT, + transitions: false, + }, + }); + + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + "about:welcome", + true + ); + + const browser = tab.linkedBrowser; + + let aboutWelcomeActor = await getAboutWelcomeParent(browser); + // Stub AboutWelcomeParent Content Message Handler + sandbox.spy(aboutWelcomeActor, "onContentMessage"); + registerCleanupFunction(() => { + BrowserTestUtils.removeTab(tab); + sandbox.restore(); + }); + + await test_screen_content( + browser, + "multistage proton step 1", + // Expected selectors: + ["div.proton.transition- .screen"], + // Unexpected selectors: + ["div.proton.transition-out"] + ); + + await onButtonClick(browser, "button.primary"); + await test_screen_content( + browser, + "multistage proton step 1 no transition to 2", + // Expected selectors: + [], + // Unexpected selectors: + ["div.proton.transition-out .screen-0"] + ); + + await doExperimentCleanup(); +}); diff --git a/browser/components/aboutwelcome/tests/browser/browser_aboutwelcome_observer.js b/browser/components/aboutwelcome/tests/browser/browser_aboutwelcome_observer.js index 58f9059532..d1fe0edc4c 100644 --- a/browser/components/aboutwelcome/tests/browser/browser_aboutwelcome_observer.js +++ b/browser/components/aboutwelcome/tests/browser/browser_aboutwelcome_observer.js @@ -70,4 +70,9 @@ add_task(async function test_About_Welcome_Location_Change() { aboutWelcomeActor.AboutWelcomeObserver.AWTerminate.ADDRESS_BAR_NAVIGATED, "Terminated due to location uri changed" ); + + // Wait for the end of the any transition happening due to + // location change before closing the window, See bug 1882067 + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + await new Promise(r => setTimeout(r, 100)); }); diff --git a/browser/components/aboutwelcome/tests/browser/browser_aboutwelcome_toolbar_button.js b/browser/components/aboutwelcome/tests/browser/browser_aboutwelcome_toolbar_button.js new file mode 100644 index 0000000000..c9180ddf2d --- /dev/null +++ b/browser/components/aboutwelcome/tests/browser/browser_aboutwelcome_toolbar_button.js @@ -0,0 +1,64 @@ +"use strict"; + +const { AboutWelcomeTelemetry } = ChromeUtils.importESModule( + "resource:///modules/aboutwelcome/AboutWelcomeTelemetry.sys.mjs" +); +const { AWToolbarButton } = ChromeUtils.importESModule( + "resource:///modules/aboutwelcome/AWToolbarUtils.sys.mjs" +); + +const TOOLBAR_PREF = "browser.aboutwelcome.toolbarButtonEnabled"; +const DID_SEE_FINAL_SCREEN_PREF = "browser.aboutwelcome.didSeeFinalScreen"; + +async function openNewTab() { + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + "about:newtab", + false + ); + + registerCleanupFunction(async () => { + BrowserTestUtils.removeTab(tab); + await SpecialPowers.popPrefEnv(); + }); + + return tab.linkedBrowser; +} + +add_task(async function test_add_and_remove_toolbar_button() { + // Clear the final screen pref, which may have been set by other tests + await SpecialPowers.pushPrefEnv({ + set: [[DID_SEE_FINAL_SCREEN_PREF, false]], + }); + // Open newtab + let win = await BrowserTestUtils.openNewBrowserWindow(); + win.BrowserOpenTab(); + ok(win, "browser exists"); + // Try to add the button. It shouldn't add because the pref is false + await AWToolbarButton.maybeAddSetupButton(); + ok( + !win.document.getElementById("aboutwelcome-button"), + "Button should not exist" + ); + // Set the pref and try again + await SpecialPowers.pushPrefEnv({ + set: [[TOOLBAR_PREF, true]], + }); + await AWToolbarButton.maybeAddSetupButton(); + // The button should exist + ok( + win.document.getElementById("aboutwelcome-button"), + "Button should be added." + ); + // Switch the pref to false and check again + await SpecialPowers.pushPrefEnv({ + set: [[TOOLBAR_PREF, false]], + }); + ok( + !win.document.getElementById("aboutwelcome-button"), + "Button should be removed" + ); + // Cleanup + await SpecialPowers.popPrefEnv(); + await BrowserTestUtils.closeWindow(win); +}); diff --git a/browser/components/aboutwelcome/tests/unit/MultiStageAWProton.test.jsx b/browser/components/aboutwelcome/tests/unit/MultiStageAWProton.test.jsx index a40af1c4a1..9b452d5c6b 100644 --- a/browser/components/aboutwelcome/tests/unit/MultiStageAWProton.test.jsx +++ b/browser/components/aboutwelcome/tests/unit/MultiStageAWProton.test.jsx @@ -385,6 +385,92 @@ describe("MultiStageAboutWelcomeProton module", () => { assert.equal(textEl.at(0).prop("data-l10n-id"), "test-string-id"); assert.equal(textEl.at(1).prop("data-l10n-id"), "test-string-id-2"); }); + + it("should render above_button_content legal copy with MultiSelect tile", async () => { + const SCREEN_PROPS = { + content: { + tiles: { + type: "multiselect", + label: "Test Subtitle", + data: [ + { + id: "checkbox-1", + type: "checkbox", + defaultValue: false, + label: { raw: "Checkbox 1" }, + }, + ], + }, + above_button_content: [ + { + type: "text", + text: { + string_id: "test-string-id", + }, + font_styles: "legal", + link_keys: ["privacy_policy", "terms_of_use"], + }, + ], + }, + setScreenMultiSelects: sandbox.stub(), + setActiveMultiSelect: sandbox.stub(), + }; + + const wrapper = mount(); + assert.ok(wrapper.exists()); + const legalText = wrapper.find(".legal-paragraph"); + assert.equal(legalText.exists(), true); + + const multiSelectContainer = wrapper.find(".multi-select-container"); + assert.equal(multiSelectContainer.exists(), true); + + sandbox.restore(); + }); + + it("should not have no-rdm property when property is not in message content", () => { + const SCREEN_PROPS = { + content: { + title: "test title", + layout: "inline", + }, + }; + const wrapper = mount(); + assert.ok(wrapper.exists()); + assert.notExists(wrapper.find("main").prop("no-rdm")); + }); + + it("should have no-rdm property when property is set in message content", () => { + const SCREEN_PROPS = { + content: { + title: "test title", + layout: "inline", + no_rdm: true, + }, + }; + const wrapper = mount(); + assert.ok(wrapper.exists()); + assert.exists(wrapper.find("main").prop("no-rdm")); + }); + + it("should correctly set reverse-split prop", () => { + const SCREEN_PROPS = { + content: { + position: "split", + reverse_split: true, + title: "test title", + primary_button: { + label: "test primary button", + }, + additional_button: { + label: "test additional button", + style: "link", + }, + }, + }; + const wrapper = mount(); + assert.ok(wrapper.exists()); + assert.equal(wrapper.find("main").prop("reverse-split"), ""); + }); }); describe("AboutWelcomeDefaults for proton", () => { @@ -568,4 +654,21 @@ describe("MultiStageAboutWelcomeProton module", () => { ); }); }); + + describe("Embedded Migration Wizard", () => { + const SCREEN_PROPS = { + content: { + title: "test title", + tiles: { + type: "migration-wizard", + }, + }, + }; + + it("should render migration wizard", async () => { + const wrapper = mount(); + assert.ok(wrapper.exists()); + assert.isTrue(wrapper.find("migration-wizard").exists()); + }); + }); }); diff --git a/browser/components/aboutwelcome/tests/xpcshell/test_AboutWelcomeAttribution.js b/browser/components/aboutwelcome/tests/xpcshell/test_AboutWelcomeAttribution.js new file mode 100644 index 0000000000..3d83f473d5 --- /dev/null +++ b/browser/components/aboutwelcome/tests/xpcshell/test_AboutWelcomeAttribution.js @@ -0,0 +1,69 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +"use strict"; + +const { AboutWelcomeDefaults } = ChromeUtils.importESModule( + "resource:///modules/aboutwelcome/AboutWelcomeDefaults.sys.mjs" +); +const { sinon } = ChromeUtils.importESModule( + "resource://testing-common/Sinon.sys.mjs" +); +const { AttributionCode } = ChromeUtils.importESModule( + "resource:///modules/AttributionCode.sys.mjs" +); +const { AddonRepository } = ChromeUtils.importESModule( + "resource://gre/modules/addons/AddonRepository.sys.mjs" +); + +const TEST_ATTRIBUTION_DATA = { + source: "addons.mozilla.org", + medium: "referral", + campaign: "non-fx-button", + content: "rta:iridium%40particlecore.github.io", +}; + +add_task(async function test_handleAddonInfoNotFound() { + let sandbox = sinon.createSandbox(); + const stub = sandbox.stub(AttributionCode, "getAttrDataAsync").resolves(null); + let result = await AboutWelcomeDefaults.getAttributionContent(); + equal(stub.callCount, 1, "Call was made"); + equal(result, null, "No data is returned"); + + sandbox.restore(); +}); + +add_task(async function test_UAAttribution() { + let sandbox = sinon.createSandbox(); + const stub = sandbox + .stub(AttributionCode, "getAttrDataAsync") + .resolves({ ua: "test" }); + let result = await AboutWelcomeDefaults.getAttributionContent(); + equal(stub.callCount, 1, "Call was made"); + equal(result.template, undefined, "Template was not returned"); + equal(result.ua, "test", "UA was returned"); + + sandbox.restore(); +}); + +add_task(async function test_formatAttributionData() { + let sandbox = sinon.createSandbox(); + const TEST_ADDON_INFO = { + sourceURI: { scheme: "https", spec: "https://test.xpi" }, + name: "Test Add-on", + icons: { 64: "http://test.svg" }, + }; + sandbox + .stub(AttributionCode, "getAttrDataAsync") + .resolves(TEST_ATTRIBUTION_DATA); + sandbox.stub(AddonRepository, "getAddonsByIDs").resolves([TEST_ADDON_INFO]); + let result = await AboutWelcomeDefaults.getAttributionContent( + TEST_ATTRIBUTION_DATA + ); + equal(AddonRepository.getAddonsByIDs.callCount, 1, "Retrieve addon content"); + equal(result.template, "return_to_amo", "RTAMO template returned"); + equal(result.name, TEST_ADDON_INFO.name, "AddonInfo returned"); + + sandbox.restore(); +}); diff --git a/browser/components/aboutwelcome/tests/xpcshell/test_AboutWelcomeTelemetry.js b/browser/components/aboutwelcome/tests/xpcshell/test_AboutWelcomeTelemetry.js new file mode 100644 index 0000000000..b8339fb39f --- /dev/null +++ b/browser/components/aboutwelcome/tests/xpcshell/test_AboutWelcomeTelemetry.js @@ -0,0 +1,90 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +"use strict"; + +const { AboutWelcomeTelemetry } = ChromeUtils.importESModule( + "resource:///modules/aboutwelcome/AboutWelcomeTelemetry.sys.mjs" +); +const { AttributionCode } = ChromeUtils.importESModule( + "resource:///modules/AttributionCode.sys.mjs" +); +const { sinon } = ChromeUtils.importESModule( + "resource://testing-common/Sinon.sys.mjs" +); +const TELEMETRY_PREF = "browser.newtabpage.activity-stream.telemetry"; + +add_setup(function setup() { + do_get_profile(); + Services.fog.initializeFOG(); +}); + +add_task(function test_enabled() { + registerCleanupFunction(() => { + Services.prefs.clearUserPref(TELEMETRY_PREF); + }); + Services.prefs.setBoolPref(TELEMETRY_PREF, true); + + const AWTelemetry = new AboutWelcomeTelemetry(); + + equal(AWTelemetry.telemetryEnabled, true, "Telemetry should be on"); + + Services.prefs.setBoolPref(TELEMETRY_PREF, false); + + equal(AWTelemetry.telemetryEnabled, false, "Telemetry should be off"); +}); + +add_task(async function test_pingPayload() { + registerCleanupFunction(() => { + Services.prefs.clearUserPref(TELEMETRY_PREF); + }); + Services.prefs.setBoolPref(TELEMETRY_PREF, true); + const AWTelemetry = new AboutWelcomeTelemetry(); + sinon.stub(AWTelemetry, "_createPing").resolves({ event: "MOCHITEST" }); + + let pingSubmitted = false; + GleanPings.messagingSystem.testBeforeNextSubmit(() => { + pingSubmitted = true; + Assert.equal(Glean.messagingSystem.event.testGetValue(), "MOCHITEST"); + }); + await AWTelemetry.sendTelemetry(); + + ok(pingSubmitted, "Glean ping was submitted"); +}); + +add_task(function test_mayAttachAttribution() { + const sandbox = sinon.createSandbox(); + const AWTelemetry = new AboutWelcomeTelemetry(); + + sandbox.stub(AttributionCode, "getCachedAttributionData").returns(null); + + let ping = AWTelemetry._maybeAttachAttribution({}); + + equal(ping.attribution, undefined, "Should not set attribution if it's null"); + + sandbox.restore(); + sandbox.stub(AttributionCode, "getCachedAttributionData").returns({}); + ping = AWTelemetry._maybeAttachAttribution({}); + + equal( + ping.attribution, + undefined, + "Should not set attribution if it's empty" + ); + + const attr = { + source: "google.com", + medium: "referral", + campaign: "Firefox-Brand-US-Chrome", + content: "(not set)", + experiment: "(not set)", + variation: "(not set)", + ua: "chrome", + }; + sandbox.restore(); + sandbox.stub(AttributionCode, "getCachedAttributionData").returns(attr); + ping = AWTelemetry._maybeAttachAttribution({}); + + equal(ping.attribution, attr, "Should set attribution if it presents"); +}); diff --git a/browser/components/aboutwelcome/tests/xpcshell/test_AboutWelcomeTelemetry_glean.js b/browser/components/aboutwelcome/tests/xpcshell/test_AboutWelcomeTelemetry_glean.js new file mode 100644 index 0000000000..5191f05d04 --- /dev/null +++ b/browser/components/aboutwelcome/tests/xpcshell/test_AboutWelcomeTelemetry_glean.js @@ -0,0 +1,238 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +"use strict"; + +const { AboutWelcomeTelemetry } = ChromeUtils.importESModule( + "resource:///modules/aboutwelcome/AboutWelcomeTelemetry.sys.mjs" +); +const TELEMETRY_PREF = "browser.newtabpage.activity-stream.telemetry"; + +add_setup(function setup() { + do_get_profile(); + Services.fog.initializeFOG(); +}); + +// We recognize two kinds of unexpected data that might reach +// `submitGleanPingForPing`: unknown keys, and keys with unexpectedly-complex +// data (ie, non-scalar). +// We report the keys in special metrics to aid in system health monitoring. +add_task(function test_weird_data() { + registerCleanupFunction(() => { + Services.prefs.clearUserPref(TELEMETRY_PREF); + }); + Services.prefs.setBoolPref(TELEMETRY_PREF, true); + + const AWTelemetry = new AboutWelcomeTelemetry(); + + const unknownKey = "some_unknown_key"; + const camelUnknownKey = AWTelemetry._snakeToCamelCase(unknownKey); + + let pingSubmitted = false; + GleanPings.messagingSystem.testBeforeNextSubmit(() => { + pingSubmitted = true; + Assert.equal( + Glean.messagingSystem.unknownKeys[camelUnknownKey].testGetValue(), + 1, + "caught the unknown key" + ); + // TODO(bug 1600008): Also check the for-testing overall count. + Assert.equal(Glean.messagingSystem.unknownKeyCount.testGetValue(), 1); + }); + AWTelemetry.submitGleanPingForPing({ + [unknownKey]: "value doesn't matter", + }); + + Assert.ok(pingSubmitted, "Ping with unknown keys was submitted"); + + const invalidNestedDataKey = "event"; + pingSubmitted = false; + GleanPings.messagingSystem.testBeforeNextSubmit(() => { + pingSubmitted = true; + Assert.equal( + Glean.messagingSystem.invalidNestedData[ + invalidNestedDataKey + ].testGetValue("messaging-system"), + 1, + "caught the invalid nested data" + ); + }); + AWTelemetry.submitGleanPingForPing({ + [invalidNestedDataKey]: { this_should: "not be", complex: "data" }, + }); + + Assert.ok(pingSubmitted, "Ping with invalid nested data submitted"); +}); + +// `event_context` is weird. It's an object, but it might have been stringified +// before being provided for recording. +add_task(function test_event_context() { + registerCleanupFunction(() => { + Services.prefs.clearUserPref(TELEMETRY_PREF); + }); + Services.prefs.setBoolPref(TELEMETRY_PREF, true); + + const AWTelemetry = new AboutWelcomeTelemetry(); + + const eventContext = { + reason: "reason", + page: "page", + source: "source", + something_else: "not specifically handled", + screen_family: "family", + screen_id: "screen_id", + screen_index: 0, + screen_initlals: "screen_initials", + }; + const stringifiedEC = JSON.stringify(eventContext); + + let pingSubmitted = false; + GleanPings.messagingSystem.testBeforeNextSubmit(() => { + pingSubmitted = true; + Assert.equal( + Glean.messagingSystem.eventReason.testGetValue(), + eventContext.reason, + "event_context.reason also in own metric." + ); + Assert.equal( + Glean.messagingSystem.eventPage.testGetValue(), + eventContext.page, + "event_context.page also in own metric." + ); + Assert.equal( + Glean.messagingSystem.eventSource.testGetValue(), + eventContext.source, + "event_context.source also in own metric." + ); + Assert.equal( + Glean.messagingSystem.eventScreenFamily.testGetValue(), + eventContext.screen_family, + "event_context.screen_family also in own metric." + ); + Assert.equal( + Glean.messagingSystem.eventScreenId.testGetValue(), + eventContext.screen_id, + "event_context.screen_id also in own metric." + ); + Assert.equal( + Glean.messagingSystem.eventScreenIndex.testGetValue(), + eventContext.screen_index, + "event_context.screen_index also in own metric." + ); + Assert.equal( + Glean.messagingSystem.eventScreenInitials.testGetValue(), + eventContext.screen_initials, + "event_context.screen_initials also in own metric." + ); + + Assert.equal( + Glean.messagingSystem.eventContext.testGetValue(), + stringifiedEC, + "whole event_context added as text." + ); + }); + AWTelemetry.submitGleanPingForPing({ + event_context: eventContext, + }); + Assert.ok(pingSubmitted, "Ping with object event_context submitted"); + + pingSubmitted = false; + GleanPings.messagingSystem.testBeforeNextSubmit(() => { + pingSubmitted = true; + Assert.equal( + Glean.messagingSystem.eventReason.testGetValue(), + eventContext.reason, + "event_context.reason also in own metric." + ); + Assert.equal( + Glean.messagingSystem.eventPage.testGetValue(), + eventContext.page, + "event_context.page also in own metric." + ); + Assert.equal( + Glean.messagingSystem.eventSource.testGetValue(), + eventContext.source, + "event_context.source also in own metric." + ); + Assert.equal( + Glean.messagingSystem.eventScreenFamily.testGetValue(), + eventContext.screen_family, + "event_context.screen_family also in own metric." + ); + Assert.equal( + Glean.messagingSystem.eventScreenId.testGetValue(), + eventContext.screen_id, + "event_context.screen_id also in own metric." + ); + Assert.equal( + Glean.messagingSystem.eventScreenIndex.testGetValue(), + eventContext.screen_index, + "event_context.screen_index also in own metric." + ); + Assert.equal( + Glean.messagingSystem.eventScreenInitials.testGetValue(), + eventContext.screen_initials, + "event_context.screen_initials also in own metric." + ); + + Assert.equal( + Glean.messagingSystem.eventContext.testGetValue(), + stringifiedEC, + "whole event_context added as text." + ); + }); + AWTelemetry.submitGleanPingForPing({ + event_context: stringifiedEC, + }); + Assert.ok(pingSubmitted, "Ping with string event_context submitted"); +}); + +// For event_context to be more useful, we want to make sure we don't error +// in cases where it doesn't make much sense, such as a plain string that +// doesnt attempt to represent a valid object. +add_task(function test_context_errors() { + registerCleanupFunction(() => { + Services.prefs.clearUserPref(TELEMETRY_PREF); + }); + Services.prefs.setBoolPref(TELEMETRY_PREF, true); + + const AWTelemetry = new AboutWelcomeTelemetry(); + + let weird_context_ping = { + event_context: "oops, this string isn't a valid JS object!", + }; + + let pingSubmitted = false; + GleanPings.messagingSystem.testBeforeNextSubmit(() => { + pingSubmitted = true; + Assert.equal( + Glean.messagingSystem.eventContextParseError.testGetValue(), + undefined, + "this poorly formed context shouldn't register because it was not an object!" + ); + }); + + AWTelemetry.submitGleanPingForPing(weird_context_ping); + + Assert.ok(pingSubmitted, "Ping with unknown keys was submitted"); + + weird_context_ping = { + event_context: + "{oops : {'this string isn't a valid JS object, but it sure looks like one!}}'", + }; + + pingSubmitted = false; + GleanPings.messagingSystem.testBeforeNextSubmit(() => { + pingSubmitted = true; + Assert.equal( + Glean.messagingSystem.eventContextParseError.testGetValue(), + 1, + "this poorly formed context should register because it was not an object!" + ); + }); + + AWTelemetry.submitGleanPingForPing(weird_context_ping); + + Assert.ok(pingSubmitted, "Ping with unknown keys was submitted"); +}); diff --git a/browser/components/aboutwelcome/tests/xpcshell/xpcshell.toml b/browser/components/aboutwelcome/tests/xpcshell/xpcshell.toml new file mode 100644 index 0000000000..d68ee3e51e --- /dev/null +++ b/browser/components/aboutwelcome/tests/xpcshell/xpcshell.toml @@ -0,0 +1,9 @@ +[DEFAULT] +skip-if = ["os == 'android'"] +firefox-appdir = "browser" + +["test_AboutWelcomeAttribution.js"] + +["test_AboutWelcomeTelemetry.js"] + +["test_AboutWelcomeTelemetry_glean.js"] -- cgit v1.2.3