/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at . */
import React from "react";
import { shallow } from "enzyme";
import SourcesTree from "../../components/PrimaryPanes/SourcesTree";
import * as I from "immutable";
import { showMenu } from "devtools-contextmenu";
import { copyToTheClipboard } from "../../utils/clipboard";
jest.mock("devtools-contextmenu", () => ({ showMenu: jest.fn() }));
jest.mock("../../utils/clipboard", () => ({ copyToTheClipboard: jest.fn() }));
describe("SourcesTree", () => {
afterEach(() => {
copyToTheClipboard.mockClear();
showMenu.mockClear();
});
it("Should show the tree with nothing expanded", async () => {
const { component } = render();
expect(component).toMatchSnapshot();
});
describe("When loading initial source", () => {
it("Shows the tree with one.js, two.js and three.js expanded", async () => {
const { component, props } = render();
await component.setProps({
...props,
expanded: ["one.js", "two.js", "three.js"]
});
expect(component).toMatchSnapshot();
});
});
describe("After changing expanded nodes", () => {
it("Shows the tree with four.js, five.js and six.js expanded", async () => {
const { component, props } = render();
await component.setProps({
...props,
expanded: ["four.js", "five.js", "six.js"]
});
expect(component).toMatchSnapshot();
});
});
describe("on receiving new props", () => {
describe("recreates tree", () => {
it("does not recreate tree if no new source is added", async () => {
const { component, props, defaultState } = render();
const mockSource = I.Map({
"server1.conn13.child1/41": createMockSource(
"server1.conn13.child1/41",
"http://mdn.com/three.js"
)
});
await component.setProps({
...props,
sources: mockSource
});
expect(component.state("uncollapsedTree")).toEqual(
defaultState.uncollapsedTree
);
});
it("updates tree with a new item", async () => {
const { component, props } = render();
const sources = props.sources.merge({
"server1.conn13.child1/42": createMockSource(
"server1.conn13.child1/42",
"http://mdn.com/four.js"
)
});
await component.setProps({
...props,
sources: sources
});
expect(
component.state("uncollapsedTree").contents[0].contents
).toHaveLength(4);
});
it("updates sources if sources are emptied", async () => {
const { component, props, defaultState } = render();
expect(defaultState.uncollapsedTree.contents).toHaveLength(1);
await component.setProps({
...props,
sources: I.Map({})
});
expect(component.state("uncollapsedTree").contents).toHaveLength(0);
});
it("recreates tree if projectRoot is changed", async () => {
const { component, props, defaultState } = render();
const sources = I.Map({
"server1.conn13.child1/41": createMockSource(
"server1.conn13.child1/41",
"http://mozilla.com/three.js"
)
});
expect(defaultState.uncollapsedTree.contents[0].contents).toHaveLength(
3
);
await component.setProps({
...props,
sources: sources,
projectRoot: "mozilla"
});
expect(
component.state("uncollapsedTree").contents[0].contents
).toHaveLength(1);
});
it("recreates tree if debugeeUrl is changed", async () => {
const { component, props, defaultState } = render();
const mockSource = I.Map({
"server1.conn13.child1/41": createMockSource(
"server1.conn13.child1/41",
"http://mdn.com/three.js"
)
});
expect(defaultState.uncollapsedTree.contents[0].contents).toHaveLength(
3
);
await component.setProps({
...props,
debuggeeUrl: "mozilla",
sources: mockSource
});
expect(
component.state("uncollapsedTree").contents[0].contents
).toHaveLength(1);
});
});
describe("updates list items", () => {
it("updates list items if shownSource changes", async () => {
const { component, props } = render();
await component.setProps({
...props,
shownSource: "http://mdn.com/three.js"
});
expect(component).toMatchSnapshot();
expect(props.selectLocation).toHaveBeenCalledWith({
sourceId: "server1.conn13.child1/41"
});
});
});
describe("updates highlighted items", () => {
it("updates highlightItems if selectedSource changes", async () => {
const { component, props } = render();
const mockSource = I.Map({
"server1.conn13.child1/41": createMockSource(
"server1.conn13.child1/41",
"http://mdn.com/three.js"
)
});
await component.setProps({
...props,
selectedSource: mockSource
});
expect(component).toMatchSnapshot();
});
});
});
describe("focusItem", () => {
it("update the focused item", async () => {
const { component, instance, props } = render();
const item = createMockItem();
await instance.focusItem(item);
await component.update();
await component
.find(".sources-list")
.simulate("keydown", { keyCode: 13 });
expect(props.selectLocation).toHaveBeenCalledWith({
sourceId: item.contents.get("id")
});
});
});
describe("with custom root", () => {
it("renders custom root source list", async () => {
const { component } = render({
projectRoot: "mdn.com"
});
expect(component).toMatchSnapshot();
});
it("calls clearProjectDirectoryRoot on click", async () => {
const { component, props } = render({
projectRoot: "mdn"
});
component.find(".sources-clear-root").simulate("click");
expect(props.clearProjectDirectoryRoot).toHaveBeenCalled();
});
it("renders empty custom root source list", async () => {
const { component } = render({
projectRoot: "custom",
sources: I.Map()
});
expect(component).toMatchSnapshot();
});
});
describe("onContextMenu of the tree", () => {
it("shows context menu on directory to set as root", async () => {
const menuOptions = [
{
accesskey: "r",
click: expect.any(Function),
disabled: false,
id: "node-set-directory-root",
label: "Set directory root"
}
];
const mockEvent = {
preventDefault: jest.fn(),
stopPropagation: jest.fn()
};
const { props, instance } = render({
projectRoot: "root/"
});
await instance.onContextMenu(mockEvent, createMockDirectory());
expect(showMenu).toHaveBeenCalledWith(mockEvent, menuOptions);
expect(mockEvent.preventDefault).toHaveBeenCalled();
expect(mockEvent.stopPropagation).toHaveBeenCalled();
showMenu.mock.calls[0][1][0].click();
expect(props.setProjectDirectoryRoot).toHaveBeenCalled();
expect(props.clearProjectDirectoryRoot).not.toHaveBeenCalled();
expect(copyToTheClipboard).not.toHaveBeenCalled();
});
it("shows context menu on file to copy source uri", async () => {
const menuOptions = [
{
accesskey: "u",
click: expect.any(Function),
disabled: false,
id: "node-menu-copy-source",
label: "Copy source URI"
}
];
const mockEvent = {
preventDefault: jest.fn(),
stopPropagation: jest.fn()
};
const { props, instance } = render({
projectRoot: "root/"
});
await instance.onContextMenu(mockEvent, createMockItem());
expect(showMenu).toHaveBeenCalledWith(mockEvent, menuOptions);
expect(mockEvent.preventDefault).toHaveBeenCalled();
expect(mockEvent.stopPropagation).toHaveBeenCalled();
showMenu.mock.calls[0][1][0].click();
expect(props.setProjectDirectoryRoot).not.toHaveBeenCalled();
expect(props.clearProjectDirectoryRoot).not.toHaveBeenCalled();
expect(copyToTheClipboard).toHaveBeenCalled();
});
it("shows context menu on root to remove directory root", async () => {
const menuOptions = [
{
click: expect.any(Function),
disabled: false,
id: "node-remove-directory-root",
label: "Remove directory root"
}
];
const { props, instance } = render({
projectRoot: "root/"
});
const mockEvent = {
preventDefault: jest.fn(),
stopPropagation: jest.fn()
};
await instance.onContextMenu(
mockEvent,
createMockDirectory("root/", "root")
);
expect(showMenu).toHaveBeenCalledWith(mockEvent, menuOptions);
expect(mockEvent.preventDefault).toHaveBeenCalled();
expect(mockEvent.stopPropagation).toHaveBeenCalled();
showMenu.mock.calls[0][1][0].click();
expect(props.setProjectDirectoryRoot).not.toHaveBeenCalled();
expect(props.clearProjectDirectoryRoot).toHaveBeenCalled();
expect(copyToTheClipboard).not.toHaveBeenCalled();
});
});
describe("renderItem", () => {
it("should show icon for webpack item", async () => {
const { instance } = render();
const item = createMockDirectory("webpack://", "webpack://");
const node = renderItem(instance, item);
expect(node).toMatchSnapshot();
});
it("should show icon for angular item", async () => {
const { instance } = render();
const item = createMockDirectory("ng://", "ng://");
const node = renderItem(instance, item);
expect(node).toMatchSnapshot();
});
it("should show icon for moz-extension item", async () => {
const { instance } = render();
const item = createMockDirectory("moz-extension://", "moz-extension://");
const node = renderItem(instance, item);
expect(node).toMatchSnapshot();
});
it("should show icon for folder with arrow", async () => {
const { instance } = render();
const node = renderItem(instance, createMockDirectory());
expect(node).toMatchSnapshot();
});
it("should show icon for folder with expanded arrow", async () => {
const { instance } = render();
const node = renderItem(instance, createMockDirectory(), 1, false, true);
expect(node).toMatchSnapshot();
});
it("should show focused item for folder with expanded arrow", async () => {
const { instance } = render();
const node = renderItem(instance, createMockDirectory(), 1, true, true);
expect(node).toMatchSnapshot();
});
it("should show source item with source icon", async () => {
const { instance } = render();
const node = renderItem(instance, createMockItem());
expect(node).toMatchSnapshot();
});
it("should show source item with source icon with focus", async () => {
const { instance } = render();
const node = renderItem(instance, createMockItem(), 1, true, false);
expect(node).toMatchSnapshot();
});
it("should show domain item", async () => {
const { instance } = render();
const item = createMockItem("root", "root");
const node = renderItem(instance, item, 0);
expect(node).toMatchSnapshot();
});
it("should show domain item as debuggee", async () => {
const { instance } = render();
const item = createMockItem("root", "http://mdn.com");
const node = renderItem(instance, item, 0);
expect(node).toMatchSnapshot();
});
it("should show domain item as debuggee with focus and arrow", async () => {
const { instance } = render();
const item = createMockDirectory("root", "http://mdn.com");
const node = renderItem(instance, item, 0, true);
expect(node).toMatchSnapshot();
});
it("should not show domain item when the projectRoot exists", async () => {
const { instance } = render({
projectRoot: "root/"
});
const node = renderItem(instance, createMockItem(), 0);
expect(node).toMatchSnapshot();
});
it("should show menu on contextmenu of an item", async () => {
const { instance } = render();
const item = createMockItem();
instance.onContextMenu = jest.fn(() => {});
const event = { event: "contextmenu" };
const node = shallow(renderItem(instance, item, 1, true));
node.simulate("contextmenu", event);
expect(instance.onContextMenu).toHaveBeenCalledWith(event, item);
});
it("should focus on and select item on click", async () => {
const { component, instance, props } = render();
const item = createMockItem();
const event = { event: "click" };
const setExpanded = jest.fn();
const node = shallow(
renderItem(instance, item, 1, true, false, setExpanded)
);
node.simulate("click", event);
await component
.find(".sources-list")
.simulate("keydown", { keyCode: 13 });
expect(props.selectLocation).toHaveBeenCalledWith({
sourceId: item.contents.get("id")
});
expect(setExpanded).not.toHaveBeenCalled();
});
it("should focus on and expand directory on click", async () => {
const { component, instance, props } = render();
const event = { event: "click" };
const setExpanded = jest.fn();
const mockDirectory = createMockDirectory();
const node = shallow(
renderItem(instance, mockDirectory, 1, true, false, setExpanded)
);
node.simulate("click", event);
expect(component.state("focusedItem")).toEqual(mockDirectory);
expect(setExpanded).toHaveBeenCalled();
expect(props.selectLocation).not.toHaveBeenCalledWith();
});
});
describe("selectItem", () => {
it("should select item with no children", async () => {
const { instance, props } = render();
instance.selectItem(createMockItem());
expect(props.selectLocation).toHaveBeenCalledWith({
sourceId: "server1.conn13.child1/39"
});
});
it("should not select item with children", async () => {
const { props, instance } = render();
instance.selectItem(createMockDirectory());
expect(props.selectLocation).not.toHaveBeenCalled();
});
it("should select item on enter onKeyDown event", async () => {
const { component, props, instance } = render();
await instance.focusItem(createMockItem());
await component.update();
await component
.find(".sources-list")
.simulate("keydown", { keyCode: 13 });
expect(props.selectLocation).toHaveBeenCalledWith({
sourceId: "server1.conn13.child1/39"
});
});
it("does not select if no item is focused on", async () => {
const { component, props } = render();
await component
.find(".sources-list")
.simulate("keydown", { keyCode: 13 });
expect(props.selectLocation).not.toHaveBeenCalled();
});
});
describe("handles items", () => {
it("getChildren from directory", async () => {
const { component } = render();
const item = createMockDirectory("http://mdn.com/views", "views", [
"a",
"b"
]);
const children = component
.find("ManagedTree")
.props()
.getChildren(item);
expect(children).toEqual(["a", "b"]);
});
it("getChildren from non directory", async () => {
const { component } = render();
const children = component
.find("ManagedTree")
.props()
.getChildren(createMockItem());
expect(children).toEqual([]);
});
it("onExpand", async () => {
const { component, props } = render();
const expandedState = ["x", "y"];
await component
.find("ManagedTree")
.props()
.onExpand({}, expandedState);
expect(props.setExpandedState).toHaveBeenCalledWith(expandedState);
});
it("onCollapse", async () => {
const { component, props } = render();
const expandedState = ["y", "z"];
await component
.find("ManagedTree")
.props()
.onCollapse({}, expandedState);
expect(props.setExpandedState).toHaveBeenCalledWith(expandedState);
});
it("getParent", async () => {
const { component } = render();
const item = component.state("sourceTree").contents[0].contents[0];
const parent = component
.find("ManagedTree")
.props()
.getParent(item);
expect(parent.path).toEqual("mdn.com");
expect(parent.contents).toHaveLength(3);
});
});
describe("getPath", () => {
it("should return path for item", async () => {
const { instance } = render();
const path = instance.getPath(createMockItem());
expect(path).toEqual("http://mdn.com/one.js/one.js/");
});
it("should return path for blackboxedboxed item", async () => {
const item = createMockItem(
"http://mdn.com/blackboxed.js",
"blackboxed.js",
I.Map({ id: "server1.conn13.child1/59" })
);
const source = I.Map({
"server1.conn13.child1/59": createMockSource(
"server1.conn13.child1/59",
"http://mdn.com/blackboxed.js",
true
)
});
const { instance } = render({
sources: source
});
const path = instance.getPath(item);
expect(path).toEqual("http://mdn.com/blackboxed.js/blackboxed.js/update");
});
});
});
function generateDefaults(overrides) {
const defaultSources = I.Map({
"server1.conn13.child1/39": createMockSource(
"server1.conn13.child1/39",
"http://mdn.com/one.js"
),
"server1.conn13.child1/40": createMockSource(
"server1.conn13.child1/40",
"http://mdn.com/two.js"
),
"server1.conn13.child1/41": createMockSource(
"server1.conn13.child1/41",
"http://mdn.com/three.js"
)
});
return {
autoExpandAll: true,
selectLocation: jest.fn(),
setExpandedState: jest.fn(),
sources: defaultSources,
debuggeeUrl: "http://mdn.com",
clearProjectDirectoryRoot: jest.fn(),
setProjectDirectoryRoot: jest.fn(),
projectRoot: "",
...overrides
};
}
function renderItem(
instance,
item = createMockItem(),
depth = 1,
focused = false,
expanded = false,
setExpanded = jest.fn()
) {
return instance.renderItem(item, depth, focused, null, expanded, {
setExpanded: setExpanded
});
}
function render(overrides = {}) {
const props = generateDefaults(overrides);
const component = shallow();
const defaultState = component.state();
const instance = component.instance();
instance.shouldComponentUpdate = () => true;
return { component, props, defaultState, instance };
}
function createMockSource(id, url, isBlackBoxed = false) {
return I.Map({
id: id,
url: url,
isPrettyPrinted: false,
isWasm: false,
sourceMapURL: null,
isBlackBoxed: isBlackBoxed,
loadedState: "unloaded"
});
}
function createMockDirectory(path = "folder/", name = "folder", contents = []) {
return {
name,
path,
contents
};
}
function createMockItem(
path = "http://mdn.com/one.js",
name = "one.js",
contents = I.Map({ id: "server1.conn13.child1/39" })
) {
return {
name,
path,
contents
};
}