summaryrefslogtreecommitdiffstats
path: root/devtools/client/debugger/src/actions/tests
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/debugger/src/actions/tests')
-rw-r--r--devtools/client/debugger/src/actions/tests/__snapshots__/expressions.spec.js.snap11
-rw-r--r--devtools/client/debugger/src/actions/tests/__snapshots__/pending-breakpoints.spec.js.snap44
-rw-r--r--devtools/client/debugger/src/actions/tests/__snapshots__/preview.spec.js.snap3
-rw-r--r--devtools/client/debugger/src/actions/tests/expressions.spec.js184
-rw-r--r--devtools/client/debugger/src/actions/tests/fixtures/immutable.js2
-rw-r--r--devtools/client/debugger/src/actions/tests/fixtures/reactComponent.js7
-rw-r--r--devtools/client/debugger/src/actions/tests/fixtures/reactFuncComponent.js5
-rw-r--r--devtools/client/debugger/src/actions/tests/fixtures/scopes.js11
-rw-r--r--devtools/client/debugger/src/actions/tests/helpers/breakpoints.js77
-rw-r--r--devtools/client/debugger/src/actions/tests/helpers/mockCommandClient.js49
-rw-r--r--devtools/client/debugger/src/actions/tests/helpers/readFixture.js14
-rw-r--r--devtools/client/debugger/src/actions/tests/navigation.spec.js29
-rw-r--r--devtools/client/debugger/src/actions/tests/pending-breakpoints.spec.js294
-rw-r--r--devtools/client/debugger/src/actions/tests/preview.spec.js217
-rw-r--r--devtools/client/debugger/src/actions/tests/sources-tree.spec.js17
-rw-r--r--devtools/client/debugger/src/actions/tests/tabs.spec.js187
-rw-r--r--devtools/client/debugger/src/actions/tests/ui.spec.js90
17 files changed, 1241 insertions, 0 deletions
diff --git a/devtools/client/debugger/src/actions/tests/__snapshots__/expressions.spec.js.snap b/devtools/client/debugger/src/actions/tests/__snapshots__/expressions.spec.js.snap
new file mode 100644
index 0000000000..f27eb26f50
--- /dev/null
+++ b/devtools/client/debugger/src/actions/tests/__snapshots__/expressions.spec.js.snap
@@ -0,0 +1,11 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`expressions should get the autocomplete matches for the input 1`] = `
+Array [
+ "toLocaleString",
+ "toSource",
+ "toString",
+ "toolbar",
+ "top",
+]
+`;
diff --git a/devtools/client/debugger/src/actions/tests/__snapshots__/pending-breakpoints.spec.js.snap b/devtools/client/debugger/src/actions/tests/__snapshots__/pending-breakpoints.spec.js.snap
new file mode 100644
index 0000000000..741e45d6d8
--- /dev/null
+++ b/devtools/client/debugger/src/actions/tests/__snapshots__/pending-breakpoints.spec.js.snap
@@ -0,0 +1,44 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`initializing when pending breakpoints exist in prefs syncs pending breakpoints 1`] = `
+Object {
+ "http://localhost:8000/examples/bar.js:5:2": Object {
+ "astLocation": Object {
+ "index": 0,
+ "name": undefined,
+ "offset": Object {
+ "line": 5,
+ },
+ },
+ "disabled": false,
+ "generatedLocation": Object {
+ "column": 2,
+ "line": 5,
+ "source": Object {
+ "id": "",
+ "url": "http://localhost:8000/examples/bar.js",
+ },
+ "sourceActor": null,
+ "sourceActorId": undefined,
+ "sourceId": "",
+ "sourceUrl": "http://localhost:8000/examples/bar.js",
+ },
+ "location": Object {
+ "column": 2,
+ "line": 5,
+ "source": Object {
+ "id": "",
+ "url": "http://localhost:8000/examples/bar.js",
+ },
+ "sourceActor": null,
+ "sourceActorId": undefined,
+ "sourceId": "",
+ "sourceUrl": "http://localhost:8000/examples/bar.js",
+ },
+ "options": Object {
+ "condition": null,
+ "hidden": false,
+ },
+ },
+}
+`;
diff --git a/devtools/client/debugger/src/actions/tests/__snapshots__/preview.spec.js.snap b/devtools/client/debugger/src/actions/tests/__snapshots__/preview.spec.js.snap
new file mode 100644
index 0000000000..026bfe4a89
--- /dev/null
+++ b/devtools/client/debugger/src/actions/tests/__snapshots__/preview.spec.js.snap
@@ -0,0 +1,3 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`preview should generate previews 1`] = `null`;
diff --git a/devtools/client/debugger/src/actions/tests/expressions.spec.js b/devtools/client/debugger/src/actions/tests/expressions.spec.js
new file mode 100644
index 0000000000..48b06ebd1a
--- /dev/null
+++ b/devtools/client/debugger/src/actions/tests/expressions.spec.js
@@ -0,0 +1,184 @@
+/* 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 { makeMockFrame } from "../../utils/test-mockup";
+
+const mockThreadFront = {
+ evaluate: (script, { frameId }) =>
+ new Promise((resolve, reject) => {
+ if (!frameId) {
+ resolve("bla");
+ } else {
+ resolve("boo");
+ }
+ }),
+ evaluateExpressions: (inputs, { frameId }) =>
+ Promise.all(
+ inputs.map(
+ input =>
+ new Promise((resolve, reject) => {
+ if (!frameId) {
+ resolve("bla");
+ } else {
+ resolve("boo");
+ }
+ })
+ )
+ ),
+ getFrameScopes: async () => {},
+ getFrames: async () => [],
+ sourceContents: () => ({ source: "", contentType: "text/javascript" }),
+ getSourceActorBreakpointPositions: async () => ({}),
+ getSourceActorBreakableLines: async () => [],
+ autocomplete: () => {
+ return new Promise(resolve => {
+ resolve({
+ from: "foo",
+ matches: ["toLocaleString", "toSource", "toString", "toolbar", "top"],
+ matchProp: "to",
+ });
+ });
+ },
+};
+
+describe("expressions", () => {
+ it("should add an expression", async () => {
+ const { dispatch, getState, cx } = createStore(mockThreadFront);
+
+ await dispatch(actions.addExpression(cx, "foo"));
+ expect(selectors.getExpressions(getState())).toHaveLength(1);
+ });
+
+ it("should not add empty expressions", () => {
+ const { dispatch, getState, cx } = createStore(mockThreadFront);
+
+ dispatch(actions.addExpression(cx, undefined));
+ dispatch(actions.addExpression(cx, ""));
+ expect(selectors.getExpressions(getState())).toHaveLength(0);
+ });
+
+ it("should not add invalid expressions", async () => {
+ const { dispatch, getState, cx } = createStore(mockThreadFront);
+ await dispatch(actions.addExpression(cx, "foo#"));
+ const state = getState();
+ expect(selectors.getExpressions(state)).toHaveLength(0);
+ expect(selectors.getExpressionError(state)).toBe(true);
+ });
+
+ it("should update an expression", async () => {
+ const { dispatch, getState, cx } = createStore(mockThreadFront);
+
+ await dispatch(actions.addExpression(cx, "foo"));
+ const expression = selectors.getExpression(getState(), "foo");
+ if (!expression) {
+ throw new Error("expression must exist");
+ }
+
+ await dispatch(actions.updateExpression(cx, "bar", expression));
+ const bar = selectors.getExpression(getState(), "bar");
+
+ expect(bar && bar.input).toBe("bar");
+ });
+
+ it("should not update an expression w/ invalid code", async () => {
+ const { dispatch, getState, cx } = createStore(mockThreadFront);
+
+ await dispatch(actions.addExpression(cx, "foo"));
+ const expression = selectors.getExpression(getState(), "foo");
+ if (!expression) {
+ throw new Error("expression must exist");
+ }
+ await dispatch(actions.updateExpression(cx, "#bar", expression));
+ expect(selectors.getExpression(getState(), "bar")).toBeUndefined();
+ });
+
+ it("should delete an expression", async () => {
+ const { dispatch, getState, cx } = createStore(mockThreadFront);
+
+ await dispatch(actions.addExpression(cx, "foo"));
+ await dispatch(actions.addExpression(cx, "bar"));
+ expect(selectors.getExpressions(getState())).toHaveLength(2);
+
+ const expression = selectors.getExpression(getState(), "foo");
+
+ if (!expression) {
+ throw new Error("expression must exist");
+ }
+
+ const bar = selectors.getExpression(getState(), "bar");
+ dispatch(actions.deleteExpression(expression));
+ expect(selectors.getExpressions(getState())).toHaveLength(1);
+ expect(bar && bar.input).toBe("bar");
+ });
+
+ it("should evaluate expressions global scope", async () => {
+ const { dispatch, getState, cx } = createStore(mockThreadFront);
+ await dispatch(actions.addExpression(cx, "foo"));
+ await dispatch(actions.addExpression(cx, "bar"));
+
+ let foo = selectors.getExpression(getState(), "foo");
+ let bar = selectors.getExpression(getState(), "bar");
+ expect(foo && foo.value).toBe("bla");
+ expect(bar && bar.value).toBe("bla");
+
+ await dispatch(actions.evaluateExpressions(cx));
+ foo = selectors.getExpression(getState(), "foo");
+ bar = selectors.getExpression(getState(), "bar");
+ expect(foo && foo.value).toBe("bla");
+ expect(bar && bar.value).toBe("bla");
+ });
+
+ it("should evaluate expressions in specific scope", async () => {
+ const { dispatch, getState } = createStore(mockThreadFront);
+ await createFrames(getState, dispatch);
+
+ const cx = selectors.getThreadContext(getState());
+ await dispatch(actions.newGeneratedSource(makeSource("source")));
+ await dispatch(actions.addExpression(cx, "foo"));
+ await dispatch(actions.addExpression(cx, "bar"));
+
+ let foo = selectors.getExpression(getState(), "foo");
+ let bar = selectors.getExpression(getState(), "bar");
+ expect(foo && foo.value).toBe("boo");
+ expect(bar && bar.value).toBe("boo");
+
+ await dispatch(actions.evaluateExpressions(cx));
+ foo = selectors.getExpression(getState(), "foo");
+ bar = selectors.getExpression(getState(), "bar");
+ expect(foo && foo.value).toBe("boo");
+ expect(bar && bar.value).toBe("boo");
+ });
+
+ it("should get the autocomplete matches for the input", async () => {
+ const { cx, dispatch, getState } = createStore(mockThreadFront);
+ await dispatch(actions.autocomplete(cx, "to", 2));
+ expect(selectors.getAutocompleteMatchset(getState())).toMatchSnapshot();
+ });
+});
+
+async function createFrames(getState, dispatch) {
+ const frame = makeMockFrame();
+ await dispatch(actions.newGeneratedSource(makeSource("example.js")));
+ await dispatch(actions.newGeneratedSource(makeSource("source")));
+
+ await dispatch(
+ actions.paused({
+ thread: "FakeThread",
+ frame,
+ frames: [frame],
+ why: { type: "just because" },
+ })
+ );
+
+ await dispatch(
+ actions.selectFrame(selectors.getThreadContext(getState()), frame)
+ );
+}
diff --git a/devtools/client/debugger/src/actions/tests/fixtures/immutable.js b/devtools/client/debugger/src/actions/tests/fixtures/immutable.js
new file mode 100644
index 0000000000..e8ac7fb233
--- /dev/null
+++ b/devtools/client/debugger/src/actions/tests/fixtures/immutable.js
@@ -0,0 +1,2 @@
+
+const m = Immutable.Map({a: 2})
diff --git a/devtools/client/debugger/src/actions/tests/fixtures/reactComponent.js b/devtools/client/debugger/src/actions/tests/fixtures/reactComponent.js
new file mode 100644
index 0000000000..526c852d99
--- /dev/null
+++ b/devtools/client/debugger/src/actions/tests/fixtures/reactComponent.js
@@ -0,0 +1,7 @@
+import React, { Component } from "react";
+
+class FixtureComponent extends Component {
+ render() {
+ return null;
+ }
+}
diff --git a/devtools/client/debugger/src/actions/tests/fixtures/reactFuncComponent.js b/devtools/client/debugger/src/actions/tests/fixtures/reactFuncComponent.js
new file mode 100644
index 0000000000..3103161fe0
--- /dev/null
+++ b/devtools/client/debugger/src/actions/tests/fixtures/reactFuncComponent.js
@@ -0,0 +1,5 @@
+import React, { Component } from "react";
+
+export default FixtureComponent = (props) => {
+ return <div>props.a</div>;
+}
diff --git a/devtools/client/debugger/src/actions/tests/fixtures/scopes.js b/devtools/client/debugger/src/actions/tests/fixtures/scopes.js
new file mode 100644
index 0000000000..3a38097f5e
--- /dev/null
+++ b/devtools/client/debugger/src/actions/tests/fixtures/scopes.js
@@ -0,0 +1,11 @@
+// Program Scope
+
+function outer() {
+ function inner() {
+ const x = 1;
+ }
+
+ const declaration = function() {
+ const x = 1;
+ };
+}
diff --git a/devtools/client/debugger/src/actions/tests/helpers/breakpoints.js b/devtools/client/debugger/src/actions/tests/helpers/breakpoints.js
new file mode 100644
index 0000000000..e0279e5fc1
--- /dev/null
+++ b/devtools/client/debugger/src/actions/tests/helpers/breakpoints.js
@@ -0,0 +1,77 @@
+/* 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 { createLocation } from "../../../utils/location";
+
+export function mockPendingBreakpoint(overrides = {}) {
+ const { sourceUrl, line, column, condition, disabled, hidden } = overrides;
+ return {
+ location: createLocation({
+ source: {
+ id: "",
+ url: sourceUrl || "http://localhost:8000/examples/bar.js",
+ },
+ sourceId: "",
+ sourceUrl: sourceUrl || "http://localhost:8000/examples/bar.js",
+ line: line || 5,
+ column: column || 1,
+ }),
+ generatedLocation: createLocation({
+ source: {
+ id: "",
+ url: sourceUrl || "http://localhost:8000/examples/bar.js",
+ },
+ sourceId: "",
+ sourceUrl: sourceUrl || "http://localhost:8000/examples/bar.js",
+ line: line || 5,
+ column: column || 1,
+ }),
+ astLocation: {
+ name: undefined,
+ offset: {
+ line: line || 5,
+ },
+ index: 0,
+ },
+ options: {
+ condition: condition || null,
+ hidden: hidden || false,
+ },
+ disabled: disabled || false,
+ };
+}
+
+export function generateBreakpoint(filename, line = 5, column = 0) {
+ return {
+ id: "breakpoint",
+ originalText: "",
+ text: "",
+ location: createLocation({
+ source: {
+ url: `http://localhost:8000/examples/${filename}`,
+ id: filename,
+ },
+ sourceUrl: `http://localhost:8000/examples/${filename}`,
+ sourceId: filename,
+ line,
+ column,
+ }),
+ generatedLocation: createLocation({
+ source: {
+ url: `http://localhost:8000/examples/${filename}`,
+ id: filename,
+ },
+ sourceUrl: `http://localhost:8000/examples/${filename}`,
+ sourceId: filename,
+ line,
+ column,
+ }),
+ astLocation: undefined,
+ options: {
+ condition: "",
+ hidden: false,
+ },
+ disabled: false,
+ };
+}
diff --git a/devtools/client/debugger/src/actions/tests/helpers/mockCommandClient.js b/devtools/client/debugger/src/actions/tests/helpers/mockCommandClient.js
new file mode 100644
index 0000000000..38dd55c274
--- /dev/null
+++ b/devtools/client/debugger/src/actions/tests/helpers/mockCommandClient.js
@@ -0,0 +1,49 @@
+/* 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/>. */
+
+export function createSource(name, code) {
+ name = name.replace(/\..*$/, "");
+ return {
+ source: code || `function ${name}() {\n return ${name} \n}`,
+ contentType: "text/javascript",
+ };
+}
+
+const sources = [
+ "a",
+ "b",
+ "foo",
+ "bar",
+ "foo1",
+ "foo2",
+ "a.js",
+ "baz.js",
+ "foobar.js",
+ "barfoo.js",
+ "foo.js",
+ "bar.js",
+ "base.js",
+ "bazz.js",
+ "jquery.js",
+];
+
+export const mockCommandClient = {
+ sourceContents: function ({ source }) {
+ return new Promise((resolve, reject) => {
+ if (sources.includes(source)) {
+ resolve(createSource(source));
+ }
+
+ reject(`unknown source: ${source}`);
+ });
+ },
+ setBreakpoint: async () => {},
+ removeBreakpoint: _id => Promise.resolve(),
+ threadFront: async () => {},
+ getFrameScopes: async () => {},
+ getFrames: async () => [],
+ evaluateExpressions: async () => {},
+ getSourceActorBreakpointPositions: async () => ({}),
+ getSourceActorBreakableLines: async () => [],
+};
diff --git a/devtools/client/debugger/src/actions/tests/helpers/readFixture.js b/devtools/client/debugger/src/actions/tests/helpers/readFixture.js
new file mode 100644
index 0000000000..6c23641226
--- /dev/null
+++ b/devtools/client/debugger/src/actions/tests/helpers/readFixture.js
@@ -0,0 +1,14 @@
+/* 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 fs from "fs";
+import path from "path";
+
+export default function readFixture(name) {
+ const text = fs.readFileSync(
+ path.join(__dirname, `../fixtures/${name}`),
+ "utf8"
+ );
+ return text;
+}
diff --git a/devtools/client/debugger/src/actions/tests/navigation.spec.js b/devtools/client/debugger/src/actions/tests/navigation.spec.js
new file mode 100644
index 0000000000..e2572fde80
--- /dev/null
+++ b/devtools/client/debugger/src/actions/tests/navigation.spec.js
@@ -0,0 +1,29 @@
+/* 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 { createStore, selectors, actions } from "../../utils/test-head";
+
+jest.mock("../../utils/editor");
+
+const { getActiveSearch } = selectors;
+
+const threadFront = {
+ sourceContents: async () => ({
+ source: "function foo1() {\n const foo = 5; return foo;\n}",
+ contentType: "text/javascript",
+ }),
+ getSourceActorBreakpointPositions: async () => ({}),
+ getSourceActorBreakableLines: async () => [],
+};
+
+describe("navigation", () => {
+ it("navigation removes activeSearch 'file' value", async () => {
+ const { dispatch, getState } = createStore(threadFront);
+ dispatch(actions.setActiveSearch("file"));
+ expect(getActiveSearch(getState())).toBe("file");
+
+ await dispatch(actions.willNavigate("will-navigate"));
+ expect(getActiveSearch(getState())).toBe(null);
+ });
+});
diff --git a/devtools/client/debugger/src/actions/tests/pending-breakpoints.spec.js b/devtools/client/debugger/src/actions/tests/pending-breakpoints.spec.js
new file mode 100644
index 0000000000..2baa17a79f
--- /dev/null
+++ b/devtools/client/debugger/src/actions/tests/pending-breakpoints.spec.js
@@ -0,0 +1,294 @@
+/* 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/>. */
+
+// TODO: we would like to mock this in the local tests
+import {
+ generateBreakpoint,
+ mockPendingBreakpoint,
+} from "./helpers/breakpoints.js";
+
+import { mockCommandClient } from "./helpers/mockCommandClient";
+import { asyncStore } from "../../utils/prefs";
+
+function loadInitialState(opts = {}) {
+ const mockedPendingBreakpoint = mockPendingBreakpoint({ ...opts, column: 2 });
+ const l = mockedPendingBreakpoint.location;
+ const id = `${l.sourceUrl}:${l.line}:${l.column}`;
+ asyncStore.pendingBreakpoints = { [id]: mockedPendingBreakpoint };
+
+ return { pendingBreakpoints: asyncStore.pendingBreakpoints };
+}
+
+jest.mock("../../utils/prefs", () => ({
+ prefs: {
+ clientSourceMapsEnabled: true,
+ expressions: [],
+ },
+ asyncStore: {
+ pendingBreakpoints: {},
+ },
+ clear: jest.fn(),
+ features: {
+ inlinePreview: true,
+ },
+}));
+
+import {
+ createStore,
+ selectors,
+ actions,
+ makeSource,
+ makeSourceURL,
+ waitForState,
+} from "../../utils/test-head";
+
+import sourceMapLoader from "devtools/client/shared/source-map-loader/index";
+
+function mockClient(bpPos = {}) {
+ return {
+ ...mockCommandClient,
+ setSkipPausing: jest.fn(),
+ getSourceActorBreakpointPositions: async () => bpPos,
+ getSourceActorBreakableLines: async () => [],
+ };
+}
+
+function mockSourceMaps() {
+ return {
+ ...sourceMapLoader,
+ getOriginalSourceText: async id => ({
+ id,
+ text: "",
+ contentType: "text/javascript",
+ }),
+ getGeneratedRangesForOriginal: async () => [
+ { start: { line: 0, column: 0 }, end: { line: 10, column: 10 } },
+ ],
+ getOriginalLocations: async items => items,
+ };
+}
+
+describe("when adding breakpoints", () => {
+ it("a corresponding pending breakpoint should be added", async () => {
+ const { dispatch, getState, cx } = createStore(
+ mockClient({ 5: [1] }),
+ loadInitialState(),
+ mockSourceMaps()
+ );
+
+ const source = await dispatch(
+ actions.newGeneratedSource(makeSource("foo.js"))
+ );
+ const sourceActor = selectors.getFirstSourceActorForGeneratedSource(
+ getState(),
+ source.id
+ );
+
+ await dispatch(actions.loadGeneratedSourceText({ cx, sourceActor }));
+
+ const bp = generateBreakpoint("foo.js", 5, 1);
+
+ await dispatch(actions.addBreakpoint(cx, bp.location));
+
+ expect(selectors.getPendingBreakpointList(getState())).toHaveLength(2);
+ });
+});
+
+describe("initializing when pending breakpoints exist in prefs", () => {
+ it("syncs pending breakpoints", async () => {
+ const { getState } = createStore(
+ mockClient({ 5: [0] }),
+ loadInitialState(),
+ mockSourceMaps()
+ );
+ const bps = selectors.getPendingBreakpoints(getState());
+ expect(bps).toMatchSnapshot();
+ });
+
+ it("re-adding breakpoints update existing pending breakpoints", async () => {
+ const { dispatch, getState, cx } = createStore(
+ mockClient({ 5: [1, 2] }),
+ loadInitialState(),
+ mockSourceMaps()
+ );
+ const bar = generateBreakpoint("bar.js", 5, 1);
+
+ const source = await dispatch(
+ actions.newGeneratedSource(makeSource("bar.js"))
+ );
+ const sourceActor = selectors.getFirstSourceActorForGeneratedSource(
+ getState(),
+ source.id
+ );
+
+ await dispatch(actions.loadGeneratedSourceText({ cx, sourceActor }));
+ await dispatch(actions.addBreakpoint(cx, bar.location));
+
+ const bps = selectors.getPendingBreakpointList(getState());
+ expect(bps).toHaveLength(2);
+ });
+
+ it("adding bps doesn't remove existing pending breakpoints", async () => {
+ const { dispatch, getState, cx } = createStore(
+ mockClient({ 5: [0] }),
+ loadInitialState(),
+ mockSourceMaps()
+ );
+ const bp = generateBreakpoint("foo.js");
+
+ const source = await dispatch(
+ actions.newGeneratedSource(makeSource("foo.js"))
+ );
+ const sourceActor = selectors.getFirstSourceActorForGeneratedSource(
+ getState(),
+ source.id
+ );
+
+ await dispatch(actions.loadGeneratedSourceText({ cx, sourceActor }));
+
+ await dispatch(actions.addBreakpoint(cx, bp.location));
+
+ const bps = selectors.getPendingBreakpointList(getState());
+ expect(bps).toHaveLength(2);
+ });
+});
+
+describe("initializing with disabled pending breakpoints in prefs", () => {
+ it("syncs breakpoints with pending breakpoints", async () => {
+ const store = createStore(
+ mockClient({ 5: [2] }),
+ loadInitialState({ disabled: true }),
+ mockSourceMaps()
+ );
+
+ const { getState, dispatch, cx } = store;
+
+ const source = await dispatch(
+ actions.newGeneratedSource(makeSource("bar.js"))
+ );
+ const sourceActor = selectors.getFirstSourceActorForGeneratedSource(
+ getState(),
+ source.id
+ );
+
+ await dispatch(actions.loadGeneratedSourceText({ cx, sourceActor }));
+
+ await waitForState(store, state => {
+ const bps = selectors.getBreakpointsForSource(state, source.id);
+ return bps && !!Object.values(bps).length;
+ });
+
+ const bp = selectors.getBreakpointsList(getState()).find(({ location }) => {
+ return (
+ location.line == 5 &&
+ location.column == 2 &&
+ location.sourceId == source.id
+ );
+ });
+
+ if (!bp) {
+ throw new Error("no bp");
+ }
+ expect(bp.location.sourceId).toEqual(source.id);
+ expect(bp.disabled).toEqual(true);
+ });
+});
+
+describe("adding sources", () => {
+ it("corresponding breakpoints are added for a single source", async () => {
+ const store = createStore(
+ mockClient({ 5: [2] }),
+ loadInitialState({ disabled: true }),
+ mockSourceMaps()
+ );
+ const { getState, dispatch, cx } = store;
+
+ expect(selectors.getBreakpointCount(getState())).toEqual(0);
+
+ const source = await dispatch(
+ actions.newGeneratedSource(makeSource("bar.js"))
+ );
+ const sourceActor = selectors.getFirstSourceActorForGeneratedSource(
+ getState(),
+ source.id
+ );
+
+ await dispatch(actions.loadGeneratedSourceText({ cx, sourceActor }));
+
+ await waitForState(store, state => selectors.getBreakpointCount(state) > 0);
+
+ expect(selectors.getBreakpointCount(getState())).toEqual(1);
+ });
+
+ it("corresponding breakpoints are added to the original source", async () => {
+ const sourceURL = makeSourceURL("bar.js");
+ const store = createStore(mockClient({ 5: [2] }), loadInitialState(), {
+ getOriginalURLs: async source => [
+ {
+ id: sourceMapLoader.generatedToOriginalId(source.id, sourceURL),
+ url: sourceURL,
+ },
+ ],
+ getOriginalSourceText: async () => ({ text: "" }),
+ getGeneratedLocation: async location => location,
+ getOriginalLocation: async location => location,
+ getGeneratedRangesForOriginal: async () => [
+ { start: { line: 0, column: 0 }, end: { line: 10, column: 10 } },
+ ],
+ getOriginalLocations: async items =>
+ items.map(item => ({
+ ...item,
+ sourceId: sourceMapLoader.generatedToOriginalId(
+ item.sourceId,
+ sourceURL
+ ),
+ })),
+ });
+
+ const { getState, dispatch } = store;
+
+ expect(selectors.getBreakpointCount(getState())).toEqual(0);
+
+ await dispatch(
+ actions.newGeneratedSource(makeSource("bar.js", { sourceMapURL: "foo" }))
+ );
+
+ await waitForState(store, state => selectors.getBreakpointCount(state) > 0);
+
+ expect(selectors.getBreakpointCount(getState())).toEqual(1);
+ });
+
+ it("add corresponding breakpoints for multiple sources", async () => {
+ const store = createStore(
+ mockClient({ 5: [2] }),
+ loadInitialState({ disabled: true }),
+ mockSourceMaps()
+ );
+ const { getState, dispatch, cx } = store;
+
+ expect(selectors.getBreakpointCount(getState())).toEqual(0);
+
+ const [source1, source2] = await dispatch(
+ actions.newGeneratedSources([makeSource("bar.js"), makeSource("foo.js")])
+ );
+ const sourceActor1 = selectors.getFirstSourceActorForGeneratedSource(
+ getState(),
+ source1.id
+ );
+ const sourceActor2 = selectors.getFirstSourceActorForGeneratedSource(
+ getState(),
+ source2.id
+ );
+
+ await dispatch(
+ actions.loadGeneratedSourceText({ cx, sourceActor: sourceActor1 })
+ );
+ await dispatch(
+ actions.loadGeneratedSourceText({ cx, sourceActor: sourceActor2 })
+ );
+
+ await waitForState(store, state => selectors.getBreakpointCount(state) > 0);
+ expect(selectors.getBreakpointCount(getState())).toEqual(1);
+ });
+});
diff --git a/devtools/client/debugger/src/actions/tests/preview.spec.js b/devtools/client/debugger/src/actions/tests/preview.spec.js
new file mode 100644
index 0000000000..3b6c9c23ac
--- /dev/null
+++ b/devtools/client/debugger/src/actions/tests/preview.spec.js
@@ -0,0 +1,217 @@
+/* eslint max-nested-callbacks: ["error", 6] */
+/* 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 {
+ createStore,
+ selectors,
+ actions,
+ makeSource,
+ makeFrame,
+ waitForState,
+ waitATick,
+} from "../../utils/test-head";
+import { createLocation } from "../../utils/location";
+
+function waitForPreview(store, expression) {
+ return waitForState(store, state => {
+ const preview = selectors.getPreview(state);
+ return preview && preview.expression == expression;
+ });
+}
+
+function mockThreadFront(overrides) {
+ return {
+ evaluate: async () => ({ result: {} }),
+ getFrameScopes: async () => {},
+ getFrames: async () => [],
+ sourceContents: async () => ({
+ source: "",
+ contentType: "text/javascript",
+ }),
+ getSourceActorBreakpointPositions: async () => ({}),
+ getSourceActorBreakableLines: async () => [],
+ evaluateExpressions: async () => [],
+ loadObjectProperties: async () => ({}),
+ ...overrides,
+ };
+}
+
+function dispatchSetPreview(dispatch, context, expression, target) {
+ return dispatch(
+ actions.setPreview(
+ context,
+ expression,
+ {
+ start: { url: "foo.js", line: 1, column: 2 },
+ end: { url: "foo.js", line: 1, column: 5 },
+ },
+ { line: 2, column: 3 },
+ target.getBoundingClientRect(),
+ target
+ )
+ );
+}
+
+async function pause(store, client) {
+ const { dispatch, cx, getState } = store;
+ const source = makeSource("base.js");
+ const base = await dispatch(actions.newGeneratedSource(source));
+ const sourceActor = selectors.getFirstSourceActorForGeneratedSource(
+ getState(),
+ base.id
+ );
+
+ await dispatch(actions.selectSource(cx, base, sourceActor));
+ const location = createLocation({ source: base, sourceActor });
+ await waitForState(store, state => selectors.getSymbols(state, location));
+
+ const { thread } = cx;
+ const frames = [makeFrame({ id: "frame1", sourceId: base.id, thread })];
+ client.getFrames = async () => frames;
+
+ await dispatch(
+ actions.paused({
+ thread,
+ frame: frames[0],
+ loadedObjects: [],
+ why: { type: "debuggerStatement" },
+ })
+ );
+}
+
+describe("preview", () => {
+ it("should generate previews", async () => {
+ const store = createStore(mockThreadFront());
+ const { dispatch, getState, cx } = store;
+ const source = makeSource("base.js");
+ const base = await dispatch(actions.newGeneratedSource(source));
+
+ await dispatch(actions.selectSource(cx, base));
+ const sourceActor = selectors.getFirstSourceActorForGeneratedSource(
+ getState(),
+ base.id
+ );
+ const location = createLocation({ source: base, sourceActor });
+
+ await waitForState(store, state => selectors.getSymbols(state, location));
+ const frames = [makeFrame({ id: "f1", sourceId: base.id })];
+
+ await dispatch(
+ actions.paused({
+ thread: store.cx.thread,
+ frame: frames[0],
+ frames,
+ loadedObjects: [],
+ why: { type: "debuggerStatement" },
+ })
+ );
+
+ const newCx = selectors.getContext(getState());
+ const firstTarget = document.createElement("div");
+
+ dispatchSetPreview(dispatch, newCx, "foo", firstTarget);
+
+ expect(selectors.getPreview(getState())).toMatchSnapshot();
+ });
+
+ // When a 2nd setPreview is called before a 1st setPreview dispatches
+ // and the 2nd setPreview has not dispatched yet,
+ // the first setPreview should not finish dispatching
+ it("queued previews (w/ the 1st finishing first)", async () => {
+ let resolveFirst, resolveSecond;
+ const promises = [
+ new Promise(resolve => {
+ resolveFirst = resolve;
+ }),
+ new Promise(resolve => {
+ resolveSecond = resolve;
+ }),
+ ];
+
+ const client = mockThreadFront({
+ loadObjectProperties: () => promises.shift(),
+ });
+ const store = createStore(client);
+
+ const { dispatch, getState } = store;
+ await pause(store, client);
+
+ const newCx = selectors.getContext(getState());
+ const firstTarget = document.createElement("div");
+ const secondTarget = document.createElement("div");
+
+ // Start the dispatch of the first setPreview. At this point, it will not
+ // finish execution until we resolve the firstSetPreview
+ dispatchSetPreview(dispatch, newCx, "firstSetPreview", firstTarget);
+
+ // Start the dispatch of the second setPreview. At this point, it will not
+ // finish execution until we resolve the secondSetPreview
+ dispatchSetPreview(dispatch, newCx, "secondSetPreview", secondTarget);
+
+ let fail = false;
+
+ resolveFirst();
+ waitForPreview(store, "firstSetPreview").then(() => {
+ fail = true;
+ });
+
+ resolveSecond();
+ await waitForPreview(store, "secondSetPreview");
+ expect(fail).toEqual(false);
+
+ const preview = selectors.getPreview(getState());
+ expect(preview && preview.expression).toEqual("secondSetPreview");
+ });
+
+ // When a 2nd setPreview is called before a 1st setPreview dispatches
+ // and the 2nd setPreview has dispatched,
+ // the first setPreview should not finish dispatching
+ it("queued previews (w/ the 2nd finishing first)", async () => {
+ let resolveFirst, resolveSecond;
+ const promises = [
+ new Promise(resolve => {
+ resolveFirst = resolve;
+ }),
+ new Promise(resolve => {
+ resolveSecond = resolve;
+ }),
+ ];
+
+ const client = mockThreadFront({
+ loadObjectProperties: () => promises.shift(),
+ });
+ const store = createStore(client);
+
+ const { dispatch, getState } = store;
+ await pause(store, client);
+
+ const cx = selectors.getThreadContext(getState());
+ const firstTarget = document.createElement("div");
+ const secondTarget = document.createElement("div");
+
+ // Start the dispatch of the first setPreview. At this point, it will not
+ // finish execution until we resolve the firstSetPreview
+ dispatchSetPreview(dispatch, cx, "firstSetPreview", firstTarget);
+
+ // Start the dispatch of the second setPreview. At this point, it will not
+ // finish execution until we resolve the secondSetPreview
+ dispatchSetPreview(dispatch, cx, "secondSetPreview", secondTarget);
+
+ let fail = false;
+
+ resolveSecond();
+ await waitForPreview(store, "secondSetPreview");
+
+ resolveFirst();
+ waitForPreview(store, "firstSetPreview").then(() => {
+ fail = true;
+ });
+
+ await waitATick(() => expect(fail).toEqual(false));
+
+ const preview = selectors.getPreview(getState());
+ expect(preview && preview.expression).toEqual("secondSetPreview");
+ });
+});
diff --git a/devtools/client/debugger/src/actions/tests/sources-tree.spec.js b/devtools/client/debugger/src/actions/tests/sources-tree.spec.js
new file mode 100644
index 0000000000..916b2d015b
--- /dev/null
+++ b/devtools/client/debugger/src/actions/tests/sources-tree.spec.js
@@ -0,0 +1,17 @@
+/* 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 } from "../../utils/test-head";
+const { getExpandedState } = selectors;
+
+describe("source tree", () => {
+ it("should set the expanded state", () => {
+ const { dispatch, getState } = createStore();
+ const expandedState = new Set(["foo", "bar"]);
+
+ expect(getExpandedState(getState())).toEqual(new Set([]));
+ dispatch(actions.setExpandedState(expandedState));
+ expect(getExpandedState(getState())).toEqual(expandedState);
+ });
+});
diff --git a/devtools/client/debugger/src/actions/tests/tabs.spec.js b/devtools/client/debugger/src/actions/tests/tabs.spec.js
new file mode 100644
index 0000000000..419e5b7e60
--- /dev/null
+++ b/devtools/client/debugger/src/actions/tests/tabs.spec.js
@@ -0,0 +1,187 @@
+/* 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";
+const { getSelectedSource, getSourceTabs } = selectors;
+import { createLocation } from "../../utils/location";
+
+import { mockCommandClient } from "./helpers/mockCommandClient";
+
+describe("closing tabs", () => {
+ it("closing a tab", async () => {
+ const { dispatch, getState, cx } = createStore(mockCommandClient);
+
+ const fooSource = await dispatch(
+ actions.newGeneratedSource(makeSource("foo.js"))
+ );
+ await dispatch(
+ actions.selectLocation(cx, createLocation({ source: fooSource, line: 1 }))
+ );
+ dispatch(actions.closeTab(cx, fooSource));
+
+ expect(getSelectedSource(getState())).toBe(undefined);
+ expect(getSourceTabs(getState())).toHaveLength(0);
+ });
+
+ it("closing the inactive tab", async () => {
+ const { dispatch, getState, cx } = createStore(mockCommandClient);
+
+ const fooSource = await dispatch(
+ actions.newGeneratedSource(makeSource("foo.js"))
+ );
+ const barSource = await dispatch(
+ actions.newGeneratedSource(makeSource("bar.js"))
+ );
+ await dispatch(
+ actions.selectLocation(cx, createLocation({ source: fooSource, line: 1 }))
+ );
+ await dispatch(
+ actions.selectLocation(cx, createLocation({ source: barSource, line: 1 }))
+ );
+ dispatch(actions.closeTab(cx, fooSource));
+
+ const selected = getSelectedSource(getState());
+ expect(selected && selected.id).toBe("bar.js");
+ expect(getSourceTabs(getState())).toHaveLength(1);
+ });
+
+ it("closing the only tab", async () => {
+ const { dispatch, getState, cx } = createStore(mockCommandClient);
+
+ const fooSource = await dispatch(
+ actions.newGeneratedSource(makeSource("foo.js"))
+ );
+ await dispatch(
+ actions.selectLocation(cx, createLocation({ source: fooSource, line: 1 }))
+ );
+ dispatch(actions.closeTab(cx, fooSource));
+
+ expect(getSelectedSource(getState())).toBe(undefined);
+ expect(getSourceTabs(getState())).toHaveLength(0);
+ });
+
+ it("closing the active tab", async () => {
+ const { dispatch, getState, cx } = createStore(mockCommandClient);
+
+ const fooSource = await dispatch(
+ actions.newGeneratedSource(makeSource("foo.js"))
+ );
+ const barSource = await dispatch(
+ actions.newGeneratedSource(makeSource("bar.js"))
+ );
+ await dispatch(
+ actions.selectLocation(cx, createLocation({ source: fooSource, line: 1 }))
+ );
+ await dispatch(
+ actions.selectLocation(cx, createLocation({ source: barSource, line: 1 }))
+ );
+ await dispatch(actions.closeTab(cx, barSource));
+
+ const selected = getSelectedSource(getState());
+ expect(selected && selected.id).toBe("foo.js");
+ expect(getSourceTabs(getState())).toHaveLength(1);
+ });
+
+ it("closing many inactive tabs", async () => {
+ const { dispatch, getState, cx } = createStore(mockCommandClient);
+
+ const fooSource = await dispatch(
+ actions.newGeneratedSource(makeSource("foo.js"))
+ );
+ const barSource = await dispatch(
+ actions.newGeneratedSource(makeSource("bar.js"))
+ );
+ const bazzSource = await dispatch(
+ actions.newGeneratedSource(makeSource("bazz.js"))
+ );
+ await dispatch(
+ actions.selectLocation(cx, createLocation({ source: fooSource, line: 1 }))
+ );
+ await dispatch(
+ actions.selectLocation(cx, createLocation({ source: barSource, line: 1 }))
+ );
+ await dispatch(
+ actions.selectLocation(
+ cx,
+ createLocation({ source: bazzSource, line: 1 })
+ )
+ );
+
+ const tabs = [
+ "http://localhost:8000/examples/foo.js",
+ "http://localhost:8000/examples/bar.js",
+ ];
+ dispatch(actions.closeTabs(cx, tabs));
+
+ const selected = getSelectedSource(getState());
+ expect(selected && selected.id).toBe("bazz.js");
+ expect(getSourceTabs(getState())).toHaveLength(1);
+ });
+
+ it("closing many tabs including the active tab", async () => {
+ const { dispatch, getState, cx } = createStore(mockCommandClient);
+
+ const fooSource = await dispatch(
+ actions.newGeneratedSource(makeSource("foo.js"))
+ );
+ const barSource = await dispatch(
+ actions.newGeneratedSource(makeSource("bar.js"))
+ );
+ const bazzSource = await dispatch(
+ actions.newGeneratedSource(makeSource("bazz.js"))
+ );
+ await dispatch(
+ actions.selectLocation(cx, createLocation({ source: fooSource, line: 1 }))
+ );
+ await dispatch(
+ actions.selectLocation(cx, createLocation({ source: barSource, line: 1 }))
+ );
+ await dispatch(
+ actions.selectLocation(
+ cx,
+ createLocation({ source: bazzSource, line: 1 })
+ )
+ );
+ const tabs = [
+ "http://localhost:8000/examples/bar.js",
+ "http://localhost:8000/examples/bazz.js",
+ ];
+ await dispatch(actions.closeTabs(cx, tabs));
+
+ const selected = getSelectedSource(getState());
+ expect(selected && selected.id).toBe("foo.js");
+ expect(getSourceTabs(getState())).toHaveLength(1);
+ });
+
+ it("closing all the tabs", async () => {
+ const { dispatch, getState, cx } = createStore(mockCommandClient);
+
+ const fooSource = await dispatch(
+ actions.newGeneratedSource(makeSource("foo.js"))
+ );
+ const barSource = await dispatch(
+ actions.newGeneratedSource(makeSource("bar.js"))
+ );
+ await dispatch(
+ actions.selectLocation(cx, createLocation({ source: fooSource, line: 1 }))
+ );
+ await dispatch(
+ actions.selectLocation(cx, createLocation({ source: barSource, line: 1 }))
+ );
+ await dispatch(
+ actions.closeTabs(cx, [
+ "http://localhost:8000/examples/foo.js",
+ "http://localhost:8000/examples/bar.js",
+ ])
+ );
+
+ expect(getSelectedSource(getState())).toBe(undefined);
+ expect(getSourceTabs(getState())).toHaveLength(0);
+ });
+});
diff --git a/devtools/client/debugger/src/actions/tests/ui.spec.js b/devtools/client/debugger/src/actions/tests/ui.spec.js
new file mode 100644
index 0000000000..0e13681a12
--- /dev/null
+++ b/devtools/client/debugger/src/actions/tests/ui.spec.js
@@ -0,0 +1,90 @@
+/* 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 {
+ createStore,
+ selectors,
+ actions,
+ makeSource,
+} from "../../utils/test-head";
+import { createLocation } from "../../utils/location";
+import { mockCommandClient } from "./helpers/mockCommandClient";
+
+const {
+ getActiveSearch,
+ getFrameworkGroupingState,
+ getPaneCollapse,
+ getHighlightedLineRangeForSelectedSource,
+} = selectors;
+
+describe("ui", () => {
+ it("should toggle the visible state of file search", () => {
+ const { dispatch, getState } = createStore();
+ expect(getActiveSearch(getState())).toBe(null);
+ dispatch(actions.setActiveSearch("file"));
+ expect(getActiveSearch(getState())).toBe("file");
+ });
+
+ it("should close file search", () => {
+ const { dispatch, getState } = createStore();
+ expect(getActiveSearch(getState())).toBe(null);
+ dispatch(actions.setActiveSearch("file"));
+ dispatch(actions.closeActiveSearch());
+ expect(getActiveSearch(getState())).toBe(null);
+ });
+
+ it("should toggle the collapse state of a pane", () => {
+ const { dispatch, getState } = createStore();
+ expect(getPaneCollapse(getState(), "start")).toBe(false);
+ dispatch(actions.togglePaneCollapse("start", true));
+ expect(getPaneCollapse(getState(), "start")).toBe(true);
+ });
+
+ it("should toggle the collapsed state of frameworks in the callstack", () => {
+ const { dispatch, getState } = createStore();
+ const currentState = getFrameworkGroupingState(getState());
+ dispatch(actions.toggleFrameworkGrouping(!currentState));
+ expect(getFrameworkGroupingState(getState())).toBe(!currentState);
+ });
+
+ it("should highlight lines", async () => {
+ const { dispatch, getState } = createStore(mockCommandClient);
+ const base = await dispatch(
+ actions.newGeneratedSource(makeSource("base.js"))
+ );
+ const sourceActor = selectors.getFirstSourceActorForGeneratedSource(
+ getState(),
+ base.id
+ );
+ const cx = selectors.getThreadContext(getState());
+ //await dispatch(actions.selectSource(cx, base, sourceActor));
+ const location = createLocation({
+ source: base,
+ line: 1,
+ sourceActor,
+ });
+ await dispatch(actions.selectLocation(cx, location));
+
+ const range = { start: 3, end: 5, sourceId: base.id };
+ dispatch(actions.highlightLineRange(range));
+ expect(getHighlightedLineRangeForSelectedSource(getState())).toEqual(range);
+ });
+
+ it("should clear highlight lines", async () => {
+ const { dispatch, getState } = createStore(mockCommandClient);
+ const base = await dispatch(
+ actions.newGeneratedSource(makeSource("base.js"))
+ );
+ const sourceActor = selectors.getFirstSourceActorForGeneratedSource(
+ getState(),
+ base.id
+ );
+ const cx = selectors.getThreadContext(getState());
+ await dispatch(actions.selectSource(cx, base, sourceActor));
+ const range = { start: 3, end: 5, sourceId: "2" };
+ dispatch(actions.highlightLineRange(range));
+ dispatch(actions.clearHighlightLineRange());
+ expect(getHighlightedLineRangeForSelectedSource(getState())).toEqual(null);
+ });
+});