diff options
Diffstat (limited to 'browser/components/newtab/test/unit/asrouter/templates')
9 files changed, 1442 insertions, 0 deletions
diff --git a/browser/components/newtab/test/unit/asrouter/templates/EOYSnippet.test.jsx b/browser/components/newtab/test/unit/asrouter/templates/EOYSnippet.test.jsx new file mode 100644 index 0000000000..f797388746 --- /dev/null +++ b/browser/components/newtab/test/unit/asrouter/templates/EOYSnippet.test.jsx @@ -0,0 +1,194 @@ +import { EOYSnippet } from "content-src/asrouter/templates/EOYSnippet/EOYSnippet"; +import { GlobalOverrider } from "test/unit/utils"; +import { mount } from "enzyme"; +import React from "react"; +import schema from "content-src/asrouter/templates/EOYSnippet/EOYSnippet.schema.json"; + +const DEFAULT_CONTENT = { + text: "foo", + donation_amount_first: 50, + donation_amount_second: 25, + donation_amount_third: 10, + donation_amount_fourth: 5, + donation_form_url: "https://submit.form", + button_label: "Donate", +}; + +describe("EOYSnippet", () => { + let sandbox; + let wrapper; + + /** + * mountAndCheckProps - Mounts a EOYSnippet 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 EOYSnippet + */ + function mountAndCheckProps(content = {}, provider = "test-provider") { + const props = { + content: Object.assign({}, DEFAULT_CONTENT, content), + provider, + onAction: sandbox.stub(), + onBlock: sandbox.stub(), + sendClick: sandbox.stub(), + }; + const comp = mount(<EOYSnippet {...props} />); + // Check schema with the final props the component receives (including defaults) + assert.jsonSchema(comp.children().get(0).props.content, schema); + return comp; + } + + beforeEach(() => { + sandbox = sinon.createSandbox(); + wrapper = mountAndCheckProps(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it("should have the correct defaults", () => { + wrapper = mountAndCheckProps(); + // SendToDeviceSnippet is a wrapper around SubmitFormSnippet + const { props } = wrapper.children().get(0); + + const defaultProperties = Object.keys(schema.properties).filter( + prop => schema.properties[prop].default + ); + assert.lengthOf(defaultProperties, 4); + defaultProperties.forEach(prop => + assert.propertyVal(props.content, prop, schema.properties[prop].default) + ); + }); + + it("should render 4 donation options", () => { + assert.lengthOf(wrapper.find("input[type='radio']"), 4); + }); + + it("should have a data-metric field", () => { + assert.ok(wrapper.find("form[data-metric='EOYSnippetForm']").exists()); + }); + + it("should select the second donation option", () => { + wrapper = mountAndCheckProps({ selected_button: "donation_amount_second" }); + + assert.propertyVal( + wrapper.find("input[type='radio']").get(1).props, + "defaultChecked", + true + ); + }); + + it("should set frequency value to monthly", () => { + const form = wrapper.find("form").instance(); + assert.equal(form.querySelector("[name='frequency']").value, "single"); + + form.querySelector("#monthly-checkbox").checked = true; + wrapper.find("form").simulate("submit"); + + assert.equal(form.querySelector("[name='frequency']").value, "monthly"); + }); + + it("should block after submitting the form", () => { + const onBlockStub = sandbox.stub(); + wrapper.setProps({ onBlock: onBlockStub }); + + wrapper.find("form").simulate("submit"); + + assert.calledOnce(onBlockStub); + }); + + it("should not block if do_not_autoblock is true", () => { + const onBlockStub = sandbox.stub(); + wrapper = mountAndCheckProps({ do_not_autoblock: true }); + wrapper.setProps({ onBlock: onBlockStub }); + + wrapper.find("form").simulate("submit"); + + assert.notCalled(onBlockStub); + }); + + it("should report form submissions", () => { + wrapper = mountAndCheckProps(); + const { sendClick } = wrapper.props(); + + wrapper.find("form").simulate("submit"); + + assert.calledOnce(sendClick); + assert.equal( + sendClick.firstCall.args[0].target.dataset.metric, + "EOYSnippetForm" + ); + }); + + it("it should preserve URL GET params as hidden inputs", () => { + wrapper = mountAndCheckProps({ + donation_form_url: + "https://donate.mozilla.org/pl/?utm_source=desktop-snippet&utm_medium=snippet&utm_campaign=donate&utm_term=7556", + }); + + const hiddenInputs = wrapper.find("input[type='hidden']"); + + assert.propertyVal( + hiddenInputs.find("[name='utm_source']").props(), + "value", + "desktop-snippet" + ); + assert.propertyVal( + hiddenInputs.find("[name='amp;utm_medium']").props(), + "value", + "snippet" + ); + assert.propertyVal( + hiddenInputs.find("[name='amp;utm_campaign']").props(), + "value", + "donate" + ); + assert.propertyVal( + hiddenInputs.find("[name='amp;utm_term']").props(), + "value", + "7556" + ); + }); + + describe("locale", () => { + let stub; + let globals; + beforeEach(() => { + globals = new GlobalOverrider(); + stub = sandbox.stub().returns({ format: () => {} }); + + globals = new GlobalOverrider(); + globals.set({ Intl: { NumberFormat: stub } }); + }); + afterEach(() => { + globals.restore(); + }); + + it("should use content.locale for Intl", () => { + // triggers component rendering and calls the function we're testing + wrapper.setProps({ + content: { + locale: "locale-foo", + donation_form_url: DEFAULT_CONTENT.donation_form_url, + }, + }); + + assert.calledOnce(stub); + assert.calledWithExactly(stub, "locale-foo", sinon.match.object); + }); + + it("should use navigator.language as locale fallback", () => { + // triggers component rendering and calls the function we're testing + wrapper.setProps({ + content: { + locale: null, + donation_form_url: DEFAULT_CONTENT.donation_form_url, + }, + }); + + assert.calledOnce(stub); + assert.calledWithExactly(stub, navigator.language, sinon.match.object); + }); + }); +}); diff --git a/browser/components/newtab/test/unit/asrouter/templates/ExtensionDoorhanger.test.jsx b/browser/components/newtab/test/unit/asrouter/templates/ExtensionDoorhanger.test.jsx new file mode 100644 index 0000000000..da97d455ac --- /dev/null +++ b/browser/components/newtab/test/unit/asrouter/templates/ExtensionDoorhanger.test.jsx @@ -0,0 +1,112 @@ +import { CFRMessageProvider } from "lib/CFRMessageProvider.jsm"; +import CFRDoorhangerSchema from "content-src/asrouter/templates/CFR/templates/ExtensionDoorhanger.schema.json"; +import CFRChicletSchema from "content-src/asrouter/templates/CFR/templates/CFRUrlbarChiclet.schema.json"; +import InfoBarSchema from "content-src/asrouter/templates/CFR/templates/InfoBar.schema.json"; + +const SCHEMAS = { + cfr_urlbar_chiclet: CFRChicletSchema, + cfr_doorhanger: CFRDoorhangerSchema, + milestone_message: CFRDoorhangerSchema, + infobar: InfoBarSchema, +}; + +const DEFAULT_CONTENT = { + layout: "addon_recommendation", + category: "dummyCategory", + bucket_id: "some_bucket_id", + notification_text: "Recommendation", + heading_text: "Recommended Extension", + info_icon: { + label: { attributes: { tooltiptext: "Why am I seeing this" } }, + sumo_path: "extensionrecommendations", + }, + addon: { + id: "1234", + title: "Addon name", + icon: "https://mozilla.org/icon", + author: "Author name", + amo_url: "https://example.com", + }, + text: "Description of addon", + buttons: { + primary: { + label: { + value: "Add Now", + attributes: { accesskey: "A" }, + }, + action: { + type: "INSTALL_ADDON_FROM_URL", + data: { url: "https://example.com" }, + }, + }, + secondary: [ + { + label: { + value: "Not Now", + attributes: { accesskey: "N" }, + }, + action: { type: "CANCEL" }, + }, + ], + }, +}; + +const L10N_CONTENT = { + layout: "addon_recommendation", + category: "dummyL10NCategory", + bucket_id: "some_bucket_id", + notification_text: { string_id: "notification_text_id" }, + heading_text: { string_id: "heading_text_id" }, + info_icon: { + label: { string_id: "why_seeing_this" }, + sumo_path: "extensionrecommendations", + }, + addon: { + id: "1234", + title: "Addon name", + icon: "https://mozilla.org/icon", + author: "Author name", + amo_url: "https://example.com", + }, + text: { string_id: "text_id" }, + buttons: { + primary: { + label: { string_id: "btn_ok_id" }, + action: { + type: "INSTALL_ADDON_FROM_URL", + data: { url: "https://example.com" }, + }, + }, + secondary: [ + { + label: { string_id: "btn_cancel_id" }, + action: { type: "CANCEL" }, + }, + ], + }, +}; + +describe("ExtensionDoorhanger", () => { + it("should validate DEFAULT_CONTENT", async () => { + const messages = await CFRMessageProvider.getMessages(); + let doorhangerMessage = messages.find(m => m.id === "FACEBOOK_CONTAINER_3"); + assert.ok(doorhangerMessage, "Message found"); + assert.jsonSchema( + { ...doorhangerMessage, content: DEFAULT_CONTENT }, + CFRDoorhangerSchema + ); + }); + it("should validate L10N_CONTENT", async () => { + const messages = await CFRMessageProvider.getMessages(); + let doorhangerMessage = messages.find(m => m.id === "FACEBOOK_CONTAINER_3"); + assert.ok(doorhangerMessage, "Message found"); + assert.jsonSchema( + { ...doorhangerMessage, content: L10N_CONTENT }, + CFRDoorhangerSchema + ); + }); + it("should validate all messages from CFRMessageProvider", async () => { + const messages = await CFRMessageProvider.getMessages(); + messages.forEach(msg => assert.jsonSchema(msg, SCHEMAS[msg.template])); + }); +}); diff --git a/browser/components/newtab/test/unit/asrouter/templates/FXASignupSnippet.test.jsx b/browser/components/newtab/test/unit/asrouter/templates/FXASignupSnippet.test.jsx new file mode 100644 index 0000000000..3833a7511c --- /dev/null +++ b/browser/components/newtab/test/unit/asrouter/templates/FXASignupSnippet.test.jsx @@ -0,0 +1,84 @@ +import { FXASignupSnippet } from "content-src/asrouter/templates/FXASignupSnippet/FXASignupSnippet"; +import { mount } from "enzyme"; +import React from "react"; +import schema from "content-src/asrouter/templates/FXASignupSnippet/FXASignupSnippet.schema.json"; +import { SnippetsTestMessageProvider } from "lib/SnippetsTestMessageProvider.jsm"; + +describe("FXASignupSnippet", () => { + let DEFAULT_CONTENT; + let sandbox; + + function mountAndCheckProps(content = {}) { + const props = { + id: "foo123", + content: Object.assign( + { utm_campaign: "foo", utm_term: "bar" }, + DEFAULT_CONTENT, + content + ), + onBlock() {}, + onDismiss: sandbox.stub(), + sendUserActionTelemetry: sandbox.stub(), + onAction: sandbox.stub(), + }; + const comp = mount(<FXASignupSnippet {...props} />); + // Check schema with the final props the component receives (including defaults) + assert.jsonSchema(comp.children().get(0).props.content, schema); + return comp; + } + + beforeEach(async () => { + sandbox = sinon.createSandbox(); + DEFAULT_CONTENT = (await SnippetsTestMessageProvider.getMessages()).find( + msg => msg.template === "fxa_signup_snippet" + ).content; + }); + afterEach(() => { + sandbox.restore(); + }); + + it("should have the correct defaults", () => { + const defaults = { + id: "foo123", + onBlock() {}, + content: {}, + onDismiss: sandbox.stub(), + sendUserActionTelemetry: sandbox.stub(), + onAction: sandbox.stub(), + }; + const wrapper = mount(<FXASignupSnippet {...defaults} />); + // FXASignupSnippet is a wrapper around SubmitFormSnippet + const { props } = wrapper.children().get(0); + + const defaultProperties = Object.keys(schema.properties).filter( + prop => schema.properties[prop].default + ); + assert.lengthOf(defaultProperties, 5); + defaultProperties.forEach(prop => + assert.propertyVal(props.content, prop, schema.properties[prop].default) + ); + + const defaultHiddenProperties = Object.keys( + schema.properties.hidden_inputs.properties + ).filter(prop => schema.properties.hidden_inputs.properties[prop].default); + assert.lengthOf(defaultHiddenProperties, 0); + }); + + it("should have a form_action", () => { + const wrapper = mountAndCheckProps(); + + assert.propertyVal( + wrapper.children().get(0).props, + "form_action", + "https://accounts.firefox.com/" + ); + }); + + it("should navigate to scene2", () => { + const wrapper = mountAndCheckProps({}); + + wrapper.find(".ASRouterButton").simulate("click"); + + assert.lengthOf(wrapper.find(".mainInput"), 1); + }); +}); diff --git a/browser/components/newtab/test/unit/asrouter/templates/NewsletterSnippet.test.jsx b/browser/components/newtab/test/unit/asrouter/templates/NewsletterSnippet.test.jsx new file mode 100644 index 0000000000..99410eb879 --- /dev/null +++ b/browser/components/newtab/test/unit/asrouter/templates/NewsletterSnippet.test.jsx @@ -0,0 +1,86 @@ +import { mount } from "enzyme"; +import { NewsletterSnippet } from "content-src/asrouter/templates/NewsletterSnippet/NewsletterSnippet"; +import React from "react"; +import schema from "content-src/asrouter/templates/NewsletterSnippet/NewsletterSnippet.schema.json"; +import { SnippetsTestMessageProvider } from "lib/SnippetsTestMessageProvider.jsm"; + +describe("NewsletterSnippet", () => { + let sandbox; + let DEFAULT_CONTENT; + + function mountAndCheckProps(content = {}) { + const props = { + id: "foo123", + content: Object.assign({}, DEFAULT_CONTENT, content), + onBlock() {}, + onDismiss: sandbox.stub(), + sendUserActionTelemetry: sandbox.stub(), + onAction: sandbox.stub(), + }; + const comp = mount(<NewsletterSnippet {...props} />); + // Check schema with the final props the component receives (including defaults) + assert.jsonSchema(comp.children().get(0).props.content, schema); + return comp; + } + + beforeEach(async () => { + sandbox = sinon.createSandbox(); + DEFAULT_CONTENT = (await SnippetsTestMessageProvider.getMessages()).find( + msg => msg.template === "newsletter_snippet" + ).content; + }); + afterEach(() => { + sandbox.restore(); + }); + + describe("schema test", () => { + it("should validate the schema and defaults", () => { + const wrapper = mountAndCheckProps(); + wrapper.find(".ASRouterButton").simulate("click"); + assert.equal(wrapper.find(".mainInput").instance().type, "email"); + }); + + it("should have all of the default fields", () => { + const defaults = { + id: "foo123", + content: {}, + onBlock() {}, + onDismiss: sandbox.stub(), + sendUserActionTelemetry: sandbox.stub(), + onAction: sandbox.stub(), + }; + const wrapper = mount(<NewsletterSnippet {...defaults} />); + // NewsletterSnippet is a wrapper around SubmitFormSnippet + const { props } = wrapper.children().get(0); + + // the `locale` properties gets used as part of hidden_fields so we + // check for it separately + const properties = { ...schema.properties }; + const { locale } = properties; + delete properties.locale; + + const defaultProperties = Object.keys(properties).filter( + prop => properties[prop].default + ); + assert.lengthOf(defaultProperties, 6); + defaultProperties.forEach(prop => + assert.propertyVal(props.content, prop, properties[prop].default) + ); + + const defaultHiddenProperties = Object.keys( + schema.properties.hidden_inputs.properties + ).filter( + prop => schema.properties.hidden_inputs.properties[prop].default + ); + assert.lengthOf(defaultHiddenProperties, 1); + defaultHiddenProperties.forEach(prop => + assert.propertyVal( + props.content.hidden_inputs, + prop, + schema.properties.hidden_inputs.properties[prop].default + ) + ); + assert.propertyVal(props.content.hidden_inputs, "lang", locale.default); + }); + }); +}); diff --git a/browser/components/newtab/test/unit/asrouter/templates/SendToDeviceSnippet.test.jsx b/browser/components/newtab/test/unit/asrouter/templates/SendToDeviceSnippet.test.jsx new file mode 100644 index 0000000000..7f8a6dec24 --- /dev/null +++ b/browser/components/newtab/test/unit/asrouter/templates/SendToDeviceSnippet.test.jsx @@ -0,0 +1,252 @@ +import { mount } from "enzyme"; +import React from "react"; +import schema from "content-src/asrouter/templates/SendToDeviceSnippet/SendToDeviceSnippet.schema.json"; +import { + SendToDeviceSnippet, + SendToDeviceScene2Snippet, +} from "content-src/asrouter/templates/SendToDeviceSnippet/SendToDeviceSnippet"; +import { SnippetsTestMessageProvider } from "lib/SnippetsTestMessageProvider.jsm"; + +async function testBodyContains(body, key, value) { + const regex = new RegExp( + `Content-Disposition: form-data; name="${key}"${value}` + ); + const match = regex.exec(body); + return match; +} + +/** + * Simulates opening the second panel (form view), filling in the input, and submitting + * @param {EnzymeWrapper} wrapper A SendToDevice wrapper + * @param {string} value Email or phone number + * @param {function?} setCustomValidity setCustomValidity stub + */ +function openFormAndSetValue(wrapper, value, setCustomValidity = () => {}) { + // expand + wrapper.find(".ASRouterButton").simulate("click"); + // Fill in email + const input = wrapper.find(".mainInput"); + input.instance().value = value; + input.simulate("change", { target: { value, setCustomValidity } }); + wrapper.find("form").simulate("submit"); +} + +describe("SendToDeviceSnippet", () => { + let sandbox; + let fetchStub; + let jsonResponse; + let DEFAULT_CONTENT; + let DEFAULT_SCENE2_CONTENT; + + function mountAndCheckProps(content = {}) { + const props = { + id: "foo123", + content: Object.assign({}, DEFAULT_CONTENT, content), + onBlock() {}, + onDismiss: sandbox.stub(), + sendUserActionTelemetry: sandbox.stub(), + onAction: sandbox.stub(), + }; + const comp = mount(<SendToDeviceSnippet {...props} />); + // Check schema with the final props the component receives (including defaults) + assert.jsonSchema(comp.children().get(0).props.content, schema); + return comp; + } + + beforeEach(async () => { + DEFAULT_CONTENT = (await SnippetsTestMessageProvider.getMessages()).find( + msg => msg.template === "send_to_device_snippet" + ).content; + DEFAULT_SCENE2_CONTENT = ( + await SnippetsTestMessageProvider.getMessages() + ).find(msg => msg.template === "send_to_device_scene2_snippet").content; + sandbox = sinon.createSandbox(); + jsonResponse = { status: "ok" }; + fetchStub = sandbox + .stub(global, "fetch") + .returns(Promise.resolve({ json: () => Promise.resolve(jsonResponse) })); + }); + afterEach(() => { + sandbox.restore(); + }); + + it("should have the correct defaults", () => { + const defaults = { + id: "foo123", + onBlock() {}, + content: {}, + onDismiss: sandbox.stub(), + sendUserActionTelemetry: sandbox.stub(), + onAction: sandbox.stub(), + form_method: "POST", + }; + const wrapper = mount(<SendToDeviceSnippet {...defaults} />); + // SendToDeviceSnippet is a wrapper around SubmitFormSnippet + const { props } = wrapper.children().get(0); + + const defaultProperties = Object.keys(schema.properties).filter( + prop => schema.properties[prop].default + ); + assert.lengthOf(defaultProperties, 7); + defaultProperties.forEach(prop => + assert.propertyVal(props.content, prop, schema.properties[prop].default) + ); + + const defaultHiddenProperties = Object.keys( + schema.properties.hidden_inputs.properties + ).filter(prop => schema.properties.hidden_inputs.properties[prop].default); + assert.lengthOf(defaultHiddenProperties, 0); + }); + + describe("form input", () => { + it("should set the input type to text if content.include_sms is true", () => { + const wrapper = mountAndCheckProps({ include_sms: true }); + wrapper.find(".ASRouterButton").simulate("click"); + assert.equal(wrapper.find(".mainInput").instance().type, "text"); + }); + it("should set the input type to email if content.include_sms is false", () => { + const wrapper = mountAndCheckProps({ include_sms: false }); + wrapper.find(".ASRouterButton").simulate("click"); + assert.equal(wrapper.find(".mainInput").instance().type, "email"); + }); + it("should validate the input with isEmailOrPhoneNumber if include_sms is true", () => { + const wrapper = mountAndCheckProps({ include_sms: true }); + const setCustomValidity = sandbox.stub(); + openFormAndSetValue(wrapper, "foo", setCustomValidity); + assert.calledWith( + setCustomValidity, + "Must be an email or a phone number." + ); + }); + it("should not custom validate the input if include_sms is false", () => { + const wrapper = mountAndCheckProps({ include_sms: false }); + const setCustomValidity = sandbox.stub(); + openFormAndSetValue(wrapper, "foo", setCustomValidity); + assert.notCalled(setCustomValidity); + }); + }); + + describe("submitting", () => { + it("should send the right information to basket.mozilla.org/news/subscribe for an email", async () => { + const wrapper = mountAndCheckProps({ + locale: "fr-CA", + include_sms: true, + message_id_email: "foo", + }); + + openFormAndSetValue(wrapper, "foo@bar.com"); + wrapper.find("form").simulate("submit"); + + assert.calledOnce(fetchStub); + const [request] = fetchStub.firstCall.args; + + assert.equal(request.url, "https://basket.mozilla.org/news/subscribe/"); + const body = await request.text(); + assert.ok(testBodyContains(body, "email", "foo@bar.com"), "has email"); + assert.ok(testBodyContains(body, "lang", "fr-CA"), "has lang"); + assert.ok( + testBodyContains(body, "newsletters", "foo"), + "has newsletters" + ); + assert.ok( + testBodyContains(body, "source_url", "foo"), + "https%3A%2F%2Fsnippets.mozilla.com%2Fshow%2Ffoo123" + ); + }); + it("should send the right information for an sms", async () => { + const wrapper = mountAndCheckProps({ + locale: "fr-CA", + include_sms: true, + message_id_sms: "foo", + country: "CA", + }); + + openFormAndSetValue(wrapper, "5371283767"); + wrapper.find("form").simulate("submit"); + + assert.calledOnce(fetchStub); + const [request] = fetchStub.firstCall.args; + + assert.equal( + request.url, + "https://basket.mozilla.org/news/subscribe_sms/" + ); + const body = await request.text(); + assert.ok( + testBodyContains(body, "mobile_number", "5371283767"), + "has number" + ); + assert.ok(testBodyContains(body, "lang", "fr-CA"), "has lang"); + assert.ok(testBodyContains(body, "country", "CA"), "CA"); + assert.ok(testBodyContains(body, "msg_name", "foo"), "has msg_name"); + }); + }); + + describe("SendToDeviceScene2Snippet", () => { + function mountWithProps(content = {}) { + const props = { + id: "foo123", + content: Object.assign({}, DEFAULT_SCENE2_CONTENT, content), + onBlock() {}, + onDismiss: sandbox.stub(), + sendUserActionTelemetry: sandbox.stub(), + onAction: sandbox.stub(), + }; + return mount(<SendToDeviceScene2Snippet {...props} />); + } + + it("should render scene 2", () => { + const wrapper = mountWithProps(); + + assert.lengthOf(wrapper.find(".scene2Icon"), 1, "Found scene 2 icon"); + assert.lengthOf( + wrapper.find(".scene2Title"), + 0, + "Should not have a large header" + ); + }); + it("should have block button", () => { + const wrapper = mountWithProps(); + + assert.lengthOf( + wrapper.find(".blockButton"), + 1, + "Found the block button" + ); + }); + it("should render title text", () => { + const wrapper = mountWithProps(); + + assert.lengthOf( + wrapper.find(".section-title-text"), + 1, + "Found the section title" + ); + assert.lengthOf( + wrapper.find(".section-title .icon"), + 2, // light and dark theme + "Found scene 2 title" + ); + }); + it("should wrap the header in an anchor tag if condition is defined", () => { + const sectionTitleProp = { + section_title_url: "https://support.mozilla.org", + }; + let wrapper = mountWithProps(sectionTitleProp); + + const element = wrapper.find(".section-title a"); + assert.lengthOf(element, 1); + }); + it("should render a header without an anchor", () => { + const sectionTitleProp = { + section_title_url: undefined, + }; + let wrapper = mountWithProps(sectionTitleProp); + assert.lengthOf(wrapper.find(".section-title a"), 0); + assert.equal( + wrapper.find(".section-title").instance().innerText, + DEFAULT_SCENE2_CONTENT.section_title_text + ); + }); + }); +}); diff --git a/browser/components/newtab/test/unit/asrouter/templates/SimpleBelowSearchSnippet.test.jsx b/browser/components/newtab/test/unit/asrouter/templates/SimpleBelowSearchSnippet.test.jsx new file mode 100644 index 0000000000..d8e46d603c --- /dev/null +++ b/browser/components/newtab/test/unit/asrouter/templates/SimpleBelowSearchSnippet.test.jsx @@ -0,0 +1,62 @@ +import { mount } from "enzyme"; +import React from "react"; +import schema from "content-src/asrouter/templates/SimpleBelowSearchSnippet/SimpleBelowSearchSnippet.schema.json"; +import { SimpleBelowSearchSnippet } from "content-src/asrouter/templates/SimpleBelowSearchSnippet/SimpleBelowSearchSnippet.jsx"; + +const DEFAULT_CONTENT = { text: "foo" }; + +describe("SimpleBelowSearchSnippet", () => { + let sandbox; + let sendUserActionTelemetryStub; + + /** + * mountAndCheckProps - Mounts a SimpleBelowSearchSnippet 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}) + * @returns enzyme wrapper for SimpleSnippet + */ + function mountAndCheckProps(content = {}, provider = "test-provider") { + const props = { + content: { ...DEFAULT_CONTENT, ...content }, + provider, + sendUserActionTelemetry: sendUserActionTelemetryStub, + onAction: sandbox.stub(), + }; + assert.jsonSchema(props.content, schema); + return mount(<SimpleBelowSearchSnippet {...props} />); + } + + beforeEach(() => { + sandbox = sinon.createSandbox(); + sendUserActionTelemetryStub = sandbox.stub(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it("should render .text", () => { + const wrapper = mountAndCheckProps({ text: "bar" }); + assert.equal(wrapper.find(".body").text(), "bar"); + }); + + it("should render .icon (light theme)", () => { + const wrapper = mountAndCheckProps({ + icon: "data:image/gif;base64,R0lGODl", + }); + assert.equal( + wrapper.find(".icon-light-theme").prop("src"), + "data:image/gif;base64,R0lGODl" + ); + }); + + it("should render .icon (dark theme)", () => { + const wrapper = mountAndCheckProps({ + icon_dark_theme: "data:image/gif;base64,R0lGODl", + }); + assert.equal( + wrapper.find(".icon-dark-theme").prop("src"), + "data:image/gif;base64,R0lGODl" + ); + }); +}); diff --git a/browser/components/newtab/test/unit/asrouter/templates/SimpleSnippet.test.jsx b/browser/components/newtab/test/unit/asrouter/templates/SimpleSnippet.test.jsx new file mode 100644 index 0000000000..cc3601cc54 --- /dev/null +++ b/browser/components/newtab/test/unit/asrouter/templates/SimpleSnippet.test.jsx @@ -0,0 +1,255 @@ +import { mount } from "enzyme"; +import React from "react"; +import schema from "content-src/asrouter/templates/SimpleSnippet/SimpleSnippet.schema.json"; +import { SimpleSnippet } from "content-src/asrouter/templates/SimpleSnippet/SimpleSnippet.jsx"; + +const DEFAULT_CONTENT = { text: "foo" }; + +describe("SimpleSnippet", () => { + let sandbox; + let onBlockStub; + let sendUserActionTelemetryStub; + + /** + * mountAndCheckProps - Mounts a SimpleSnippet 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 SimpleSnippet + */ + function mountAndCheckProps(content = {}, provider = "test-provider") { + const props = { + content: Object.assign({}, DEFAULT_CONTENT, content), + provider, + onBlock: onBlockStub, + sendUserActionTelemetry: sendUserActionTelemetryStub, + onAction: sandbox.stub(), + }; + assert.jsonSchema(props.content, schema); + return mount(<SimpleSnippet {...props} />); + } + + beforeEach(() => { + sandbox = sinon.createSandbox(); + onBlockStub = sandbox.stub(); + sendUserActionTelemetryStub = sandbox.stub(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it("should have the correct defaults", () => { + const wrapper = mountAndCheckProps(); + [["button", "title", "block_button_text"]].forEach(prop => { + const props = wrapper.find(prop[0]).props(); + assert.propertyVal(props, prop[1], schema.properties[prop[2]].default); + }); + }); + + it("should render .text", () => { + const wrapper = mountAndCheckProps({ 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({ title: "Foo" }); + assert.equal( + wrapper + .find(".title") + .text() + .trim(), + "Foo" + ); + }); + it("should render a light theme variant .icon", () => { + const wrapper = mountAndCheckProps({ + icon: "data:image/gif;base64,R0lGODl", + }); + assert.equal( + wrapper.find(".icon-light-theme").prop("src"), + "data:image/gif;base64,R0lGODl" + ); + }); + it("should render a dark theme variant .icon", () => { + const wrapper = mountAndCheckProps({ + 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 a light theme variant .icon as fallback", () => { + const wrapper = mountAndCheckProps({ + icon_dark_theme: "", + icon: "data:image/gif;base64,R0lGODp", + }); + assert.equal( + wrapper.find(".icon-dark-theme").prop("src"), + "data:image/gif;base64,R0lGODp" + ); + }); + it("should render .button_label and default className", () => { + const wrapper = mountAndCheckProps({ + button_label: "Click here", + button_action: "OPEN_APPLICATIONS_MENU", + button_action_args: "appMenu", + }); + + const button = wrapper.find("button.ASRouterButton"); + button.simulate("click"); + + assert.equal(button.text(), "Click here"); + assert.equal(button.prop("className"), "ASRouterButton secondary"); + assert.calledOnce(wrapper.props().onAction); + assert.calledWithExactly(wrapper.props().onAction, { + type: "OPEN_APPLICATIONS_MENU", + data: { args: "appMenu" }, + }); + }); + it("should not wrap the main content if a section header is not present", () => { + const wrapper = mountAndCheckProps({ text: "bar" }); + assert.lengthOf(wrapper.find(".innerContentWrapper"), 0); + }); + it("should wrap the main content if a section header is present", () => { + const wrapper = mountAndCheckProps({ + section_title_icon: "data:image/gif;base64,R0lGODl", + section_title_text: "Messages from Mozilla", + }); + + assert.lengthOf(wrapper.find(".innerContentWrapper"), 1); + }); + it("should render a section header if text and icon (light-theme) are specified", () => { + const wrapper = mountAndCheckProps({ + section_title_icon: "data:image/gif;base64,R0lGODl", + section_title_text: "Messages from Mozilla", + }); + + assert.equal( + wrapper.find(".section-title .icon-light-theme").prop("style") + .backgroundImage, + 'url("data:image/gif;base64,R0lGODl")' + ); + assert.equal( + wrapper + .find(".section-title-text") + .text() + .trim(), + "Messages from Mozilla" + ); + // ensure there is no <a> when a section_title_url is not specified + assert.lengthOf(wrapper.find(".section-title a"), 0); + }); + it("should render a section header if text and icon (light-theme) are specified", () => { + const wrapper = mountAndCheckProps({ + section_title_icon: "data:image/gif;base64,R0lGODl", + section_title_icon_dark_theme: "data:image/gif;base64,R0lGODl", + section_title_text: "Messages from Mozilla", + }); + + assert.equal( + wrapper.find(".section-title .icon-dark-theme").prop("style") + .backgroundImage, + 'url("data:image/gif;base64,R0lGODl")' + ); + assert.equal( + wrapper + .find(".section-title-text") + .text() + .trim(), + "Messages from Mozilla" + ); + // ensure there is no <a> when a section_title_url is not specified + assert.lengthOf(wrapper.find(".section-title a"), 0); + }); + it("should render a section header wrapped in an <a> tag if a url is provided", () => { + const wrapper = mountAndCheckProps({ + section_title_icon: "data:image/gif;base64,R0lGODl", + section_title_text: "Messages from Mozilla", + section_title_url: "https://www.mozilla.org", + }); + + assert.equal( + wrapper.find(".section-title a").prop("href"), + "https://www.mozilla.org" + ); + }); + it("should send an OPEN_URL action when button_url is defined and button is clicked", () => { + const wrapper = mountAndCheckProps({ + button_label: "Button", + button_url: "https://mozilla.org", + }); + + const button = wrapper.find("button.ASRouterButton"); + button.simulate("click"); + + assert.calledOnce(wrapper.props().onAction); + assert.calledWithExactly(wrapper.props().onAction, { + type: "OPEN_URL", + data: { args: "https://mozilla.org" }, + }); + }); + it("should send an OPEN_ABOUT_PAGE action with entrypoint when the button is clicked", () => { + const wrapper = mountAndCheckProps({ + button_label: "Button", + button_action: "OPEN_ABOUT_PAGE", + button_entrypoint_value: "snippet", + button_entrypoint_name: "entryPoint", + button_action_args: "logins", + }); + + const button = wrapper.find("button.ASRouterButton"); + button.simulate("click"); + + assert.calledOnce(wrapper.props().onAction); + assert.calledWithExactly(wrapper.props().onAction, { + type: "OPEN_ABOUT_PAGE", + data: { args: "logins", entrypoint: "entryPoint=snippet" }, + }); + }); + it("should send an OPEN_PREFERENCE_PAGE action with entrypoint when the button is clicked", () => { + const wrapper = mountAndCheckProps({ + button_label: "Button", + button_action: "OPEN_PREFERENCE_PAGE", + button_entrypoint_value: "entry=snippet", + button_action_args: "home", + }); + + const button = wrapper.find("button.ASRouterButton"); + button.simulate("click"); + + assert.calledOnce(wrapper.props().onAction); + assert.calledWithExactly(wrapper.props().onAction, { + type: "OPEN_PREFERENCE_PAGE", + data: { args: "home", entrypoint: "entry=snippet" }, + }); + }); + it("should call props.onBlock and sendUserActionTelemetry when CTA button is clicked", () => { + const wrapper = mountAndCheckProps({ text: "bar" }); + + wrapper.instance().onButtonClick(); + + assert.calledOnce(onBlockStub); + assert.calledOnce(sendUserActionTelemetryStub); + }); + + it("should not call props.onBlock if do_not_autoblock is true", () => { + const wrapper = mountAndCheckProps({ text: "bar", do_not_autoblock: true }); + + wrapper.instance().onButtonClick(); + + assert.notCalled(onBlockStub); + }); + + it("should not call sendUserActionTelemetry for preview message when CTA button is clicked", () => { + const wrapper = mountAndCheckProps({ text: "bar" }, "preview"); + + wrapper.instance().onButtonClick(); + + assert.calledOnce(onBlockStub); + assert.notCalled(sendUserActionTelemetryStub); + }); +}); 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..0620e8c1e7 --- /dev/null +++ b/browser/components/newtab/test/unit/asrouter/templates/SubmitFormSnippet.test.jsx @@ -0,0 +1,341 @@ +import { mount } from "enzyme"; +import React from "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; + + /** + * 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} />); + } + + 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()); + }); + }); +}); diff --git a/browser/components/newtab/test/unit/asrouter/templates/isEmailOrPhoneNumber.test.js b/browser/components/newtab/test/unit/asrouter/templates/isEmailOrPhoneNumber.test.js new file mode 100644 index 0000000000..32eaf2160e --- /dev/null +++ b/browser/components/newtab/test/unit/asrouter/templates/isEmailOrPhoneNumber.test.js @@ -0,0 +1,56 @@ +import { isEmailOrPhoneNumber } from "content-src/asrouter/templates/SendToDeviceSnippet/isEmailOrPhoneNumber"; + +const CONTENT = {}; + +describe("isEmailOrPhoneNumber", () => { + it("should return 'email' for emails", () => { + assert.equal(isEmailOrPhoneNumber("foobar@asd.com", CONTENT), "email"); + assert.equal(isEmailOrPhoneNumber("foobar@asd.co.uk", CONTENT), "email"); + }); + it("should return 'phone' for valid en-US/en-CA phone numbers", () => { + assert.equal( + isEmailOrPhoneNumber("14582731273", { locale: "en-US" }), + "phone" + ); + assert.equal( + isEmailOrPhoneNumber("4582731273", { locale: "en-CA" }), + "phone" + ); + }); + it("should return an empty string for invalid phone number lengths in en-US/en-CA", () => { + // Not enough digits + assert.equal(isEmailOrPhoneNumber("4522", { locale: "en-US" }), ""); + assert.equal(isEmailOrPhoneNumber("4522", { locale: "en-CA" }), ""); + }); + it("should return 'phone' for valid German phone numbers", () => { + assert.equal( + isEmailOrPhoneNumber("145827312732", { locale: "de" }), + "phone" + ); + }); + it("should return 'phone' for any number of digits in other locales", () => { + assert.equal(isEmailOrPhoneNumber("4", CONTENT), "phone"); + }); + it("should return an empty string for other invalid inputs", () => { + assert.equal( + isEmailOrPhoneNumber("abc", CONTENT), + "", + "abc should be invalid" + ); + assert.equal( + isEmailOrPhoneNumber("abc@", CONTENT), + "", + "abc@ should be invalid" + ); + assert.equal( + isEmailOrPhoneNumber("abc@foo", CONTENT), + "", + "abc@foo should be invalid" + ); + assert.equal( + isEmailOrPhoneNumber("123d1232", CONTENT), + "", + "123d1232 should be invalid" + ); + }); +}); |