/* 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 . */
// @flow
/**
* Utils for Jest
* @module utils/test-head
*/
import { combineReducers, type Store } from "redux";
import sourceMaps from "devtools-source-map";
import reducers from "../reducers";
import actions from "../actions";
import * as selectors from "../selectors";
import { parserWorker, evaluationsParser } from "../test/tests-setup";
import configureStore from "../actions/utils/create-store";
import sourceQueue from "../utils/source-queue";
import type {
ThreadContext,
Source,
OriginalSourceData,
GeneratedSourceData,
} from "../types";
import type { State } from "../reducers/types";
import type { Action } from "../actions/types";
type TestStore = Store & {
thunkArgs: () => {
dispatch: any,
getState: () => State,
client: any,
sourceMaps: any,
panel: {||},
},
cx: ThreadContext,
};
/**
* This file contains older interfaces used by tests that have not been
* converted to use test-mockup.js
*/
/**
* @memberof utils/test-head
* @static
*/
function createStore(
client: any,
initialState: any = {},
sourceMapsMock: any
): TestStore {
const store: any = configureStore({
log: false,
makeThunkArgs: args => {
return {
...args,
client,
sourceMaps: sourceMapsMock !== undefined ? sourceMapsMock : sourceMaps,
parser: parserWorker,
evaluationsParser,
};
},
})(combineReducers(reducers), initialState);
sourceQueue.clear();
sourceQueue.initialize({
newQueuedSources: sources =>
store.dispatch(actions.newQueuedSources(sources)),
});
store.thunkArgs = () => ({
dispatch: store.dispatch,
getState: store.getState,
client,
sourceMaps,
panel: {},
});
// Put the initial context in the store, for convenience to unit tests.
store.cx = selectors.getThreadContext(store.getState());
return store;
}
/**
* @memberof utils/test-head
* @static
*/
function commonLog(msg: string, data: any = {}) {
console.log(`[INFO] ${msg} ${JSON.stringify(data)}`);
}
function makeFrame({ id, sourceId, thread }: Object, opts: Object = {}) {
return {
id,
scope: { bindings: { variables: {}, arguments: [] } },
location: { sourceId, line: 4 },
thread: thread || "FakeThread",
...opts,
};
}
function createSourceObject(
filename: string,
props: {
isBlackBoxed?: boolean,
} = {}
): Source {
return ({
id: filename,
url: makeSourceURL(filename),
isBlackBoxed: !!props.isBlackBoxed,
isPrettyPrinted: false,
isExtension: false,
isOriginal: filename.includes("originalSource"),
}: any);
}
function createOriginalSourceObject(generated: Source): Source {
const rv = {
...generated,
id: `${generated.id}/originalSource`,
};
return (rv: any);
}
function makeSourceURL(filename: string) {
return `http://localhost:8000/examples/${filename}`;
}
type MakeSourceProps = {
sourceMapBaseURL?: string,
sourceMapURL?: string,
introductionType?: string,
isBlackBoxed?: boolean,
};
function createMakeSource(): (
// The name of the file that this actor is part of.
name: string,
props?: MakeSourceProps
) => GeneratedSourceData {
const indicies = {};
return function(name, props = {}) {
const index = (indicies[name] | 0) + 1;
indicies[name] = index;
return {
id: name,
thread: "FakeThread",
source: {
actor: `${name}-${index}-actor`,
url: `http://localhost:8000/examples/${name}`,
sourceMapBaseURL: props.sourceMapBaseURL || null,
sourceMapURL: props.sourceMapURL || null,
introductionType: props.introductionType || null,
isBlackBoxed: !!props.isBlackBoxed,
extensionName: null,
},
isServiceWorker: false,
};
};
}
/**
* @memberof utils/test-head
* @static
*/
let creator;
beforeEach(() => {
creator = createMakeSource();
});
afterEach(() => {
creator = null;
});
function makeSource(name: string, props?: MakeSourceProps) {
if (!creator) {
throw new Error("makeSource() cannot be called outside of a test");
}
return creator(name, props);
}
function makeOriginalSource(source: Source): OriginalSourceData {
return {
id: `${source.id}/originalSource`,
url: `${source.url}-original`,
};
}
function makeFuncLocation(startLine, endLine) {
if (!endLine) {
endLine = startLine + 1;
}
return {
start: {
line: startLine,
},
end: {
line: endLine,
},
};
}
function makeSymbolDeclaration(
name: string,
start: number,
end: ?number,
klass: ?string
) {
return {
id: `${name}:${start}`,
name,
location: makeFuncLocation(start, end),
klass,
};
}
/**
* @memberof utils/test-head
* @static
*/
function waitForState(store: any, predicate: any): Promise {
return new Promise(resolve => {
let ret = predicate(store.getState());
if (ret) {
resolve(ret);
}
const unsubscribe = store.subscribe(() => {
ret = predicate(store.getState());
if (ret) {
unsubscribe();
// NOTE: memoizableAction adds an additional tick for validating context
setTimeout(() => resolve(ret));
}
});
});
}
function watchForState(store: any, predicate: any): () => boolean {
let sawState = false;
const checkState = function() {
if (!sawState && predicate(store.getState())) {
sawState = true;
}
return sawState;
};
let unsubscribe;
if (!checkState()) {
unsubscribe = store.subscribe(() => {
if (checkState()) {
unsubscribe();
}
});
}
return function read() {
if (unsubscribe) {
unsubscribe();
}
return sawState;
};
}
function getTelemetryEvents(eventName: string) {
return window.dbg._telemetry.events[eventName] || [];
}
function waitATick(callback: Function): Promise<*> {
return new Promise(resolve => {
setTimeout(() => {
callback();
resolve();
});
});
}
export {
actions,
selectors,
reducers,
createStore,
commonLog,
getTelemetryEvents,
makeFrame,
createSourceObject,
createOriginalSourceObject,
createMakeSource,
makeSourceURL,
makeSource,
makeOriginalSource,
makeSymbolDeclaration,
waitForState,
watchForState,
waitATick,
};