import { mount } from "enzyme";
import React from "react";
import { FluentBundle, FluentResource } from "@fluent/bundle";
import { LocalizationProvider, ReactLocalization } from "@fluent/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.sys.mjs";
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 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 },
};
}
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(
,
mockL10nWrapper(props.content)
);
// 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(
,
mockL10nWrapper(DEFAULT_CONTENT)
);
// 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(
,
mockL10nWrapper(props.content)
);
}
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
);
});
});
});