summaryrefslogtreecommitdiffstats
path: root/browser/components/newtab/test/unit/aboutwelcome
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/newtab/test/unit/aboutwelcome')
-rw-r--r--browser/components/newtab/test/unit/aboutwelcome/AWScreenUtils.test.jsx140
-rw-r--r--browser/components/newtab/test/unit/aboutwelcome/CTAParagraph.test.jsx49
-rw-r--r--browser/components/newtab/test/unit/aboutwelcome/HeroImage.test.jsx40
-rw-r--r--browser/components/newtab/test/unit/aboutwelcome/MRColorways.test.jsx328
-rw-r--r--browser/components/newtab/test/unit/aboutwelcome/MobileDownloads.test.jsx69
-rw-r--r--browser/components/newtab/test/unit/aboutwelcome/MultiSelect.test.jsx151
-rw-r--r--browser/components/newtab/test/unit/aboutwelcome/MultiStageAWProton.test.jsx564
-rw-r--r--browser/components/newtab/test/unit/aboutwelcome/MultiStageAboutWelcome.test.jsx824
-rw-r--r--browser/components/newtab/test/unit/aboutwelcome/OnboardingVideoTest.test.jsx45
9 files changed, 2210 insertions, 0 deletions
diff --git a/browser/components/newtab/test/unit/aboutwelcome/AWScreenUtils.test.jsx b/browser/components/newtab/test/unit/aboutwelcome/AWScreenUtils.test.jsx
new file mode 100644
index 0000000000..a9f401f6b7
--- /dev/null
+++ b/browser/components/newtab/test/unit/aboutwelcome/AWScreenUtils.test.jsx
@@ -0,0 +1,140 @@
+import { AWScreenUtils } from "lib/AWScreenUtils.jsm";
+import { GlobalOverrider } from "test/unit/utils";
+import { ASRouter } from "lib/ASRouter.jsm";
+
+describe("AWScreenUtils", () => {
+ let sandbox;
+ let globals;
+
+ beforeEach(() => {
+ globals = new GlobalOverrider();
+ globals.set({
+ ASRouter,
+ ASRouterTargeting: {
+ Environment: {},
+ },
+ });
+
+ sandbox = sinon.createSandbox();
+ });
+ afterEach(() => {
+ sandbox.restore();
+ globals.restore();
+ });
+ describe("removeScreens", () => {
+ it("should run callback function once for each array element", async () => {
+ const callback = sandbox.stub().resolves(false);
+ const arr = ["foo", "bar"];
+ await AWScreenUtils.removeScreens(arr, callback);
+ assert.calledTwice(callback);
+ });
+ it("should remove screen when passed function evaluates true", async () => {
+ const callback = sandbox.stub().resolves(true);
+ const arr = ["foo", "bar"];
+ await AWScreenUtils.removeScreens(arr, callback);
+ assert.deepEqual(arr, []);
+ });
+ });
+ describe("evaluateScreenTargeting", () => {
+ it("should return the eval result if the eval succeeds", async () => {
+ const evalStub = sandbox.stub(ASRouter, "evaluateExpression").resolves({
+ evaluationStatus: {
+ success: true,
+ result: false,
+ },
+ });
+ const result = await AWScreenUtils.evaluateScreenTargeting(
+ "test expression"
+ );
+ assert.calledOnce(evalStub);
+ assert.equal(result, false);
+ });
+ it("should return true if the targeting eval fails", async () => {
+ const evalStub = sandbox.stub(ASRouter, "evaluateExpression").resolves({
+ evaluationStatus: {
+ success: false,
+ result: false,
+ },
+ });
+ const result = await AWScreenUtils.evaluateScreenTargeting(
+ "test expression"
+ );
+ assert.calledOnce(evalStub);
+ assert.equal(result, true);
+ });
+ });
+ describe("evaluateTargetingAndRemoveScreens", () => {
+ it("should manipulate an array of screens", async () => {
+ const screens = [
+ {
+ id: "first",
+ targeting: true,
+ },
+ {
+ id: "second",
+ targeting: false,
+ },
+ ];
+
+ const expectedScreens = [
+ {
+ id: "first",
+ targeting: true,
+ },
+ ];
+ sandbox.stub(ASRouter, "evaluateExpression").callsFake(targeting => {
+ return {
+ evaluationStatus: {
+ success: true,
+ result: targeting.expression,
+ },
+ };
+ });
+ const evaluatedStrings =
+ await AWScreenUtils.evaluateTargetingAndRemoveScreens(screens);
+ assert.deepEqual(evaluatedStrings, expectedScreens);
+ });
+ it("should not remove screens with no targeting", async () => {
+ const screens = [
+ {
+ id: "first",
+ },
+ {
+ id: "second",
+ targeting: false,
+ },
+ ];
+
+ const expectedScreens = [
+ {
+ id: "first",
+ },
+ ];
+ sandbox
+ .stub(AWScreenUtils, "evaluateScreenTargeting")
+ .callsFake(targeting => {
+ if (targeting === undefined) {
+ return true;
+ }
+ return targeting;
+ });
+ const evaluatedStrings =
+ await AWScreenUtils.evaluateTargetingAndRemoveScreens(screens);
+ assert.deepEqual(evaluatedStrings, expectedScreens);
+ });
+ });
+
+ describe("addScreenImpression", () => {
+ it("Should call addScreenImpression with provided screen ID", () => {
+ const addScreenImpressionStub = sandbox.stub(
+ ASRouter,
+ "addScreenImpression"
+ );
+ const testScreen = { id: "test" };
+ AWScreenUtils.addScreenImpression(testScreen);
+
+ assert.calledOnce(addScreenImpressionStub);
+ assert.equal(addScreenImpressionStub.firstCall.args[0].id, testScreen.id);
+ });
+ });
+});
diff --git a/browser/components/newtab/test/unit/aboutwelcome/CTAParagraph.test.jsx b/browser/components/newtab/test/unit/aboutwelcome/CTAParagraph.test.jsx
new file mode 100644
index 0000000000..57773b0e82
--- /dev/null
+++ b/browser/components/newtab/test/unit/aboutwelcome/CTAParagraph.test.jsx
@@ -0,0 +1,49 @@
+import React from "react";
+import { shallow } from "enzyme";
+import { CTAParagraph } from "content-src/aboutwelcome/components/CTAParagraph";
+
+describe("CTAParagraph component", () => {
+ let sandbox;
+ let wrapper;
+ let handleAction;
+
+ beforeEach(() => {
+ sandbox = sinon.createSandbox();
+ handleAction = sandbox.stub();
+ wrapper = shallow(
+ <CTAParagraph
+ content={{
+ text: {
+ raw: "Link Text",
+ string_name: "Test Name",
+ },
+ }}
+ handleAction={handleAction}
+ />
+ );
+ });
+
+ afterEach(() => {
+ sandbox.restore();
+ });
+
+ it("should render CTAParagraph component", () => {
+ assert.ok(wrapper.exists());
+ });
+
+ it("should render CTAParagraph component if only CTA text is passed", () => {
+ wrapper.setProps({ content: { text: "CTA Text" } });
+ assert.ok(wrapper.exists());
+ });
+
+ it("should call handleAction method when button is link is clicked", () => {
+ const btnLink = wrapper.find(".cta-paragraph span");
+ btnLink.simulate("click");
+ assert.calledOnce(handleAction);
+ });
+
+ it("should not render CTAParagraph component if CTA text is not passed", () => {
+ wrapper.setProps({ content: { text: null } });
+ assert.ok(wrapper.isEmptyRender());
+ });
+});
diff --git a/browser/components/newtab/test/unit/aboutwelcome/HeroImage.test.jsx b/browser/components/newtab/test/unit/aboutwelcome/HeroImage.test.jsx
new file mode 100644
index 0000000000..8c9bdc8e50
--- /dev/null
+++ b/browser/components/newtab/test/unit/aboutwelcome/HeroImage.test.jsx
@@ -0,0 +1,40 @@
+import React from "react";
+import { shallow } from "enzyme";
+import { HeroImage } from "content-src/aboutwelcome/components/HeroImage";
+
+describe("HeroImage component", () => {
+ const imageUrl = "https://example.com";
+ const imageHeight = "100px";
+ const imageAlt = "Alt text";
+
+ let wrapper;
+ beforeEach(() => {
+ wrapper = shallow(
+ <HeroImage url={imageUrl} alt={imageAlt} height={imageHeight} />
+ );
+ });
+
+ it("should render HeroImage component", () => {
+ assert.ok(wrapper.exists());
+ });
+
+ it("should render an image element with src prop", () => {
+ let imgEl = wrapper.find("img");
+ assert.strictEqual(imgEl.prop("src"), imageUrl);
+ });
+
+ it("should render image element with alt text prop", () => {
+ let imgEl = wrapper.find("img");
+ assert.equal(imgEl.prop("alt"), imageAlt);
+ });
+
+ it("should render an image with a set height prop", () => {
+ let imgEl = wrapper.find("img");
+ assert.propertyVal(imgEl.prop("style"), "height", imageHeight);
+ });
+
+ it("should not render HeroImage component", () => {
+ wrapper.setProps({ url: null });
+ assert.ok(wrapper.isEmptyRender());
+ });
+});
diff --git a/browser/components/newtab/test/unit/aboutwelcome/MRColorways.test.jsx b/browser/components/newtab/test/unit/aboutwelcome/MRColorways.test.jsx
new file mode 100644
index 0000000000..c8829d76a7
--- /dev/null
+++ b/browser/components/newtab/test/unit/aboutwelcome/MRColorways.test.jsx
@@ -0,0 +1,328 @@
+import React from "react";
+import { shallow } from "enzyme";
+import {
+ Colorways,
+ computeColorWay,
+ ColorwayDescription,
+ computeVariationIndex,
+} from "content-src/aboutwelcome/components/MRColorways";
+import { WelcomeScreen } from "content-src/aboutwelcome/components/MultiStageAboutWelcome";
+
+describe("Multistage AboutWelcome module", () => {
+ let sandbox;
+ let COLORWAY_SCREEN_PROPS;
+ beforeEach(() => {
+ sandbox = sinon.createSandbox();
+ COLORWAY_SCREEN_PROPS = {
+ id: "test-colorway-screen",
+ totalNumberofScreens: 1,
+ content: {
+ subtitle: "test subtitle",
+ tiles: {
+ type: "colorway",
+ action: {
+ theme: "<event>",
+ },
+ defaultVariationIndex: 0,
+ systemVariations: ["automatic", "light"],
+ variations: ["soft", "bold"],
+ colorways: [
+ {
+ id: "default",
+ label: "Default",
+ },
+ {
+ id: "abstract",
+ label: "Abstract",
+ },
+ ],
+ },
+ primary_button: {
+ action: {},
+ label: "test button",
+ },
+ },
+ messageId: "test-mr-colorway-screen",
+ activeTheme: "automatic",
+ };
+ });
+ afterEach(() => {
+ sandbox.restore();
+ });
+
+ describe("MRColorway component", () => {
+ it("should render WelcomeScreen", () => {
+ const wrapper = shallow(<WelcomeScreen {...COLORWAY_SCREEN_PROPS} />);
+
+ assert.ok(wrapper.exists());
+ });
+
+ it("should use default when activeTheme is not set", () => {
+ const wrapper = shallow(<Colorways {...COLORWAY_SCREEN_PROPS} />);
+ wrapper.setProps({ activeTheme: null });
+
+ const colorwaysOptionIcons = wrapper.find(
+ ".tiles-theme-section .theme .icon"
+ );
+ assert.strictEqual(colorwaysOptionIcons.length, 2);
+
+ // Default automatic theme is selected by default
+ assert.strictEqual(
+ colorwaysOptionIcons.first().prop("className").includes("selected"),
+ true
+ );
+
+ assert.strictEqual(
+ colorwaysOptionIcons.first().prop("className").includes("default"),
+ true
+ );
+ });
+
+ it("should use default when activeTheme is alpenglow", () => {
+ const wrapper = shallow(<Colorways {...COLORWAY_SCREEN_PROPS} />);
+ wrapper.setProps({ activeTheme: "alpenglow" });
+
+ const colorwaysOptionIcons = wrapper.find(
+ ".tiles-theme-section .theme .icon"
+ );
+ assert.strictEqual(colorwaysOptionIcons.length, 2);
+
+ // Default automatic theme is selected when unsupported in colorway alpenglow theme is active
+ assert.strictEqual(
+ colorwaysOptionIcons.first().prop("className").includes("selected"),
+ true
+ );
+
+ assert.strictEqual(
+ colorwaysOptionIcons.first().prop("className").includes("default"),
+ true
+ );
+ });
+
+ it("should render colorways options", () => {
+ const wrapper = shallow(<Colorways {...COLORWAY_SCREEN_PROPS} />);
+
+ const colorwaysOptions = wrapper.find(
+ ".tiles-theme-section .theme input[name='theme']"
+ );
+
+ const colorwaysOptionIcons = wrapper.find(
+ ".tiles-theme-section .theme .icon"
+ );
+
+ const colorwaysLabels = wrapper.find(
+ ".tiles-theme-section .theme span.sr-only"
+ );
+
+ assert.strictEqual(colorwaysOptions.length, 2);
+ assert.strictEqual(colorwaysOptionIcons.length, 2);
+ assert.strictEqual(colorwaysLabels.length, 2);
+
+ // First colorway option
+ // Default theme radio option is selected by default
+ assert.strictEqual(
+ colorwaysOptionIcons.first().prop("className").includes("selected"),
+ true
+ );
+
+ //Colorway should be using id property
+ assert.strictEqual(
+ colorwaysOptions.first().prop("data-colorway"),
+ "default"
+ );
+
+ // Second colorway option
+ assert.strictEqual(
+ colorwaysOptionIcons.last().prop("className").includes("selected"),
+ false
+ );
+
+ //Colorway should be using id property
+ assert.strictEqual(
+ colorwaysOptions.last().prop("data-colorway"),
+ "abstract"
+ );
+
+ //Colorway should be labelled for screen readers (parent label is for tooltip only, and does not describe the Colorway)
+ assert.strictEqual(
+ colorwaysOptions.last().prop("aria-labelledby"),
+ "abstract-label"
+ );
+ });
+
+ it("should handle colorway clicks", () => {
+ sandbox.stub(React, "useEffect").callsFake((fn, vals) => {
+ if (vals === undefined) {
+ fn();
+ } else if (vals[0] === "in") {
+ fn();
+ }
+ });
+
+ const handleAction = sandbox.stub();
+ const wrapper = shallow(
+ <Colorways handleAction={handleAction} {...COLORWAY_SCREEN_PROPS} />
+ );
+ const colorwaysOptions = wrapper.find(
+ ".tiles-theme-section .theme input[name='theme']"
+ );
+
+ let props = wrapper.find(ColorwayDescription).props();
+ assert.propertyVal(props.colorway, "label", "Default");
+
+ const option = colorwaysOptions.last();
+ assert.propertyVal(option.props(), "value", "abstract-soft");
+ colorwaysOptions.last().simulate("click");
+ assert.calledOnce(handleAction);
+ });
+
+ it("should render colorway description", () => {
+ const wrapper = shallow(<Colorways {...COLORWAY_SCREEN_PROPS} />);
+
+ let descriptionsWrapper = wrapper.find(ColorwayDescription);
+ assert.ok(descriptionsWrapper.exists());
+
+ let props = descriptionsWrapper.props();
+
+ // Colorway description should display Default theme desc by default
+ assert.strictEqual(props.colorway.label, "Default");
+ });
+
+ it("ColorwayDescription should display active colorway desc", () => {
+ let TEST_COLORWAY_PROPS = {
+ colorway: {
+ label: "Activist",
+ description: "Test Activist",
+ },
+ };
+ const descWrapper = shallow(
+ <ColorwayDescription {...TEST_COLORWAY_PROPS} />
+ );
+ assert.ok(descWrapper.exists());
+ const descText = descWrapper.find(".colorway-text");
+ assert.equal(
+ descText.props()["data-l10n-args"].includes("Activist"),
+ true
+ );
+ });
+
+ it("should computeColorWayId for default active theme", () => {
+ let TEST_COLORWAY_PROPS = {
+ ...COLORWAY_SCREEN_PROPS,
+ };
+
+ const colorwayId = computeColorWay(
+ TEST_COLORWAY_PROPS.activeTheme,
+ TEST_COLORWAY_PROPS.content.tiles.systemVariations
+ );
+ assert.strictEqual(colorwayId, "default");
+ });
+
+ it("should computeColorWayId for non-default active theme", () => {
+ let TEST_COLORWAY_PROPS = {
+ ...COLORWAY_SCREEN_PROPS,
+ activeTheme: "abstract-soft",
+ };
+
+ const colorwayId = computeColorWay(
+ TEST_COLORWAY_PROPS.activeTheme,
+ TEST_COLORWAY_PROPS.content.tiles.systemVariations
+ );
+ assert.strictEqual(colorwayId, "abstract");
+ });
+
+ it("should computeVariationIndex for default active theme", () => {
+ let TEST_COLORWAY_PROPS = {
+ ...COLORWAY_SCREEN_PROPS,
+ };
+
+ const variationIndex = computeVariationIndex(
+ TEST_COLORWAY_PROPS.activeTheme,
+ TEST_COLORWAY_PROPS.content.tiles.systemVariations,
+ TEST_COLORWAY_PROPS.content.tiles.variations,
+ TEST_COLORWAY_PROPS.content.tiles.defaultVariationIndex
+ );
+ assert.strictEqual(
+ variationIndex,
+ TEST_COLORWAY_PROPS.content.tiles.defaultVariationIndex
+ );
+ });
+
+ it("should computeVariationIndex for active theme", () => {
+ let TEST_COLORWAY_PROPS = {
+ ...COLORWAY_SCREEN_PROPS,
+ };
+
+ const variationIndex = computeVariationIndex(
+ "light",
+ TEST_COLORWAY_PROPS.content.tiles.systemVariations,
+ TEST_COLORWAY_PROPS.content.tiles.variations,
+ TEST_COLORWAY_PROPS.content.tiles.defaultVariationIndex
+ );
+ assert.strictEqual(variationIndex, 1);
+ });
+
+ it("should computeVariationIndex for colorway theme", () => {
+ let TEST_COLORWAY_PROPS = {
+ ...COLORWAY_SCREEN_PROPS,
+ };
+
+ const variationIndex = computeVariationIndex(
+ "abstract-bold",
+ TEST_COLORWAY_PROPS.content.tiles.systemVariations,
+ TEST_COLORWAY_PROPS.content.tiles.variations,
+ TEST_COLORWAY_PROPS.content.tiles.defaultVariationIndex
+ );
+ assert.strictEqual(variationIndex, 1);
+ });
+
+ describe("random colorways", () => {
+ let test;
+ beforeEach(() => {
+ COLORWAY_SCREEN_PROPS.handleAction = sandbox.stub();
+ sandbox.stub(window, "matchMedia");
+ // eslint-disable-next-line max-nested-callbacks
+ sandbox.stub(React, "useEffect").callsFake((fn, vals) => {
+ if (vals?.length === 0) {
+ fn();
+ }
+ });
+ test = () => {
+ shallow(<Colorways {...COLORWAY_SCREEN_PROPS} />);
+ return COLORWAY_SCREEN_PROPS.handleAction.firstCall.firstArg
+ .currentTarget;
+ };
+ });
+
+ it("should select a random colorway", () => {
+ const { value } = test();
+
+ assert.strictEqual(value, "abstract-soft");
+ assert.calledThrice(React.useEffect);
+ assert.notCalled(window.matchMedia);
+ });
+
+ it("should select a random soft colorway when not dark", () => {
+ window.matchMedia.returns({ matches: false });
+ COLORWAY_SCREEN_PROPS.content.tiles.darkVariation = 1;
+
+ const { value } = test();
+
+ assert.strictEqual(value, "abstract-soft");
+ assert.calledThrice(React.useEffect);
+ assert.calledOnce(window.matchMedia);
+ });
+
+ it("should select a random bold colorway when dark", () => {
+ window.matchMedia.returns({ matches: true });
+ COLORWAY_SCREEN_PROPS.content.tiles.darkVariation = 1;
+
+ const { value } = test();
+
+ assert.strictEqual(value, "abstract-bold");
+ assert.calledThrice(React.useEffect);
+ assert.calledOnce(window.matchMedia);
+ });
+ });
+ });
+});
diff --git a/browser/components/newtab/test/unit/aboutwelcome/MobileDownloads.test.jsx b/browser/components/newtab/test/unit/aboutwelcome/MobileDownloads.test.jsx
new file mode 100644
index 0000000000..e3ed5d2bb0
--- /dev/null
+++ b/browser/components/newtab/test/unit/aboutwelcome/MobileDownloads.test.jsx
@@ -0,0 +1,69 @@
+import React from "react";
+import { shallow, mount } from "enzyme";
+import { GlobalOverrider } from "test/unit/utils";
+import { MobileDownloads } from "content-src/aboutwelcome/components/MobileDownloads";
+
+describe("Multistage AboutWelcome MobileDownloads module", () => {
+ let globals;
+ let sandbox;
+
+ beforeEach(async () => {
+ globals = new GlobalOverrider();
+ globals.set({
+ AWFinish: () => Promise.resolve(),
+ AWSendToDeviceEmailsSupported: () => Promise.resolve(),
+ });
+ sandbox = sinon.createSandbox();
+ });
+
+ afterEach(() => {
+ sandbox.restore();
+ globals.restore();
+ });
+
+ describe("Mobile Downloads component", () => {
+ const MOBILE_DOWNLOADS_PROPS = {
+ data: {
+ QR_code: {
+ image_url:
+ "chrome://browser/components/privatebrowsing/content/assets/focus-qr-code.svg",
+ alt_text: {
+ string_id: "spotlight-focus-promo-qr-code",
+ },
+ },
+ email: {
+ link_text: "Email yourself a link",
+ },
+ marketplace_buttons: ["ios", "android"],
+ },
+ handleAction: () => {
+ window.AWFinish();
+ },
+ };
+
+ it("should render MobileDownloads", () => {
+ const wrapper = shallow(<MobileDownloads {...MOBILE_DOWNLOADS_PROPS} />);
+
+ assert.ok(wrapper.exists());
+ });
+
+ it("should handle action on markeplace badge click", () => {
+ const wrapper = mount(<MobileDownloads {...MOBILE_DOWNLOADS_PROPS} />);
+
+ const stub = sandbox.stub(global, "AWFinish");
+ wrapper.find(".ios button").simulate("click");
+ wrapper.find(".android button").simulate("click");
+
+ assert.calledTwice(stub);
+ });
+
+ it("should handle action on email button click", () => {
+ const wrapper = shallow(<MobileDownloads {...MOBILE_DOWNLOADS_PROPS} />);
+
+ const stub = sandbox.stub(global, "AWFinish");
+ wrapper.find("button.email-link").simulate("click");
+
+ assert.calledOnce(stub);
+ });
+ });
+});
diff --git a/browser/components/newtab/test/unit/aboutwelcome/MultiSelect.test.jsx b/browser/components/newtab/test/unit/aboutwelcome/MultiSelect.test.jsx
new file mode 100644
index 0000000000..cb1ce3651a
--- /dev/null
+++ b/browser/components/newtab/test/unit/aboutwelcome/MultiSelect.test.jsx
@@ -0,0 +1,151 @@
+import React from "react";
+import { shallow, mount } from "enzyme";
+import { MultiSelect } from "content-src/aboutwelcome/components/MultiSelect";
+
+describe("Multistage AboutWelcome module", () => {
+ let sandbox;
+ let MULTISELECT_SCREEN_PROPS;
+ let setActiveMultiSelect;
+ beforeEach(() => {
+ sandbox = sinon.createSandbox();
+ setActiveMultiSelect = sandbox.stub();
+ MULTISELECT_SCREEN_PROPS = {
+ id: "multiselect-screen",
+ content: {
+ position: "split",
+ split_narrow_bkg_position: "-60px",
+ image_alt_text: {
+ string_id: "mr2022-onboarding-default-image-alt",
+ },
+ background:
+ "url('chrome://activity-stream/content/data/content/assets/mr-settodefault.svg') var(--mr-secondary-position) no-repeat var(--mr-screen-background-color)",
+ progress_bar: true,
+ logo: {},
+ title: "Test Title",
+ subtitle: "Test SubTitle",
+ tiles: {
+ type: "multiselect",
+ data: [
+ {
+ id: "checkbox-1",
+ defaultValue: true,
+ label: {
+ string_id: "mr2022-onboarding-set-default-primary-button-label",
+ },
+ action: {
+ type: "SET_DEFAULT_BROWSER",
+ },
+ },
+ {
+ id: "checkbox-2",
+ defaultValue: true,
+ label: "Test Checkbox 2",
+ action: {
+ type: "SHOW_MIGRATION_WIZARD",
+ data: {},
+ },
+ },
+ {
+ id: "checkbox-3",
+ defaultValue: false,
+ label: "Test Checkbox 3",
+ action: {
+ type: "SHOW_MIGRATION_WIZARD",
+ data: {},
+ },
+ },
+ ],
+ },
+ primary_button: {
+ label: "Save and Continue",
+ action: {
+ type: "MULTI_ACTION",
+ collectSelect: true,
+ navigate: true,
+ data: { actions: [] },
+ },
+ },
+ secondary_button: {
+ label: "Skip",
+ action: {
+ navigate: true,
+ },
+ has_arrow_icon: true,
+ },
+ },
+ };
+ });
+ afterEach(() => {
+ sandbox.restore();
+ });
+
+ describe("MultiSelect component", () => {
+ it("should call setActiveMultiSelect with ids of checkboxes with defaultValue true", () => {
+ const wrapper = mount(
+ <MultiSelect
+ setActiveMultiSelect={setActiveMultiSelect}
+ {...MULTISELECT_SCREEN_PROPS}
+ />
+ );
+
+ wrapper.setProps({ activeMultiSelect: null });
+ assert.calledOnce(setActiveMultiSelect);
+ assert.calledWith(setActiveMultiSelect, ["checkbox-1", "checkbox-2"]);
+ });
+
+ it("should use activeMultiSelect ids to set checked state for respective checkbox", () => {
+ const wrapper = mount(
+ <MultiSelect
+ setActiveMultiSelect={setActiveMultiSelect}
+ {...MULTISELECT_SCREEN_PROPS}
+ />
+ );
+
+ wrapper.setProps({ activeMultiSelect: ["checkbox-1", "checkbox-2"] });
+ const checkBoxes = wrapper.find(".checkbox-container input");
+ assert.strictEqual(checkBoxes.length, 3);
+
+ assert.strictEqual(checkBoxes.first().props().checked, true);
+ assert.strictEqual(checkBoxes.at(1).props().checked, true);
+ assert.strictEqual(checkBoxes.last().props().checked, false);
+ });
+
+ it("should filter out id when checkbox is unchecked", () => {
+ const wrapper = shallow(
+ <MultiSelect
+ setActiveMultiSelect={setActiveMultiSelect}
+ {...MULTISELECT_SCREEN_PROPS}
+ />
+ );
+ wrapper.setProps({ activeMultiSelect: ["checkbox-1", "checkbox-2"] });
+
+ const ckbx1 = wrapper.find(".checkbox-container input").at(0);
+ assert.strictEqual(ckbx1.prop("value"), "checkbox-1");
+ ckbx1.simulate("change", {
+ currentTarget: { value: "checkbox-1", checked: false },
+ });
+ assert.calledWith(setActiveMultiSelect, ["checkbox-2"]);
+ });
+
+ it("should add id when checkbox is checked", () => {
+ const wrapper = shallow(
+ <MultiSelect
+ setActiveMultiSelect={setActiveMultiSelect}
+ {...MULTISELECT_SCREEN_PROPS}
+ />
+ );
+ wrapper.setProps({ activeMultiSelect: ["checkbox-1", "checkbox-2"] });
+
+ const ckbx3 = wrapper.find(".checkbox-container input").at(2);
+ assert.strictEqual(ckbx3.prop("value"), "checkbox-3");
+ ckbx3.simulate("change", {
+ currentTarget: { value: "checkbox-3", checked: true },
+ });
+ assert.calledWith(setActiveMultiSelect, [
+ "checkbox-1",
+ "checkbox-2",
+ "checkbox-3",
+ ]);
+ });
+ });
+});
diff --git a/browser/components/newtab/test/unit/aboutwelcome/MultiStageAWProton.test.jsx b/browser/components/newtab/test/unit/aboutwelcome/MultiStageAWProton.test.jsx
new file mode 100644
index 0000000000..22070101cf
--- /dev/null
+++ b/browser/components/newtab/test/unit/aboutwelcome/MultiStageAWProton.test.jsx
@@ -0,0 +1,564 @@
+import { AboutWelcomeDefaults } from "aboutwelcome/lib/AboutWelcomeDefaults.jsm";
+import { MultiStageProtonScreen } from "content-src/aboutwelcome/components/MultiStageProtonScreen";
+import { AWScreenUtils } from "lib/AWScreenUtils.jsm";
+import React from "react";
+import { mount } from "enzyme";
+
+describe("MultiStageAboutWelcomeProton module", () => {
+ let sandbox;
+ let clock;
+ beforeEach(() => {
+ clock = sinon.useFakeTimers();
+ sandbox = sinon.createSandbox();
+ });
+ afterEach(() => {
+ clock.restore();
+ sandbox.restore();
+ });
+
+ describe("MultiStageAWProton component", () => {
+ it("should render MultiStageProton Screen", () => {
+ const SCREEN_PROPS = {
+ content: {
+ title: "test title",
+ subtitle: "test subtitle",
+ },
+ };
+ const wrapper = mount(<MultiStageProtonScreen {...SCREEN_PROPS} />);
+ assert.ok(wrapper.exists());
+ });
+
+ it("should render secondary section for split positioned screens", () => {
+ const SCREEN_PROPS = {
+ content: {
+ position: "split",
+ title: "test title",
+ hero_text: "test subtitle",
+ },
+ };
+ const wrapper = mount(<MultiStageProtonScreen {...SCREEN_PROPS} />);
+ assert.ok(wrapper.exists());
+ assert.equal(wrapper.find(".welcome-text h1").text(), "test title");
+ assert.equal(
+ wrapper.find(".section-secondary h1").text(),
+ "test subtitle"
+ );
+ assert.equal(wrapper.find("main").prop("pos"), "split");
+ });
+
+ it("should render secondary section with content background for split positioned screens", () => {
+ const BACKGROUND_URL =
+ "chrome://activity-stream/content/data/content/assets/confetti.svg";
+ const SCREEN_PROPS = {
+ content: {
+ position: "split",
+ background: `url(${BACKGROUND_URL}) var(--mr-secondary-position) no-repeat`,
+ split_narrow_bkg_position: "10px",
+ title: "test title",
+ },
+ };
+ const wrapper = mount(<MultiStageProtonScreen {...SCREEN_PROPS} />);
+ assert.ok(wrapper.exists());
+ assert.ok(
+ wrapper
+ .find("div.section-secondary")
+ .prop("style")
+ .background.includes("--mr-secondary-position")
+ );
+ assert.ok(
+ wrapper.find("div.section-secondary").prop("style")[
+ "--mr-secondary-background-position-y"
+ ],
+ "10px"
+ );
+ });
+
+ it("should render with secondary section for split positioned screens", () => {
+ const SCREEN_PROPS = {
+ content: {
+ position: "split",
+ title: "test title",
+ hero_text: "test subtitle",
+ },
+ };
+ const wrapper = mount(<MultiStageProtonScreen {...SCREEN_PROPS} />);
+ assert.ok(wrapper.exists());
+ assert.equal(wrapper.find(".welcome-text h1").text(), "test title");
+ assert.equal(
+ wrapper.find(".section-secondary h1").text(),
+ "test subtitle"
+ );
+ assert.equal(wrapper.find("main").prop("pos"), "split");
+ });
+
+ it("should render with no secondary section for center positioned screens", () => {
+ const SCREEN_PROPS = {
+ content: {
+ position: "center",
+ title: "test title",
+ },
+ };
+ const wrapper = mount(<MultiStageProtonScreen {...SCREEN_PROPS} />);
+ assert.ok(wrapper.exists());
+ assert.equal(wrapper.find(".section-secondary").exists(), false);
+ assert.equal(wrapper.find(".welcome-text h1").text(), "test title");
+ assert.equal(wrapper.find("main").prop("pos"), "center");
+ });
+
+ it("should not render multiple action buttons if an additional button does not exist", () => {
+ const SCREEN_PROPS = {
+ content: {
+ title: "test title",
+ primary_button: {
+ label: "test primary button",
+ },
+ },
+ };
+ const wrapper = mount(<MultiStageProtonScreen {...SCREEN_PROPS} />);
+ assert.ok(wrapper.exists());
+ assert.isFalse(wrapper.find(".additional-cta").exists());
+ });
+
+ it("should render an additional action button with primary styling if no style has been specified", () => {
+ const SCREEN_PROPS = {
+ content: {
+ title: "test title",
+ primary_button: {
+ label: "test primary button",
+ },
+ additional_button: {
+ label: "test additional button",
+ },
+ },
+ };
+ const wrapper = mount(<MultiStageProtonScreen {...SCREEN_PROPS} />);
+ assert.ok(wrapper.exists());
+ assert.isTrue(wrapper.find(".additional-cta.primary").exists());
+ });
+
+ it("should render an additional action button with secondary styling", () => {
+ const SCREEN_PROPS = {
+ content: {
+ title: "test title",
+ primary_button: {
+ label: "test primary button",
+ },
+ additional_button: {
+ label: "test additional button",
+ style: "secondary",
+ },
+ },
+ };
+ const wrapper = mount(<MultiStageProtonScreen {...SCREEN_PROPS} />);
+ assert.ok(wrapper.exists());
+ assert.equal(wrapper.find(".additional-cta.secondary").exists(), true);
+ });
+
+ it("should render an additional action button with primary styling", () => {
+ const SCREEN_PROPS = {
+ content: {
+ title: "test title",
+ primary_button: {
+ label: "test primary button",
+ },
+ additional_button: {
+ label: "test additional button",
+ style: "primary",
+ },
+ },
+ };
+ const wrapper = mount(<MultiStageProtonScreen {...SCREEN_PROPS} />);
+ assert.ok(wrapper.exists());
+ assert.equal(wrapper.find(".additional-cta.primary").exists(), true);
+ });
+
+ it("should render an additional action with link styling", () => {
+ const SCREEN_PROPS = {
+ content: {
+ position: "split",
+ title: "test title",
+ primary_button: {
+ label: "test primary button",
+ },
+ additional_button: {
+ label: "test additional button",
+ style: "link",
+ },
+ },
+ };
+ const wrapper = mount(<MultiStageProtonScreen {...SCREEN_PROPS} />);
+ assert.ok(wrapper.exists());
+ assert.equal(wrapper.find(".additional-cta.cta-link").exists(), true);
+ });
+
+ it("should render an additional button with vertical orientation", () => {
+ const SCREEN_PROPS = {
+ content: {
+ position: "center",
+ title: "test title",
+ primary_button: {
+ label: "test primary button",
+ },
+ additional_button: {
+ label: "test additional button",
+ style: "secondary",
+ flow: "column",
+ },
+ },
+ };
+ const wrapper = mount(<MultiStageProtonScreen {...SCREEN_PROPS} />);
+ assert.ok(wrapper.exists());
+ assert.equal(
+ wrapper.find(".additional-cta-container[flow='column']").exists(),
+ true
+ );
+ });
+
+ it("should not render a progress bar if there is 1 step", () => {
+ const SCREEN_PROPS = {
+ content: {
+ title: "test title",
+ progress_bar: true,
+ },
+ isSingleScreen: true,
+ };
+ const wrapper = mount(<MultiStageProtonScreen {...SCREEN_PROPS} />);
+ assert.ok(wrapper.exists());
+ assert.equal(wrapper.find(".steps.progress-bar").exists(), false);
+ });
+
+ it("should render a progress bar if there are 2 steps", () => {
+ const SCREEN_PROPS = {
+ content: {
+ title: "test title",
+ progress_bar: true,
+ },
+ totalNumberOfScreens: 2,
+ };
+ const wrapper = mount(<MultiStageProtonScreen {...SCREEN_PROPS} />);
+ assert.ok(wrapper.exists());
+ assert.equal(wrapper.find(".steps.progress-bar").exists(), true);
+ });
+ });
+
+ describe("AboutWelcomeDefaults for proton", () => {
+ const getData = () => AboutWelcomeDefaults.getDefaults();
+
+ async function prepConfig(config, evalFalseScreenIds) {
+ let data = await getData();
+
+ if (evalFalseScreenIds?.length) {
+ data.screens.forEach(async screen => {
+ if (evalFalseScreenIds.includes(screen.id)) {
+ screen.targeting = false;
+ }
+ });
+ data.screens = await AWScreenUtils.evaluateTargetingAndRemoveScreens(
+ data.screens
+ );
+ }
+
+ return AboutWelcomeDefaults.prepareContentForReact({
+ ...data,
+ ...config,
+ });
+ }
+ beforeEach(() => {
+ sandbox.stub(global.Services.prefs, "getBoolPref").returns(true);
+ sandbox.stub(AWScreenUtils, "evaluateScreenTargeting").returnsArg(0);
+ // This is necessary because there are still screens being removed with
+ // `removeScreens` in `prepareContentForReact()`. Once we've migrated
+ // to using screen targeting instead of manually removing screens,
+ // we can remove this stub.
+ sandbox
+ .stub(global.AWScreenUtils, "removeScreens")
+ .callsFake((screens, callback) =>
+ AWScreenUtils.removeScreens(screens, callback)
+ );
+ });
+ it("should have 'pin' button by default", async () => {
+ const data = await prepConfig({ needPin: true }, [
+ "AW_EASY_SETUP",
+ "AW_WELCOME_BACK",
+ ]);
+ assert.propertyVal(
+ data.screens[0].content.primary_button.action,
+ "type",
+ "PIN_FIREFOX_TO_TASKBAR"
+ );
+ });
+ it("should have 'pin' button if we need default and pin", async () => {
+ const data = await prepConfig(
+ {
+ needDefault: true,
+ needPin: true,
+ },
+ ["AW_EASY_SETUP", "AW_WELCOME_BACK"]
+ );
+
+ assert.propertyVal(
+ data.screens[0].content.primary_button.action,
+ "type",
+ "PIN_FIREFOX_TO_TASKBAR"
+ );
+ assert.propertyVal(data.screens[0], "id", "AW_PIN_FIREFOX");
+ assert.propertyVal(data.screens[1], "id", "AW_SET_DEFAULT");
+ assert.lengthOf(data.screens, getData().screens.length - 3);
+ });
+ it("should keep 'pin' and remove 'default' if already default", async () => {
+ const data = await prepConfig({ needPin: true }, [
+ "AW_EASY_SETUP",
+ "AW_WELCOME_BACK",
+ ]);
+
+ assert.propertyVal(data.screens[0], "id", "AW_PIN_FIREFOX");
+ assert.propertyVal(data.screens[1], "id", "AW_IMPORT_SETTINGS");
+ assert.lengthOf(data.screens, getData().screens.length - 4);
+ });
+ it("should switch to 'default' if already pinned", async () => {
+ const data = await prepConfig({ needDefault: true }, [
+ "AW_EASY_SETUP",
+ "AW_WELCOME_BACK",
+ ]);
+
+ assert.propertyVal(data.screens[0], "id", "AW_ONLY_DEFAULT");
+ assert.propertyVal(data.screens[1], "id", "AW_IMPORT_SETTINGS");
+ assert.lengthOf(data.screens, getData().screens.length - 4);
+ });
+ it("should switch to 'start' if already pinned and default", async () => {
+ const data = await prepConfig({}, ["AW_EASY_SETUP", "AW_WELCOME_BACK"]);
+
+ assert.propertyVal(data.screens[0], "id", "AW_GET_STARTED");
+ assert.propertyVal(data.screens[1], "id", "AW_IMPORT_SETTINGS");
+ assert.lengthOf(data.screens, getData().screens.length - 4);
+ });
+ it("should have a FxA button", async () => {
+ const data = await prepConfig({}, ["AW_WELCOME_BACK"]);
+
+ assert.notProperty(data, "skipFxA");
+ assert.property(data.screens[0].content, "secondary_button_top");
+ });
+ it("should remove the FxA button if pref disabled", async () => {
+ global.Services.prefs.getBoolPref.returns(false);
+
+ const data = await prepConfig();
+
+ assert.property(data, "skipFxA", true);
+ assert.notProperty(data.screens[0].content, "secondary_button_top");
+ });
+ it("should remove the caption if deleteIfNotEn is true", async () => {
+ sandbox.stub(global.Services.locale, "appLocaleAsBCP47").value("de");
+
+ const data = await prepConfig({
+ id: "DEFAULT_ABOUTWELCOME_PROTON",
+ template: "multistage",
+ transitions: true,
+ background_url:
+ "chrome://activity-stream/content/data/content/assets/confetti.svg",
+ screens: [
+ {
+ id: "AW_PIN_FIREFOX",
+ content: {
+ position: "corner",
+ help_text: {
+ deleteIfNotEn: true,
+ string_id: "mr1-onboarding-welcome-image-caption",
+ },
+ },
+ },
+ ],
+ });
+
+ assert.notProperty(data.screens[0].content, "help_text");
+ });
+ });
+
+ describe("AboutWelcomeDefaults for MR split template proton", () => {
+ const getData = () => AboutWelcomeDefaults.getDefaults(true);
+ beforeEach(() => {
+ sandbox.stub(global.Services.prefs, "getBoolPref").returns(true);
+ });
+
+ it("should use 'split' position template by default", async () => {
+ const data = await getData();
+ assert.propertyVal(data.screens[0].content, "position", "split");
+ });
+
+ it("should not include noodles by default", async () => {
+ const data = await getData();
+ assert.notProperty(data.screens[0].content, "has_noodles");
+ });
+ });
+
+ describe("AboutWelcomeDefaults prepareMobileDownload", () => {
+ const TEST_CONTENT = {
+ screens: [
+ {
+ id: "AW_MOBILE_DOWNLOAD",
+ content: {
+ title: "test",
+ hero_image: {
+ url: "https://example.com/test.svg",
+ },
+ cta_paragraph: {
+ text: {},
+ action: {},
+ },
+ },
+ },
+ ],
+ };
+ it("should not set url for default qrcode svg", async () => {
+ sandbox.stub(global.AppConstants, "isChinaRepack").returns(false);
+ const data = await AboutWelcomeDefaults.prepareContentForReact(
+ TEST_CONTENT
+ );
+ assert.propertyVal(
+ data.screens[0].content.hero_image,
+ "url",
+ "https://example.com/test.svg"
+ );
+ });
+ it("should set url for cn qrcode svg", async () => {
+ sandbox.stub(global.AppConstants, "isChinaRepack").returns(true);
+ const data = await AboutWelcomeDefaults.prepareContentForReact(
+ TEST_CONTENT
+ );
+ assert.propertyVal(
+ data.screens[0].content.hero_image,
+ "url",
+ "https://example.com/test-cn.svg"
+ );
+ });
+ });
+
+ describe("AboutWelcomeDefaults prepareContentForReact", () => {
+ it("should not set action without screens", async () => {
+ const data = await AboutWelcomeDefaults.prepareContentForReact({
+ ua: "test",
+ });
+
+ assert.propertyVal(data, "ua", "test");
+ assert.notProperty(data, "screens");
+ });
+ it("should set action for import action", async () => {
+ const TEST_CONTENT = {
+ ua: "test",
+ screens: [
+ {
+ id: "AW_IMPORT_SETTINGS",
+ content: {
+ primary_button: {
+ action: {
+ type: "SHOW_MIGRATION_WIZARD",
+ },
+ },
+ },
+ },
+ ],
+ };
+ const data = await AboutWelcomeDefaults.prepareContentForReact(
+ TEST_CONTENT
+ );
+ assert.propertyVal(data, "ua", "test");
+ assert.propertyVal(
+ data.screens[0].content.primary_button.action.data,
+ "source",
+ "test"
+ );
+ });
+ it("should not set action if the action type != SHOW_MIGRATION_WIZARD", async () => {
+ const TEST_CONTENT = {
+ ua: "test",
+ screens: [
+ {
+ id: "AW_IMPORT_SETTINGS",
+ content: {
+ primary_button: {
+ action: {
+ type: "SHOW_FIREFOX_ACCOUNTS",
+ data: {},
+ },
+ },
+ },
+ },
+ ],
+ };
+ const data = await AboutWelcomeDefaults.prepareContentForReact(
+ TEST_CONTENT
+ );
+ assert.propertyVal(data, "ua", "test");
+ assert.notPropertyVal(
+ data.screens[0].content.primary_button.action.data,
+ "source",
+ "test"
+ );
+ });
+ it("should remove theme screens on win7", async () => {
+ sandbox
+ .stub(global.AppConstants, "isPlatformAndVersionAtMost")
+ .returns(true);
+ sandbox
+ .stub(global.AWScreenUtils, "removeScreens")
+ .callsFake((screens, screen) =>
+ AWScreenUtils.removeScreens(screens, screen)
+ );
+
+ const { screens } = await AboutWelcomeDefaults.prepareContentForReact({
+ screens: [
+ {
+ content: {
+ tiles: { type: "theme" },
+ },
+ },
+ { id: "hello" },
+ {
+ content: {
+ tiles: { type: "theme" },
+ },
+ },
+ { id: "world" },
+ ],
+ });
+
+ assert.deepEqual(screens, [{ id: "hello" }, { id: "world" }]);
+ });
+ it("shouldn't remove colorway screens on win7", async () => {
+ sandbox
+ .stub(global.AppConstants, "isPlatformAndVersionAtMost")
+ .returns(true);
+ sandbox
+ .stub(global.AWScreenUtils, "removeScreens")
+ .callsFake((screens, screen) =>
+ AWScreenUtils.removeScreens(screens, screen)
+ );
+
+ const { screens } = await AboutWelcomeDefaults.prepareContentForReact({
+ screens: [
+ {
+ content: {
+ tiles: { type: "colorway" },
+ },
+ },
+ { id: "hello" },
+ {
+ content: {
+ tiles: { type: "theme" },
+ },
+ },
+ { id: "world" },
+ ],
+ });
+
+ assert.deepEqual(screens, [
+ {
+ content: {
+ tiles: { type: "colorway" },
+ },
+ },
+ { id: "hello" },
+ { id: "world" },
+ ]);
+ });
+ });
+});
diff --git a/browser/components/newtab/test/unit/aboutwelcome/MultiStageAboutWelcome.test.jsx b/browser/components/newtab/test/unit/aboutwelcome/MultiStageAboutWelcome.test.jsx
new file mode 100644
index 0000000000..d017f94b7f
--- /dev/null
+++ b/browser/components/newtab/test/unit/aboutwelcome/MultiStageAboutWelcome.test.jsx
@@ -0,0 +1,824 @@
+import { GlobalOverrider } from "test/unit/utils";
+import {
+ MultiStageAboutWelcome,
+ SecondaryCTA,
+ StepsIndicator,
+ ProgressBar,
+ WelcomeScreen,
+} from "content-src/aboutwelcome/components/MultiStageAboutWelcome";
+import { Themes } from "content-src/aboutwelcome/components/Themes";
+import React from "react";
+import { shallow, mount } from "enzyme";
+import { AboutWelcomeDefaults } from "aboutwelcome/lib/AboutWelcomeDefaults.jsm";
+import { AboutWelcomeUtils } from "content-src/lib/aboutwelcome-utils";
+
+describe("MultiStageAboutWelcome module", () => {
+ let globals;
+ let sandbox;
+
+ const DEFAULT_PROPS = {
+ defaultScreens: AboutWelcomeDefaults.getDefaults().screens,
+ metricsFlowUri: "http://localhost/",
+ message_id: "DEFAULT_ABOUTWELCOME",
+ utm_term: "default",
+ startScreen: 0,
+ };
+
+ beforeEach(async () => {
+ globals = new GlobalOverrider();
+ globals.set({
+ AWGetSelectedTheme: () => Promise.resolve("automatic"),
+ AWSendEventTelemetry: () => {},
+ AWWaitForMigrationClose: () => Promise.resolve(),
+ AWSelectTheme: () => Promise.resolve(),
+ AWFinish: () => Promise.resolve(),
+ });
+ sandbox = sinon.createSandbox();
+ });
+
+ afterEach(() => {
+ sandbox.restore();
+ globals.restore();
+ });
+
+ describe("MultiStageAboutWelcome functional component", () => {
+ it("should render MultiStageAboutWelcome", () => {
+ const wrapper = shallow(<MultiStageAboutWelcome {...DEFAULT_PROPS} />);
+
+ assert.ok(wrapper.exists());
+ });
+
+ it("should pass activeTheme and initialTheme props to WelcomeScreen", async () => {
+ let wrapper = mount(<MultiStageAboutWelcome {...DEFAULT_PROPS} />);
+ // Spin the event loop to allow the useEffect hooks to execute,
+ // any promises to resolve, and re-rendering to happen after the
+ // promises have updated the state/props
+ await new Promise(resolve => setTimeout(resolve, 0));
+ // sync up enzyme's representation with the real DOM
+ wrapper.update();
+
+ let welcomeScreenWrapper = wrapper.find(WelcomeScreen);
+ assert.strictEqual(welcomeScreenWrapper.prop("activeTheme"), "automatic");
+ assert.strictEqual(
+ welcomeScreenWrapper.prop("initialTheme"),
+ "automatic"
+ );
+ });
+
+ it("should handle primary Action", () => {
+ const screens = [
+ {
+ content: {
+ title: "test title",
+ subtitle: "test subtitle",
+ primary_button: {
+ label: "Test button",
+ action: {
+ navigate: true,
+ },
+ },
+ },
+ },
+ ];
+
+ const PRIMARY_ACTION_PROPS = {
+ defaultScreens: screens,
+ metricsFlowUri: "http://localhost/",
+ message_id: "DEFAULT_ABOUTWELCOME",
+ utm_term: "default",
+ startScreen: 0,
+ };
+
+ const stub = sinon.stub(AboutWelcomeUtils, "sendActionTelemetry");
+ let wrapper = mount(<MultiStageAboutWelcome {...PRIMARY_ACTION_PROPS} />);
+ wrapper.update();
+
+ let welcomeScreenWrapper = wrapper.find(WelcomeScreen);
+ const btnPrimary = welcomeScreenWrapper.find(".primary");
+ btnPrimary.simulate("click");
+ assert.calledOnce(stub);
+ assert.equal(
+ stub.firstCall.args[0],
+ welcomeScreenWrapper.props().messageId
+ );
+ assert.equal(stub.firstCall.args[1], "primary_button");
+ stub.restore();
+ });
+
+ it("should autoAdvance on last screen and send appropriate telemetry", () => {
+ let clock = sinon.useFakeTimers();
+ const screens = [
+ {
+ auto_advance: "primary_button",
+ content: {
+ title: "test title",
+ subtitle: "test subtitle",
+ primary_button: {
+ label: "Test Button",
+ action: {
+ navigate: true,
+ },
+ },
+ },
+ },
+ ];
+ const AUTO_ADVANCE_PROPS = {
+ defaultScreens: screens,
+ metricsFlowUri: "http://localhost/",
+ message_id: "DEFAULT_ABOUTWELCOME",
+ utm_term: "default",
+ startScreen: 0,
+ };
+ const wrapper = mount(<MultiStageAboutWelcome {...AUTO_ADVANCE_PROPS} />);
+ wrapper.update();
+ const finishStub = sandbox.stub(global, "AWFinish");
+ const telemetryStub = sinon.stub(
+ AboutWelcomeUtils,
+ "sendActionTelemetry"
+ );
+
+ assert.notCalled(finishStub);
+ clock.tick(20001);
+ assert.calledOnce(finishStub);
+ assert.calledOnce(telemetryStub);
+ assert.equal(telemetryStub.lastCall.args[2], "AUTO_ADVANCE");
+ clock.restore();
+ finishStub.restore();
+ telemetryStub.restore();
+ });
+
+ it("should send telemetry ping on collectSelect", () => {
+ const screens = [
+ {
+ id: "EASY_SETUP_TEST",
+ content: {
+ tiles: {
+ type: "multiselect",
+ data: [
+ {
+ id: "checkbox-1",
+ defaultValue: true,
+ },
+ ],
+ },
+ primary_button: {
+ label: "Test Button",
+ action: {
+ collectSelect: true,
+ },
+ },
+ },
+ },
+ ];
+ const EASY_SETUP_PROPS = {
+ defaultScreens: screens,
+ message_id: "DEFAULT_ABOUTWELCOME",
+ startScreen: 0,
+ };
+ const stub = sinon.stub(AboutWelcomeUtils, "sendActionTelemetry");
+ let wrapper = mount(<MultiStageAboutWelcome {...EASY_SETUP_PROPS} />);
+ wrapper.update();
+
+ let welcomeScreenWrapper = wrapper.find(WelcomeScreen);
+ const btnPrimary = welcomeScreenWrapper.find(".primary");
+ btnPrimary.simulate("click");
+ assert.calledTwice(stub);
+ assert.equal(
+ stub.firstCall.args[0],
+ welcomeScreenWrapper.props().messageId
+ );
+ assert.equal(stub.firstCall.args[1], "primary_button");
+ assert.equal(
+ stub.lastCall.args[0],
+ welcomeScreenWrapper.props().messageId
+ );
+ assert.ok(stub.lastCall.args[1].includes("checkbox-1"));
+ assert.equal(stub.lastCall.args[2], "SELECT_CHECKBOX");
+ stub.restore();
+ });
+ });
+
+ describe("WelcomeScreen component", () => {
+ describe("get started screen", () => {
+ const screen = AboutWelcomeDefaults.getDefaults().screens.find(
+ s => s.id === "AW_PIN_FIREFOX"
+ );
+
+ const GET_STARTED_SCREEN_PROPS = {
+ id: screen.id,
+ totalNumberOfScreens: 1,
+ content: screen.content,
+ messageId: `${DEFAULT_PROPS.message_id}_${screen.id}`,
+ UTMTerm: DEFAULT_PROPS.utm_term,
+ flowParams: null,
+ };
+
+ it("should render GetStarted Screen", () => {
+ const wrapper = shallow(
+ <WelcomeScreen {...GET_STARTED_SCREEN_PROPS} />
+ );
+ assert.ok(wrapper.exists());
+ });
+
+ it("should render secondary.top button", () => {
+ let SCREEN_PROPS = {
+ content: {
+ title: "Step",
+ secondary_button_top: {
+ text: "test",
+ label: "test label",
+ },
+ },
+ position: "top",
+ };
+ const wrapper = mount(<SecondaryCTA {...SCREEN_PROPS} />);
+ assert.ok(wrapper.find("div.secondary-cta.top").exists());
+ });
+
+ it("should render the arrow icon in the secondary button", () => {
+ let SCREEN_PROPS = {
+ content: {
+ title: "Step",
+ secondary_button: {
+ has_arrow_icon: true,
+ label: "test label",
+ },
+ },
+ };
+ const wrapper = mount(<SecondaryCTA {...SCREEN_PROPS} />);
+ assert.ok(wrapper.find("button.arrow-icon").exists());
+ });
+
+ it("should render steps indicator", () => {
+ let PROPS = { totalNumberOfScreens: 1 };
+ const wrapper = mount(<StepsIndicator {...PROPS} />);
+ assert.ok(wrapper.find("div.indicator").exists());
+ });
+
+ it("should assign the total number of screens and current screen to the aria-valuemax and aria-valuenow labels", () => {
+ const EXTRA_PROPS = { totalNumberOfScreens: 3, order: 1 };
+ const wrapper = mount(
+ <WelcomeScreen {...GET_STARTED_SCREEN_PROPS} {...EXTRA_PROPS} />
+ );
+ const steps = wrapper.find(`div.steps`);
+ assert.ok(steps.exists());
+ const { attributes } = steps.getDOMNode();
+ assert.equal(
+ parseInt(attributes.getNamedItem("aria-valuemax").value, 10),
+ EXTRA_PROPS.totalNumberOfScreens
+ );
+ assert.equal(
+ parseInt(attributes.getNamedItem("aria-valuenow").value, 10),
+ EXTRA_PROPS.order + 1
+ );
+ });
+
+ it("should render progress bar", () => {
+ let SCREEN_PROPS = {
+ step: 1,
+ previousStep: 0,
+ totalNumberOfScreens: 2,
+ };
+ const wrapper = mount(<ProgressBar {...SCREEN_PROPS} />);
+ assert.ok(wrapper.find("div.indicator").exists());
+ assert.propertyVal(
+ wrapper.find("div.indicator").prop("style"),
+ "--progress-bar-progress",
+ "50%"
+ );
+ });
+
+ it("should have a primary, secondary and secondary.top button in the rendered input", () => {
+ const wrapper = mount(<WelcomeScreen {...GET_STARTED_SCREEN_PROPS} />);
+ assert.ok(wrapper.find(".primary").exists());
+ assert.ok(
+ wrapper
+ .find(".secondary-cta button.secondary[value='secondary_button']")
+ .exists()
+ );
+ assert.ok(
+ wrapper
+ .find(
+ ".secondary-cta.top button.secondary[value='secondary_button_top']"
+ )
+ .exists()
+ );
+ });
+ });
+
+ describe("theme screen", () => {
+ const THEME_SCREEN_PROPS = {
+ id: "test-theme-screen",
+ totalNumberOfScreens: 1,
+ content: {
+ title: "test title",
+ subtitle: "test subtitle",
+ tiles: {
+ type: "theme",
+ action: {
+ theme: "<event>",
+ },
+ data: [
+ {
+ theme: "automatic",
+ label: "test-label",
+ tooltip: "test-tooltip",
+ description: "test-description",
+ },
+ ],
+ },
+ primary_button: {
+ action: {},
+ label: "test button",
+ },
+ },
+ navigate: null,
+ messageId: `${DEFAULT_PROPS.message_id}_"test-theme-screen"`,
+ UTMTerm: DEFAULT_PROPS.utm_term,
+ flowParams: null,
+ activeTheme: "automatic",
+ };
+
+ it("should render WelcomeScreen", () => {
+ const wrapper = shallow(<WelcomeScreen {...THEME_SCREEN_PROPS} />);
+
+ assert.ok(wrapper.exists());
+ });
+
+ it("should check this.props.activeTheme in the rendered input", () => {
+ const wrapper = shallow(<Themes {...THEME_SCREEN_PROPS} />);
+
+ const selectedThemeInput = wrapper.find(".theme input[checked=true]");
+ assert.strictEqual(
+ selectedThemeInput.prop("value"),
+ THEME_SCREEN_PROPS.activeTheme
+ );
+ });
+ });
+ describe("import screen", () => {
+ const IMPORT_SCREEN_PROPS = {
+ content: {
+ title: "test title",
+ subtitle: "test subtitle",
+ help_text: {
+ text: "test help text",
+ position: "default",
+ },
+ },
+ };
+ it("should render ImportScreen", () => {
+ const wrapper = mount(<WelcomeScreen {...IMPORT_SCREEN_PROPS} />);
+ assert.ok(wrapper.exists());
+ });
+ it("should not have a primary or secondary button", () => {
+ const wrapper = mount(<WelcomeScreen {...IMPORT_SCREEN_PROPS} />);
+ assert.isFalse(wrapper.find(".primary").exists());
+ assert.isFalse(
+ wrapper.find(".secondary button[value='secondary_button']").exists()
+ );
+ assert.isFalse(
+ wrapper
+ .find(".secondary button[value='secondary_button_top']")
+ .exists()
+ );
+ });
+ });
+ describe("#handleAction", () => {
+ let SCREEN_PROPS;
+ let TEST_ACTION;
+ beforeEach(() => {
+ SCREEN_PROPS = {
+ content: {
+ title: "test title",
+ subtitle: "test subtitle",
+ primary_button: {
+ action: {},
+ label: "test button",
+ },
+ },
+ navigate: sandbox.stub(),
+ setActiveTheme: sandbox.stub(),
+ UTMTerm: "you_tee_emm",
+ };
+ TEST_ACTION = SCREEN_PROPS.content.primary_button.action;
+ sandbox.stub(AboutWelcomeUtils, "handleUserAction").resolves();
+ });
+ it("should handle navigate", () => {
+ TEST_ACTION.navigate = true;
+ const wrapper = mount(<WelcomeScreen {...SCREEN_PROPS} />);
+
+ wrapper.find(".primary").simulate("click");
+
+ assert.calledOnce(SCREEN_PROPS.navigate);
+ });
+ it("should handle theme", () => {
+ TEST_ACTION.theme = "test";
+ const wrapper = mount(<WelcomeScreen {...SCREEN_PROPS} />);
+
+ wrapper.find(".primary").simulate("click");
+
+ assert.calledWith(SCREEN_PROPS.setActiveTheme, "test");
+ });
+ it("should handle dismiss", () => {
+ SCREEN_PROPS.content.dismiss_button = {
+ action: { dismiss: true },
+ };
+ const finishStub = sandbox.stub(global, "AWFinish");
+ const wrapper = mount(<WelcomeScreen {...SCREEN_PROPS} />);
+
+ wrapper.find(".dismiss-button").simulate("click");
+
+ assert.calledOnce(finishStub);
+ });
+ it("should handle SHOW_FIREFOX_ACCOUNTS", () => {
+ TEST_ACTION.type = "SHOW_FIREFOX_ACCOUNTS";
+ const wrapper = mount(<WelcomeScreen {...SCREEN_PROPS} />);
+
+ wrapper.find(".primary").simulate("click");
+
+ assert.calledWith(AboutWelcomeUtils.handleUserAction, {
+ data: {
+ extraParams: {
+ utm_campaign: "firstrun",
+ utm_medium: "referral",
+ utm_source: "activity-stream",
+ utm_term: "you_tee_emm-screen",
+ },
+ },
+ type: "SHOW_FIREFOX_ACCOUNTS",
+ });
+ });
+ it("should handle SHOW_MIGRATION_WIZARD", () => {
+ TEST_ACTION.type = "SHOW_MIGRATION_WIZARD";
+ const wrapper = mount(<WelcomeScreen {...SCREEN_PROPS} />);
+
+ wrapper.find(".primary").simulate("click");
+
+ assert.calledWith(AboutWelcomeUtils.handleUserAction, {
+ type: "SHOW_MIGRATION_WIZARD",
+ });
+ });
+ it("should handle SHOW_MIGRATION_WIZARD INSIDE MULTI_ACTION", async () => {
+ const migrationCloseStub = sandbox.stub(
+ global,
+ "AWWaitForMigrationClose"
+ );
+ const MULTI_ACTION_SCREEN_PROPS = {
+ content: {
+ title: "test title",
+ subtitle: "test subtitle",
+ primary_button: {
+ action: {
+ type: "MULTI_ACTION",
+ navigate: true,
+ data: {
+ actions: [
+ {
+ type: "PIN_FIREFOX_TO_TASKBAR",
+ },
+ {
+ type: "SET_DEFAULT_BROWSER",
+ },
+ {
+ type: "SHOW_MIGRATION_WIZARD",
+ data: {},
+ },
+ ],
+ },
+ },
+ label: "test button",
+ },
+ },
+ navigate: sandbox.stub(),
+ };
+ const wrapper = mount(<WelcomeScreen {...MULTI_ACTION_SCREEN_PROPS} />);
+
+ wrapper.find(".primary").simulate("click");
+ assert.calledWith(AboutWelcomeUtils.handleUserAction, {
+ type: "MULTI_ACTION",
+ navigate: true,
+ data: {
+ actions: [
+ {
+ type: "PIN_FIREFOX_TO_TASKBAR",
+ },
+ {
+ type: "SET_DEFAULT_BROWSER",
+ },
+ {
+ type: "SHOW_MIGRATION_WIZARD",
+ data: {},
+ },
+ ],
+ },
+ });
+ // handleUserAction returns a Promise, so let's let the microtask queue
+ // flush so that anything waiting for the handleUserAction Promise to
+ // resolve can run.
+ await new Promise(resolve => queueMicrotask(resolve));
+ assert.calledOnce(migrationCloseStub);
+ });
+
+ it("should handle SHOW_MIGRATION_WIZARD INSIDE NESTED MULTI_ACTION", async () => {
+ const migrationCloseStub = sandbox.stub(
+ global,
+ "AWWaitForMigrationClose"
+ );
+ const MULTI_ACTION_SCREEN_PROPS = {
+ content: {
+ title: "test title",
+ subtitle: "test subtitle",
+ primary_button: {
+ action: {
+ type: "MULTI_ACTION",
+ navigate: true,
+ data: {
+ actions: [
+ {
+ type: "PIN_FIREFOX_TO_TASKBAR",
+ },
+ {
+ type: "SET_DEFAULT_BROWSER",
+ },
+ {
+ type: "MULTI_ACTION",
+ data: {
+ actions: [
+ {
+ type: "SET_PREF",
+ },
+ {
+ type: "SHOW_MIGRATION_WIZARD",
+ data: {},
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ label: "test button",
+ },
+ },
+ navigate: sandbox.stub(),
+ };
+ const wrapper = mount(<WelcomeScreen {...MULTI_ACTION_SCREEN_PROPS} />);
+
+ wrapper.find(".primary").simulate("click");
+ assert.calledWith(AboutWelcomeUtils.handleUserAction, {
+ type: "MULTI_ACTION",
+ navigate: true,
+ data: {
+ actions: [
+ {
+ type: "PIN_FIREFOX_TO_TASKBAR",
+ },
+ {
+ type: "SET_DEFAULT_BROWSER",
+ },
+ {
+ type: "MULTI_ACTION",
+ data: {
+ actions: [
+ {
+ type: "SET_PREF",
+ },
+ {
+ type: "SHOW_MIGRATION_WIZARD",
+ data: {},
+ },
+ ],
+ },
+ },
+ ],
+ },
+ });
+ // handleUserAction returns a Promise, so let's let the microtask queue
+ // flush so that anything waiting for the handleUserAction Promise to
+ // resolve can run.
+ await new Promise(resolve => queueMicrotask(resolve));
+ assert.calledOnce(migrationCloseStub);
+ });
+ it("should unset prefs from unchecked checkboxes", () => {
+ const PREF_SCREEN_PROPS = {
+ content: {
+ title: "Checkboxes",
+ tiles: {
+ type: "multiselect",
+ data: [
+ {
+ id: "checkbox-1",
+ label: "checkbox 1",
+ checkedAction: {
+ type: "SET_PREF",
+ data: {
+ pref: {
+ name: "pref-a",
+ value: true,
+ },
+ },
+ },
+ uncheckedAction: {
+ type: "SET_PREF",
+ data: {
+ pref: {
+ name: "pref-a",
+ },
+ },
+ },
+ },
+ {
+ id: "checkbox-2",
+ label: "checkbox 2",
+ checkedAction: {
+ type: "MULTI_ACTION",
+ data: {
+ actions: [
+ {
+ type: "SET_PREF",
+ data: {
+ pref: {
+ name: "pref-b",
+ value: "pref-b",
+ },
+ },
+ },
+ {
+ type: "SET_PREF",
+ data: {
+ pref: {
+ name: "pref-c",
+ value: 3,
+ },
+ },
+ },
+ ],
+ },
+ },
+ uncheckedAction: {
+ type: "SET_PREF",
+ data: {
+ pref: { name: "pref-b" },
+ },
+ },
+ },
+ ],
+ },
+ primary_button: {
+ label: "Set Prefs",
+ action: {
+ type: "MULTI_ACTION",
+ collectSelect: true,
+ isDynamic: true,
+ navigate: true,
+ data: {
+ actions: [],
+ },
+ },
+ },
+ },
+ navigate: sandbox.stub(),
+ setActiveMultiSelect: sandbox.stub(),
+ };
+
+ // No checkboxes checked. All prefs will be unset and pref-c will not be
+ // reset.
+ {
+ const wrapper = mount(
+ <WelcomeScreen {...PREF_SCREEN_PROPS} activeMultiSelect={[]} />
+ );
+ wrapper.find(".primary").simulate("click");
+ assert.calledWith(AboutWelcomeUtils.handleUserAction, {
+ type: "MULTI_ACTION",
+ collectSelect: true,
+ isDynamic: true,
+ navigate: true,
+ data: {
+ actions: [
+ { type: "SET_PREF", data: { pref: { name: "pref-a" } } },
+ { type: "SET_PREF", data: { pref: { name: "pref-b" } } },
+ ],
+ },
+ });
+
+ AboutWelcomeUtils.handleUserAction.resetHistory();
+ }
+
+ // The first checkbox is checked. Only pref-a will be set and pref-c
+ // will not be reset.
+ {
+ const wrapper = mount(
+ <WelcomeScreen
+ {...PREF_SCREEN_PROPS}
+ activeMultiSelect={["checkbox-1"]}
+ />
+ );
+ wrapper.find(".primary").simulate("click");
+ assert.calledWith(AboutWelcomeUtils.handleUserAction, {
+ type: "MULTI_ACTION",
+ collectSelect: true,
+ isDynamic: true,
+ navigate: true,
+ data: {
+ actions: [
+ {
+ type: "SET_PREF",
+ data: {
+ pref: {
+ name: "pref-a",
+ value: true,
+ },
+ },
+ },
+ { type: "SET_PREF", data: { pref: { name: "pref-b" } } },
+ ],
+ },
+ });
+
+ AboutWelcomeUtils.handleUserAction.resetHistory();
+ }
+
+ // The second checkbox is checked. Prefs pref-b and pref-c will be set.
+ {
+ const wrapper = mount(
+ <WelcomeScreen
+ {...PREF_SCREEN_PROPS}
+ activeMultiSelect={["checkbox-2"]}
+ />
+ );
+ wrapper.find(".primary").simulate("click");
+ assert.calledWith(AboutWelcomeUtils.handleUserAction, {
+ type: "MULTI_ACTION",
+ collectSelect: true,
+ isDynamic: true,
+ navigate: true,
+ data: {
+ actions: [
+ { type: "SET_PREF", data: { pref: { name: "pref-a" } } },
+ {
+ type: "MULTI_ACTION",
+ data: {
+ actions: [
+ {
+ type: "SET_PREF",
+ data: { pref: { name: "pref-b", value: "pref-b" } },
+ },
+ {
+ type: "SET_PREF",
+ data: { pref: { name: "pref-c", value: 3 } },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ });
+
+ AboutWelcomeUtils.handleUserAction.resetHistory();
+ }
+
+ // // Both checkboxes are checked. All prefs will be set.
+ {
+ const wrapper = mount(
+ <WelcomeScreen
+ {...PREF_SCREEN_PROPS}
+ activeMultiSelect={["checkbox-1", "checkbox-2"]}
+ />
+ );
+ wrapper.find(".primary").simulate("click");
+ assert.calledWith(AboutWelcomeUtils.handleUserAction, {
+ type: "MULTI_ACTION",
+ collectSelect: true,
+ isDynamic: true,
+ navigate: true,
+ data: {
+ actions: [
+ {
+ type: "SET_PREF",
+ data: { pref: { name: "pref-a", value: true } },
+ },
+ {
+ type: "MULTI_ACTION",
+ data: {
+ actions: [
+ {
+ type: "SET_PREF",
+ data: { pref: { name: "pref-b", value: "pref-b" } },
+ },
+ {
+ type: "SET_PREF",
+ data: { pref: { name: "pref-c", value: 3 } },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ });
+
+ AboutWelcomeUtils.handleUserAction.resetHistory();
+ }
+ });
+ });
+ });
+});
diff --git a/browser/components/newtab/test/unit/aboutwelcome/OnboardingVideoTest.test.jsx b/browser/components/newtab/test/unit/aboutwelcome/OnboardingVideoTest.test.jsx
new file mode 100644
index 0000000000..db6d8ba10a
--- /dev/null
+++ b/browser/components/newtab/test/unit/aboutwelcome/OnboardingVideoTest.test.jsx
@@ -0,0 +1,45 @@
+import React from "react";
+import { mount } from "enzyme";
+import { OnboardingVideo } from "content-src/aboutwelcome/components/OnboardingVideo";
+
+describe("OnboardingVideo component", () => {
+ let sandbox;
+
+ beforeEach(async () => {
+ sandbox = sinon.createSandbox();
+ });
+
+ afterEach(() => {
+ sandbox.restore();
+ });
+
+ const SCREEN_PROPS = {
+ content: {
+ title: "Test title",
+ video_container: {
+ video_url: "test url",
+ },
+ },
+ };
+
+ it("should handle video_start action when video is played", () => {
+ const handleAction = sandbox.stub();
+ const wrapper = mount(
+ <OnboardingVideo handleAction={handleAction} {...SCREEN_PROPS} />
+ );
+ wrapper.find("video").simulate("play");
+ assert.calledWith(handleAction, {
+ currentTarget: { value: "video_start" },
+ });
+ });
+ it("should handle video_end action when video has completed playing", () => {
+ const handleAction = sandbox.stub();
+ const wrapper = mount(
+ <OnboardingVideo handleAction={handleAction} {...SCREEN_PROPS} />
+ );
+ wrapper.find("video").simulate("ended");
+ assert.calledWith(handleAction, {
+ currentTarget: { value: "video_end" },
+ });
+ });
+});