/* 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 . */
// @flow
import React from "react";
import { shallow } from "enzyme";
import Outline from "../../components/PrimaryPanes/Outline";
import { makeSymbolDeclaration } from "../../utils/test-head";
import { mockcx } from "../../utils/test-mockup";
import { showMenu } from "../../context-menu/menu";
import { copyToTheClipboard } from "../../utils/clipboard";
jest.mock("../../context-menu/menu", () => ({ showMenu: jest.fn() }));
jest.mock("../../utils/clipboard", () => ({ copyToTheClipboard: jest.fn() }));
const sourceId = "id";
const mockFunctionText = "mock function text";
function generateDefaults(overrides) {
return {
cx: mockcx,
selectLocation: jest.fn(),
selectedSource: { id: sourceId },
getFunctionText: jest.fn().mockReturnValue(mockFunctionText),
flashLineRange: jest.fn(),
isHidden: false,
symbols: {},
selectedLocation: { sourceId },
onAlphabetizeClick: jest.fn(),
...overrides,
};
}
function render(overrides = {}) {
const props = generateDefaults(overrides);
// $FlowIgnore
const component = shallow();
const instance = component.instance();
return { component, props, instance };
}
describe("Outline", () => {
afterEach(() => {
(copyToTheClipboard: any).mockClear();
showMenu.mockClear();
});
it("renders a list of functions when properties change", async () => {
const symbols = {
functions: [
makeSymbolDeclaration("my_example_function1", 21),
makeSymbolDeclaration("my_example_function2", 22),
],
};
const { component } = render({ symbols });
expect(component).toMatchSnapshot();
});
it("selects a line of code in the current file on click", async () => {
const startLine = 12;
const symbols = {
functions: [makeSymbolDeclaration("my_example_function", startLine)],
};
const { component, props } = render({ symbols });
const { selectLocation } = props;
const listItem = component.find("li").first();
listItem.simulate("click");
expect(selectLocation).toHaveBeenCalledWith(mockcx, {
line: startLine,
sourceId,
});
});
describe("renders outline", () => {
describe("renders loading", () => {
it("if symbols is not defined", () => {
const { component } = render({
symbols: (null: any),
});
expect(component).toMatchSnapshot();
});
it("if symbols are loading", () => {
const { component } = render({
symbols: {
loading: true,
},
});
expect(component).toMatchSnapshot();
});
});
it("renders ignore anonymous functions", async () => {
const symbols = {
functions: [
makeSymbolDeclaration("my_example_function1", 21),
makeSymbolDeclaration("anonymous", 25),
],
};
const { component } = render({ symbols });
expect(component).toMatchSnapshot();
});
describe("renders placeholder", () => {
it("`No File Selected` if selectedSource is not defined", async () => {
const { component } = render({
selectedSource: (null: any),
});
expect(component).toMatchSnapshot();
});
it("`No functions` if all func are anonymous", async () => {
const symbols = {
functions: [
makeSymbolDeclaration("anonymous", 25),
makeSymbolDeclaration("anonymous", 30),
],
};
const { component } = render({ symbols });
expect(component).toMatchSnapshot();
});
it("`No functions` if symbols has no func", async () => {
const symbols = {
functions: [],
};
const { component } = render({ symbols });
expect(component).toMatchSnapshot();
});
});
it("sorts functions alphabetically by function name", async () => {
const symbols = {
functions: [
makeSymbolDeclaration("c_function", 25),
makeSymbolDeclaration("x_function", 30),
makeSymbolDeclaration("a_function", 70),
],
};
const { component } = render({
symbols,
alphabetizeOutline: true,
});
expect(component).toMatchSnapshot();
});
it("calls onAlphabetizeClick when sort button is clicked", async () => {
const symbols = {
functions: [makeSymbolDeclaration("example_function", 25)],
};
const { component, props } = render({ symbols });
await component
.find(".outline-footer")
.find("button")
.simulate("click", {});
expect(props.onAlphabetizeClick).toHaveBeenCalled();
});
it("renders functions by function class", async () => {
const symbols = {
functions: [
makeSymbolDeclaration("x_function", 25, 26, "x_klass"),
makeSymbolDeclaration("a2_function", 30, 31, "a_klass"),
makeSymbolDeclaration("a1_function", 70, 71, "a_klass"),
],
classes: [
makeSymbolDeclaration("x_klass", 24, 27),
makeSymbolDeclaration("a_klass", 29, 72),
],
};
const { component } = render({ symbols });
expect(component).toMatchSnapshot();
});
it("renders functions by function class, alphabetically", async () => {
const symbols = {
functions: [
makeSymbolDeclaration("x_function", 25, 26, "x_klass"),
makeSymbolDeclaration("a2_function", 30, 31, "a_klass"),
makeSymbolDeclaration("a1_function", 70, 71, "a_klass"),
],
classes: [
makeSymbolDeclaration("x_klass", 24, 27),
makeSymbolDeclaration("a_klass", 29, 72),
],
};
const { component } = render({
symbols,
alphabetizeOutline: true,
});
expect(component).toMatchSnapshot();
});
it("selects class on click on class headline", async () => {
const symbols = {
functions: [makeSymbolDeclaration("x_function", 25, 26, "x_klass")],
classes: [makeSymbolDeclaration("x_klass", 24, 27)],
};
const { component, props } = render({ symbols });
await component.find("h2").simulate("click", {});
expect(props.selectLocation).toHaveBeenCalledWith(mockcx, {
line: 24,
sourceId,
});
});
it("does not select an item if selectedSource is not defined", async () => {
const { instance, props } = render({ selectedSource: (null: any) });
await instance.selectItem({});
expect(props.selectLocation).not.toHaveBeenCalled();
});
});
describe("onContextMenu of Outline", () => {
it("is called onContextMenu for each item", async () => {
const event = { event: "oncontextmenu" };
const fn = makeSymbolDeclaration("exmple_function", 2);
const symbols = {
functions: [fn],
};
const { component, instance } = render({ symbols });
instance.onContextMenu = jest.fn(() => {});
await component
.find(".outline-list__element")
.simulate("contextmenu", event);
expect(instance.onContextMenu).toHaveBeenCalledWith(event, fn);
});
it("does not show menu with no selected source", async () => {
const mockEvent = {
preventDefault: jest.fn(),
stopPropagation: jest.fn(),
};
const { instance } = render({
selectedSource: (null: any),
});
await instance.onContextMenu(mockEvent, {});
expect(mockEvent.preventDefault).toHaveBeenCalled();
expect(mockEvent.stopPropagation).toHaveBeenCalled();
expect(showMenu).not.toHaveBeenCalled();
});
it("shows menu to copy func, copies to clipboard on click", async () => {
const startLine = 12;
const endLine = 21;
const func = makeSymbolDeclaration(
"my_example_function",
startLine,
endLine
);
const symbols = {
functions: [func],
};
const mockEvent = {
preventDefault: jest.fn(),
stopPropagation: jest.fn(),
};
const { instance, props } = render({ symbols });
await instance.onContextMenu(mockEvent, func);
expect(mockEvent.preventDefault).toHaveBeenCalled();
expect(mockEvent.stopPropagation).toHaveBeenCalled();
const expectedMenuOptions = [
{
accesskey: "F",
click: expect.any(Function),
disabled: false,
id: "node-menu-copy-function",
label: "Copy function",
},
];
expect(props.getFunctionText).toHaveBeenCalledWith(12);
expect(showMenu).toHaveBeenCalledWith(mockEvent, expectedMenuOptions);
showMenu.mock.calls[0][1][0].click();
expect(copyToTheClipboard).toHaveBeenCalledWith(mockFunctionText);
expect(props.flashLineRange).toHaveBeenCalledWith({
end: endLine,
sourceId,
start: startLine,
});
});
});
});