diff options
Diffstat (limited to 'devtools/client/debugger/src/actions/sources/tests')
4 files changed, 1072 insertions, 0 deletions
diff --git a/devtools/client/debugger/src/actions/sources/tests/blackbox.spec.js b/devtools/client/debugger/src/actions/sources/tests/blackbox.spec.js new file mode 100644 index 0000000000..2ff8420b23 --- /dev/null +++ b/devtools/client/debugger/src/actions/sources/tests/blackbox.spec.js @@ -0,0 +1,249 @@ +/* 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 { + actions, + selectors, + createStore, + makeSource, +} from "../../../utils/test-head"; + +import { initialSourceBlackBoxState } from "../../../reducers/source-blackbox"; + +describe("blackbox", () => { + it("should blackbox and unblackbox a source based on the current state of the source ", async () => { + const store = createStore({ + blackBox: async () => true, + getSourceActorBreakableLines: async () => [], + }); + const { dispatch, getState, cx } = store; + + const fooSource = await dispatch( + actions.newGeneratedSource(makeSource("foo")) + ); + await dispatch(actions.toggleBlackBox(cx, fooSource)); + + expect(selectors.isSourceBlackBoxed(getState(), fooSource)).toEqual(true); + + let blackboxRanges = selectors.getBlackBoxRanges(getState()); + expect(blackboxRanges[fooSource.url]).toEqual([]); + + await dispatch(actions.toggleBlackBox(cx, fooSource)); + + expect(selectors.isSourceBlackBoxed(getState(), fooSource)).toEqual(false); + + blackboxRanges = selectors.getBlackBoxRanges(getState()); + expect(blackboxRanges[fooSource.url]).toEqual(undefined); + }); + + it("should blackbox and unblackbox a source when explicilty specified", async () => { + const store = createStore({ + blackBox: async () => true, + getSourceActorBreakableLines: async () => [], + }); + const { dispatch, getState, cx } = store; + + const fooSource = await dispatch( + actions.newGeneratedSource(makeSource("foo")) + ); + + // check the state before trying to blackbox + expect(selectors.isSourceBlackBoxed(getState(), fooSource)).toEqual(false); + + let blackboxRanges = selectors.getBlackBoxRanges(getState()); + expect(blackboxRanges[fooSource.url]).toEqual(undefined); + + // should blackbox the whole source + await dispatch(actions.toggleBlackBox(cx, fooSource, true, [])); + + expect(selectors.isSourceBlackBoxed(getState(), fooSource)).toEqual(true); + + blackboxRanges = selectors.getBlackBoxRanges(getState()); + expect(blackboxRanges[fooSource.url]).toEqual([]); + + // should unblackbox the whole source + await dispatch(actions.toggleBlackBox(cx, fooSource, false, [])); + + expect(selectors.isSourceBlackBoxed(getState(), fooSource)).toEqual(false); + + blackboxRanges = selectors.getBlackBoxRanges(getState()); + expect(blackboxRanges[fooSource.url]).toEqual(undefined); + }); + + it("should blackbox and unblackbox lines in a source", async () => { + const store = createStore({ + blackBox: async () => true, + getSourceActorBreakableLines: async () => [], + }); + const { dispatch, getState, cx } = store; + + const fooSource = await dispatch( + actions.newGeneratedSource(makeSource("foo")) + ); + + const range1 = { + start: { line: 10, column: 3 }, + end: { line: 15, column: 4 }, + }; + + const range2 = { + start: { line: 5, column: 3 }, + end: { line: 7, column: 6 }, + }; + + await dispatch(actions.toggleBlackBox(cx, fooSource, true, [range1])); + + expect(selectors.isSourceBlackBoxed(getState(), fooSource)).toEqual(true); + + let blackboxRanges = selectors.getBlackBoxRanges(getState()); + expect(blackboxRanges[fooSource.url]).toEqual([range1]); + + // add new blackbox lines in the second range + await dispatch(actions.toggleBlackBox(cx, fooSource, true, [range2])); + + expect(selectors.isSourceBlackBoxed(getState(), fooSource)).toEqual(true); + + blackboxRanges = selectors.getBlackBoxRanges(getState()); + // ranges are stored asc order + expect(blackboxRanges[fooSource.url]).toEqual([range2, range1]); + + // un-blackbox lines in the first range + await dispatch(actions.toggleBlackBox(cx, fooSource, false, [range1])); + + expect(selectors.isSourceBlackBoxed(getState(), fooSource)).toEqual(true); + + blackboxRanges = selectors.getBlackBoxRanges(getState()); + expect(blackboxRanges[fooSource.url]).toEqual([range2]); + + // un-blackbox lines in the second range + await dispatch(actions.toggleBlackBox(cx, fooSource, false, [range2])); + + expect(selectors.isSourceBlackBoxed(getState(), fooSource)).toEqual(false); + + blackboxRanges = selectors.getBlackBoxRanges(getState()); + expect(blackboxRanges[fooSource.url]).toEqual(undefined); + }); + + it("should undo blackboxed lines when whole source unblackboxed", async () => { + const store = createStore({ + blackBox: async () => true, + getSourceActorBreakableLines: async () => [], + }); + const { dispatch, getState, cx } = store; + + const fooSource = await dispatch( + actions.newGeneratedSource(makeSource("foo")) + ); + + const range1 = { + start: { line: 1, column: 5 }, + end: { line: 3, column: 4 }, + }; + + const range2 = { + start: { line: 5, column: 3 }, + end: { line: 7, column: 6 }, + }; + + await dispatch( + actions.toggleBlackBox(cx, fooSource, true, [range1, range2]) + ); + + expect(selectors.isSourceBlackBoxed(getState(), fooSource)).toEqual(true); + + let blackboxRanges = selectors.getBlackBoxRanges(getState()); + // The ranges are ordered in based on the lines & cols in ascending + expect(blackboxRanges[fooSource.url]).toEqual([range2, range1]); + + // un-blackbox the whole source + await dispatch(actions.toggleBlackBox(cx, fooSource)); + + expect(selectors.isSourceBlackBoxed(getState(), fooSource)).toEqual(false); + + blackboxRanges = selectors.getBlackBoxRanges(getState()); + expect(blackboxRanges[fooSource.url]).toEqual(undefined); + }); + + it("should restore the blackboxed state correctly debugger load", async () => { + const mockAsyncStoreBlackBoxedRanges = { + "http://localhost:8000/examples/foo": [ + { + start: { line: 1, column: 5 }, + end: { line: 3, column: 4 }, + }, + ], + }; + + function loadInitialState() { + const blackboxedRanges = mockAsyncStoreBlackBoxedRanges; + return { + sourceBlackBox: initialSourceBlackBoxState({ blackboxedRanges }), + }; + } + const store = createStore( + { + blackBox: async () => true, + getSourceActorBreakableLines: async () => [], + }, + loadInitialState() + ); + const { dispatch, getState } = store; + + const fooSource = await dispatch( + actions.newGeneratedSource(makeSource("foo")) + ); + + expect(selectors.isSourceBlackBoxed(getState(), fooSource)).toEqual(true); + + const blackboxRanges = selectors.getBlackBoxRanges(getState()); + const mockFooSourceRange = mockAsyncStoreBlackBoxedRanges[fooSource.url]; + expect(blackboxRanges[fooSource.url]).toEqual(mockFooSourceRange); + }); + + it("should unblackbox lines after blackboxed state has been restored", async () => { + const mockAsyncStoreBlackBoxedRanges = { + "http://localhost:8000/examples/foo": [ + { + start: { line: 1, column: 5 }, + end: { line: 3, column: 4 }, + }, + ], + }; + + function loadInitialState() { + const blackboxedRanges = mockAsyncStoreBlackBoxedRanges; + return { + sourceBlackBox: initialSourceBlackBoxState({ blackboxedRanges }), + }; + } + const store = createStore( + { + blackBox: async () => true, + getSourceActorBreakableLines: async () => [], + }, + loadInitialState() + ); + const { dispatch, getState, cx } = store; + + const fooSource = await dispatch( + actions.newGeneratedSource(makeSource("foo")) + ); + + expect(selectors.isSourceBlackBoxed(getState(), fooSource)).toEqual(true); + + let blackboxRanges = selectors.getBlackBoxRanges(getState()); + const mockFooSourceRange = mockAsyncStoreBlackBoxedRanges[fooSource.url]; + expect(blackboxRanges[fooSource.url]).toEqual(mockFooSourceRange); + + //unblackbox the blackboxed line + await dispatch( + actions.toggleBlackBox(cx, fooSource, false, mockFooSourceRange) + ); + + expect(selectors.isSourceBlackBoxed(getState(), fooSource)).toEqual(false); + + blackboxRanges = selectors.getBlackBoxRanges(getState()); + expect(blackboxRanges[fooSource.url]).toEqual(undefined); + }); +}); diff --git a/devtools/client/debugger/src/actions/sources/tests/loadSource.spec.js b/devtools/client/debugger/src/actions/sources/tests/loadSource.spec.js new file mode 100644 index 0000000000..f81fc856dd --- /dev/null +++ b/devtools/client/debugger/src/actions/sources/tests/loadSource.spec.js @@ -0,0 +1,363 @@ +/* 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 { + actions, + selectors, + watchForState, + createStore, + makeOriginalSource, + makeSource, +} from "../../../utils/test-head"; +import { + createSource, + mockCommandClient, +} from "../../tests/helpers/mockCommandClient"; +import { getBreakpointsList } from "../../../selectors"; +import { isFulfilled, isRejected } from "../../../utils/async-value"; +import { createLocation } from "../../../utils/location"; + +describe("loadGeneratedSourceText", () => { + it("should load source text", async () => { + const store = createStore(mockCommandClient); + const { dispatch, getState, cx } = store; + + const foo1Source = await dispatch( + actions.newGeneratedSource(makeSource("foo1")) + ); + const foo1SourceActor = selectors.getFirstSourceActorForGeneratedSource( + getState(), + foo1Source.id + ); + await dispatch( + actions.loadGeneratedSourceText({ + cx, + sourceActor: foo1SourceActor, + }) + ); + + const foo1Content = selectors.getSettledSourceTextContent( + getState(), + createLocation({ + source: foo1Source, + sourceActor: foo1SourceActor, + }) + ); + + expect( + foo1Content && + isFulfilled(foo1Content) && + foo1Content.value.type === "text" + ? foo1Content.value.value.indexOf("return foo1") + : -1 + ).not.toBe(-1); + + const foo2Source = await dispatch( + actions.newGeneratedSource(makeSource("foo2")) + ); + const foo2SourceActor = selectors.getFirstSourceActorForGeneratedSource( + getState(), + foo2Source.id + ); + + await dispatch( + actions.loadGeneratedSourceText({ + cx, + sourceActor: foo2SourceActor, + }) + ); + + const foo2Content = selectors.getSettledSourceTextContent( + getState(), + createLocation({ + source: foo2Source, + sourceActor: foo2SourceActor, + }) + ); + + expect( + foo2Content && + isFulfilled(foo2Content) && + foo2Content.value.type === "text" + ? foo2Content.value.value.indexOf("return foo2") + : -1 + ).not.toBe(-1); + }); + + it("should update breakpoint text when a source loads", async () => { + const fooOrigContent = createSource("fooOrig", "var fooOrig = 42;"); + const fooGenContent = createSource("fooGen", "var fooGen = 42;"); + + const store = createStore( + { + ...mockCommandClient, + sourceContents: async () => fooGenContent, + getSourceActorBreakpointPositions: async () => ({ 1: [0] }), + getSourceActorBreakableLines: async () => [], + }, + {}, + { + getGeneratedRangesForOriginal: async () => [ + { start: { line: 1, column: 0 }, end: { line: 1, column: 1 } }, + ], + getOriginalLocations: async items => + items.map(item => ({ + ...item, + sourceId: + item.sourceId === fooGenSource1.id + ? fooOrigSources1[0].id + : fooOrigSources2[0].id, + })), + getOriginalSourceText: async s => ({ + text: fooOrigContent.source, + contentType: fooOrigContent.contentType, + }), + } + ); + const { cx, dispatch, getState } = store; + + const fooGenSource1 = await dispatch( + actions.newGeneratedSource(makeSource("fooGen1")) + ); + + const fooOrigSources1 = await dispatch( + actions.newOriginalSources([makeOriginalSource(fooGenSource1)]) + ); + const fooGenSource2 = await dispatch( + actions.newGeneratedSource(makeSource("fooGen2")) + ); + + const fooOrigSources2 = await dispatch( + actions.newOriginalSources([makeOriginalSource(fooGenSource2)]) + ); + + await dispatch( + actions.loadOriginalSourceText({ + cx, + source: fooOrigSources1[0], + }) + ); + + await dispatch( + actions.addBreakpoint( + cx, + createLocation({ + source: fooOrigSources1[0], + line: 1, + column: 0, + }), + {} + ) + ); + + const breakpoint1 = getBreakpointsList(getState())[0]; + expect(breakpoint1.text).toBe(""); + expect(breakpoint1.originalText).toBe("var fooOrig = 42;"); + + const fooGenSource1SourceActor = + selectors.getFirstSourceActorForGeneratedSource( + getState(), + fooGenSource1.id + ); + + await dispatch( + actions.loadGeneratedSourceText({ + cx, + sourceActor: fooGenSource1SourceActor, + }) + ); + + const breakpoint2 = getBreakpointsList(getState())[0]; + expect(breakpoint2.text).toBe("var fooGen = 42;"); + expect(breakpoint2.originalText).toBe("var fooOrig = 42;"); + + const fooGenSource2SourceActor = + selectors.getFirstSourceActorForGeneratedSource( + getState(), + fooGenSource2.id + ); + + await dispatch( + actions.loadGeneratedSourceText({ + cx, + sourceActor: fooGenSource2SourceActor, + }) + ); + + await dispatch( + actions.addBreakpoint( + cx, + createLocation({ + source: fooGenSource2, + line: 1, + column: 0, + }), + {} + ) + ); + + const breakpoint3 = getBreakpointsList(getState())[1]; + expect(breakpoint3.text).toBe("var fooGen = 42;"); + expect(breakpoint3.originalText).toBe(""); + + await dispatch( + actions.loadOriginalSourceText({ + cx, + source: fooOrigSources2[0], + }) + ); + + const breakpoint4 = getBreakpointsList(getState())[1]; + expect(breakpoint4.text).toBe("var fooGen = 42;"); + expect(breakpoint4.originalText).toBe("var fooOrig = 42;"); + }); + + it("loads two sources w/ one request", async () => { + let resolve; + let count = 0; + const { dispatch, getState, cx } = createStore({ + sourceContents: () => + new Promise(r => { + count++; + resolve = r; + }), + getSourceActorBreakpointPositions: async () => ({}), + getSourceActorBreakableLines: async () => [], + }); + const id = "foo"; + + const source = await dispatch(actions.newGeneratedSource(makeSource(id))); + const sourceActor = selectors.getFirstSourceActorForGeneratedSource( + getState(), + source.id + ); + + dispatch(actions.loadGeneratedSourceText({ cx, sourceActor })); + + const loading = dispatch( + actions.loadGeneratedSourceText({ cx, sourceActor }) + ); + + if (!resolve) { + throw new Error("no resolve"); + } + resolve({ source: "yay", contentType: "text/javascript" }); + await loading; + expect(count).toEqual(1); + + const content = selectors.getSettledSourceTextContent( + getState(), + createLocation({ + source, + sourceActor, + }) + ); + expect( + content && + isFulfilled(content) && + content.value.type === "text" && + content.value.value + ).toEqual("yay"); + }); + + it("doesn't re-load loaded sources", async () => { + let resolve; + let count = 0; + const { dispatch, getState, cx } = createStore({ + sourceContents: () => + new Promise(r => { + count++; + resolve = r; + }), + getSourceActorBreakpointPositions: async () => ({}), + getSourceActorBreakableLines: async () => [], + }); + const id = "foo"; + + const source = await dispatch(actions.newGeneratedSource(makeSource(id))); + const sourceActor = selectors.getFirstSourceActorForGeneratedSource( + getState(), + source.id + ); + const loading = dispatch( + actions.loadGeneratedSourceText({ cx, sourceActor }) + ); + + if (!resolve) { + throw new Error("no resolve"); + } + resolve({ source: "yay", contentType: "text/javascript" }); + await loading; + + await dispatch(actions.loadGeneratedSourceText({ cx, sourceActor })); + expect(count).toEqual(1); + + const content = selectors.getSettledSourceTextContent( + getState(), + createLocation({ + source, + sourceActor, + }) + ); + expect( + content && + isFulfilled(content) && + content.value.type === "text" && + content.value.value + ).toEqual("yay"); + }); + + it("should indicate a loading source", async () => { + const store = createStore(mockCommandClient); + const { dispatch, cx, getState } = store; + + const source = await dispatch( + actions.newGeneratedSource(makeSource("foo2")) + ); + + const sourceActor = selectors.getFirstSourceActorForGeneratedSource( + getState(), + source.id + ); + + const wasLoading = watchForState(store, state => { + return !selectors.getSettledSourceTextContent( + state, + createLocation({ + source, + sourceActor, + }) + ); + }); + await dispatch(actions.loadGeneratedSourceText({ cx, sourceActor })); + + expect(wasLoading()).toBe(true); + }); + + it("should indicate an errored source text", async () => { + const { dispatch, getState, cx } = createStore(mockCommandClient); + + const source = await dispatch( + actions.newGeneratedSource(makeSource("bad-id")) + ); + const sourceActor = selectors.getFirstSourceActorForGeneratedSource( + getState(), + source.id + ); + await dispatch(actions.loadGeneratedSourceText({ cx, sourceActor })); + + const content = selectors.getSettledSourceTextContent( + getState(), + createLocation({ + source, + sourceActor, + }) + ); + expect( + content && isRejected(content) && typeof content.value === "string" + ? content.value.indexOf("sourceContents failed") + : -1 + ).not.toBe(-1); + }); +}); diff --git a/devtools/client/debugger/src/actions/sources/tests/newSources.spec.js b/devtools/client/debugger/src/actions/sources/tests/newSources.spec.js new file mode 100644 index 0000000000..730c5b32eb --- /dev/null +++ b/devtools/client/debugger/src/actions/sources/tests/newSources.spec.js @@ -0,0 +1,172 @@ +/* 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 { + actions, + selectors, + createStore, + makeSource, + makeSourceURL, + makeOriginalSource, + waitForState, +} from "../../../utils/test-head"; +const { getSource, getSourceCount, getSelectedSource, getSourceByURL } = + selectors; +import sourceQueue from "../../../utils/source-queue"; +import { generatedToOriginalId } from "devtools/client/shared/source-map-loader/index"; + +import { mockCommandClient } from "../../tests/helpers/mockCommandClient"; + +describe("sources - new sources", () => { + it("should add sources to state", async () => { + const { dispatch, getState } = createStore(mockCommandClient); + await dispatch(actions.newGeneratedSource(makeSource("base.js"))); + await dispatch(actions.newGeneratedSource(makeSource("jquery.js"))); + + expect(getSourceCount(getState())).toEqual(2); + const base = getSource(getState(), "base.js"); + const jquery = getSource(getState(), "jquery.js"); + expect(base && base.id).toEqual("base.js"); + expect(jquery && jquery.id).toEqual("jquery.js"); + }); + + it("should not add multiple identical generated sources", async () => { + const { dispatch, getState } = createStore(mockCommandClient); + + const generated = await dispatch( + actions.newGeneratedSource(makeSource("base.js")) + ); + + await dispatch(actions.newOriginalSources([makeOriginalSource(generated)])); + await dispatch(actions.newOriginalSources([makeOriginalSource(generated)])); + + expect(getSourceCount(getState())).toEqual(2); + }); + + it("should not add multiple identical original sources", async () => { + const { dispatch, getState } = createStore(mockCommandClient); + + await dispatch(actions.newGeneratedSource(makeSource("base.js"))); + await dispatch(actions.newGeneratedSource(makeSource("base.js"))); + + expect(getSourceCount(getState())).toEqual(1); + }); + + it("should automatically select a pending source", async () => { + const { dispatch, getState, cx } = createStore(mockCommandClient); + const baseSourceURL = makeSourceURL("base.js"); + await dispatch(actions.selectSourceURL(cx, baseSourceURL)); + + expect(getSelectedSource(getState())).toBe(undefined); + const baseSource = await dispatch( + actions.newGeneratedSource(makeSource("base.js")) + ); + + const selected = getSelectedSource(getState()); + expect(selected && selected.url).toBe(baseSource.url); + }); + + it("should add original sources", async () => { + const { dispatch, getState } = createStore( + mockCommandClient, + {}, + { + getOriginalURLs: async source => [ + { + id: generatedToOriginalId(source.id, "magic.js"), + url: "magic.js", + }, + ], + getOriginalLocations: async items => items, + getOriginalLocation: location => location, + } + ); + + await dispatch( + actions.newGeneratedSource( + makeSource("base.js", { sourceMapURL: "base.js.map" }) + ) + ); + const magic = getSourceByURL(getState(), "magic.js"); + expect(magic && magic.url).toEqual("magic.js"); + }); + + // eslint-disable-next-line + it("should not attempt to fetch original sources if it's missing a source map url", async () => { + const getOriginalURLs = jest.fn(); + const { dispatch } = createStore( + mockCommandClient, + {}, + { + getOriginalURLs, + getOriginalLocations: async items => items, + getOriginalLocation: location => location, + } + ); + + await dispatch(actions.newGeneratedSource(makeSource("base.js"))); + expect(getOriginalURLs).not.toHaveBeenCalled(); + }); + + // eslint-disable-next-line + it("should process new sources immediately, without waiting for source maps to be fetched first", async () => { + const { dispatch, getState } = createStore( + mockCommandClient, + {}, + { + getOriginalURLs: async () => new Promise(_ => {}), + getOriginalLocations: async items => items, + getOriginalLocation: location => location, + } + ); + await dispatch( + actions.newGeneratedSource( + makeSource("base.js", { sourceMapURL: "base.js.map" }) + ) + ); + expect(getSourceCount(getState())).toEqual(1); + const base = getSource(getState(), "base.js"); + expect(base && base.id).toEqual("base.js"); + }); + + // eslint-disable-next-line + it("shouldn't let one slow loading source map delay all the other source maps", async () => { + const dbg = createStore( + mockCommandClient, + {}, + { + getOriginalURLs: async source => { + if (source.id == "foo.js") { + // simulate a hang loading foo.js.map + return new Promise(_ => {}); + } + const url = source.id.replace(".js", ".cljs"); + return [ + { + id: generatedToOriginalId(source.id, url), + url, + }, + ]; + }, + getOriginalLocations: async items => items, + getGeneratedLocation: location => location, + } + ); + const { dispatch, getState } = dbg; + await dispatch( + actions.newGeneratedSources([ + makeSource("foo.js", { sourceMapURL: "foo.js.map" }), + makeSource("bar.js", { sourceMapURL: "bar.js.map" }), + makeSource("bazz.js", { sourceMapURL: "bazz.js.map" }), + ]) + ); + await sourceQueue.flush(); + await waitForState(dbg, state => getSourceCount(state) == 5); + expect(getSourceCount(getState())).toEqual(5); + const barCljs = getSourceByURL(getState(), "bar.cljs"); + expect(barCljs && barCljs.url).toEqual("bar.cljs"); + const bazzCljs = getSourceByURL(getState(), "bazz.cljs"); + expect(bazzCljs && bazzCljs.url).toEqual("bazz.cljs"); + }); +}); diff --git a/devtools/client/debugger/src/actions/sources/tests/select.spec.js b/devtools/client/debugger/src/actions/sources/tests/select.spec.js new file mode 100644 index 0000000000..3fcf24f2b7 --- /dev/null +++ b/devtools/client/debugger/src/actions/sources/tests/select.spec.js @@ -0,0 +1,288 @@ +/* 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 { + actions, + selectors, + createStore, + createSourceObject, + makeFrame, + makeSource, + makeSourceURL, + waitForState, + makeOriginalSource, +} from "../../../utils/test-head"; +import { + getSource, + getSourceCount, + getSelectedSource, + getSourceTabs, + getSelectedLocation, + getSymbols, +} from "../../../selectors/"; +import { createLocation } from "../../../utils/location"; + +import { mockCommandClient } from "../../tests/helpers/mockCommandClient"; + +process.on("unhandledRejection", (reason, p) => {}); + +function initialLocation(sourceId) { + return createLocation({ source: createSourceObject(sourceId), line: 1 }); +} + +describe("sources", () => { + it("should select a source", async () => { + // Note that we pass an empty client in because the action checks + // if it exists. + const store = createStore(mockCommandClient); + const { dispatch, getState } = store; + + const frame = makeFrame({ id: "1", sourceId: "foo1" }); + + const baseSource = await dispatch( + actions.newGeneratedSource(makeSource("foo1")) + ); + await dispatch( + actions.paused({ + thread: "FakeThread", + why: { type: "debuggerStatement" }, + frame, + frames: [frame], + }) + ); + + const cx = selectors.getThreadContext(getState()); + await dispatch( + actions.selectLocation( + cx, + createLocation({ source: baseSource, line: 1, column: 5 }) + ) + ); + + const selectedSource = getSelectedSource(getState()); + if (!selectedSource) { + throw new Error("bad selectedSource"); + } + expect(selectedSource.id).toEqual("foo1"); + + const source = getSource(getState(), selectedSource.id); + if (!source) { + throw new Error("bad source"); + } + expect(source.id).toEqual("foo1"); + }); + + it("should select next tab on tab closed if no previous tab", async () => { + const { dispatch, getState, cx } = createStore(mockCommandClient); + + const fooSource = await dispatch( + actions.newGeneratedSource(makeSource("foo.js")) + ); + await dispatch(actions.newGeneratedSource(makeSource("bar.js"))); + await dispatch(actions.newGeneratedSource(makeSource("baz.js"))); + + // 3rd tab + await dispatch(actions.selectLocation(cx, initialLocation("foo.js"))); + + // 2nd tab + await dispatch(actions.selectLocation(cx, initialLocation("bar.js"))); + + // 1st tab + await dispatch(actions.selectLocation(cx, initialLocation("baz.js"))); + + // 3rd tab is reselected + await dispatch(actions.selectLocation(cx, initialLocation("foo.js"))); + + // closes the 1st tab, which should have no previous tab + await dispatch(actions.closeTab(cx, fooSource)); + + const selected = getSelectedSource(getState()); + expect(selected && selected.id).toBe("bar.js"); + expect(getSourceTabs(getState())).toHaveLength(2); + }); + + it("should open a tab for the source", async () => { + const { dispatch, getState, cx } = createStore(mockCommandClient); + await dispatch(actions.newGeneratedSource(makeSource("foo.js"))); + await dispatch(actions.selectLocation(cx, initialLocation("foo.js"))); + + const tabs = getSourceTabs(getState()); + expect(tabs).toHaveLength(1); + expect(tabs[0].url).toEqual("http://localhost:8000/examples/foo.js"); + }); + + it("should select previous tab on tab closed", async () => { + const { dispatch, getState, cx } = createStore(mockCommandClient); + await dispatch(actions.newGeneratedSource(makeSource("foo.js"))); + await dispatch(actions.newGeneratedSource(makeSource("bar.js"))); + + const bazSource = await dispatch( + actions.newGeneratedSource(makeSource("baz.js")) + ); + + await dispatch(actions.selectLocation(cx, initialLocation("foo.js"))); + await dispatch(actions.selectLocation(cx, initialLocation("bar.js"))); + await dispatch(actions.selectLocation(cx, initialLocation("baz.js"))); + await dispatch(actions.closeTab(cx, bazSource)); + + const selected = getSelectedSource(getState()); + expect(selected && selected.id).toBe("bar.js"); + expect(getSourceTabs(getState())).toHaveLength(2); + }); + + it("should keep the selected source when other tab closed", async () => { + const { dispatch, getState, cx } = createStore(mockCommandClient); + + await dispatch(actions.newGeneratedSource(makeSource("foo.js"))); + await dispatch(actions.newGeneratedSource(makeSource("bar.js"))); + const bazSource = await dispatch( + actions.newGeneratedSource(makeSource("baz.js")) + ); + + // 3rd tab + await dispatch(actions.selectLocation(cx, initialLocation("foo.js"))); + + // 2nd tab + await dispatch(actions.selectLocation(cx, initialLocation("bar.js"))); + + // 1st tab + await dispatch(actions.selectLocation(cx, initialLocation("baz.js"))); + + // 3rd tab is reselected + await dispatch(actions.selectLocation(cx, initialLocation("foo.js"))); + await dispatch(actions.closeTab(cx, bazSource)); + + const selected = getSelectedSource(getState()); + expect(selected && selected.id).toBe("foo.js"); + expect(getSourceTabs(getState())).toHaveLength(2); + }); + + it("should not select new sources that lack a URL", async () => { + const { dispatch, getState } = createStore(mockCommandClient); + + await dispatch( + actions.newGeneratedSource({ + ...makeSource("foo"), + url: "", + }) + ); + + expect(getSourceCount(getState())).toEqual(1); + const selectedLocation = getSelectedLocation(getState()); + expect(selectedLocation).toEqual(undefined); + }); + + it("sets and clears selected location correctly", async () => { + const { dispatch, getState, cx } = createStore(mockCommandClient); + const source = await dispatch( + actions.newGeneratedSource(makeSource("testSource")) + ); + const location = createLocation({ source }); + + // set value + dispatch(actions.setSelectedLocation(cx, location)); + expect(getSelectedLocation(getState())).toEqual({ + sourceId: source.id, + ...location, + }); + + // clear value + dispatch(actions.clearSelectedLocation(cx)); + expect(getSelectedLocation(getState())).toEqual(null); + }); + + it("sets and clears pending selected location correctly", () => { + const { dispatch, getState, cx } = createStore(mockCommandClient); + const url = "testURL"; + const options = { line: "testLine", column: "testColumn" }; + + // set value + dispatch(actions.setPendingSelectedLocation(cx, url, options)); + const setResult = getState().sources.pendingSelectedLocation; + expect(setResult).toEqual({ + url, + line: options.line, + column: options.column, + }); + + // clear value + dispatch(actions.clearSelectedLocation(cx)); + const clearResult = getState().sources.pendingSelectedLocation; + expect(clearResult).toEqual({ url: "" }); + }); + + it("should keep the generated the viewing context", async () => { + const store = createStore(mockCommandClient); + const { dispatch, getState, cx } = store; + const baseSource = await dispatch( + actions.newGeneratedSource(makeSource("base.js")) + ); + const sourceActor = selectors.getFirstSourceActorForGeneratedSource( + getState(), + baseSource.id + ); + + const location = createLocation({ + source: baseSource, + line: 1, + sourceActor, + }); + await dispatch(actions.selectLocation(cx, location)); + + const selected = getSelectedSource(getState()); + expect(selected && selected.id).toBe(baseSource.id); + await waitForState(store, state => getSymbols(state, location)); + }); + + it("should change the original the viewing context", async () => { + const { dispatch, getState, cx } = createStore( + mockCommandClient, + {}, + { + getOriginalLocation: async location => ({ ...location, line: 12 }), + getOriginalLocations: async items => items, + getGeneratedRangesForOriginal: async () => [], + getOriginalSourceText: async () => ({ text: "" }), + } + ); + + const baseGenSource = await dispatch( + actions.newGeneratedSource(makeSource("base.js")) + ); + + const baseSources = await dispatch( + actions.newOriginalSources([makeOriginalSource(baseGenSource)]) + ); + await dispatch(actions.selectSource(cx, baseSources[0])); + + await dispatch( + actions.selectSpecificLocation( + cx, + createLocation({ + source: baseSources[0], + line: 1, + }) + ) + ); + + const selected = getSelectedLocation(getState()); + expect(selected && selected.line).toBe(1); + }); + + describe("selectSourceURL", () => { + it("should automatically select a pending source", async () => { + const { dispatch, getState, cx } = createStore(mockCommandClient); + const baseSourceURL = makeSourceURL("base.js"); + await dispatch(actions.selectSourceURL(cx, baseSourceURL)); + + expect(getSelectedSource(getState())).toBe(undefined); + const baseSource = await dispatch( + actions.newGeneratedSource(makeSource("base.js")) + ); + + const selected = getSelectedSource(getState()); + expect(selected && selected.url).toBe(baseSource.url); + }); + }); +}); |