/* 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 . */ // 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 id = makePendingLocationId(mockedPendingBreakpoint.location); 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"; import { makePendingLocationId } from "../../utils/breakpoint"; 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); const id = makePendingLocationId(bp.location); await dispatch(actions.addBreakpoint(cx, bp.location)); const pendingBps = selectors.getPendingBreakpoints(getState()); expect(selectors.getPendingBreakpointList(getState())).toHaveLength(2); expect(pendingBps[id]).toMatchSnapshot(); }); describe("adding and deleting breakpoints", () => { let breakpoint1; let breakpoint2; let breakpointLocationId1; let breakpointLocationId2; beforeEach(() => { breakpoint1 = generateBreakpoint("foo"); breakpoint2 = generateBreakpoint("foo2"); breakpointLocationId1 = makePendingLocationId(breakpoint1.location); breakpointLocationId2 = makePendingLocationId(breakpoint2.location); }); it("add a corresponding pendingBreakpoint for each addition", async () => { const { dispatch, getState, cx } = createStore( mockClient({ "5": [0] }), loadInitialState(), mockSourceMaps() ); const source1 = await dispatch( actions.newGeneratedSource(makeSource("foo")) ); const sourceActor1 = selectors.getFirstSourceActorForGeneratedSource( getState(), source1.id ); const source2 = await dispatch( actions.newGeneratedSource(makeSource("foo2")) ); const sourceActor2 = selectors.getFirstSourceActorForGeneratedSource( getState(), source2.id ); await dispatch( actions.loadGeneratedSourceText({ cx, sourceActor: sourceActor1 }) ); await dispatch( actions.loadGeneratedSourceText({ cx, sourceActor: sourceActor2 }) ); await dispatch(actions.addBreakpoint(cx, breakpoint1.location)); await dispatch(actions.addBreakpoint(cx, breakpoint2.location)); const pendingBps = selectors.getPendingBreakpoints(getState()); // NOTE the sourceId should be `foo2/originalSource`, but is `foo2` // because we do not have a real source map for `getOriginalLocation` // to map. expect(pendingBps[breakpointLocationId1]).toMatchSnapshot(); expect(pendingBps[breakpointLocationId2]).toMatchSnapshot(); }); it("hidden breakponts do not create pending bps", async () => { const { dispatch, getState, cx } = createStore( mockClient({ "5": [0] }), loadInitialState(), mockSourceMaps() ); const source = await dispatch( actions.newGeneratedSource(makeSource("foo")) ); const sourceActor = selectors.getFirstSourceActorForGeneratedSource( getState(), source.id ); await dispatch(actions.loadGeneratedSourceText({ cx, sourceActor })); await dispatch( actions.addBreakpoint(cx, breakpoint1.location, { hidden: true }) ); const pendingBps = selectors.getPendingBreakpoints(getState()); expect(pendingBps[breakpointLocationId1]).toBeUndefined(); }); it("remove a corresponding pending breakpoint when deleting", async () => { const { dispatch, getState, cx } = createStore( mockClient({ "5": [0] }), loadInitialState(), mockSourceMaps() ); const source1 = await dispatch( actions.newGeneratedSource(makeSource("foo")) ); const sourceActor1 = selectors.getFirstSourceActorForGeneratedSource( getState(), source1.id ); const source2 = await dispatch( actions.newGeneratedSource(makeSource("foo2")) ); const sourceActor2 = selectors.getFirstSourceActorForGeneratedSource( getState(), source2.id ); await dispatch( actions.loadGeneratedSourceText({ cx, sourceActor: sourceActor1 }) ); await dispatch( actions.loadGeneratedSourceText({ cx, sourceActor: sourceActor2 }) ); await dispatch(actions.addBreakpoint(cx, breakpoint1.location)); await dispatch(actions.addBreakpoint(cx, breakpoint2.location)); await dispatch(actions.removeBreakpoint(cx, breakpoint1)); const pendingBps = selectors.getPendingBreakpoints(getState()); expect(pendingBps.hasOwnProperty(breakpointLocationId1)).toBe(false); expect(pendingBps.hasOwnProperty(breakpointLocationId2)).toBe(true); }); }); }); describe("when changing an existing breakpoint", () => { it("updates corresponding pendingBreakpoint", async () => { const { dispatch, getState, cx } = createStore( mockClient({ "5": [0] }), loadInitialState(), mockSourceMaps() ); const bp = generateBreakpoint("foo"); const id = makePendingLocationId(bp.location); const source = await dispatch( actions.newGeneratedSource(makeSource("foo")) ); const sourceActor = selectors.getFirstSourceActorForGeneratedSource( getState(), source.id ); await dispatch(actions.loadGeneratedSourceText({ cx, sourceActor })); await dispatch(actions.addBreakpoint(cx, bp.location)); await dispatch( actions.setBreakpointOptions(cx, bp.location, { condition: "2" }) ); const bps = selectors.getPendingBreakpoints(getState()); const breakpoint = bps[id]; expect(breakpoint.options.condition).toBe("2"); }); it("if disabled, updates corresponding pendingBreakpoint", async () => { const { dispatch, getState, cx } = createStore( mockClient({ "5": [0] }), loadInitialState(), mockSourceMaps() ); const bp = generateBreakpoint("foo"); const id = makePendingLocationId(bp.location); const source = await dispatch( actions.newGeneratedSource(makeSource("foo")) ); const sourceActor = selectors.getFirstSourceActorForGeneratedSource( getState(), source.id ); await dispatch(actions.loadGeneratedSourceText({ cx, sourceActor })); await dispatch(actions.addBreakpoint(cx, bp.location)); await dispatch(actions.disableBreakpoint(cx, bp)); const bps = selectors.getPendingBreakpoints(getState()); const breakpoint = bps[id]; expect(breakpoint.disabled).toBe(true); }); it("does not delete the pre-existing pendingBreakpoint", 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 })); const id = makePendingLocationId(bp.location); await dispatch(actions.addBreakpoint(cx, bp.location)); await dispatch( actions.setBreakpointOptions(cx, bp.location, { condition: "2" }) ); const bps = selectors.getPendingBreakpoints(getState()); const breakpoint = bps[id]; expect(breakpoint.options.condition).toBe("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 => ({ line: location.line, column: location.column, sourceId: location.sourceId, }), 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); }); });