diff --git a/src/actions/tests/ui.js b/src/actions/tests/ui.js index 97cd51c8f7..47ce303adb 100644 --- a/src/actions/tests/ui.js +++ b/src/actions/tests/ui.js @@ -9,7 +9,9 @@ const { getPaneCollapse, getSymbolSearchState, getSymbolSearchType, - getHighlightedLineRange + getHighlightedLineRange, + getSearchResults, + getSymbolSearchResults } = selectors; describe("ui", () => { @@ -42,6 +44,24 @@ describe("ui", () => { expect(getFrameworkGroupingState(getState())).toBe(!currentState); }); + it("should update search results", () => { + const { dispatch, getState } = createStore(); + expect(getSearchResults(getState())).toEqual({ index: -1, count: 0 }); + + const results = { count: 3, index: 2 }; + dispatch(actions.updateSearchResults(results)); + expect(getSearchResults(getState())).toEqual(results); + }); + + fit("should update symbol search results", () => { + const { dispatch, getState } = createStore(); + expect(getSymbolSearchResults(getState())).toEqual([]); + + const results = [{ foo: "foo" }]; + dispatch(actions.updateSymbolSearchResults(results)); + expect(getSymbolSearchResults(getState())).toEqual(results); + }); + it("should close file search", () => { const { dispatch, getState } = createStore(); expect(getFileSearchState(getState())).toBe(false); diff --git a/src/actions/ui.js b/src/actions/ui.js index 168ada5e73..9f5e5ebf20 100644 --- a/src/actions/ui.js +++ b/src/actions/ui.js @@ -79,6 +79,20 @@ export function setFileSearchQuery(query: string) { }; } +export function updateSearchResults(results: Object) { + return { + type: "UPDATE_SEARCH_RESULTS", + results + }; +} + +export function updateSymbolSearchResults(results: Array<*>) { + return { + type: "UPDATE_SYMBOL_SEARCH_RESULTS", + results + }; +} + export function toggleFileSearchModifier(modifier: string) { return { type: "TOGGLE_FILE_SEARCH_MODIFIER", modifier }; } diff --git a/src/components/Editor/SearchBar.js b/src/components/Editor/SearchBar.js index f544ab65d3..5dde329046 100644 --- a/src/components/Editor/SearchBar.js +++ b/src/components/Editor/SearchBar.js @@ -11,6 +11,8 @@ import { getFileSearchState, getFileSearchQueryState, getFileSearchModifierState, + getSearchResults, + getSymbolSearchResults, getSymbolSearchState, getSymbolSearchType, getSelectedSource, @@ -34,7 +36,7 @@ import { SourceEditor } from "devtools-source-editor"; import type { SourceRecord } from "../../reducers/sources"; import type { FileSearchModifiers, SymbolSearchType } from "../../reducers/ui"; import type { SelectSourceOptions } from "../../actions/sources"; -import type { SearchResults } from "."; +import type { SearchResults } from "../../reducers/ui"; import type { SymbolDeclarations } from "../../utils/parser/getSymbols"; import type { Location as BabelLocation } from "babel-traverse"; import _SearchInput from "../shared/SearchInput"; @@ -84,7 +86,6 @@ type ToggleSymbolSearchOpts = { }; type SearchBarState = { - symbolSearchResults: Array, selectedResultIndex: number, count: number, index: number @@ -98,6 +99,7 @@ class SearchBar extends Component { props: { editor?: SourceEditor, symbols: SymbolDeclarations, + symbolSearchResults: Array<*>, selectSource: (string, ?SelectSourceOptions) => any, selectedSource?: SourceRecord, highlightLineRange: ({ start: number, end: number }) => any, @@ -113,13 +115,13 @@ class SearchBar extends Component { setSelectedSymbolType: SymbolSearchType => any, query: string, setFileSearchQuery: string => any, - updateSearchResults: ({ count: number, index?: number }) => any + updateSearchResults: ({ count: number, index?: number }) => any, + updateSymbolSearchResults: ({ count: number, index?: number }) => any }; constructor(props) { super(props); this.state = { - symbolSearchResults: [], selectedResultIndex: 0, count: 0, index: -1 @@ -360,6 +362,7 @@ class SearchBar extends Component { const { selectedSource, updateSearchResults, + updateSymbolSearchResults, selectedSymbolType, symbols } = this.props; @@ -373,7 +376,7 @@ class SearchBar extends Component { }); updateSearchResults({ count: symbolSearchResults.length }); - return this.setState({ symbolSearchResults }); + updateSymbolSearchResults(symbolSearchResults); } doSearch(query: string) { @@ -423,8 +426,8 @@ class SearchBar extends Component { } traverseSymbolResults(rev: boolean) { - const { symbolSearchResults, selectedResultIndex } = this.state; - const searchResults = symbolSearchResults; + const { selectedResultIndex } = this.state; + const searchResults = this.props.symbolSearchResults; const resultCount = searchResults.length; if (rev) { @@ -543,8 +546,7 @@ class SearchBar extends Component { } onKeyDown(e: SyntheticKeyboardEvent) { - const { symbolSearchOn } = this.props; - const { symbolSearchResults } = this.state; + const { symbolSearchOn, symbolSearchResults } = this.props; if (!symbolSearchOn || this.props.query == "") { return; } @@ -571,14 +573,15 @@ class SearchBar extends Component { // Renderers buildSummaryMsg() { - if (this.props.symbolSearchOn) { - if (this.state.symbolSearchResults.length > 1) { + const { symbolSearchOn, symbolSearchResults } = this.props; + if (symbolSearchOn) { + if (symbolSearchResults.length > 1) { return L10N.getFormatStr( "editor.searchResults", this.state.selectedResultIndex + 1, - this.state.symbolSearchResults.length + symbolSearchResults.length ); - } else if (this.state.symbolSearchResults.length === 1) { + } else if (symbolSearchResults.length === 1) { return L10N.getFormatStr("editor.singleResult"); } } @@ -698,8 +701,8 @@ class SearchBar extends Component { } renderResults() { - const { symbolSearchResults, selectedResultIndex } = this.state; - const { query, symbolSearchOn } = this.props; + const { selectedResultIndex } = this.state; + const { query, symbolSearchResults, symbolSearchOn } = this.props; if (query == "" || !symbolSearchOn || !symbolSearchResults.length) { return; } @@ -764,6 +767,8 @@ export default connect( query: getFileSearchQueryState(state), modifiers: getFileSearchModifierState(state), symbolSearchOn: getSymbolSearchState(state), + symbolSearchResults: getSymbolSearchResults(state), + searchResults: getSearchResults(state), symbols: _getFormattedSymbols(state), selectedSymbolType: getSymbolSearchType(state) }; diff --git a/src/components/Editor/index.js b/src/components/Editor/index.js index 5633725fc7..108b83c76a 100644 --- a/src/components/Editor/index.js +++ b/src/components/Editor/index.js @@ -86,13 +86,7 @@ const cssVars = { footerHeight: "var(--editor-footer-height)" }; -export type SearchResults = { - index: number, - count: number -}; - type EditorState = { - searchResults: SearchResults, highlightedLineRange: ?Object, selectedToken: ?HTMLElement }; @@ -113,10 +107,6 @@ class Editor extends PureComponent { this.lastJumpLine = null; this.state = { - searchResults: { - index: -1, - count: 0 - }, highlightedLineRange: null, selectedToken: null }; @@ -139,7 +129,6 @@ class Editor extends PureComponent { this ); self.toggleConditionalPanel = this.toggleConditionalPanel.bind(this); - self.updateSearchResults = this.updateSearchResults.bind(this); } componentWillReceiveProps(nextProps) { @@ -433,10 +422,6 @@ class Editor extends PureComponent { }); } - updateSearchResults({ count, index = -1 }: { count: number, index: number }) { - this.setState({ searchResults: { count, index } }); - } - onGutterClick(cm, line, gutter, ev) { const { selectedSource } = this.props; @@ -834,8 +819,6 @@ class Editor extends PureComponent { horizontal } = this.props; - const { searchResults } = this.state; - return dom.div( { className: classnames("editor-wrapper", { "coverage-on": coverageOn }) @@ -846,9 +829,7 @@ class Editor extends PureComponent { selectedSource, highlightLineRange, clearHighlightLineRange, - sourceText, - searchResults, - updateSearchResults: this.updateSearchResults + sourceText }), dom.div({ className: "editor-mount devtools-monospace", diff --git a/src/components/Editor/tests/Editor.js b/src/components/Editor/tests/Editor.js new file mode 100644 index 0000000000..7517122854 --- /dev/null +++ b/src/components/Editor/tests/Editor.js @@ -0,0 +1,39 @@ +import React from "react"; +import { shallow } from "enzyme"; +import Editor from "../index"; +import * as I from "immutable"; + +const EditorComponent = React.createFactory(Editor.WrappedComponent); + +function generateDefaults(overrides) { + return { + breakpoints: I.Map(), + addBreakpoint: jest.fn(), + disableBreakpoint: jest.fn(), + enableBreakpoint: jest.fn(), + removeBreakpoint: jest.fn(), + setBreakpointCondition: jest.fn(), + getExpression: jest.fn(), + addExpression: jest.fn(), + query: "", + searchModifiers: I.Record({ + caseSensitive: false, + regexMatch: false, + wholeWord: false + })(), + clearSelection: jest.fn + }; +} + +function render(overrides = {}) { + const props = generateDefaults(overrides); + const component = shallow(new EditorComponent(props)); + return { component, props }; +} + +describe("Editor", () => { + it("should render", async () => { + const { component } = render(); + expect(component).toMatchSnapshot(); + }); +}); diff --git a/src/components/Editor/tests/SearchBar.js b/src/components/Editor/tests/SearchBar.js new file mode 100644 index 0000000000..8989013ae2 --- /dev/null +++ b/src/components/Editor/tests/SearchBar.js @@ -0,0 +1,39 @@ +import { createFactory } from "react"; +import { shallow } from "enzyme"; +import SearchBar from "../SearchBar"; + +const SearchBarComponent = createFactory(SearchBar.WrappedComponent); + +function generateDefaults() { + return { + query: "", + searchOn: true, + symbolSearchOn: true, + searchResults: {}, + selectedSymbolType: "functions", + symbolSearchResults: [], + selectedResultIndex: 0 + }; +} + +function render(overrides = {}) { + const defaults = generateDefaults(); + const props = { ...defaults, ...overrides }; + const component = shallow(new SearchBarComponent(props)); + return { component, props }; +} + +describe("SearchBar", () => { + it("should render", () => { + const { component } = render(); + expect(component).toMatchSnapshot(); + }); + + it("should have a result list with symbolSearchResults", () => { + const symbolSearchResults = [1, 2, 3]; + const query = "query"; + const { component } = render({ symbolSearchResults, query }); + const resultList = component.find("ResultList"); + expect(resultList.prop("items")).toBe(symbolSearchResults); + }); +}); diff --git a/src/components/Editor/tests/__snapshots__/Editor.js.snap b/src/components/Editor/tests/__snapshots__/Editor.js.snap new file mode 100644 index 0000000000..343ca60ad7 --- /dev/null +++ b/src/components/Editor/tests/__snapshots__/Editor.js.snap @@ -0,0 +1,240 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Editor should render 1`] = ` +ShallowWrapper { + "complexSelector": ComplexSelector { + "buildPredicate": [Function], + "childrenOfNode": [Function], + "findWhereUnwrapped": [Function], + }, + "length": 1, + "node":
+ +
+ Array [] + +
, + "nodes": Array [ +
+ +
+ Array [] + +
, + ], + "options": Object {}, + "renderer": ReactShallowRenderer { + "_instance": ShallowComponentWrapper { + "_calledComponentWillUnmount": false, + "_compositeType": 1, + "_context": Object {}, + "_currentElement": , + "_debugID": 1, + "_hostContainerInfo": null, + "_hostParent": null, + "_instance": Editor { + "_reactInternalInstance": [Circular], + "cbPanel": null, + "closeConditionalPanel": [Function], + "context": Object { + "shortcuts": undefined, + }, + "editor": null, + "lastJumpLine": null, + "onEscape": [Function], + "onGutterClick": [Function], + "onGutterContextMenu": [Function], + "onScroll": [Function], + "onSearchAgain": [Function], + "onToggleBreakpoint": [Function], + "pendingJumpLine": null, + "previewSelectedToken": [Function], + "props": Object { + "addBreakpoint": [Function], + "addExpression": [Function], + "breakpoints": Immutable.Map { +}, + "clearSelection": [Function], + "disableBreakpoint": [Function], + "enableBreakpoint": [Function], + "getExpression": [Function], + "query": "", + "removeBreakpoint": [Function], + "searchModifiers": Object { + "caseSensitive": false, + "regexMatch": false, + "wholeWord": false, + }, + "setBreakpointCondition": [Function], + }, + "refs": Object {}, + "state": Object { + "highlightedLineRange": null, + "selectedToken": null, + }, + "toggleBreakpoint": [Function], + "toggleBreakpointDisabledStatus": [Function], + "toggleConditionalPanel": [Function], + "updater": Object { + "enqueueCallback": [Function], + "enqueueCallbackInternal": [Function], + "enqueueElementInternal": [Function], + "enqueueForceUpdate": [Function], + "enqueueReplaceState": [Function], + "enqueueSetState": [Function], + "isMounted": [Function], + "validateCallback": [Function], + }, + }, + "_mountOrder": 5, + "_pendingCallbacks": null, + "_pendingElement": null, + "_pendingForceUpdate": false, + "_pendingReplaceState": false, + "_pendingStateQueue": null, + "_renderedComponent": Object { + "_currentElement":
+ +
+ Array [] + +
, + "_debugID": 2, + "_renderedOutput":
+ +
+ Array [] + +
, + }, + "_renderedNodeType": 0, + "_rootNodeID": 0, + "_topLevelWrapper": null, + "_updateBatchNumber": null, + "_warnedAboutRefsInRender": false, + }, + "getRenderOutput": [Function], + "render": [Function], + }, + "root": [Circular], + "unrendered": , +} +`; diff --git a/src/components/Editor/tests/__snapshots__/SearchBar.js.snap b/src/components/Editor/tests/__snapshots__/SearchBar.js.snap new file mode 100644 index 0000000000..d3e9bd1e54 --- /dev/null +++ b/src/components/Editor/tests/__snapshots__/SearchBar.js.snap @@ -0,0 +1,284 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`SearchBar should render 1`] = ` +ShallowWrapper { + "complexSelector": ComplexSelector { + "buildPredicate": [Function], + "childrenOfNode": [Function], + "findWhereUnwrapped": [Function], + }, + "length": 1, + "node":
+ +
+
+

+ Search for: +

+ + +
+
+
, + "nodes": Array [ +
+ +
+
+

+ Search for: +

+ + +
+
+
, + ], + "options": Object {}, + "renderer": ReactShallowRenderer { + "_instance": ShallowComponentWrapper { + "_calledComponentWillUnmount": false, + "_compositeType": 0, + "_context": Object {}, + "_currentElement": , + "_debugID": 1, + "_hostContainerInfo": null, + "_hostParent": null, + "_instance": SearchBar { + "_reactInternalInstance": [Circular], + "buildPlaceHolder": [Function], + "buildSummaryMsg": [Function], + "clearSearch": [Function], + "closeSearch": [Function], + "context": Object { + "shortcuts": undefined, + }, + "doSearch": [Function], + "onChange": [Function], + "onEscape": [Function], + "onKeyDown": [Function], + "onKeyUp": [Function], + "onSelectResultItem": [Function], + "props": Object { + "query": "", + "searchOn": true, + "searchResults": Object {}, + "selectedResultIndex": 0, + "selectedSymbolType": "functions", + "symbolSearchOn": true, + "symbolSearchResults": Array [], + }, + "refs": Object {}, + "renderBottomBar": [Function], + "renderResults": [Function], + "renderSearchModifiers": [Function], + "renderSearchTypeToggle": [Function], + "searchContents": [Function], + "searchInput": [Function], + "selectResultItem": [Function], + "selectSearchInput": [Function], + "setSearchValue": [Function], + "state": Object { + "count": 0, + "index": -1, + "selectedResultIndex": 0, + }, + "toggleSearch": [Function], + "toggleSymbolSearch": [Function], + "traverseCodeResults": [Function], + "traverseResults": [Function], + "traverseSymbolResults": [Function], + "updateSymbolSearchResults": [Function], + "updater": Object { + "enqueueCallback": [Function], + "enqueueCallbackInternal": [Function], + "enqueueElementInternal": [Function], + "enqueueForceUpdate": [Function], + "enqueueReplaceState": [Function], + "enqueueSetState": [Function], + "isMounted": [Function], + "validateCallback": [Function], + }, + }, + "_mountOrder": 1, + "_pendingCallbacks": null, + "_pendingElement": null, + "_pendingForceUpdate": false, + "_pendingReplaceState": false, + "_pendingStateQueue": null, + "_renderedComponent": Object { + "_currentElement":
+ +
+
+

+ Search for: +

+ + +
+
+
, + "_debugID": 2, + "_renderedOutput":
+ +
+
+

+ Search for: +

+ + +
+
+
, + }, + "_renderedNodeType": 0, + "_rootNodeID": 0, + "_topLevelWrapper": null, + "_updateBatchNumber": null, + "_warnedAboutRefsInRender": false, + }, + "getRenderOutput": [Function], + "render": [Function], + }, + "root": [Circular], + "unrendered": , +} +`; diff --git a/src/reducers/ui.js b/src/reducers/ui.js index 1f854284c4..44e7ebbbac 100644 --- a/src/reducers/ui.js +++ b/src/reducers/ui.js @@ -19,6 +19,11 @@ export type FileSearchModifiers = Record<{ export type SymbolSearchType = "functions" | "variables"; +export type SearchResults = { + index: number, + count: number +}; + export type UIState = { fileSearchOn: boolean, fileSearchQuery: string, @@ -26,6 +31,8 @@ export type UIState = { projectSearchOn: boolean, symbolSearchOn: boolean, symbolSearchType: SymbolSearchType, + searchResults: SearchResults, + symbolSearchResults: Array<*>, shownSource: string, startPanelCollapsed: boolean, endPanelCollapsed: boolean, @@ -49,6 +56,11 @@ export const State = makeRecord( projectSearchOn: false, symbolSearchOn: false, symbolSearchType: "functions", + symbolSearchResults: [], + searchResults: { + index: -1, + count: 0 + }, shownSource: "", startPanelCollapsed: prefs.startPanelCollapsed, endPanelCollapsed: prefs.endPanelCollapsed, @@ -83,6 +95,14 @@ function update( return state.set("fileSearchQuery", action.query); } + case "UPDATE_SEARCH_RESULTS": { + return state.set("searchResults", action.results); + } + + case "UPDATE_SYMBOL_SEARCH_RESULTS": { + return state.set("symbolSearchResults", action.results); + } + case "TOGGLE_FILE_SEARCH_MODIFIER": { const actionVal = !state.getIn(["fileSearchModifiers", action.modifier]); @@ -157,6 +177,14 @@ export function getFileSearchModifierState( return state.ui.get("fileSearchModifiers"); } +export function getSymbolSearchResults(state: OuterState): string { + return state.ui.get("symbolSearchResults"); +} + +export function getSearchResults(state: OuterState): string { + return state.ui.get("searchResults"); +} + export function getFrameworkGroupingState(state: OuterState): boolean { return state.ui.get("frameworkGroupingOn"); } diff --git a/src/selectors.js b/src/selectors.js index 670395eb10..dcdb4b205e 100644 --- a/src/selectors.js +++ b/src/selectors.js @@ -58,6 +58,8 @@ module.exports = { getFileSearchState: ui.getFileSearchState, getFileSearchQueryState: ui.getFileSearchQueryState, getFileSearchModifierState: ui.getFileSearchModifierState, + getSymbolSearchResults: ui.getSymbolSearchResults, + getSearchResults: ui.getSearchResults, getFrameworkGroupingState: ui.getFrameworkGroupingState, getSymbolSearchState: ui.getSymbolSearchState, getSymbolSearchType: ui.getSymbolSearchType,