/* 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 . */ 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"); }); });