diff options
Diffstat (limited to 'browser/components/newtab/test/unit/aboutwelcome')
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" }, + }); + }); +}); |