diff options
Diffstat (limited to 'browser/components/newtab/test/unit/asrouter/templates/SubmitFormSnippet.test.jsx')
-rw-r--r-- | browser/components/newtab/test/unit/asrouter/templates/SubmitFormSnippet.test.jsx | 354 |
1 files changed, 354 insertions, 0 deletions
diff --git a/browser/components/newtab/test/unit/asrouter/templates/SubmitFormSnippet.test.jsx b/browser/components/newtab/test/unit/asrouter/templates/SubmitFormSnippet.test.jsx new file mode 100644 index 0000000000..12e4f96863 --- /dev/null +++ b/browser/components/newtab/test/unit/asrouter/templates/SubmitFormSnippet.test.jsx @@ -0,0 +1,354 @@ +import { mount } from "enzyme"; +import React from "react"; +import { FluentBundle, FluentResource } from "@fluent/bundle"; +import { LocalizationProvider, ReactLocalization } from "@fluent/react"; +import { RichText } from "content-src/asrouter/components/RichText/RichText.jsx"; +import schema from "content-src/asrouter/templates/SubmitFormSnippet/SubmitFormSnippet.schema.json"; +import { SubmitFormSnippet } from "content-src/asrouter/templates/SubmitFormSnippet/SubmitFormSnippet.jsx"; + +const DEFAULT_CONTENT = { + scene1_text: "foo", + scene2_text: "bar", + scene1_button_label: "Sign Up", + retry_button_label: "Try again", + form_action: "foo.com", + hidden_inputs: { foo: "foo" }, + error_text: "error", + success_text: "success", +}; + +describe("SubmitFormSnippet", () => { + let sandbox; + let onBlockStub; + + function mockL10nWrapper(content) { + const bundle = new FluentBundle("en-US"); + for (const [id, value] of Object.entries(content)) { + if (typeof value === "string") { + bundle.addResource(new FluentResource(`${id} = ${value}`)); + } + } + const l10n = new ReactLocalization([bundle]); + return { + wrappingComponent: LocalizationProvider, + wrappingComponentProps: { l10n }, + }; + } + + /** + * mountAndCheckProps - Mounts a SubmitFormSnippet with DEFAULT_CONTENT extended with any props + * passed in the content param and validates props against the schema. + * @param {obj} content Object containing custom message content (e.g. {text, icon, title}) + * @returns enzyme wrapper for SubmitFormSnippet + */ + function mountAndCheckProps(content = {}) { + const props = { + content: Object.assign({}, DEFAULT_CONTENT, content), + onBlock: onBlockStub, + onDismiss: sandbox.stub(), + sendUserActionTelemetry: sandbox.stub(), + onAction: sandbox.stub(), + form_method: "POST", + }; + assert.jsonSchema(props.content, schema); + return mount( + <SubmitFormSnippet {...props} />, + mockL10nWrapper(props.content) + ); + } + + beforeEach(() => { + sandbox = sinon.createSandbox(); + onBlockStub = sandbox.stub(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it("should render .text", () => { + const wrapper = mountAndCheckProps({ scene1_text: "bar" }); + assert.equal(wrapper.find(".body").text(), "bar"); + }); + it("should not render title element if no .title prop is supplied", () => { + const wrapper = mountAndCheckProps(); + assert.lengthOf(wrapper.find(".title"), 0); + }); + it("should render .title", () => { + const wrapper = mountAndCheckProps({ scene1_title: "Foo" }); + assert.equal(wrapper.find(".title").text().trim(), "Foo"); + }); + it("should render light-theme .icon", () => { + const wrapper = mountAndCheckProps({ + scene1_icon: "data:image/gif;base64,R0lGODl", + }); + assert.equal( + wrapper.find(".icon-light-theme").prop("src"), + "data:image/gif;base64,R0lGODl" + ); + }); + it("should render dark-theme .icon", () => { + const wrapper = mountAndCheckProps({ + scene1_icon_dark_theme: "data:image/gif;base64,R0lGODl", + }); + assert.equal( + wrapper.find(".icon-dark-theme").prop("src"), + "data:image/gif;base64,R0lGODl" + ); + }); + it("should render .button_label and default className", () => { + const wrapper = mountAndCheckProps({ scene1_button_label: "Click here" }); + + const button = wrapper.find("button.ASRouterButton"); + assert.equal(button.text(), "Click here"); + assert.equal(button.prop("className"), "ASRouterButton secondary"); + }); + + describe("#SignupView", () => { + let wrapper; + const fetchOk = { json: () => Promise.resolve({ status: "ok" }) }; + const fetchFail = { json: () => Promise.resolve({ status: "fail" }) }; + + beforeEach(() => { + wrapper = mountAndCheckProps({ + scene1_text: "bar", + scene2_email_placeholder_text: "Email", + scene2_text: "signup", + }); + }); + + it("should set the input type if provided through props.inputType", () => { + wrapper.setProps({ inputType: "number" }); + wrapper.setState({ expanded: true }); + assert.equal(wrapper.find(".mainInput").instance().type, "number"); + }); + + it("should validate via props.validateInput if provided", () => { + function validateInput(value, content) { + if (content.country === "CA" && value === "poutine") { + return ""; + } + return "Must be poutine"; + } + const setCustomValidity = sandbox.stub(); + wrapper.setProps({ + validateInput, + content: { ...DEFAULT_CONTENT, country: "CA" }, + }); + wrapper.setState({ expanded: true }); + const input = wrapper.find(".mainInput"); + input.instance().value = "poutine"; + input.simulate("change", { + target: { value: "poutine", setCustomValidity }, + }); + assert.calledWith(setCustomValidity, ""); + + input.instance().value = "fried chicken"; + input.simulate("change", { + target: { value: "fried chicken", setCustomValidity }, + }); + assert.calledWith(setCustomValidity, "Must be poutine"); + }); + + it("should show the signup form if state.expanded is true", () => { + wrapper.setState({ expanded: true }); + + assert.isTrue(wrapper.find("form").exists()); + }); + it("should dismiss the snippet", () => { + wrapper.setState({ expanded: true }); + + wrapper.find(".ASRouterButton.secondary").simulate("click"); + + assert.calledOnce(wrapper.props().onDismiss); + }); + it("should send a DISMISS event ping", () => { + wrapper.setState({ expanded: true }); + + wrapper.find(".ASRouterButton.secondary").simulate("click"); + + assert.equal( + wrapper.props().sendUserActionTelemetry.firstCall.args[0].event, + "DISMISS" + ); + }); + it("should render hidden inputs + email input", () => { + wrapper.setState({ expanded: true }); + + assert.lengthOf(wrapper.find("input[type='hidden']"), 1); + }); + it("should open the SignupView when the action button is clicked", () => { + assert.isFalse(wrapper.find("form").exists()); + + wrapper.find(".ASRouterButton").simulate("click"); + + assert.isTrue(wrapper.state().expanded); + assert.isTrue(wrapper.find("form").exists()); + }); + it("should submit telemetry when the action button is clicked", () => { + assert.isFalse(wrapper.find("form").exists()); + + wrapper.find(".ASRouterButton").simulate("click"); + + assert.equal( + wrapper.props().sendUserActionTelemetry.firstCall.args[0].event_context, + "scene1-button-learn-more" + ); + }); + it("should submit form data when submitted", () => { + sandbox.stub(window, "fetch").resolves(fetchOk); + wrapper.setState({ expanded: true }); + + wrapper.find("form").simulate("submit"); + assert.calledOnce(window.fetch); + }); + it("should send user telemetry when submitted", () => { + wrapper.setState({ expanded: true }); + + wrapper.find("form").simulate("submit"); + + assert.equal( + wrapper.props().sendUserActionTelemetry.firstCall.args[0].event_context, + "conversion-subscribe-activation" + ); + }); + it("should set signupSuccess when submission status is ok", async () => { + sandbox.stub(window, "fetch").resolves(fetchOk); + wrapper.setState({ expanded: true }); + await wrapper.instance().handleSubmit({ preventDefault: sandbox.stub() }); + + assert.equal(wrapper.state().signupSuccess, true); + assert.equal(wrapper.state().signupSubmitted, true); + assert.calledOnce(onBlockStub); + assert.calledWithExactly(onBlockStub, { preventDismiss: true }); + }); + it("should send user telemetry when submission status is ok", async () => { + sandbox.stub(window, "fetch").resolves(fetchOk); + wrapper.setState({ expanded: true }); + await wrapper.instance().handleSubmit({ preventDefault: sandbox.stub() }); + + assert.equal( + wrapper.props().sendUserActionTelemetry.secondCall.args[0] + .event_context, + "subscribe-success" + ); + }); + it("should not block the snippet if submission failed", async () => { + sandbox.stub(window, "fetch").resolves(fetchFail); + wrapper.setState({ expanded: true }); + await wrapper.instance().handleSubmit({ preventDefault: sandbox.stub() }); + + assert.equal(wrapper.state().signupSuccess, false); + assert.equal(wrapper.state().signupSubmitted, true); + assert.notCalled(onBlockStub); + }); + it("should not block if do_not_autoblock is true", async () => { + sandbox.stub(window, "fetch").resolves(fetchOk); + wrapper = mountAndCheckProps({ + scene1_text: "bar", + scene2_email_placeholder_text: "Email", + scene2_text: "signup", + do_not_autoblock: true, + }); + wrapper.setState({ expanded: true }); + await wrapper.instance().handleSubmit({ preventDefault: sandbox.stub() }); + + assert.equal(wrapper.state().signupSuccess, true); + assert.equal(wrapper.state().signupSubmitted, true); + assert.notCalled(onBlockStub); + }); + it("should send user telemetry if submission failed", async () => { + sandbox.stub(window, "fetch").resolves(fetchFail); + wrapper.setState({ expanded: true }); + await wrapper.instance().handleSubmit({ preventDefault: sandbox.stub() }); + + assert.equal( + wrapper.props().sendUserActionTelemetry.secondCall.args[0] + .event_context, + "subscribe-error" + ); + }); + it("should render the signup success message", () => { + wrapper.setProps({ content: { success_text: "success" } }); + wrapper.setState({ signupSuccess: true, signupSubmitted: true }); + + assert.isTrue(wrapper.find(".submissionStatus").exists()); + assert.propertyVal( + wrapper.find(RichText).props(), + "localization_id", + "success_text" + ); + assert.propertyVal( + wrapper.find(RichText).props(), + "success_text", + "success" + ); + assert.isFalse(wrapper.find(".ASRouterButton").exists()); + }); + it("should render the signup error message", () => { + wrapper.setProps({ content: { error_text: "trouble" } }); + wrapper.setState({ signupSuccess: false, signupSubmitted: true }); + + assert.isTrue(wrapper.find(".submissionStatus").exists()); + assert.propertyVal( + wrapper.find(RichText).props(), + "localization_id", + "error_text" + ); + assert.propertyVal( + wrapper.find(RichText).props(), + "error_text", + "trouble" + ); + assert.isTrue(wrapper.find(".ASRouterButton").exists()); + }); + it("should render the button to return to the signup form if there was an error", () => { + wrapper.setState({ signupSubmitted: true, signupSuccess: false }); + + const button = wrapper.find("button.ASRouterButton"); + assert.equal(button.text(), "Try again"); + wrapper.find(".ASRouterButton").simulate("click"); + + assert.equal(wrapper.state().signupSubmitted, false); + }); + it("should not render the privacy notice checkbox if prop is missing", () => { + wrapper.setState({ expanded: true }); + + assert.isFalse(wrapper.find(".privacyNotice").exists()); + }); + it("should render the privacy notice checkbox if prop is provided", () => { + wrapper.setProps({ + content: { ...DEFAULT_CONTENT, scene2_privacy_html: "privacy notice" }, + }); + wrapper.setState({ expanded: true }); + + assert.isTrue(wrapper.find(".privacyNotice").exists()); + }); + it("should not call fetch if form_method is GET", async () => { + sandbox.stub(window, "fetch").resolves(fetchOk); + wrapper.setProps({ form_method: "GET" }); + wrapper.setState({ expanded: true }); + + await wrapper.instance().handleSubmit({ preventDefault: sandbox.stub() }); + + assert.notCalled(window.fetch); + }); + it("should block the snippet when form_method is GET", () => { + wrapper.setProps({ form_method: "GET" }); + wrapper.setState({ expanded: true }); + + wrapper.instance().handleSubmit({ preventDefault: sandbox.stub() }); + + assert.calledOnce(onBlockStub); + assert.calledWithExactly(onBlockStub, { preventDismiss: true }); + }); + it("should return to scene 2 alt when clicking the retry button", async () => { + wrapper.setState({ signupSubmitted: true }); + wrapper.setProps({ expandedAlt: true }); + + wrapper.find(".ASRouterButton").simulate("click"); + + assert.isTrue(wrapper.find(".scene2Alt").exists()); + }); + }); +}); |