/* eslint max-nested-callbacks: ["error", 4] */ /* 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 "devtools/client/shared/vendor/react"; import { Provider } from "devtools/client/shared/vendor/react-redux"; import configureStore from "redux-mock-store"; import { shallow, mount } from "enzyme"; import { getDisplayURL } from "../../utils/sources-tree/getURL"; import { searchKeys } from "../../constants"; // it's important to mock the module before importing the QuickOpenModal jest.mock("devtools/client/shared/vendor/fuzzaldrin-plus.js", () => { return { filter: jest.fn(() => []), prepareQuery: jest.fn(() => {}), wrap: jest.fn(() => {}), }; }); import { QuickOpenModal } from "../QuickOpenModal"; const { filter } = require("devtools/client/shared/vendor/fuzzaldrin-plus.js"); function generateModal(propOverrides, renderType = "shallow") { const mockStore = configureStore([]); const store = mockStore({ ui: { mutableSearchOptions: { [searchKeys.QUICKOPEN_SEARCH]: { regexMatch: false, wholeWord: false, caseSensitive: false, excludePatterns: "", }, }, }, }); const props = { enabled: false, query: "", searchType: "sources", displayedSources: [], blackBoxRanges: {}, openedTabUrls: [], selectedLocation: { source: { id: "foo" } }, selectSpecificLocation: jest.fn(), setQuickOpenQuery: jest.fn(), highlightLineRange: jest.fn(), clearHighlightLineRange: jest.fn(), closeQuickOpen: jest.fn(), getFunctionSymbols: jest.fn(() => []), shortcutsModalEnabled: false, toggleShortcutsModal: jest.fn(), isOriginal: false, thread: "FakeThread", ...propOverrides, }; return { wrapper: renderType === "shallow" ? shallow( ).dive() : mount( ), props, }; } async function waitForUpdateResultsThrottle() { await new Promise(res => setTimeout(res, QuickOpenModal.UPDATE_RESULTS_THROTTLE) ); } describe("QuickOpenModal", () => { beforeEach(() => { filter.mockClear(); }); test("Doesn't render when disabled", () => { const { wrapper } = generateModal(); expect(wrapper).toMatchSnapshot(); }); test("Renders when enabled", () => { const { wrapper } = generateModal({ enabled: true }); expect(wrapper).toMatchSnapshot(); }); test("Basic render with mount", () => { const { wrapper } = generateModal({ enabled: true }, "mount"); expect(wrapper).toMatchSnapshot(); }); test("Basic render with mount & searchType = functions", () => { const { wrapper } = generateModal( { enabled: true, query: "@", searchType: "functions", }, "mount" ); expect(wrapper).toMatchSnapshot(); }); test("toggles shortcut modal if enabled", () => { const { props } = generateModal( { enabled: true, query: "test", shortcutsModalEnabled: true, toggleShortcutsModal: jest.fn(), }, "shallow" ); expect(props.toggleShortcutsModal).toHaveBeenCalled(); }); test("shows top sources", () => { const { wrapper } = generateModal( { enabled: true, query: "", displayedSources: [ { url: "mozilla.com", shortName: "mozilla.com", displayURL: getDisplayURL("mozilla.com"), }, ], openedTabUrls: ["mozilla.com"], }, "shallow" ); expect(wrapper.state("results")).toEqual([ { id: undefined, icon: "tab result-item-icon", subtitle: "mozilla.com", title: "mozilla.com", url: "mozilla.com", value: "mozilla.com", source: { url: "mozilla.com", shortName: "mozilla.com", displayURL: getDisplayURL("mozilla.com"), }, }, ]); }); describe("shows loading", () => { it("loads with function type search", () => { const { wrapper } = generateModal( { enabled: true, query: "", searchType: "functions", }, "shallow" ); expect(wrapper).toMatchSnapshot(); }); }); test("Basic render with mount & searchType = variables", () => { const { wrapper } = generateModal( { enabled: true, query: "#", searchType: "variables", }, "mount" ); expect(wrapper).toMatchSnapshot(); }); test("Basic render with mount & searchType = shortcuts", () => { const { wrapper } = generateModal( { enabled: true, query: "?", searchType: "shortcuts", }, "mount" ); expect(wrapper.find("ResultList")).toHaveLength(1); expect(wrapper.find("li")).toHaveLength(3); }); test("updateResults on enable", () => { const { wrapper } = generateModal({}, "mount"); expect(wrapper).toMatchSnapshot(); wrapper.setProps({ enabled: true }); expect(wrapper).toMatchSnapshot(); }); test("basic source search", async () => { const { wrapper } = generateModal( { enabled: true, }, "mount" ); wrapper.find("input").simulate("change", { target: { value: "somefil" } }); await waitForUpdateResultsThrottle(); expect(filter).toHaveBeenCalledWith([], "somefil", { key: "value", maxResults: 100, }); }); test("basic gotoSource search", async () => { const { wrapper } = generateModal( { enabled: true, searchType: "gotoSource", }, "mount" ); wrapper .find("input") .simulate("change", { target: { value: "somefil:33" } }); await waitForUpdateResultsThrottle(); expect(filter).toHaveBeenCalledWith([], "somefil", { key: "value", maxResults: 100, }); }); describe("empty symbol search", () => { it("basic symbol search", async () => { const { wrapper } = generateModal( { enabled: true, searchType: "functions", // symbol searching relies on a source being selected. // So we dummy out the source and the API. selectedLocation: { source: { id: "foo", text: "yo" } }, selectedContentLoaded: true, }, "mount" ); wrapper .find("input") .simulate("change", { target: { value: "@someFunc" } }); await waitForUpdateResultsThrottle(); expect(filter).toHaveBeenCalledWith([], "someFunc", { key: "name", maxResults: 100, preparedQuery: undefined, }); }); it("does not do symbol search if no selected source", () => { const { wrapper } = generateModal( { enabled: true, searchType: "functions", // symbol searching relies on a source being selected. // So we dummy out the source and the API. selectedLocation: null, selectedContentLoaded: false, }, "mount" ); wrapper .find("input") .simulate("change", { target: { value: "@someFunc" } }); expect(filter).not.toHaveBeenCalled(); }); }); test("Simple goto search query = :abc & searchType = goto", () => { const { wrapper } = generateModal( { enabled: true, query: ":abc", searchType: "goto", }, "mount" ); expect(wrapper.childAt(0)).toMatchSnapshot(); expect(wrapper.childAt(0).state().results).toEqual(null); }); describe("onEnter", () => { it("on Enter go to location", () => { const { wrapper, props } = generateModal( { enabled: true, query: ":34:12", searchType: "goto", selectedLocation: { source: { id: "foo" } }, }, "shallow" ); const event = { key: "Enter", }; wrapper.find("Connect(SearchInput)").simulate("keydown", event); expect(props.selectSpecificLocation).toHaveBeenCalledWith({ column: 11, line: 34, source: { id: "foo", }, sourceActorId: undefined, sourceActor: null, }); }); it("on Enter go to location with sourceId", () => { const sourceId = "source_id"; const { wrapper, props } = generateModal( { enabled: true, query: ":34:12", searchType: "goto", selectedLocation: { source: { id: sourceId } }, selectedContentLoaded: true, }, "shallow" ); const event = { key: "Enter", }; wrapper.find("Connect(SearchInput)").simulate("keydown", event); expect(props.selectSpecificLocation).toHaveBeenCalledWith({ column: 11, line: 34, source: { id: sourceId, }, sourceActorId: undefined, sourceActor: null, }); }); it("on Enter with no location, does no action", () => { const { wrapper, props } = generateModal( { enabled: true, query: ":", searchType: "goto", }, "shallow" ); const event = { key: "Enter", }; wrapper.find("Connect(SearchInput)").simulate("keydown", event); expect(props.setQuickOpenQuery).not.toHaveBeenCalled(); expect(props.selectSpecificLocation).not.toHaveBeenCalled(); expect(props.highlightLineRange).not.toHaveBeenCalled(); }); it("on Enter with empty results, handle no item", () => { const { wrapper, props } = generateModal( { enabled: true, query: "", searchType: "shortcuts", }, "shallow" ); wrapper.setState(() => ({ results: [], selectedIndex: 0, })); const event = { key: "Enter", }; wrapper.find("Connect(SearchInput)").simulate("keydown", event); expect(props.setQuickOpenQuery).not.toHaveBeenCalled(); expect(props.selectSpecificLocation).not.toHaveBeenCalled(); expect(props.highlightLineRange).not.toHaveBeenCalled(); }); it("on Enter with results, handle symbol shortcut", () => { const symbols = [":", "#", "@"]; for (const symbol of symbols) { const { wrapper, props } = generateModal( { enabled: true, query: "", searchType: "shortcuts", }, "shallow" ); wrapper.setState(() => ({ results: [{ id: symbol }], selectedIndex: 0, })); const event = { key: "Enter", }; wrapper.find("Connect(SearchInput)").simulate("keydown", event); expect(props.setQuickOpenQuery).toHaveBeenCalledWith(symbol); } }); it("on Enter, returns the result with the selected index", () => { const { wrapper, props } = generateModal( { enabled: true, query: "@test", searchType: "shortcuts", }, "shallow" ); wrapper.setState(() => ({ results: [{ id: "@" }, { id: ":" }, { id: "#" }], selectedIndex: 1, })); const event = { key: "Enter", }; wrapper.find("Connect(SearchInput)").simulate("keydown", event); expect(props.setQuickOpenQuery).toHaveBeenCalledWith(":"); }); it("on Enter with results, handle result item", () => { const id = "test_id"; const { wrapper, props } = generateModal( { enabled: true, query: "@test", searchType: "other", selectedLocation: { source: { id } }, }, "shallow" ); wrapper.setState(() => ({ results: [{}, { id }], selectedIndex: 1, })); const event = { key: "Enter", }; wrapper.find("Connect(SearchInput)").simulate("keydown", event); expect(props.selectSpecificLocation).toHaveBeenCalledWith({ column: undefined, line: 0, source: { id }, sourceActorId: undefined, sourceActor: null, }); expect(props.setQuickOpenQuery).not.toHaveBeenCalled(); }); it("on Enter with results, handle functions result item", () => { const id = "test_id"; const { wrapper, props } = generateModal( { enabled: true, query: "@test", searchType: "functions", selectedLocation: { source: { id } }, }, "shallow" ); wrapper.setState(() => ({ results: [{}, { id }], selectedIndex: 1, })); const event = { key: "Enter", }; wrapper.find("Connect(SearchInput)").simulate("keydown", event); expect(props.selectSpecificLocation).toHaveBeenCalledWith({ column: undefined, line: 0, source: { id }, sourceActorId: undefined, sourceActor: null, }); expect(props.setQuickOpenQuery).not.toHaveBeenCalled(); }); it("on Enter with results, handle gotoSource search", () => { const id = "test_id"; const { wrapper, props } = generateModal( { enabled: true, query: ":3:4", searchType: "gotoSource", selectedLocation: { source: { id } }, }, "shallow" ); wrapper.setState(() => ({ results: [{}, { id }], selectedIndex: 1, })); const event = { key: "Enter", }; wrapper.find("Connect(SearchInput)").simulate("keydown", event); expect(props.selectSpecificLocation).toHaveBeenCalledWith({ column: 3, line: 3, source: { id }, sourceActorId: undefined, sourceActor: null, }); expect(props.setQuickOpenQuery).not.toHaveBeenCalled(); }); it("on Enter with results, handle shortcuts search", () => { const { wrapper, props } = generateModal( { enabled: true, query: "@", searchType: "shortcuts", }, "shallow" ); const id = "#"; wrapper.setState(() => ({ results: [{}, { id }], selectedIndex: 1, })); const event = { key: "Enter", }; wrapper.find("Connect(SearchInput)").simulate("keydown", event); expect(props.selectSpecificLocation).not.toHaveBeenCalled(); expect(props.setQuickOpenQuery).toHaveBeenCalledWith(id); }); }); describe("onKeyDown", () => { it("does nothing if search type is not goto", () => { const { wrapper, props } = generateModal( { enabled: true, query: "test", searchType: "other", }, "shallow" ); wrapper.find("Connect(SearchInput)").simulate("keydown", {}); expect(props.selectSpecificLocation).not.toHaveBeenCalled(); expect(props.setQuickOpenQuery).not.toHaveBeenCalled(); }); it("on Tab, close modal", () => { const { wrapper, props } = generateModal( { enabled: true, query: ":34:12", searchType: "goto", }, "shallow" ); const event = { key: "Tab", }; wrapper.find("Connect(SearchInput)").simulate("keydown", event); expect(props.closeQuickOpen).toHaveBeenCalled(); expect(props.selectSpecificLocation).not.toHaveBeenCalled(); }); }); describe("with arrow keys", () => { it("on ArrowUp, traverse results up with functions", () => { const sourceId = "sourceId"; const { wrapper, props } = generateModal( { enabled: true, query: "test", searchType: "functions", selectedLocation: { source: { id: sourceId } }, selectedContentLoaded: true, }, "shallow" ); const event = { preventDefault: jest.fn(), key: "ArrowUp", }; const location = { sourceId: "sourceId", start: { line: 1, }, end: { line: 3, }, }; wrapper.setState(() => ({ results: [{ id: "0", location }, { id: "1" }, { id: "2" }], selectedIndex: 1, })); wrapper.find("Connect(SearchInput)").simulate("keydown", event); expect(event.preventDefault).toHaveBeenCalled(); expect(wrapper.state().selectedIndex).toEqual(0); expect(props.highlightLineRange).toHaveBeenCalledWith({ sourceId: "sourceId", end: 3, start: 1, }); }); it("on ArrowDown, traverse down with no results", () => { const { wrapper, props } = generateModal( { enabled: true, query: "test", searchType: "goto", }, "shallow" ); const event = { preventDefault: jest.fn(), key: "ArrowDown", }; wrapper.setState(() => ({ results: null, selectedIndex: 1, })); wrapper.find("Connect(SearchInput)").simulate("keydown", event); expect(event.preventDefault).toHaveBeenCalled(); expect(wrapper.state().selectedIndex).toEqual(0); expect(props.selectSpecificLocation).not.toHaveBeenCalledWith(); expect(props.highlightLineRange).not.toHaveBeenCalled(); }); it("on ArrowUp, traverse results up to function with no location", () => { const sourceId = "sourceId"; const { wrapper, props } = generateModal( { enabled: true, query: "test", searchType: "functions", selectedLocation: { source: { id: sourceId } }, selectedContentLoaded: true, }, "shallow" ); const event = { preventDefault: jest.fn(), key: "ArrowUp", }; wrapper.setState(() => ({ results: [{ id: "0", location: null }, { id: "1" }, { id: "2" }], selectedIndex: 1, })); wrapper.find("Connect(SearchInput)").simulate("keydown", event); expect(event.preventDefault).toHaveBeenCalled(); expect(wrapper.state().selectedIndex).toEqual(0); expect(props.highlightLineRange).not.toHaveBeenCalled(); expect(props.clearHighlightLineRange).toHaveBeenCalled(); }); it( "on ArrowDown, traverse down results, without " + "taking action if no selectedSource", () => { const { wrapper, props } = generateModal( { enabled: true, query: "test", searchType: "variables", selectedLocation: null, selectedContentLoaded: true, }, "shallow" ); const event = { preventDefault: jest.fn(), key: "ArrowDown", }; const location = { sourceId: "sourceId", start: { line: 7, }, }; wrapper.setState(() => ({ results: [{ id: "0", location }, { id: "1" }, { id: "2" }], selectedIndex: 1, })); wrapper.find("Connect(SearchInput)").simulate("keydown", event); expect(event.preventDefault).toHaveBeenCalled(); expect(wrapper.state().selectedIndex).toEqual(2); expect(props.selectSpecificLocation).not.toHaveBeenCalled(); expect(props.highlightLineRange).not.toHaveBeenCalled(); } ); it( "on ArrowUp, traverse up results, without taking action if " + "the query is not for variables or functions", () => { const sourceId = "sourceId"; const { wrapper, props } = generateModal( { enabled: true, query: "test", searchType: "other", selectedLocation: { source: { id: sourceId } }, selectedContentLoaded: true, }, "shallow" ); const event = { preventDefault: jest.fn(), key: "ArrowUp", }; const location = { sourceId: "sourceId", start: { line: 7, }, }; wrapper.setState(() => ({ results: [{ id: "0", location }, { id: "1" }, { id: "2" }], selectedIndex: 1, })); wrapper.find("Connect(SearchInput)").simulate("keydown", event); expect(event.preventDefault).toHaveBeenCalled(); expect(wrapper.state().selectedIndex).toEqual(0); expect(props.selectSpecificLocation).not.toHaveBeenCalled(); expect(props.highlightLineRange).not.toHaveBeenCalled(); } ); }); describe("showErrorEmoji", () => { it("true when no count + query", () => { const { wrapper } = generateModal( { enabled: true, query: "test", searchType: "other", }, "mount" ); expect(wrapper).toMatchSnapshot(); }); it("false when count + query", () => { const { wrapper } = generateModal( { enabled: true, query: "dasdasdas", }, "mount" ); wrapper.setState(() => ({ results: [1, 2], })); expect(wrapper).toMatchSnapshot(); }); it("false when no query", () => { const { wrapper } = generateModal( { enabled: true, query: "", searchType: "other", }, "mount" ); expect(wrapper).toMatchSnapshot(); }); it("false when goto numeric ':2222'", () => { const { wrapper } = generateModal( { enabled: true, query: ":2222", searchType: "goto", }, "mount" ); expect(wrapper).toMatchSnapshot(); }); it("true when goto not numeric ':22k22'", () => { const { wrapper } = generateModal( { enabled: true, query: ":22k22", searchType: "goto", }, "mount" ); expect(wrapper).toMatchSnapshot(); }); }); });