diff options
Diffstat (limited to 'devtools/client/debugger/src/components/test/QuickOpenModal.spec.js')
-rw-r--r-- | devtools/client/debugger/src/components/test/QuickOpenModal.spec.js | 898 |
1 files changed, 898 insertions, 0 deletions
diff --git a/devtools/client/debugger/src/components/test/QuickOpenModal.spec.js b/devtools/client/debugger/src/components/test/QuickOpenModal.spec.js new file mode 100644 index 0000000000..3cd21bac05 --- /dev/null +++ b/devtools/client/debugger/src/components/test/QuickOpenModal.spec.js @@ -0,0 +1,898 @@ +/* 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 <http://mozilla.org/MPL/2.0/>. */ + +import React from "react"; +import { Provider } from "react-redux"; +import configureStore from "redux-mock-store"; + +import { shallow, mount } from "enzyme"; +import { QuickOpenModal } from "../QuickOpenModal"; +import { mockcx } from "../../utils/test-mockup"; +import { getDisplayURL } from "../../utils/sources-tree/getURL"; +import { searchKeys } from "../../constants"; + +jest.mock("fuzzaldrin-plus"); + +import { filter } from "fuzzaldrin-plus"; + +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 = { + cx: mockcx, + enabled: false, + query: "", + searchType: "sources", + displayedSources: [], + blackBoxRanges: {}, + tabUrls: [], + selectSpecificLocation: jest.fn(), + setQuickOpenQuery: jest.fn(), + highlightLineRange: jest.fn(), + clearHighlightLineRange: jest.fn(), + closeQuickOpen: jest.fn(), + shortcutsModalEnabled: false, + symbols: { functions: [] }, + symbolsLoading: false, + toggleShortcutsModal: jest.fn(), + isOriginal: false, + thread: "FakeThread", + ...propOverrides, + }; + return { + wrapper: + renderType === "shallow" + ? shallow( + <Provider store={store}> + <QuickOpenModal {...props} /> + </Provider> + ).dive() + : mount( + <Provider store={store}> + <QuickOpenModal {...props} /> + </Provider> + ), + props, + }; +} + +function generateQuickOpenResult(title) { + return { + id: "qor", + value: "", + title, + }; +} + +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", + symbols: { + functions: [], + variables: [], + }, + }, + "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", + displayURL: getDisplayURL("mozilla.com"), + }, + ], + tabUrls: ["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", + displayURL: getDisplayURL("mozilla.com"), + }, + }, + ]); + }); + + describe("shows loading", () => { + it("loads with function type search", () => { + const { wrapper } = generateModal( + { + enabled: true, + query: "", + searchType: "functions", + symbolsLoading: true, + }, + "shallow" + ); + expect(wrapper).toMatchSnapshot(); + }); + }); + + test("Ensure anonymous functions do not render in QuickOpenModal", () => { + const { wrapper } = generateModal( + { + enabled: true, + query: "@", + searchType: "functions", + symbols: { + functions: [ + generateQuickOpenResult("anonymous"), + generateQuickOpenResult("c"), + generateQuickOpenResult("anonymous"), + ], + variables: [], + }, + }, + "mount" + ); + expect(wrapper.find("ResultList")).toHaveLength(1); + expect(wrapper.find("li")).toHaveLength(1); + }); + + test("Basic render with mount & searchType = variables", () => { + const { wrapper } = generateModal( + { + enabled: true, + query: "#", + searchType: "variables", + symbols: { + functions: [], + variables: [], + }, + }, + "mount" + ); + expect(wrapper).toMatchSnapshot(); + }); + + test("Basic render with mount & searchType = shortcuts", () => { + const { wrapper } = generateModal( + { + enabled: true, + query: "?", + searchType: "shortcuts", + symbols: { + functions: [], + variables: [], + }, + }, + "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, + symbols: { + functions: [], + variables: [], + }, + }, + "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", + symbols: { + functions: [], + variables: [], + }, + }, + "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", + symbols: { + functions: [], + variables: [], + }, + // symbol searching relies on a source being selected. + // So we dummy out the source and the API. + selectedSource: { id: "foo", text: "yo" }, + selectedContentLoaded: true, + }, + "mount" + ); + + wrapper + .find("input") + .simulate("change", { target: { value: "@someFunc" } }); + await waitForUpdateResultsThrottle(); + expect(filter).toHaveBeenCalledWith([], "someFunc", { + key: "value", + maxResults: 100, + }); + }); + + it("does not do symbol search if no selected source", () => { + const { wrapper } = generateModal( + { + enabled: true, + searchType: "functions", + symbols: { + functions: [], + variables: [], + }, + // symbol searching relies on a source being selected. + // So we dummy out the source and the API. + selectedSource: 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", + symbols: { + functions: [], + variables: [], + }, + }, + "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", + selectedSource: { id: "foo" }, + }, + "shallow" + ); + const event = { + key: "Enter", + }; + wrapper.find("Connect(SearchInput)").simulate("keydown", event); + expect(props.selectSpecificLocation).toHaveBeenCalledWith(mockcx, { + column: 12, + line: 34, + sourceId: "foo", + source: { + id: "foo", + }, + sourceActorId: undefined, + sourceActor: null, + sourceUrl: "", + }); + }); + + it("on Enter go to location with sourceId", () => { + const sourceId = "source_id"; + const { wrapper, props } = generateModal( + { + enabled: true, + query: ":34:12", + searchType: "goto", + selectedSource: { id: sourceId }, + selectedContentLoaded: true, + }, + "shallow" + ); + const event = { + key: "Enter", + }; + wrapper.find("Connect(SearchInput)").simulate("keydown", event); + expect(props.selectSpecificLocation).toHaveBeenCalledWith(mockcx, { + column: 12, + line: 34, + sourceId, + source: { + id: sourceId, + }, + sourceActorId: undefined, + sourceActor: null, + sourceUrl: "", + }); + }); + + 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", + selectedSource: { id }, + }, + "shallow" + ); + wrapper.setState(() => ({ + results: [{}, { id }], + selectedIndex: 1, + })); + const event = { + key: "Enter", + }; + wrapper.find("Connect(SearchInput)").simulate("keydown", event); + expect(props.selectSpecificLocation).toHaveBeenCalledWith(mockcx, { + column: undefined, + sourceId: id, + line: 0, + source: { id }, + sourceActorId: undefined, + sourceActor: null, + sourceUrl: "", + }); + 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", + symbols: { + functions: [], + variables: {}, + }, + selectedSource: { id }, + }, + "shallow" + ); + wrapper.setState(() => ({ + results: [{}, { id }], + selectedIndex: 1, + })); + const event = { + key: "Enter", + }; + wrapper.find("Connect(SearchInput)").simulate("keydown", event); + expect(props.selectSpecificLocation).toHaveBeenCalledWith(mockcx, { + column: undefined, + line: 0, + sourceId: id, + source: { id }, + sourceActorId: undefined, + sourceActor: null, + sourceUrl: "", + }); + 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", + symbols: { + functions: [], + variables: {}, + }, + selectedSource: { id }, + }, + "shallow" + ); + wrapper.setState(() => ({ + results: [{}, { id }], + selectedIndex: 1, + })); + const event = { + key: "Enter", + }; + wrapper.find("Connect(SearchInput)").simulate("keydown", event); + expect(props.selectSpecificLocation).toHaveBeenCalledWith(mockcx, { + column: 4, + line: 3, + sourceId: id, + source: { id }, + sourceActorId: undefined, + sourceActor: null, + sourceUrl: "", + }); + expect(props.setQuickOpenQuery).not.toHaveBeenCalled(); + }); + + it("on Enter with results, handle shortcuts search", () => { + const { wrapper, props } = generateModal( + { + enabled: true, + query: "@", + searchType: "shortcuts", + symbols: { + functions: [], + variables: {}, + }, + }, + "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", + selectedSource: { id: sourceId }, + selectedContentLoaded: true, + symbols: { + functions: [], + variables: {}, + }, + }, + "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", + selectedSource: { id: sourceId }, + selectedContentLoaded: true, + symbols: { + functions: [], + variables: {}, + }, + }, + "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", + selectedSource: null, + selectedContentLoaded: true, + symbols: { + functions: [], + variables: {}, + }, + }, + "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", + selectedSource: { id: sourceId }, + selectedContentLoaded: true, + symbols: { + functions: [], + variables: {}, + }, + }, + "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(); + }); + }); +}); |