diff options
Diffstat (limited to 'devtools/client/debugger/src/utils/editor/tests')
7 files changed, 876 insertions, 0 deletions
diff --git a/devtools/client/debugger/src/utils/editor/tests/__snapshots__/create-editor.spec.js.snap b/devtools/client/debugger/src/utils/editor/tests/__snapshots__/create-editor.spec.js.snap new file mode 100644 index 0000000000..f5bba6cd3e --- /dev/null +++ b/devtools/client/debugger/src/utils/editor/tests/__snapshots__/create-editor.spec.js.snap @@ -0,0 +1,60 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`createEditor Adds codeFolding 1`] = ` +Object { + "cursorBlinkRate": 530, + "enableCodeFolding": true, + "extraKeys": Object { + "Cmd-F": false, + "Cmd-G": false, + "Ctrl-F": false, + "Ctrl-G": false, + "Esc": false, + }, + "foldGutter": true, + "gutters": Array [ + "breakpoints", + "hit-markers", + "CodeMirror-linenumbers", + "CodeMirror-foldgutter", + ], + "lineNumbers": true, + "lineWrapping": false, + "matchBrackets": true, + "mode": "javascript", + "readOnly": true, + "showAnnotationRuler": true, + "styleActiveLine": false, + "theme": "mozilla", + "value": " ", +} +`; + +exports[`createEditor Returns a SourceEditor 1`] = ` +Object { + "cursorBlinkRate": 530, + "enableCodeFolding": false, + "extraKeys": Object { + "Cmd-F": false, + "Cmd-G": false, + "Ctrl-F": false, + "Ctrl-G": false, + "Esc": false, + }, + "foldGutter": false, + "gutters": Array [ + "breakpoints", + "hit-markers", + "CodeMirror-linenumbers", + ], + "lineNumbers": true, + "lineWrapping": false, + "matchBrackets": true, + "mode": "javascript", + "readOnly": true, + "showAnnotationRuler": true, + "styleActiveLine": false, + "theme": "mozilla", + "value": " ", +} +`; diff --git a/devtools/client/debugger/src/utils/editor/tests/create-editor.spec.js b/devtools/client/debugger/src/utils/editor/tests/create-editor.spec.js new file mode 100644 index 0000000000..38e7241b2e --- /dev/null +++ b/devtools/client/debugger/src/utils/editor/tests/create-editor.spec.js @@ -0,0 +1,25 @@ +/* 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 { createEditor } from "../create-editor"; +import SourceEditor from "../source-editor"; + +import { features } from "../../prefs"; + +describe("createEditor", () => { + test("Returns a SourceEditor", () => { + const editor = createEditor(); + expect(editor).toBeInstanceOf(SourceEditor); + expect(editor.opts).toMatchSnapshot(); + expect(editor.opts.gutters).not.toContain("CodeMirror-foldgutter"); + }); + + test("Adds codeFolding", () => { + features.codeFolding = true; + const editor = createEditor(); + expect(editor).toBeInstanceOf(SourceEditor); + expect(editor.opts).toMatchSnapshot(); + expect(editor.opts.gutters).toContain("CodeMirror-foldgutter"); + }); +}); diff --git a/devtools/client/debugger/src/utils/editor/tests/editor.spec.js b/devtools/client/debugger/src/utils/editor/tests/editor.spec.js new file mode 100644 index 0000000000..d657437b19 --- /dev/null +++ b/devtools/client/debugger/src/utils/editor/tests/editor.spec.js @@ -0,0 +1,203 @@ +/* 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 { + toEditorLine, + toEditorPosition, + toEditorRange, + toSourceLine, + scrollToColumn, + markText, + lineAtHeight, + getSourceLocationFromMouseEvent, + forEachLine, + removeLineClass, + clearLineClass, + getTextForLine, + getCursorLine, +} from "../index"; + +import { makeMockSource } from "../../test-mockup"; + +describe("toEditorLine", () => { + it("returns an editor line", () => { + const testId = "test-123"; + const line = 30; + expect(toEditorLine(testId, line)).toEqual(29); + }); +}); + +describe("toEditorPosition", () => { + it("returns an editor position", () => { + const loc = { source: { id: "source" }, line: 100, column: 25 }; + expect(toEditorPosition(loc)).toEqual({ + line: 99, + column: 25, + }); + }); +}); + +describe("toEditorRange", () => { + it("returns an editor range", () => { + const testId = "test-123"; + const loc = { + start: { source: { id: testId }, line: 100, column: 25 }, + end: { source: { id: testId }, line: 200, column: 0 }, + }; + expect(toEditorRange(testId, loc)).toEqual({ + start: { line: 99, column: 25 }, + end: { line: 199, column: 0 }, + }); + }); +}); + +describe("toSourceLine", () => { + it("returns a source line", () => { + const testId = "test-123"; + const line = 30; + expect(toSourceLine(testId, line)).toEqual(31); + }); +}); + +const codeMirror = { + doc: { + iter: jest.fn((_, __, cb) => cb()), + }, + lineCount: jest.fn(() => 100), + getLine: jest.fn(() => "something"), + getCursor: jest.fn(() => ({ line: 3 })), + getScrollerElement: jest.fn(() => ({ + offsetWidth: 100, + offsetHeight: 100, + })), + getScrollInfo: () => ({ + top: 0, + right: 0, + bottom: 0, + left: 0, + clientHeight: 100, + clientWidth: 100, + }), + removeLineClass: jest.fn(), + operation: jest.fn(cb => cb()), + charCoords: jest.fn(() => ({ + top: 100, + right: 50, + bottom: 100, + left: 50, + })), + coordsChar: jest.fn(() => ({ line: 6, ch: 30 })), + lineAtHeight: jest.fn(() => 300), + markText: jest.fn(), + scrollTo: jest.fn(), + defaultCharWidth: jest.fn(() => 8), + defaultTextHeight: jest.fn(() => 16), +}; + +const editor = { codeMirror }; + +describe("scrollToColumn", () => { + it("calls codemirror APIs charCoords, getScrollerElement, scrollTo", () => { + scrollToColumn(codeMirror, 60, 123); + expect(codeMirror.charCoords).toHaveBeenCalledWith( + { line: 60, ch: 123 }, + "local" + ); + expect(codeMirror.scrollTo).toHaveBeenCalledWith(0, 50); + }); +}); + +describe("markText", () => { + it("calls codemirror API markText & returns marker", () => { + const loc = { + start: { line: 10, column: 0 }, + end: { line: 30, column: 50 }, + }; + markText(editor, "test-123", loc); + expect(codeMirror.markText).toHaveBeenCalledWith( + { ch: loc.start.column, line: loc.start.line }, + { ch: loc.end.column, line: loc.end.line }, + { className: "test-123" } + ); + }); +}); + +describe("lineAtHeight", () => { + it("calls codemirror API lineAtHeight", () => { + const e = { clientX: 30, clientY: 60 }; + expect(lineAtHeight(editor, "test-123", e)).toEqual(301); + expect(editor.codeMirror.lineAtHeight).toHaveBeenCalledWith(e.clientY); + }); +}); + +describe("getSourceLocationFromMouseEvent", () => { + it("calls codemirror API coordsChar & returns location", () => { + const source = makeMockSource(undefined, "test-123"); + const e = { clientX: 30, clientY: 60 }; + expect(getSourceLocationFromMouseEvent(editor, source, e)).toEqual({ + source, + sourceId: source.id, + line: 7, + column: 31, + sourceActorId: undefined, + sourceActor: null, + sourceUrl: "", + }); + expect(editor.codeMirror.coordsChar).toHaveBeenCalledWith({ + left: 30, + top: 60, + }); + }); +}); + +describe("forEachLine", () => { + it("calls codemirror API operation && doc.iter across a doc", () => { + const test = jest.fn(); + forEachLine(codeMirror, test); + expect(codeMirror.operation).toHaveBeenCalled(); + expect(codeMirror.doc.iter).toHaveBeenCalledWith(0, 100, test); + }); +}); + +describe("removeLineClass", () => { + it("calls codemirror API removeLineClass", () => { + const line = 3; + const className = "test-class"; + removeLineClass(codeMirror, line, className); + expect(codeMirror.removeLineClass).toHaveBeenCalledWith( + line, + "wrap", + className + ); + }); +}); + +describe("clearLineClass", () => { + it("Uses forEachLine & removeLineClass to clear class on all lines", () => { + codeMirror.operation.mockClear(); + codeMirror.doc.iter.mockClear(); + codeMirror.removeLineClass.mockClear(); + clearLineClass(codeMirror, "test-class"); + expect(codeMirror.operation).toHaveBeenCalled(); + expect(codeMirror.doc.iter).toHaveBeenCalledWith( + 0, + 100, + expect.any(Function) + ); + expect(codeMirror.removeLineClass).toHaveBeenCalled(); + }); +}); + +describe("getTextForLine", () => { + it("calls codemirror API getLine & returns line text", () => { + getTextForLine(codeMirror, 3); + expect(codeMirror.getLine).toHaveBeenCalledWith(2); + }); +}); +describe("getCursorLine", () => { + it("calls codemirror API getCursor & returns line number", () => { + getCursorLine(codeMirror); + expect(codeMirror.getCursor).toHaveBeenCalled(); + }); +}); diff --git a/devtools/client/debugger/src/utils/editor/tests/get-expression.spec.js b/devtools/client/debugger/src/utils/editor/tests/get-expression.spec.js new file mode 100644 index 0000000000..65ab5152f6 --- /dev/null +++ b/devtools/client/debugger/src/utils/editor/tests/get-expression.spec.js @@ -0,0 +1,160 @@ +/* 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 CodeMirror from "codemirror"; +import { getExpressionFromCoords } from "../get-expression"; + +describe("get-expression", () => { + let isCreateTextRangeDefined; + + beforeAll(() => { + if (document.body.createTextRange) { + isCreateTextRangeDefined = true; + } else { + isCreateTextRangeDefined = false; + // CodeMirror needs createTextRange + // https://discuss.codemirror.net/t/working-in-jsdom-or-node-js-natively/138/5 + document.body.createTextRange = () => ({ + getBoundingClientRect: jest.fn(), + getClientRects: () => ({}), + }); + } + }); + + afterAll(() => { + if (!isCreateTextRangeDefined) { + delete document.body.createTextRange; + } + }); + + describe("getExpressionFromCoords", () => { + it("returns null when location.line is greater than the lineCount", () => { + const cm = CodeMirror(document.body, { + value: "let Line1;\n" + "let Line2;\n", + mode: "javascript", + }); + + const result = getExpressionFromCoords(cm, { + line: 3, + column: 1, + }); + expect(result).toBeNull(); + }); + + it("gets the expression using CodeMirror.getTokenAt", () => { + const codemirrorMock = { + lineCount: () => 100, + getTokenAt: jest.fn(() => ({ start: 0, end: 0 })), + doc: { + getLine: () => "", + }, + }; + getExpressionFromCoords(codemirrorMock, { line: 1, column: 1 }); + expect(codemirrorMock.getTokenAt).toHaveBeenCalled(); + }); + + it("requests the correct line and column from codeMirror", () => { + const codemirrorMock = { + lineCount: () => 100, + getTokenAt: jest.fn(() => ({ start: 0, end: 1 })), + doc: { + getLine: jest.fn(() => ""), + }, + }; + getExpressionFromCoords(codemirrorMock, { line: 20, column: 5 }); + // getExpressionsFromCoords uses one based line indexing + // CodeMirror uses zero based line indexing + expect(codemirrorMock.getTokenAt).toHaveBeenCalledWith({ + line: 19, + ch: 5, + }); + expect(codemirrorMock.doc.getLine).toHaveBeenCalledWith(19); + }); + + it("when called with column 0 returns null", () => { + const cm = CodeMirror(document.body, { + value: "foo bar;\n", + mode: "javascript", + }); + + const result = getExpressionFromCoords(cm, { + line: 1, + column: 0, + }); + expect(result).toBeNull(); + }); + + it("gets the expression when first token on the line", () => { + const cm = CodeMirror(document.body, { + value: "foo bar;\n", + mode: "javascript", + }); + + const result = getExpressionFromCoords(cm, { + line: 1, + column: 1, + }); + if (!result) { + throw new Error("no result"); + } + expect(result.expression).toEqual("foo"); + expect(result.location.start).toEqual({ line: 1, column: 0 }); + expect(result.location.end).toEqual({ line: 1, column: 3 }); + }); + + it("includes previous tokens in the expression", () => { + const cm = CodeMirror(document.body, { + value: "foo.bar;\n", + mode: "javascript", + }); + + const result = getExpressionFromCoords(cm, { + line: 1, + column: 5, + }); + if (!result) { + throw new Error("no result"); + } + expect(result.expression).toEqual("foo.bar"); + expect(result.location.start).toEqual({ line: 1, column: 0 }); + expect(result.location.end).toEqual({ line: 1, column: 7 }); + }); + + it("includes multiple previous tokens in the expression", () => { + const cm = CodeMirror(document.body, { + value: "foo.bar.baz;\n", + mode: "javascript", + }); + + const result = getExpressionFromCoords(cm, { + line: 1, + column: 10, + }); + if (!result) { + throw new Error("no result"); + } + expect(result.expression).toEqual("foo.bar.baz"); + expect(result.location.start).toEqual({ line: 1, column: 0 }); + expect(result.location.end).toEqual({ line: 1, column: 11 }); + }); + + it("does not include tokens not part of the expression", () => { + const cm = CodeMirror(document.body, { + value: "foo bar.baz;\n", + mode: "javascript", + }); + + const result = getExpressionFromCoords(cm, { + line: 1, + column: 10, + }); + if (!result) { + throw new Error("no result"); + } + expect(result.expression).toEqual("bar.baz"); + expect(result.location.start).toEqual({ line: 1, column: 4 }); + expect(result.location.end).toEqual({ line: 1, column: 11 }); + }); + }); +}); diff --git a/devtools/client/debugger/src/utils/editor/tests/get-token-location.spec.js b/devtools/client/debugger/src/utils/editor/tests/get-token-location.spec.js new file mode 100644 index 0000000000..c4aa277f26 --- /dev/null +++ b/devtools/client/debugger/src/utils/editor/tests/get-token-location.spec.js @@ -0,0 +1,31 @@ +/* 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 { getTokenLocation } from "../get-token-location"; + +describe("getTokenLocation", () => { + const codemirror = { + coordsChar: jest.fn(() => ({ + line: 1, + ch: "C", + })), + }; + const token = { + getBoundingClientRect() { + return { + left: 10, + top: 20, + width: 10, + height: 10, + }; + }, + }; + it("calls into codeMirror", () => { + getTokenLocation(codemirror, token); + expect(codemirror.coordsChar).toHaveBeenCalledWith({ + left: 15, + top: 25, + }); + }); +}); diff --git a/devtools/client/debugger/src/utils/editor/tests/source-documents.spec.js b/devtools/client/debugger/src/utils/editor/tests/source-documents.spec.js new file mode 100644 index 0000000000..9d4b42e263 --- /dev/null +++ b/devtools/client/debugger/src/utils/editor/tests/source-documents.spec.js @@ -0,0 +1,215 @@ +/* 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 { getMode } from "../source-documents.js"; + +import { + makeMockSourceWithContent, + makeMockWasmSourceWithContent, +} from "../../test-mockup"; + +const defaultSymbolDeclarations = { + classes: [], + functions: [], + memberExpressions: [], + callExpressions: [], + objectProperties: [], + identifiers: [], + imports: [], + comments: [], + literals: [], + hasJsx: false, + hasTypes: false, + framework: undefined, +}; + +describe("source-documents", () => { + describe("getMode", () => { + it("// ", () => { + const source = makeMockSourceWithContent( + undefined, + undefined, + "text/javascript", + "// @flow" + ); + expect(getMode(source, source.content)).toEqual({ + name: "javascript", + typescript: true, + }); + }); + + it("/* @flow */", () => { + const source = makeMockSourceWithContent( + undefined, + undefined, + "text/javascript", + " /* @flow */" + ); + expect(getMode(source, source.content)).toEqual({ + name: "javascript", + typescript: true, + }); + }); + + it("mixed html", () => { + const source = makeMockSourceWithContent( + undefined, + undefined, + "", + " <html" + ); + expect(getMode(source, source.content)).toEqual({ name: "htmlmixed" }); + }); + + it("elm", () => { + const source = makeMockSourceWithContent( + undefined, + undefined, + "text/x-elm", + 'main = text "Hello, World!"' + ); + expect(getMode(source, source.content)).toEqual({ name: "elm" }); + }); + + it("returns jsx if contentType jsx is given", () => { + const source = makeMockSourceWithContent( + undefined, + undefined, + "text/jsx", + "<h1></h1>" + ); + expect(getMode(source, source.content)).toEqual({ name: "jsx" }); + }); + + it("returns jsx if sourceMetaData says it's a react component", () => { + const source = makeMockSourceWithContent( + undefined, + undefined, + "", + "<h1></h1>" + ); + expect( + getMode(source, source.content, { + ...defaultSymbolDeclarations, + hasJsx: true, + }) + ).toEqual({ name: "jsx" }); + }); + + it("returns jsx if the fileExtension is .jsx", () => { + const source = makeMockSourceWithContent( + "myComponent.jsx", + undefined, + "", + "<h1></h1>" + ); + expect(getMode(source, source.content)).toEqual({ name: "jsx" }); + }); + + it("returns text/x-haxe if the file extension is .hx", () => { + const source = makeMockSourceWithContent( + "myComponent.hx", + undefined, + "", + "function foo(){}" + ); + expect(getMode(source, source.content)).toEqual({ name: "text/x-haxe" }); + }); + + it("typescript", () => { + const source = makeMockSourceWithContent( + undefined, + undefined, + "text/typescript", + "function foo(){}" + ); + expect(getMode(source, source.content)).toEqual({ + name: "javascript", + typescript: true, + }); + }); + + it("typescript-jsx", () => { + const source = makeMockSourceWithContent( + undefined, + undefined, + "text/typescript-jsx", + "<h1></h1>" + ); + expect(getMode(source, source.content).base).toEqual({ + name: "javascript", + typescript: true, + }); + }); + + it("cross-platform clojure(script) with reader conditionals", () => { + const source = makeMockSourceWithContent( + "my-clojurescript-source-with-reader-conditionals.cljc", + undefined, + "text/x-clojure", + "(defn str->int [s] " + + " #?(:clj (java.lang.Integer/parseInt s) " + + " :cljs (js/parseInt s)))" + ); + expect(getMode(source, source.content)).toEqual({ name: "clojure" }); + }); + + it("clojurescript", () => { + const source = makeMockSourceWithContent( + "my-clojurescript-source.cljs", + undefined, + "text/x-clojurescript", + "(+ 1 2 3)" + ); + expect(getMode(source, source.content)).toEqual({ name: "clojure" }); + }); + + it("coffeescript", () => { + const source = makeMockSourceWithContent( + undefined, + undefined, + "text/coffeescript", + "x = (a) -> 3" + ); + expect(getMode(source, source.content)).toEqual({ name: "coffeescript" }); + }); + + it("wasm", () => { + const source = makeMockWasmSourceWithContent({ + binary: "\x00asm\x01\x00\x00\x00", + }); + expect(getMode(source, source.content.value)).toEqual({ name: "text" }); + }); + + it("marko", () => { + const source = makeMockSourceWithContent( + "http://localhost.com:7999/increment/sometestfile.marko", + undefined, + "does not matter", + "function foo(){}" + ); + expect(getMode(source, source.content)).toEqual({ name: "javascript" }); + }); + + it("es6", () => { + const source = makeMockSourceWithContent( + "http://localhost.com:7999/increment/sometestfile.es6", + undefined, + "does not matter", + "function foo(){}" + ); + expect(getMode(source, source.content)).toEqual({ name: "javascript" }); + }); + + it("vue", () => { + const source = makeMockSourceWithContent( + "http://localhost.com:7999/increment/sometestfile.vue?query=string", + undefined, + "does not matter", + "function foo(){}" + ); + expect(getMode(source, source.content)).toEqual({ name: "javascript" }); + }); + }); +}); diff --git a/devtools/client/debugger/src/utils/editor/tests/source-search.spec.js b/devtools/client/debugger/src/utils/editor/tests/source-search.spec.js new file mode 100644 index 0000000000..33f479766a --- /dev/null +++ b/devtools/client/debugger/src/utils/editor/tests/source-search.spec.js @@ -0,0 +1,182 @@ +/* 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 { + find, + searchSourceForHighlight, + getMatchIndex, + removeOverlay, +} from "../source-search"; + +const getCursor = jest.fn(() => ({ line: 90, ch: 54 })); +const cursor = { + find: jest.fn(), + from: jest.fn(), + to: jest.fn(), +}; +const getSearchCursor = jest.fn(() => cursor); +const modifiers = { + caseSensitive: false, + regexMatch: false, + wholeWord: false, +}; + +const getCM = () => ({ + operation: jest.fn(cb => cb()), + addOverlay: jest.fn(), + removeOverlay: jest.fn(), + getCursor, + getSearchCursor, + firstLine: jest.fn(), + state: {}, +}); + +describe("source-search", () => { + describe("find", () => { + it("calls into CodeMirror APIs via clearSearch & doSearch", () => { + const ctx = { cm: getCM() }; + expect(ctx.cm.state).toEqual({}); + find(ctx, "test", false, modifiers); + // First we check the APIs called via clearSearch + expect(ctx.cm.removeOverlay).toHaveBeenCalledWith(null); + // Next those via doSearch + expect(ctx.cm.operation).toHaveBeenCalled(); + expect(ctx.cm.removeOverlay).toHaveBeenCalledWith(null); + expect(ctx.cm.addOverlay).toHaveBeenCalledWith( + { token: expect.any(Function) }, + { opaque: false } + ); + expect(ctx.cm.getCursor).toHaveBeenCalledWith("anchor"); + expect(ctx.cm.getCursor).toHaveBeenCalledWith("head"); + const search = { + query: "test", + posTo: { line: 0, ch: 0 }, + posFrom: { line: 0, ch: 0 }, + overlay: { token: expect.any(Function) }, + results: [], + }; + expect(ctx.cm.state).toEqual({ search }); + }); + + it("clears a previous overlay", () => { + const ctx = { cm: getCM() }; + ctx.cm.state.search = { + query: "foo", + posTo: null, + posFrom: null, + overlay: { token: expect.any(Function) }, + results: [], + }; + find(ctx, "test", true, modifiers); + expect(ctx.cm.removeOverlay).toHaveBeenCalledWith({ + token: expect.any(Function), + }); + }); + + it("clears for empty queries", () => { + const ctx = { cm: getCM() }; + ctx.cm.state.search = { + query: "foo", + posTo: null, + posFrom: null, + overlay: null, + results: [], + }; + find(ctx, "", true, modifiers); + expect(ctx.cm.removeOverlay).toHaveBeenCalledWith(null); + ctx.cm.removeOverlay.mockClear(); + ctx.cm.state.search.query = "bar"; + find(ctx, "", true, modifiers); + expect(ctx.cm.removeOverlay).toHaveBeenCalledWith(null); + }); + }); + + describe("searchSourceForHighlight", () => { + it("calls into CodeMirror APIs and sets the correct selection", () => { + const line = 15; + const from = { line, ch: 1 }; + const to = { line, ch: 5 }; + const cm = { + ...getCM(), + setSelection: jest.fn(), + getSearchCursor: () => ({ + find: () => true, + from: () => from, + to: () => to, + }), + }; + const ed = { alignLine: jest.fn() }; + const ctx = { cm, ed }; + + expect(ctx.cm.state).toEqual({}); + searchSourceForHighlight(ctx, false, "test", false, modifiers, line, 1); + + expect(ctx.cm.operation).toHaveBeenCalled(); + expect(ctx.cm.removeOverlay).toHaveBeenCalledWith(null); + expect(ctx.cm.addOverlay).toHaveBeenCalledWith( + { token: expect.any(Function) }, + { opaque: false } + ); + expect(ctx.cm.getCursor).toHaveBeenCalledWith("anchor"); + expect(ctx.cm.getCursor).toHaveBeenCalledWith("head"); + expect(ed.alignLine).toHaveBeenCalledWith(line, "center"); + expect(cm.setSelection).toHaveBeenCalledWith(from, to); + }); + }); + + describe("findNext", () => {}); + + describe("findPrev", () => {}); + + describe("getMatchIndex", () => { + it("iterates in the matches", () => { + const count = 3; + + // reverse 2, 1, 0, 2 + + let matchIndex = getMatchIndex(count, 2, true); + expect(matchIndex).toBe(1); + + matchIndex = getMatchIndex(count, 1, true); + expect(matchIndex).toBe(0); + + matchIndex = getMatchIndex(count, 0, true); + expect(matchIndex).toBe(2); + + // forward 1, 2, 0, 1 + + matchIndex = getMatchIndex(count, 1, false); + expect(matchIndex).toBe(2); + + matchIndex = getMatchIndex(count, 2, false); + expect(matchIndex).toBe(0); + + matchIndex = getMatchIndex(count, 0, false); + expect(matchIndex).toBe(1); + }); + }); + + describe("removeOverlay", () => { + it("calls CodeMirror APIs: removeOverlay, getCursor & setSelection", () => { + const ctx = { + cm: { + removeOverlay: jest.fn(), + getCursor, + state: {}, + doc: { + setSelection: jest.fn(), + }, + }, + }; + removeOverlay(ctx, "test"); + expect(ctx.cm.removeOverlay).toHaveBeenCalled(); + expect(ctx.cm.getCursor).toHaveBeenCalled(); + expect(ctx.cm.doc.setSelection).toHaveBeenCalledWith( + { line: 90, ch: 54 }, + { line: 90, ch: 54 }, + { scroll: false } + ); + }); + }); +}); |