Source code
Revision control
Copy as Markdown
Other Tools
import { actionCreators as ac, actionTypes as at } from "common/Actions.mjs";
import { GlobalOverrider } from "test/unit/utils";
import { MIN_RICH_FAVICON_SIZE } from "content-src/components/TopSites/TopSitesConstants";
import {
TOP_SITES_DEFAULT_ROWS,
TOP_SITES_MAX_SITES_PER_ROW,
} from "common/Reducers.sys.mjs";
import {
TopSite,
TopSiteLink,
_TopSiteList as TopSiteList,
TopSitePlaceholder,
} from "content-src/components/TopSites/TopSite";
import {
INTERSECTION_RATIO,
TopSiteImpressionWrapper,
} from "content-src/components/TopSites/TopSiteImpressionWrapper";
import { A11yLinkButton } from "content-src/components/A11yLinkButton/A11yLinkButton";
import { LinkMenu } from "content-src/components/LinkMenu/LinkMenu";
import React from "react";
import { mount, shallow } from "enzyme";
import { TopSiteForm } from "content-src/components/TopSites/TopSiteForm";
import { TopSiteFormInput } from "content-src/components/TopSites/TopSiteFormInput";
import { _TopSites as TopSites } from "content-src/components/TopSites/TopSites";
import { ContextMenuButton } from "content-src/components/ContextMenu/ContextMenuButton";
const perfSvc = {
mark() {},
getMostRecentAbsMarkStartByName() {},
};
const DEFAULT_PROPS = {
Prefs: { values: { featureConfig: {} } },
TopSites: { initialized: true, rows: [] },
TopSitesRows: TOP_SITES_DEFAULT_ROWS,
topSiteIconType: () => "no_image",
dispatch() {},
perfSvc,
};
describe("<TopSites>", () => {
let sandbox;
beforeEach(() => {
sandbox = sinon.createSandbox();
});
afterEach(() => {
sandbox.restore();
});
it("should render a TopSites element", () => {
const wrapper = shallow(<TopSites {...DEFAULT_PROPS} />);
assert.ok(wrapper.exists());
});
describe("#_dispatchTopSitesStats", () => {
let globals;
let wrapper;
let dispatchStatsSpy;
beforeEach(() => {
globals = new GlobalOverrider();
sandbox.stub(DEFAULT_PROPS, "dispatch");
wrapper = shallow(<TopSites {...DEFAULT_PROPS} />, {
disableLifecycleMethods: true,
});
dispatchStatsSpy = sandbox.spy(
wrapper.instance(),
"_dispatchTopSitesStats"
);
});
afterEach(() => {
globals.restore();
sandbox.restore();
});
it("should call _dispatchTopSitesStats on componentDidMount", () => {
wrapper.instance().componentDidMount();
assert.calledOnce(dispatchStatsSpy);
});
it("should call _dispatchTopSitesStats on componentDidUpdate", () => {
wrapper.instance().componentDidUpdate();
assert.calledOnce(dispatchStatsSpy);
});
it("should dispatch SAVE_SESSION_PERF_DATA", () => {
wrapper.instance()._dispatchTopSitesStats();
assert.calledOnce(DEFAULT_PROPS.dispatch);
assert.calledWithExactly(
DEFAULT_PROPS.dispatch,
ac.AlsoToMain({
type: at.SAVE_SESSION_PERF_DATA,
data: {
topsites_icon_stats: {
custom_screenshot: 0,
screenshot: 0,
tippytop: 0,
rich_icon: 0,
no_image: 0,
},
topsites_pinned: 0,
topsites_search_shortcuts: 0,
},
})
);
});
it("should correctly count TopSite images - just screenshot", () => {
const rows = [{ screenshot: true }];
sandbox.stub(DEFAULT_PROPS.TopSites, "rows").value(rows);
wrapper.instance()._dispatchTopSitesStats();
assert.calledOnce(DEFAULT_PROPS.dispatch);
assert.calledWithExactly(
DEFAULT_PROPS.dispatch,
ac.AlsoToMain({
type: at.SAVE_SESSION_PERF_DATA,
data: {
topsites_icon_stats: {
custom_screenshot: 0,
screenshot: 1,
tippytop: 0,
rich_icon: 0,
no_image: 0,
},
topsites_pinned: 0,
topsites_search_shortcuts: 0,
},
})
);
});
it("should correctly count TopSite images - custom_screenshot", () => {
const rows = [{ customScreenshotURL: true }];
sandbox.stub(DEFAULT_PROPS.TopSites, "rows").value(rows);
wrapper.instance()._dispatchTopSitesStats();
assert.calledOnce(DEFAULT_PROPS.dispatch);
assert.calledWithExactly(
DEFAULT_PROPS.dispatch,
ac.AlsoToMain({
type: at.SAVE_SESSION_PERF_DATA,
data: {
topsites_icon_stats: {
custom_screenshot: 1,
screenshot: 0,
tippytop: 0,
rich_icon: 0,
no_image: 0,
},
topsites_pinned: 0,
topsites_search_shortcuts: 0,
},
})
);
});
it("should correctly count TopSite images - rich_icon", () => {
const rows = [{ faviconSize: MIN_RICH_FAVICON_SIZE }];
sandbox.stub(DEFAULT_PROPS.TopSites, "rows").value(rows);
wrapper.instance()._dispatchTopSitesStats();
assert.calledOnce(DEFAULT_PROPS.dispatch);
assert.calledWithExactly(
DEFAULT_PROPS.dispatch,
ac.AlsoToMain({
type: at.SAVE_SESSION_PERF_DATA,
data: {
topsites_icon_stats: {
custom_screenshot: 0,
screenshot: 0,
tippytop: 0,
rich_icon: 1,
no_image: 0,
},
topsites_pinned: 0,
topsites_search_shortcuts: 0,
},
})
);
});
it("should correctly count TopSite images - tippytop", () => {
const rows = [
{ tippyTopIcon: "foo" },
{ faviconRef: "tippytop" },
{ faviconRef: "foobar" },
];
sandbox.stub(DEFAULT_PROPS.TopSites, "rows").value(rows);
wrapper.instance()._dispatchTopSitesStats();
assert.calledOnce(DEFAULT_PROPS.dispatch);
assert.calledWithExactly(
DEFAULT_PROPS.dispatch,
ac.AlsoToMain({
type: at.SAVE_SESSION_PERF_DATA,
data: {
topsites_icon_stats: {
custom_screenshot: 0,
screenshot: 0,
tippytop: 2,
rich_icon: 0,
no_image: 1,
},
topsites_pinned: 0,
topsites_search_shortcuts: 0,
},
})
);
});
it("should correctly count TopSite images - no image", () => {
const rows = [{}];
sandbox.stub(DEFAULT_PROPS.TopSites, "rows").value(rows);
wrapper.instance()._dispatchTopSitesStats();
assert.calledOnce(DEFAULT_PROPS.dispatch);
assert.calledWithExactly(
DEFAULT_PROPS.dispatch,
ac.AlsoToMain({
type: at.SAVE_SESSION_PERF_DATA,
data: {
topsites_icon_stats: {
custom_screenshot: 0,
screenshot: 0,
tippytop: 0,
rich_icon: 0,
no_image: 1,
},
topsites_pinned: 0,
topsites_search_shortcuts: 0,
},
})
);
});
it("should correctly count pinned Top Sites", () => {
const rows = [
{ isPinned: true },
{ isPinned: false },
{ isPinned: true },
];
sandbox.stub(DEFAULT_PROPS.TopSites, "rows").value(rows);
wrapper.instance()._dispatchTopSitesStats();
assert.calledOnce(DEFAULT_PROPS.dispatch);
assert.calledWithExactly(
DEFAULT_PROPS.dispatch,
ac.AlsoToMain({
type: at.SAVE_SESSION_PERF_DATA,
data: {
topsites_icon_stats: {
custom_screenshot: 0,
screenshot: 0,
tippytop: 0,
rich_icon: 0,
no_image: 3,
},
topsites_pinned: 2,
topsites_search_shortcuts: 0,
},
})
);
});
it("should correctly count search shortcut Top Sites", () => {
const rows = [{ searchTopSite: true }, { searchTopSite: true }];
sandbox.stub(DEFAULT_PROPS.TopSites, "rows").value(rows);
wrapper.instance()._dispatchTopSitesStats();
assert.calledOnce(DEFAULT_PROPS.dispatch);
assert.calledWithExactly(
DEFAULT_PROPS.dispatch,
ac.AlsoToMain({
type: at.SAVE_SESSION_PERF_DATA,
data: {
topsites_icon_stats: {
custom_screenshot: 0,
screenshot: 0,
tippytop: 0,
rich_icon: 0,
no_image: 2,
},
topsites_pinned: 0,
topsites_search_shortcuts: 2,
},
})
);
});
it("should only count visible top sites on wide layout", () => {
globals.set("matchMedia", () => ({ matches: true }));
const rows = [
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
];
sandbox.stub(DEFAULT_PROPS.TopSites, "rows").value(rows);
wrapper.instance()._dispatchTopSitesStats();
assert.calledOnce(DEFAULT_PROPS.dispatch);
assert.calledWithExactly(
DEFAULT_PROPS.dispatch,
ac.AlsoToMain({
type: at.SAVE_SESSION_PERF_DATA,
data: {
topsites_icon_stats: {
custom_screenshot: 0,
screenshot: 0,
tippytop: 0,
rich_icon: 0,
no_image: 8,
},
topsites_pinned: 0,
topsites_search_shortcuts: 0,
},
})
);
});
it("should only count visible top sites on normal layout", () => {
globals.set("matchMedia", () => ({ matches: false }));
const rows = [
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
];
sandbox.stub(DEFAULT_PROPS.TopSites, "rows").value(rows);
wrapper.instance()._dispatchTopSitesStats();
assert.calledOnce(DEFAULT_PROPS.dispatch);
assert.calledWithExactly(
DEFAULT_PROPS.dispatch,
ac.AlsoToMain({
type: at.SAVE_SESSION_PERF_DATA,
data: {
topsites_icon_stats: {
custom_screenshot: 0,
screenshot: 0,
tippytop: 0,
rich_icon: 0,
no_image: 6,
},
topsites_pinned: 0,
topsites_search_shortcuts: 0,
},
})
);
});
});
});
describe("<TopSiteLink>", () => {
let globals;
let link;
let url;
beforeEach(() => {
globals = new GlobalOverrider();
url = {
createObjectURL: globals.sandbox.stub().returns(DEFAULT_BLOB_URL),
revokeObjectURL: globals.sandbox.spy(),
};
globals.set("URL", url);
});
afterEach(() => globals.restore());
it("should add the right url", () => {
const wrapper = shallow(<TopSiteLink link={link} />);
assert.propertyVal(
wrapper.find("a").props(),
"href",
);
});
it("should not add the url to the href if it a search shortcut", () => {
link.searchTopSite = true;
const wrapper = shallow(<TopSiteLink link={link} />);
assert.isUndefined(wrapper.find("a").props().href);
});
it("should have rtl direction automatically set for text", () => {
const wrapper = shallow(<TopSiteLink link={link} />);
assert.isTrue(!!wrapper.find("[dir='auto']").length);
});
it("should render a title", () => {
const wrapper = shallow(<TopSiteLink link={link} title="foobar" />);
const titleEl = wrapper.find(".title");
assert.equal(titleEl.text(), "foobar");
});
it("should have only the title as the text of the link", () => {
const wrapper = shallow(<TopSiteLink link={link} title="foobar" />);
assert.equal(wrapper.find("a").text(), "foobar");
});
it("should render the pin icon for pinned links", () => {
link.isPinned = true;
link.pinnedIndex = 7;
const wrapper = shallow(<TopSiteLink link={link} />);
assert.equal(wrapper.find(".icon-pin-small").length, 1);
});
it("should not render the pin icon for non pinned links", () => {
link.isPinned = false;
const wrapper = shallow(<TopSiteLink link={link} />);
assert.equal(wrapper.find(".icon-pin-small").length, 0);
});
it("should render the first letter of the title as a fallback for missing icons", () => {
const wrapper = shallow(<TopSiteLink link={link} title={"foo"} />);
assert.equal(wrapper.find(".icon-wrapper").prop("data-fallback"), "f");
});
it("should render the tippy top icon if provided and not a small icon", () => {
link.tippyTopIcon = "foo.png";
link.backgroundColor = "#FFFFFF";
const wrapper = shallow(<TopSiteLink link={link} />);
assert.lengthOf(wrapper.find(".screenshot"), 0);
assert.lengthOf(wrapper.find(".default-icon"), 0);
const tippyTop = wrapper.find(".rich-icon");
assert.propertyVal(
tippyTop.props().style,
"backgroundImage",
"url(foo.png)"
);
assert.propertyVal(tippyTop.props().style, "backgroundColor", "#FFFFFF");
});
it("should render a rich icon if provided and not a small icon", () => {
link.favicon = "foo.png";
link.faviconSize = 196;
link.backgroundColor = "#FFFFFF";
const wrapper = shallow(<TopSiteLink link={link} />);
assert.lengthOf(wrapper.find(".screenshot"), 0);
assert.lengthOf(wrapper.find(".default-icon"), 0);
const richIcon = wrapper.find(".rich-icon");
assert.propertyVal(
richIcon.props().style,
"backgroundImage",
"url(foo.png)"
);
assert.propertyVal(richIcon.props().style, "backgroundColor", "#FFFFFF");
});
it("should not render a rich icon if it is smaller than 96x96", () => {
link.favicon = "foo.png";
link.faviconSize = 48;
link.backgroundColor = "#FFFFFF";
const wrapper = shallow(<TopSiteLink link={link} />);
assert.lengthOf(wrapper.find(".default-icon"), 1);
assert.equal(wrapper.find(".rich-icon").length, 0);
});
it("should apply just the default class name to the outer link if props.className is falsey", () => {
const wrapper = shallow(<TopSiteLink className={false} />);
assert.ok(wrapper.find("li").hasClass("top-site-outer"));
});
it("should add props.className to the outer link element", () => {
const wrapper = shallow(<TopSiteLink className="foo bar" />);
assert.ok(wrapper.find("li").hasClass("top-site-outer foo bar"));
});
describe("#_allowDrop", () => {
let wrapper;
let event;
beforeEach(() => {
event = {
dataTransfer: {
types: ["text/topsite-index"],
},
};
wrapper = shallow(
<TopSiteLink isDraggable={true} onDragEvent={() => {}} />
);
});
it("should be droppable for basic case", () => {
const result = wrapper.instance()._allowDrop(event);
assert.isTrue(result);
});
it("should not be droppable for sponsored_position", () => {
wrapper.setProps({ link: { sponsored_position: 1 } });
const result = wrapper.instance()._allowDrop(event);
assert.isFalse(result);
});
it("should not be droppable for link.type", () => {
wrapper.setProps({ link: { type: "SPOC" } });
const result = wrapper.instance()._allowDrop(event);
assert.isFalse(result);
});
});
describe("#onDragEvent", () => {
let simulate;
let wrapper;
beforeEach(() => {
wrapper = shallow(
<TopSiteLink isDraggable={true} onDragEvent={() => {}} />
);
simulate = type => {
const event = {
dataTransfer: { setData() {}, types: { includes() {} } },
preventDefault() {
this.prevented = true;
},
target: { blur() {} },
type,
};
wrapper.simulate(type, event);
return event;
};
});
it("should allow clicks without dragging", () => {
simulate("mousedown");
simulate("mouseup");
const event = simulate("click");
assert.notOk(event.prevented);
});
it("should prevent clicks after dragging", () => {
simulate("mousedown");
simulate("dragstart");
simulate("dragenter");
simulate("drop");
simulate("dragend");
simulate("mouseup");
const event = simulate("click");
assert.ok(event.prevented);
});
it("should allow clicks after dragging then clicking", () => {
simulate("mousedown");
simulate("dragstart");
simulate("dragenter");
simulate("drop");
simulate("dragend");
simulate("mouseup");
simulate("click");
simulate("mousedown");
simulate("mouseup");
const event = simulate("click");
assert.notOk(event.prevented);
});
it("should prevent dragging with sponsored_position from dragstart", () => {
const preventDefault = sinon.stub();
// eslint-disable-next-line no-shadow
const blur = sinon.stub();
wrapper.setProps({ link: { sponsored_position: 1 } });
wrapper.instance().onDragEvent({
type: "dragstart",
preventDefault,
target: { blur },
});
assert.calledOnce(preventDefault);
assert.calledOnce(blur);
assert.isUndefined(wrapper.instance().dragged);
});
it("should prevent dragging with link.shim from dragstart", () => {
const preventDefault = sinon.stub();
// eslint-disable-next-line no-shadow
const blur = sinon.stub();
wrapper.setProps({ link: { type: "SPOC" } });
wrapper.instance().onDragEvent({
type: "dragstart",
preventDefault,
target: { blur },
});
assert.calledOnce(preventDefault);
assert.calledOnce(blur);
assert.isUndefined(wrapper.instance().dragged);
});
});
describe("#generateColor", () => {
let colors;
beforeEach(() => {
colors = "#0090ED,#FF4F5F,#2AC3A2";
});
it("should generate a random color but always pick the same color for the same string", async () => {
let wrapper = shallow(
<TopSiteLink colors={colors} title={"food"} link={link} />
);
assert.equal(wrapper.find(".icon-wrapper").prop("data-fallback"), "f");
assert.equal(
wrapper.find(".icon-wrapper").prop("style").backgroundColor,
colors.split(",")[1]
);
assert.ok(true);
});
it("should generate a different random color", async () => {
let wrapper = shallow(
<TopSiteLink colors={colors} title={"fam"} link={link} />
);
assert.equal(
wrapper.find(".icon-wrapper").prop("style").backgroundColor,
colors.split(",")[2]
);
assert.ok(true);
});
it("should generate a third random color", async () => {
let wrapper = shallow(<TopSiteLink colors={colors} title={"foo"} />);
assert.equal(wrapper.find(".icon-wrapper").prop("data-fallback"), "f");
assert.equal(
wrapper.find(".icon-wrapper").prop("style").backgroundColor,
colors.split(",")[0]
);
assert.ok(true);
});
});
});
describe("<TopSite>", () => {
let link;
beforeEach(() => {
});
// Build IntersectionObserver class with the arg `entries` for the intersect callback.
function buildIntersectionObserver(entries) {
return class {
constructor(callback) {
this.callback = callback;
}
observe() {
this.callback(entries);
}
unobserve() {}
};
}
it("should render a TopSite", () => {
const wrapper = shallow(<TopSite link={link} />);
assert.ok(wrapper.exists());
});
it("should render a shortened title based off the url", () => {
link.hostname = "foobar";
link.eTLD = "org";
const wrapper = shallow(<TopSite link={link} />);
assert.equal(wrapper.find(TopSiteLink).props().title, "foobar");
});
it("should parse args for fluent correctly", () => {
const title = '"fluent"';
link.hostname = title;
const wrapper = mount(<TopSite link={link} />);
const button = wrapper.find(
"button[data-l10n-id='newtab-menu-content-tooltip']"
);
assert.equal(button.prop("data-l10n-args"), JSON.stringify({ title }));
});
it("should have .active class, on top-site-outer if context menu is open", () => {
const wrapper = shallow(<TopSite link={link} index={1} activeIndex={1} />);
wrapper.setState({ showContextMenu: true });
assert.equal(wrapper.find(TopSiteLink).props().className.trim(), "active");
});
it("should not add .active class, on top-site-outer if context menu is closed", () => {
const wrapper = shallow(<TopSite link={link} index={1} />);
wrapper.setState({ showContextMenu: false, activeTile: 1 });
assert.equal(wrapper.find(TopSiteLink).props().className, "");
});
it("should render a context menu button", () => {
const wrapper = shallow(<TopSite link={link} />);
assert.equal(wrapper.find(ContextMenuButton).length, 1);
});
it("should render a link menu", () => {
const wrapper = shallow(<TopSite link={link} />);
assert.equal(wrapper.find(LinkMenu).length, 1);
});
it("should pass onUpdate, site, options, and index to LinkMenu", () => {
const wrapper = shallow(<TopSite link={link} />);
const linkMenuProps = wrapper.find(LinkMenu).props();
["onUpdate", "site", "index", "options"].forEach(prop =>
assert.property(linkMenuProps, prop)
);
});
it("should pass through the correct menu options to LinkMenu", () => {
const wrapper = shallow(<TopSite link={link} />);
const linkMenuProps = wrapper.find(LinkMenu).props();
assert.deepEqual(linkMenuProps.options, [
"CheckPinTopSite",
"EditTopSite",
"Separator",
"OpenInNewWindow",
"OpenInPrivateWindow",
"Separator",
"BlockUrl",
"DeleteUrl",
]);
});
it("should record impressions for visible organic Top Sites", () => {
const dispatch = sinon.stub();
const wrapper = shallow(
<TopSite
link={link}
index={3}
dispatch={dispatch}
IntersectionObserver={buildIntersectionObserver([
{
isIntersecting: true,
intersectionRatio: INTERSECTION_RATIO,
},
])}
document={{
visibilityState: "visible",
addEventListener: sinon.stub(),
removeEventListener: sinon.stub(),
}}
/>
);
const linkWrapper = wrapper.find(TopSiteLink).dive();
assert.ok(linkWrapper.exists());
const impressionWrapper = linkWrapper.find(TopSiteImpressionWrapper).dive();
assert.ok(impressionWrapper.exists());
assert.calledOnce(dispatch);
let [action] = dispatch.firstCall.args;
assert.equal(action.type, at.TOP_SITES_ORGANIC_IMPRESSION_STATS);
assert.propertyVal(action.data, "type", "impression");
assert.propertyVal(action.data, "source", "newtab");
assert.propertyVal(action.data, "position", 3);
});
it("should record impressions for visible sponsored Top Sites", () => {
const dispatch = sinon.stub();
const wrapper = shallow(
<TopSite
link={Object.assign({}, link, {
sponsored_position: 2,
sponsored_tile_id: 12345,
})}
index={3}
dispatch={dispatch}
IntersectionObserver={buildIntersectionObserver([
{
isIntersecting: true,
intersectionRatio: INTERSECTION_RATIO,
},
])}
document={{
visibilityState: "visible",
addEventListener: sinon.stub(),
removeEventListener: sinon.stub(),
}}
/>
);
const linkWrapper = wrapper.find(TopSiteLink).dive();
assert.ok(linkWrapper.exists());
const impressionWrapper = linkWrapper.find(TopSiteImpressionWrapper).dive();
assert.ok(impressionWrapper.exists());
assert.calledOnce(dispatch);
let [action] = dispatch.firstCall.args;
assert.equal(action.type, at.TOP_SITES_SPONSORED_IMPRESSION_STATS);
assert.propertyVal(action.data, "type", "impression");
assert.propertyVal(action.data, "tile_id", 12345);
assert.propertyVal(action.data, "source", "newtab");
assert.propertyVal(action.data, "position", 3);
assert.propertyVal(
action.data,
"reporting_url",
);
assert.propertyVal(action.data, "advertiser", "foo");
});
describe("#onLinkClick", () => {
it("should call dispatch when the link is clicked", () => {
const dispatch = sinon.stub();
const wrapper = shallow(
<TopSite link={link} index={3} dispatch={dispatch} />
);
wrapper.find(TopSiteLink).simulate("click", { preventDefault() {} });
let [action] = dispatch.firstCall.args;
assert.isUserEventAction(action);
assert.propertyVal(action.data, "event", "CLICK");
assert.propertyVal(action.data, "source", "TOP_SITES");
assert.propertyVal(action.data, "action_position", 3);
[action] = dispatch.secondCall.args;
assert.propertyVal(action, "type", at.OPEN_LINK);
// Organic Top Site click event.
[action] = dispatch.thirdCall.args;
assert.equal(action.type, at.TOP_SITES_ORGANIC_IMPRESSION_STATS);
assert.propertyVal(action.data, "type", "click");
assert.propertyVal(action.data, "source", "newtab");
assert.propertyVal(action.data, "position", 3);
});
it("should dispatch a UserEventAction with the right data", () => {
const dispatch = sinon.stub();
const wrapper = shallow(
<TopSite
link={Object.assign({}, link, {
iconType: "rich_icon",
isPinned: true,
})}
index={3}
dispatch={dispatch}
/>
);
wrapper.find(TopSiteLink).simulate("click", { preventDefault() {} });
const [action] = dispatch.firstCall.args;
assert.isUserEventAction(action);
assert.propertyVal(action.data, "event", "CLICK");
assert.propertyVal(action.data, "source", "TOP_SITES");
assert.propertyVal(action.data, "action_position", 3);
assert.propertyVal(action.data.value, "card_type", "pinned");
assert.propertyVal(action.data.value, "icon_type", "rich_icon");
});
it("should dispatch a UserEventAction with the right data for search top site", () => {
const dispatch = sinon.stub();
const siteInfo = {
iconType: "tippytop",
isPinned: true,
searchTopSite: true,
hostname: "google",
label: "@google",
};
const wrapper = shallow(
<TopSite
link={Object.assign({}, link, siteInfo)}
index={3}
dispatch={dispatch}
/>
);
wrapper.find(TopSiteLink).simulate("click", { preventDefault() {} });
const [action] = dispatch.firstCall.args;
assert.isUserEventAction(action);
assert.propertyVal(action.data, "event", "CLICK");
assert.propertyVal(action.data, "source", "TOP_SITES");
assert.propertyVal(action.data, "action_position", 3);
assert.propertyVal(action.data.value, "card_type", "search");
assert.propertyVal(action.data.value, "icon_type", "tippytop");
assert.propertyVal(action.data.value, "search_vendor", "google");
});
it("should dispatch a UserEventAction with the right data for SPOC top site", () => {
const dispatch = sinon.stub();
const siteInfo = {
id: 1,
iconType: "custom_screenshot",
type: "SPOC",
pos: 1,
label: "test advertiser",
shim: { click: "shim_click_id" },
};
const wrapper = shallow(
<TopSite
link={Object.assign({}, link, siteInfo)}
index={0}
dispatch={dispatch}
/>
);
wrapper.find(TopSiteLink).simulate("click", { preventDefault() {} });
let [action] = dispatch.firstCall.args;
assert.isUserEventAction(action);
assert.propertyVal(action.data, "event", "CLICK");
assert.propertyVal(action.data, "source", "TOP_SITES");
assert.propertyVal(action.data, "action_position", 0);
assert.propertyVal(action.data.value, "card_type", "spoc");
assert.propertyVal(action.data.value, "icon_type", "custom_screenshot");
// Pocket SPOC click event.
[action] = dispatch.getCall(2).args;
assert.equal(action.type, at.TELEMETRY_IMPRESSION_STATS);
assert.propertyVal(action.data, "click", 0);
assert.propertyVal(action.data, "source", "TOP_SITES");
[action] = dispatch.getCall(3).args;
assert.equal(action.type, at.DISCOVERY_STREAM_USER_EVENT);
assert.propertyVal(action.data, "event", "CLICK");
assert.propertyVal(action.data, "action_position", 1);
assert.propertyVal(action.data, "source", "TOP_SITES");
assert.propertyVal(action.data.value, "card_type", "spoc");
assert.propertyVal(action.data.value, "tile_id", 1);
assert.propertyVal(action.data.value, "shim", "shim_click_id");
// Topsite SPOC click event.
[action] = dispatch.getCall(4).args;
assert.equal(action.type, at.TOP_SITES_SPONSORED_IMPRESSION_STATS);
assert.propertyVal(action.data, "type", "click");
assert.propertyVal(action.data, "tile_id", 1);
assert.propertyVal(action.data, "source", "newtab");
assert.propertyVal(action.data, "position", 1);
assert.propertyVal(action.data, "advertiser", "test advertiser");
});
it("should dispatch OPEN_LINK with the right data", () => {
const dispatch = sinon.stub();
const wrapper = shallow(
<TopSite
link={Object.assign({}, link, { typedBonus: true })}
index={3}
dispatch={dispatch}
/>
);
wrapper.find(TopSiteLink).simulate("click", { preventDefault() {} });
const [action] = dispatch.secondCall.args;
assert.propertyVal(action, "type", at.OPEN_LINK);
assert.propertyVal(action.data, "typedBonus", true);
});
});
});
describe("<TopSiteForm>", () => {
let wrapper;
let sandbox;
function testSetup(props = {}) {
sandbox = sinon.createSandbox();
const customProps = Object.assign(
{},
{ onClose: sandbox.spy(), dispatch: sandbox.spy() },
props
);
wrapper = mount(<TopSiteForm {...customProps} />);
}
describe("validateForm", () => {
it("should return true for a correct URL", () => {
wrapper.setState({ url: "foo" });
assert.isTrue(wrapper.instance().validateForm());
});
it("should return false for a incorrect URL", () => {
wrapper.setState({ url: " " });
assert.isNull(wrapper.instance().validateForm());
assert.isTrue(wrapper.state().validationError);
});
it("should return true for a correct custom screenshot URL", () => {
wrapper.setState({ customScreenshotUrl: "foo" });
assert.isTrue(wrapper.instance().validateForm());
});
it("should return false for a incorrect custom screenshot URL", () => {
wrapper.setState({ customScreenshotUrl: " " });
assert.isNull(wrapper.instance().validateForm());
});
it("should return true for an empty custom screenshot URL", () => {
wrapper.setState({ customScreenshotUrl: "" });
assert.isTrue(wrapper.instance().validateForm());
});
it("should return false for file: protocol", () => {
assert.isFalse(wrapper.instance().validateForm());
});
});
describe("#previewButton", () => {
beforeEach(() =>
testSetup({
previewResponse: null,
})
);
it("should render the preview button on invalid urls", () => {
assert.equal(0, wrapper.find(".preview").length);
wrapper.setState({ customScreenshotUrl: " " });
assert.equal(1, wrapper.find(".preview").length);
});
it("should render the preview button when input value updated", () => {
assert.equal(0, wrapper.find(".preview").length);
wrapper.setState({
screenshotPreview: null,
});
assert.equal(1, wrapper.find(".preview").length);
});
});
describe("preview request", () => {
beforeEach(() => {
testSetup({
previewResponse: null,
});
});
it("shouldn't dispatch a request for invalid urls", () => {
wrapper.setState({ customScreenshotUrl: " ", url: "foo" });
wrapper.find(".preview").simulate("click");
assert.notCalled(wrapper.props().dispatch);
});
it("should dispatch a PREVIEW_REQUEST", () => {
wrapper.setState({ customScreenshotUrl: "screenshot" });
wrapper.find(".preview").simulate("submit");
assert.calledTwice(wrapper.props().dispatch);
assert.calledWith(
wrapper.props().dispatch,
ac.AlsoToMain({
type: at.PREVIEW_REQUEST,
})
);
assert.calledWith(
wrapper.props().dispatch,
ac.UserEvent({
event: "PREVIEW_REQUEST",
source: "TOP_SITES",
})
);
});
});
describe("#TopSiteLink", () => {
beforeEach(() => {
testSetup();
});
it("should display a TopSiteLink preview", () => {
assert.equal(wrapper.find(TopSiteLink).length, 1);
});
it("should display an icon for tippyTop sites", () => {
wrapper.setProps({ site: { tippyTopIcon: "bar" } });
assert.equal(
wrapper.find(".top-site-icon").getDOMNode().style["background-image"],
'url("bar")'
);
});
it("should not display a preview screenshot", () => {
wrapper.setProps({ previewResponse: "foo", previewUrl: "foo" });
assert.lengthOf(wrapper.find(".screenshot"), 0);
});
it("should not render any icon on error", () => {
wrapper.setProps({ previewResponse: "" });
assert.equal(wrapper.find(".top-site-icon").length, 0);
});
it("should render the search icon when searchTopSite is true", () => {
wrapper.setProps({ site: { tippyTopIcon: "bar", searchTopSite: true } });
assert.equal(
wrapper.find(".rich-icon").getDOMNode().style["background-image"],
'url("bar")'
);
assert.isTrue(wrapper.find(".search-topsite").exists());
});
});
describe("#addMode", () => {
beforeEach(() => testSetup());
it("should render the component", () => {
assert.ok(wrapper.find(TopSiteForm).exists());
});
it("should have the correct header", () => {
assert.equal(
wrapper.findWhere(
n =>
n.length &&
n.prop("data-l10n-id") === "newtab-topsites-add-shortcut-header"
).length,
1
);
});
it("should have the correct button text", () => {
assert.equal(
wrapper.findWhere(
n =>
n.length && n.prop("data-l10n-id") === "newtab-topsites-save-button"
).length,
0
);
assert.equal(
wrapper.findWhere(
n =>
n.length && n.prop("data-l10n-id") === "newtab-topsites-add-button"
).length,
1
);
});
it("should not render a preview button", () => {
assert.equal(0, wrapper.find(".custom-image-input-container").length);
});
it("should call onClose if Cancel button is clicked", () => {
wrapper.find(".cancel").simulate("click");
assert.calledOnce(wrapper.instance().props.onClose);
});
it("should set validationError if url is empty", () => {
assert.equal(wrapper.state().validationError, false);
wrapper.find(".done").simulate("submit");
assert.equal(wrapper.state().validationError, true);
});
it("should set validationError if url is invalid", () => {
wrapper.setState({ url: "not valid" });
assert.equal(wrapper.state().validationError, false);
wrapper.find(".done").simulate("submit");
assert.equal(wrapper.state().validationError, true);
});
it("should call onClose and dispatch with right args if URL is valid", () => {
wrapper.setState({ url: "valid.com", label: "a label" });
wrapper.find(".done").simulate("submit");
assert.calledOnce(wrapper.instance().props.onClose);
assert.calledWith(wrapper.instance().props.dispatch, {
data: {
index: -1,
},
meta: { from: "ActivityStream:Content", to: "ActivityStream:Main" },
type: at.TOP_SITES_PIN,
});
assert.calledWith(wrapper.instance().props.dispatch, {
data: {
action_position: -1,
source: "TOP_SITES",
event: "TOP_SITES_EDIT",
},
meta: { from: "ActivityStream:Content", to: "ActivityStream:Main" },
type: at.TELEMETRY_USER_EVENT,
});
});
it("should not pass empty string label in dispatch data", () => {
wrapper.setState({ url: "valid.com", label: "" });
wrapper.find(".done").simulate("submit");
assert.calledWith(wrapper.instance().props.dispatch, {
meta: { from: "ActivityStream:Content", to: "ActivityStream:Main" },
type: at.TOP_SITES_PIN,
});
});
it("should open the custom screenshot input", () => {
assert.isFalse(wrapper.state().showCustomScreenshotForm);
wrapper.find(A11yLinkButton).simulate("click");
assert.isTrue(wrapper.state().showCustomScreenshotForm);
});
});
describe("edit existing Topsite", () => {
beforeEach(() =>
testSetup({
site: {
label: "baz",
},
index: 7,
})
);
it("should render the component", () => {
assert.ok(wrapper.find(TopSiteForm).exists());
});
it("should have the correct header", () => {
assert.equal(
wrapper.findWhere(
n => n.prop("data-l10n-id") === "newtab-topsites-edit-shortcut-header"
).length,
1
);
});
it("should have the correct button text", () => {
assert.equal(
wrapper.findWhere(
n => n.prop("data-l10n-id") === "newtab-topsites-add-button"
).length,
0
);
assert.equal(
wrapper.findWhere(
n => n.prop("data-l10n-id") === "newtab-topsites-save-button"
).length,
1
);
});
it("should call onClose if Cancel button is clicked", () => {
wrapper.find(".cancel").simulate("click");
assert.calledOnce(wrapper.instance().props.onClose);
});
it("should show error and not call onClose or dispatch if URL is empty", () => {
wrapper.setState({ url: "" });
assert.equal(wrapper.state().validationError, false);
wrapper.find(".done").simulate("submit");
assert.equal(wrapper.state().validationError, true);
assert.notCalled(wrapper.instance().props.onClose);
assert.notCalled(wrapper.instance().props.dispatch);
});
it("should show error and not call onClose or dispatch if URL is invalid", () => {
wrapper.setState({ url: "not valid" });
assert.equal(wrapper.state().validationError, false);
wrapper.find(".done").simulate("submit");
assert.equal(wrapper.state().validationError, true);
assert.notCalled(wrapper.instance().props.onClose);
assert.notCalled(wrapper.instance().props.dispatch);
});
it("should call onClose and dispatch with right args if URL is valid", () => {
wrapper.find(".done").simulate("submit");
assert.calledOnce(wrapper.instance().props.onClose);
assert.calledTwice(wrapper.instance().props.dispatch);
assert.calledWith(wrapper.instance().props.dispatch, {
data: {
site: {
label: "baz",
},
index: 7,
},
meta: { from: "ActivityStream:Content", to: "ActivityStream:Main" },
type: at.TOP_SITES_PIN,
});
assert.calledWith(wrapper.instance().props.dispatch, {
data: {
action_position: 7,
source: "TOP_SITES",
event: "TOP_SITES_EDIT",
},
meta: { from: "ActivityStream:Content", to: "ActivityStream:Main" },
type: at.TELEMETRY_USER_EVENT,
});
});
it("should set customScreenshotURL to null if it was removed", () => {
wrapper.setState({ customScreenshotUrl: "" });
wrapper.find(".done").simulate("submit");
assert.calledWith(wrapper.instance().props.dispatch, {
data: {
site: {
label: "baz",
customScreenshotURL: null,
},
index: 7,
},
meta: { from: "ActivityStream:Content", to: "ActivityStream:Main" },
type: at.TOP_SITES_PIN,
});
});
it("should call onClose and dispatch with right args if URL is valid (negative index)", () => {
wrapper.setProps({ index: -1 });
wrapper.find(".done").simulate("submit");
assert.calledOnce(wrapper.instance().props.onClose);
assert.calledTwice(wrapper.instance().props.dispatch);
assert.calledWith(wrapper.instance().props.dispatch, {
data: {
site: {
label: "baz",
},
index: -1,
},
meta: { from: "ActivityStream:Content", to: "ActivityStream:Main" },
type: at.TOP_SITES_PIN,
});
});
it("should not pass empty string label in dispatch data", () => {
wrapper.setState({ label: "" });
wrapper.find(".done").simulate("submit");
assert.calledWith(wrapper.instance().props.dispatch, {
data: {
index: 7,
},
meta: { from: "ActivityStream:Content", to: "ActivityStream:Main" },
type: at.TOP_SITES_PIN,
});
});
it("should render the save button if custom screenshot request finished", () => {
wrapper.setState({
customScreenshotUrl: "foo",
screenshotPreview: "custom",
});
assert.equal(0, wrapper.find(".preview").length);
assert.equal(1, wrapper.find(".done").length);
});
it("should render the save button if custom screenshot url was cleared", () => {
wrapper.setState({ customScreenshotUrl: "" });
wrapper.setProps({ site: { customScreenshotURL: "foo" } });
assert.equal(0, wrapper.find(".preview").length);
assert.equal(1, wrapper.find(".done").length);
});
});
describe("#previewMode", () => {
beforeEach(() => testSetup({ previewResponse: null }));
it("should transition from save to preview", () => {
wrapper.setProps({
index: 7,
});
assert.equal(
wrapper.findWhere(
n =>
n.length && n.prop("data-l10n-id") === "newtab-topsites-save-button"
).length,
1
);
wrapper.setState({ customScreenshotUrl: "foo" });
assert.equal(
wrapper.findWhere(
n =>
n.length &&
n.prop("data-l10n-id") === "newtab-topsites-preview-button"
).length,
1
);
});
it("should transition from add to preview", () => {
assert.equal(
wrapper.findWhere(
n =>
n.length && n.prop("data-l10n-id") === "newtab-topsites-add-button"
).length,
1
);
wrapper.setState({ customScreenshotUrl: "foo" });
assert.equal(
wrapper.findWhere(
n =>
n.length &&
n.prop("data-l10n-id") === "newtab-topsites-preview-button"
).length,
1
);
});
});
describe("#validateUrl", () => {
it("should properly validate URLs", () => {
testSetup();
assert.ok(wrapper.instance().validateUrl("mozilla.org"));
assert.ok(
wrapper
.instance()
.validateUrl(
)
);
assert.ok(wrapper.instance().validateUrl("httpfoobar"));
assert.ok(wrapper.instance().validateUrl("httpsfoo.bar"));
assert.isNull(wrapper.instance().validateUrl("mozilla org"));
assert.isNull(wrapper.instance().validateUrl(""));
});
});
describe("#cleanUrl", () => {
it("should properly prepend http:// to URLs when required", () => {
testSetup();
assert.equal(
wrapper.instance().cleanUrl("mozilla.org")
);
assert.equal(
wrapper.instance().cleanUrl("https.org")
);
assert.equal(
);
assert.equal(
);
});
});
});
describe("<TopSiteList>", () => {
const APP = { isForStartupCache: false };
it("should render a TopSiteList element", () => {
const wrapper = shallow(<TopSiteList {...DEFAULT_PROPS} App={{ APP }} />);
assert.ok(wrapper.exists());
});
it("should render a TopSite for each link with the right url", () => {
const wrapper = shallow(
<TopSiteList {...DEFAULT_PROPS} TopSites={{ rows }} App={{ APP }} />
);
const links = wrapper.find(TopSite);
assert.lengthOf(links, 2);
rows.forEach((row, i) =>
assert.equal(links.get(i).props.link.url, row.url)
);
});
it("should slice the TopSite rows to the TopSitesRows pref", () => {
const rows = [];
for (
let i = 0;
i < TOP_SITES_DEFAULT_ROWS * TOP_SITES_MAX_SITES_PER_ROW + 3;
i++
) {
}
const wrapper = shallow(
<TopSiteList
{...DEFAULT_PROPS}
TopSites={{ rows }}
TopSitesRows={TOP_SITES_DEFAULT_ROWS}
App={{ APP }}
/>
);
const links = wrapper.find(TopSite);
assert.lengthOf(
links,
TOP_SITES_DEFAULT_ROWS * TOP_SITES_MAX_SITES_PER_ROW
);
});
it("should add a single placeholder is there is availible space in the row", () => {
const availibleRows = 1;
const wrapper = shallow(
<TopSiteList
{...DEFAULT_PROPS}
TopSites={{ rows }}
TopSitesRows={availibleRows}
App={{ APP }}
/>
);
assert.lengthOf(wrapper.find(TopSite), 2, "topSites");
assert.lengthOf(
wrapper.find(TopSitePlaceholder),
availibleRows >= wrapper.find(TopSite).length ? 0 : 1,
"placeholders"
);
});
it("should fill sponsored top sites with placeholders while rendering for startup cache", () => {
const rows = [
];
const wrapper = shallow(
<TopSiteList
{...DEFAULT_PROPS}
TopSites={{ rows }}
TopSitesRows={1}
App={{ isForStartupCache: true }}
/>
);
assert.lengthOf(wrapper.find(TopSite), 2, "topSites");
assert.lengthOf(wrapper.find(TopSitePlaceholder), 4, "placeholders");
});
it("should fill any holes in TopSites with placeholders", () => {
const wrapper = shallow(
<TopSiteList
{...DEFAULT_PROPS}
TopSites={{ rows }}
TopSitesRows={1}
App={{ APP }}
/>
);
assert.lengthOf(wrapper.find(TopSite), 2, "topSites");
assert.lengthOf(wrapper.find(TopSitePlaceholder), 1, "placeholders");
});
it("should update state onDragStart and clear it onDragEnd", () => {
const wrapper = shallow(<TopSiteList {...DEFAULT_PROPS} App={{ APP }} />);
const instance = wrapper.instance();
const index = 7;
const title = "foo";
instance.onDragEvent({ type: "dragstart" }, index, link, title);
assert.equal(instance.state.draggedIndex, index);
assert.equal(instance.state.draggedSite, link);
assert.equal(instance.state.draggedTitle, title);
instance.onDragEvent({ type: "dragend" });
assert.deepEqual(instance.state, TopSiteList.DEFAULT_STATE);
});
it("should clear state when new props arrive after a drop", () => {
const rows = [site1, site2];
const wrapper = shallow(
<TopSiteList {...DEFAULT_PROPS} TopSites={{ rows }} App={{ APP }} />
);
const instance = wrapper.instance();
instance.setState({
draggedIndex: 1,
draggedSite: site2,
draggedTitle: "bar",
topSitesPreview: [],
});
wrapper.setProps({ TopSites: { rows: [site2, site1] } });
assert.deepEqual(instance.state, TopSiteList.DEFAULT_STATE);
});
it("should dispatch events on drop", () => {
const dispatch = sinon.spy();
const wrapper = shallow(
<TopSiteList {...DEFAULT_PROPS} dispatch={dispatch} App={{ APP }} />
);
const instance = wrapper.instance();
const index = 7;
const title = "foo";
instance.onDragEvent({ type: "dragstart" }, index, link, title);
dispatch.resetHistory();
instance.onDragEvent({ type: "drop" }, 3);
assert.calledTwice(dispatch);
assert.calledWith(dispatch, {
data: {
draggedFromIndex: 7,
index: 3,
site: {
label: "foo",
customScreenshotURL: "foo",
},
},
meta: { from: "ActivityStream:Content", to: "ActivityStream:Main" },
type: "TOP_SITES_INSERT",
});
assert.calledWith(dispatch, {
data: { action_position: 3, event: "DROP", source: "TOP_SITES" },
meta: { from: "ActivityStream:Content", to: "ActivityStream:Main" },
type: "TELEMETRY_USER_EVENT",
});
});
it("should make a topSitesPreview onDragEnter", () => {
const wrapper = shallow(<TopSiteList {...DEFAULT_PROPS} App={{ APP }} />);
const instance = wrapper.instance();
instance.setState({
draggedIndex: 4,
draggedSite: site,
draggedTitle: "foo",
});
const draggedSite = Object.assign({}, site, {
isPinned: true,
isDragged: true,
});
instance.onDragEvent({ type: "dragenter" }, 2);
assert.ok(instance.state.topSitesPreview);
assert.deepEqual(instance.state.topSitesPreview[2], draggedSite);
});
it("should _makeTopSitesPreview correctly", () => {
const rows = [site1, site2, site3];
let wrapper = shallow(
<TopSiteList
{...DEFAULT_PROPS}
TopSites={{ rows }}
TopSitesRows={1}
App={{ APP }}
/>
);
const addButton = { isAddButton: true };
let instance = wrapper.instance();
instance.setState({
draggedIndex: 0,
draggedSite: site1,
draggedTitle: "foo",
});
let draggedSite = Object.assign({}, site1, {
isPinned: true,
isDragged: true,
});
assert.deepEqual(instance._makeTopSitesPreview(1), [
site2,
draggedSite,
site3,
addButton,
null,
null,
null,
null,
]);
assert.deepEqual(instance._makeTopSitesPreview(2), [
site2,
site3,
draggedSite,
addButton,
null,
null,
null,
null,
]);
assert.deepEqual(instance._makeTopSitesPreview(3), [
site2,
site3,
addButton,
draggedSite,
null,
null,
null,
null,
]);
site2.isPinned = true;
assert.deepEqual(instance._makeTopSitesPreview(1), [
site2,
draggedSite,
site3,
addButton,
null,
null,
null,
null,
]);
assert.deepEqual(instance._makeTopSitesPreview(2), [
site3,
site2,
draggedSite,
addButton,
null,
null,
null,
null,
]);
site3.isPinned = true;
assert.deepEqual(instance._makeTopSitesPreview(1), [
site2,
draggedSite,
site3,
addButton,
null,
null,
null,
null,
]);
assert.deepEqual(instance._makeTopSitesPreview(2), [
site2,
site3,
draggedSite,
addButton,
null,
null,
null,
null,
]);
site2.isPinned = false;
assert.deepEqual(instance._makeTopSitesPreview(1), [
site2,
draggedSite,
site3,
addButton,
null,
null,
null,
null,
]);
assert.deepEqual(instance._makeTopSitesPreview(2), [
site2,
site3,
draggedSite,
addButton,
null,
null,
null,
null,
]);
site3.isPinned = false;
instance.setState({
draggedIndex: 1,
draggedSite: site2,
draggedTitle: "bar",
});
draggedSite = Object.assign({}, site2, { isPinned: true, isDragged: true });
assert.deepEqual(instance._makeTopSitesPreview(0), [
draggedSite,
site1,
site3,
addButton,
null,
null,
null,
null,
]);
assert.deepEqual(instance._makeTopSitesPreview(2), [
site1,
site3,
draggedSite,
addButton,
null,
null,
null,
null,
]);
site2.type = "SPOC";
instance.setState({
draggedIndex: 2,
draggedSite: site3,
draggedTitle: "baz",
});
draggedSite = Object.assign({}, site3, { isPinned: true, isDragged: true });
assert.deepEqual(instance._makeTopSitesPreview(0), [
draggedSite,
site2,
site1,
addButton,
null,
null,
null,
null,
]);
site2.type = "";
site2.sponsored_position = 2;
instance.setState({
draggedIndex: 2,
draggedSite: site3,
draggedTitle: "baz",
});
draggedSite = Object.assign({}, site3, { isPinned: true, isDragged: true });
assert.deepEqual(instance._makeTopSitesPreview(0), [
draggedSite,
site2,
site1,
addButton,
null,
null,
null,
null,
]);
});
it("should add a className hide-for-narrow to sites after 6/row", () => {
const rows = [];
for (let i = 0; i < TOP_SITES_MAX_SITES_PER_ROW; i++) {
}
const wrapper = mount(
<TopSiteList
{...DEFAULT_PROPS}
TopSites={{ rows }}
TopSitesRows={1}
App={{ APP }}
/>
);
assert.lengthOf(wrapper.find("li.hide-for-narrow"), 2);
});
});
describe("TopSitePlaceholder", () => {
it("should dispatch a TOP_SITES_EDIT action when the addbutton is clicked", () => {
const dispatch = sinon.spy();
const wrapper = shallow(
<TopSitePlaceholder dispatch={dispatch} index={7} isAddButton={true} />
);
wrapper.find(".add-button").first().simulate("click");
assert.calledOnce(dispatch);
assert.calledWithExactly(dispatch, {
type: at.TOP_SITES_EDIT,
data: { index: 7 },
});
});
});
describe("#TopSiteFormInput", () => {
let wrapper;
let onChangeStub;
describe("no errors", () => {
beforeEach(() => {
onChangeStub = sinon.stub();
wrapper = mount(
<TopSiteFormInput
titleId="newtab-topsites-title-label"
placeholderId="newtab-topsites-title-input"
errorMessageId="newtab-topsites-url-validation"
onChange={onChangeStub}
value="foo"
/>
);
});
it("should render the provided title", () => {
const title = wrapper.find("span");
assert.propertyVal(
title.props(),
"data-l10n-id",
"newtab-topsites-title-label"
);
});
it("should render the provided value", () => {
const input = wrapper.find("input");
assert.equal(input.getDOMNode().value, "foo");
});
it("should render the clear button if cb is provided", () => {
assert.equal(wrapper.find(".icon-clear-input").length, 0);
wrapper.setProps({ onClear: sinon.stub() });
assert.equal(wrapper.find(".icon-clear-input").length, 1);
});
it("should show the loading indicator", () => {
assert.equal(wrapper.find(".loading-container").length, 0);
wrapper.setProps({ loading: true });
assert.equal(wrapper.find(".loading-container").length, 1);
});
it("should disable the input when loading indicator is present", () => {
assert.isFalse(wrapper.find("input").getDOMNode().disabled);
wrapper.setProps({ loading: true });
assert.isTrue(wrapper.find("input").getDOMNode().disabled);
});
});
describe("with error", () => {
beforeEach(() => {
onChangeStub = sinon.stub();
wrapper = mount(
<TopSiteFormInput
titleId="newtab-topsites-title-label"
placeholderId="newtab-topsites-title-input"
onChange={onChangeStub}
validationError={true}
errorMessageId="newtab-topsites-url-validation"
value="foo"
/>
);
});
it("should render the error message", () => {
assert.equal(
wrapper.findWhere(
n => n.prop("data-l10n-id") === "newtab-topsites-url-validation"
).length,
1
);
});
it("should reset the error state on value change", () => {
wrapper.find("input").simulate("change", { target: { value: "bar" } });
assert.isFalse(wrapper.state().validationError);
});
});
});