legacy app state includes TimelineCred (#1428)
By keeping the TimelineCred in state instead of the Graph, we can access the plugin information (and potentially other config) from TimelineCred. Note that the legacy app does still use old-style cred calculation (no time weighting). Test plan: `yarn test`. It's just a refactor. Part of https://discourse.sourcecred.io/t/fixup-legacy-explorer/316
This commit is contained in:
parent
d896f73329
commit
3754cafb7d
|
@ -121,7 +121,7 @@ export function createApp(
|
|||
weightFileManager={weightFileManager}
|
||||
manualWeights={this.state.weights.nodeManualWeights}
|
||||
declarations={[githubDeclaration]}
|
||||
graph={appState.graph}
|
||||
graph={appState.timelineCred.graph()}
|
||||
onManualWeightsChange={(addr: NodeAddressT, weight: number) =>
|
||||
this.setState(({weights}) => {
|
||||
weights.nodeManualWeights.set(addr, weight);
|
||||
|
@ -156,7 +156,7 @@ export function createApp(
|
|||
appState.loading === "LOADING"
|
||||
}
|
||||
onClick={() =>
|
||||
this.stateTransitionMachine.loadGraphAndRunPagerank(
|
||||
this.stateTransitionMachine.loadTimelineCredAndRunPagerank(
|
||||
this.props.assets,
|
||||
this.state.weights,
|
||||
{
|
||||
|
|
|
@ -9,23 +9,25 @@ import testLocalStore from "../../webutil/testLocalStore";
|
|||
|
||||
import {PagerankTable} from "./pagerankTable/Table";
|
||||
import {createApp, LoadingIndicator, ProjectDetail} from "./App";
|
||||
import {TimelineCred} from "../../analysis/timeline/timelineCred";
|
||||
import {defaultParams} from "../../analysis/timeline/params";
|
||||
|
||||
require("../../webutil/testUtil").configureEnzyme();
|
||||
|
||||
describe("explorer/legacy/App", () => {
|
||||
function example() {
|
||||
let setState, getState;
|
||||
const loadGraph = jest.fn();
|
||||
const loadTimelineCred = jest.fn();
|
||||
const runPagerank = jest.fn();
|
||||
const loadGraphAndRunPagerank = jest.fn();
|
||||
const loadTimelineCredAndRunPagerank = jest.fn();
|
||||
const localStore = testLocalStore();
|
||||
function createMockSTM(_getState, _setState) {
|
||||
setState = _setState;
|
||||
getState = _getState;
|
||||
return {
|
||||
loadGraph,
|
||||
loadTimelineCred,
|
||||
runPagerank,
|
||||
loadGraphAndRunPagerank,
|
||||
loadTimelineCredAndRunPagerank,
|
||||
};
|
||||
}
|
||||
const App = createApp(createMockSTM);
|
||||
|
@ -43,9 +45,9 @@ describe("explorer/legacy/App", () => {
|
|||
el,
|
||||
setState,
|
||||
getState,
|
||||
loadGraph,
|
||||
loadTimelineCred,
|
||||
runPagerank,
|
||||
loadGraphAndRunPagerank,
|
||||
loadTimelineCredAndRunPagerank,
|
||||
localStore,
|
||||
};
|
||||
}
|
||||
|
@ -63,7 +65,13 @@ describe("explorer/legacy/App", () => {
|
|||
type: "READY_TO_RUN_PAGERANK",
|
||||
projectId: "foo/bar",
|
||||
loading: loadingState,
|
||||
graph: new Graph(),
|
||||
timelineCred: new TimelineCred(
|
||||
new Graph(),
|
||||
[],
|
||||
new Map(),
|
||||
defaultParams(),
|
||||
[]
|
||||
),
|
||||
});
|
||||
},
|
||||
pagerankEvaluated: (loadingState) => {
|
||||
|
@ -71,7 +79,13 @@ describe("explorer/legacy/App", () => {
|
|||
type: "PAGERANK_EVALUATED",
|
||||
projectId: "foo/bar",
|
||||
loading: loadingState,
|
||||
graph: new Graph(),
|
||||
timelineCred: new TimelineCred(
|
||||
new Graph(),
|
||||
[],
|
||||
new Map(),
|
||||
defaultParams(),
|
||||
[]
|
||||
),
|
||||
pagerankNodeDecomposition: new Map(),
|
||||
});
|
||||
},
|
||||
|
@ -123,7 +137,7 @@ describe("explorer/legacy/App", () => {
|
|||
function testAnalyzeCredButton(stateFn, {disabled}) {
|
||||
const adjective = disabled ? "disabled" : "working";
|
||||
it(`has a ${adjective} analyze cred button`, () => {
|
||||
const {el, loadGraphAndRunPagerank, setState} = example();
|
||||
const {el, loadTimelineCredAndRunPagerank, setState} = example();
|
||||
setState(stateFn());
|
||||
el.update();
|
||||
const button = el.findWhere(
|
||||
|
@ -134,7 +148,7 @@ describe("explorer/legacy/App", () => {
|
|||
} else {
|
||||
expect(button.props().disabled).toBe(false);
|
||||
button.simulate("click");
|
||||
expect(loadGraphAndRunPagerank).toBeCalledTimes(1);
|
||||
expect(loadTimelineCredAndRunPagerank).toBeCalledTimes(1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import {
|
|||
type PagerankOptions,
|
||||
pagerank,
|
||||
} from "../../analysis/pagerank";
|
||||
import {TimelineCred} from "../../analysis/timeline/timelineCred";
|
||||
|
||||
import type {Weights} from "../../analysis/weights";
|
||||
import {weightsToEdgeEvaluator} from "../../analysis/weightsToEdgeEvaluator";
|
||||
|
@ -38,12 +39,12 @@ export type ReadyToLoadGraph = {|
|
|||
export type ReadyToRunPagerank = {|
|
||||
+type: "READY_TO_RUN_PAGERANK",
|
||||
+projectId: string,
|
||||
+graph: Graph,
|
||||
+timelineCred: TimelineCred,
|
||||
+loading: LoadingState,
|
||||
|};
|
||||
export type PagerankEvaluated = {|
|
||||
+type: "PAGERANK_EVALUATED",
|
||||
+graph: Graph,
|
||||
+timelineCred: TimelineCred,
|
||||
+projectId: string,
|
||||
+pagerankNodeDecomposition: PagerankNodeDecomposition,
|
||||
+loading: LoadingState,
|
||||
|
@ -57,14 +58,19 @@ export function createStateTransitionMachine(
|
|||
getState: () => AppState,
|
||||
setState: (AppState) => void
|
||||
): StateTransitionMachine {
|
||||
return new StateTransitionMachine(getState, setState, doLoadGraph, pagerank);
|
||||
return new StateTransitionMachine(
|
||||
getState,
|
||||
setState,
|
||||
doLoadTimelineCred,
|
||||
pagerank
|
||||
);
|
||||
}
|
||||
|
||||
// Exported for testing purposes.
|
||||
export interface StateTransitionMachineInterface {
|
||||
+loadGraph: (Assets) => Promise<boolean>;
|
||||
+loadTimelineCred: (Assets) => Promise<boolean>;
|
||||
+runPagerank: (Weights, NodeAndEdgeTypes, NodeAddressT) => Promise<void>;
|
||||
+loadGraphAndRunPagerank: (
|
||||
+loadTimelineCredAndRunPagerank: (
|
||||
Assets,
|
||||
Weights,
|
||||
NodeAndEdgeTypes,
|
||||
|
@ -72,13 +78,16 @@ export interface StateTransitionMachineInterface {
|
|||
) => Promise<void>;
|
||||
}
|
||||
/* In production, instantiate via createStateTransitionMachine; the constructor
|
||||
* implementation allows specification of the loadGraph and
|
||||
* implementation allows specification of the loadTimelineCred and
|
||||
* pagerank functions for DI/testing purposes.
|
||||
**/
|
||||
export class StateTransitionMachine implements StateTransitionMachineInterface {
|
||||
getState: () => AppState;
|
||||
setState: (AppState) => void;
|
||||
doLoadGraph: (assets: Assets, projectId: string) => Promise<Graph>;
|
||||
doLoadTimelineCred: (
|
||||
assets: Assets,
|
||||
projectId: string
|
||||
) => Promise<TimelineCred>;
|
||||
pagerank: (
|
||||
Graph,
|
||||
EdgeEvaluator,
|
||||
|
@ -88,7 +97,10 @@ export class StateTransitionMachine implements StateTransitionMachineInterface {
|
|||
constructor(
|
||||
getState: () => AppState,
|
||||
setState: (AppState) => void,
|
||||
doLoadGraph: (assets: Assets, projectId: string) => Promise<Graph>,
|
||||
doLoadTimelineCred: (
|
||||
assets: Assets,
|
||||
projectId: string
|
||||
) => Promise<TimelineCred>,
|
||||
pagerank: (
|
||||
Graph,
|
||||
EdgeEvaluator,
|
||||
|
@ -97,15 +109,15 @@ export class StateTransitionMachine implements StateTransitionMachineInterface {
|
|||
) {
|
||||
this.getState = getState;
|
||||
this.setState = setState;
|
||||
this.doLoadGraph = doLoadGraph;
|
||||
this.doLoadTimelineCred = doLoadTimelineCred;
|
||||
this.pagerank = pagerank;
|
||||
}
|
||||
|
||||
/** Loads the graph, reports whether it was successful */
|
||||
async loadGraph(assets: Assets): Promise<boolean> {
|
||||
async loadTimelineCred(assets: Assets): Promise<boolean> {
|
||||
const state = this.getState();
|
||||
if (state.type !== "READY_TO_LOAD_GRAPH") {
|
||||
throw new Error("Tried to loadGraph in incorrect state");
|
||||
throw new Error("Tried to loadTimelineCred in incorrect state");
|
||||
}
|
||||
const {projectId} = state;
|
||||
const loadingState = {...state, loading: "LOADING"};
|
||||
|
@ -113,10 +125,10 @@ export class StateTransitionMachine implements StateTransitionMachineInterface {
|
|||
let newState: ?AppState;
|
||||
let success = true;
|
||||
try {
|
||||
const graph = await this.doLoadGraph(assets, projectId);
|
||||
const timelineCred = await this.doLoadTimelineCred(assets, projectId);
|
||||
newState = {
|
||||
type: "READY_TO_RUN_PAGERANK",
|
||||
graph,
|
||||
timelineCred,
|
||||
projectId,
|
||||
loading: "NOT_LOADING",
|
||||
};
|
||||
|
@ -150,7 +162,7 @@ export class StateTransitionMachine implements StateTransitionMachineInterface {
|
|||
? {...state, loading: "LOADING"}
|
||||
: {...state, loading: "LOADING"};
|
||||
this.setState(loadingState);
|
||||
const graph = state.graph;
|
||||
const graph = state.timelineCred.graph();
|
||||
let newState: ?AppState;
|
||||
try {
|
||||
const pagerankNodeDecomposition = await this.pagerank(
|
||||
|
@ -164,7 +176,7 @@ export class StateTransitionMachine implements StateTransitionMachineInterface {
|
|||
newState = {
|
||||
type: "PAGERANK_EVALUATED",
|
||||
pagerankNodeDecomposition,
|
||||
graph: state.graph,
|
||||
timelineCred: state.timelineCred,
|
||||
projectId: state.projectId,
|
||||
loading: "NOT_LOADING",
|
||||
};
|
||||
|
@ -181,7 +193,7 @@ export class StateTransitionMachine implements StateTransitionMachineInterface {
|
|||
}
|
||||
}
|
||||
|
||||
async loadGraphAndRunPagerank(
|
||||
async loadTimelineCredAndRunPagerank(
|
||||
assets: Assets,
|
||||
weights: Weights,
|
||||
types: NodeAndEdgeTypes,
|
||||
|
@ -191,8 +203,8 @@ export class StateTransitionMachine implements StateTransitionMachineInterface {
|
|||
const type = state.type;
|
||||
switch (type) {
|
||||
case "READY_TO_LOAD_GRAPH":
|
||||
const loadedGraph = await this.loadGraph(assets);
|
||||
if (loadedGraph) {
|
||||
const loadedTimelineCred = await this.loadTimelineCred(assets);
|
||||
if (loadedTimelineCred) {
|
||||
await this.runPagerank(weights, types, totalScoreNodePrefix);
|
||||
}
|
||||
break;
|
||||
|
@ -206,13 +218,13 @@ export class StateTransitionMachine implements StateTransitionMachineInterface {
|
|||
}
|
||||
}
|
||||
|
||||
export async function doLoadGraph(
|
||||
export async function doLoadTimelineCred(
|
||||
assets: Assets,
|
||||
projectId: string
|
||||
): Promise<Graph> {
|
||||
): Promise<TimelineCred> {
|
||||
const loadResult = await defaultLoader(assets, projectId);
|
||||
if (loadResult.type !== "SUCCESS") {
|
||||
throw new Error(loadResult);
|
||||
}
|
||||
return loadResult.timelineCred.graph();
|
||||
return loadResult.timelineCred;
|
||||
}
|
||||
|
|
|
@ -10,6 +10,8 @@ import type {
|
|||
PagerankNodeDecomposition,
|
||||
PagerankOptions,
|
||||
} from "../../analysis/pagerank";
|
||||
import {TimelineCred} from "../../analysis/timeline/timelineCred";
|
||||
import {defaultParams} from "../../analysis/timeline/params";
|
||||
|
||||
describe("explorer/legacy/state", () => {
|
||||
function example(startingState: AppState) {
|
||||
|
@ -18,9 +20,9 @@ describe("explorer/legacy/state", () => {
|
|||
const setState = (appState) => {
|
||||
stateContainer.appState = appState;
|
||||
};
|
||||
const loadGraphMock: JestMockFn<
|
||||
const loadTimelineCredMock: JestMockFn<
|
||||
[Assets, string],
|
||||
Promise<Graph>
|
||||
Promise<TimelineCred>
|
||||
> = jest.fn();
|
||||
|
||||
const pagerankMock: JestMockFn<
|
||||
|
@ -30,10 +32,10 @@ describe("explorer/legacy/state", () => {
|
|||
const stm = new StateTransitionMachine(
|
||||
getState,
|
||||
setState,
|
||||
loadGraphMock,
|
||||
loadTimelineCredMock,
|
||||
pagerankMock
|
||||
);
|
||||
return {getState, stm, loadGraphMock, pagerankMock};
|
||||
return {getState, stm, loadTimelineCredMock, pagerankMock};
|
||||
}
|
||||
function readyToLoadGraph(): AppState {
|
||||
return {
|
||||
|
@ -47,14 +49,26 @@ describe("explorer/legacy/state", () => {
|
|||
type: "READY_TO_RUN_PAGERANK",
|
||||
projectId: "foo/bar",
|
||||
loading: "NOT_LOADING",
|
||||
graph: new Graph(),
|
||||
timelineCred: new TimelineCred(
|
||||
new Graph(),
|
||||
[],
|
||||
new Map(),
|
||||
defaultParams(),
|
||||
[]
|
||||
),
|
||||
};
|
||||
}
|
||||
function pagerankEvaluated(): AppState {
|
||||
return {
|
||||
type: "PAGERANK_EVALUATED",
|
||||
projectId: "foo/bar",
|
||||
graph: new Graph(),
|
||||
timelineCred: new TimelineCred(
|
||||
new Graph(),
|
||||
[],
|
||||
new Map(),
|
||||
defaultParams(),
|
||||
[]
|
||||
),
|
||||
pagerankNodeDecomposition: pagerankNodeDecomposition(),
|
||||
loading: "NOT_LOADING",
|
||||
};
|
||||
|
@ -69,36 +83,43 @@ describe("explorer/legacy/state", () => {
|
|||
return state.loading;
|
||||
}
|
||||
|
||||
describe("loadGraph", () => {
|
||||
describe("loadTimelineCred", () => {
|
||||
it("can only be called when READY_TO_LOAD_GRAPH", async () => {
|
||||
const badStates = [readyToRunPagerank(), pagerankEvaluated()];
|
||||
for (const b of badStates) {
|
||||
const {stm} = example(b);
|
||||
await expect(stm.loadGraph(new Assets("/my/gateway/"))).rejects.toThrow(
|
||||
"incorrect state"
|
||||
);
|
||||
await expect(
|
||||
stm.loadTimelineCred(new Assets("/my/gateway/"))
|
||||
).rejects.toThrow("incorrect state");
|
||||
}
|
||||
});
|
||||
it("passes along the projectId", () => {
|
||||
const {stm, loadGraphMock} = example(readyToLoadGraph());
|
||||
expect(loadGraphMock).toHaveBeenCalledTimes(0);
|
||||
const {stm, loadTimelineCredMock} = example(readyToLoadGraph());
|
||||
expect(loadTimelineCredMock).toHaveBeenCalledTimes(0);
|
||||
const assets = new Assets("/my/gateway/");
|
||||
stm.loadGraph(assets);
|
||||
expect(loadGraphMock).toHaveBeenCalledTimes(1);
|
||||
expect(loadGraphMock).toHaveBeenCalledWith(assets, "foo/bar");
|
||||
stm.loadTimelineCred(assets);
|
||||
expect(loadTimelineCredMock).toHaveBeenCalledTimes(1);
|
||||
expect(loadTimelineCredMock).toHaveBeenCalledWith(assets, "foo/bar");
|
||||
});
|
||||
it("immediately sets loading status", () => {
|
||||
const {getState, stm} = example(readyToLoadGraph());
|
||||
expect(loading(getState())).toBe("NOT_LOADING");
|
||||
stm.loadGraph(new Assets("/my/gateway/"));
|
||||
stm.loadTimelineCred(new Assets("/my/gateway/"));
|
||||
expect(loading(getState())).toBe("LOADING");
|
||||
expect(getState().type).toBe("READY_TO_LOAD_GRAPH");
|
||||
});
|
||||
it("transitions to READY_TO_RUN_PAGERANK on success", async () => {
|
||||
const {getState, stm, loadGraphMock} = example(readyToLoadGraph());
|
||||
const graph = new Graph();
|
||||
loadGraphMock.mockReturnValue(Promise.resolve(graph));
|
||||
const succeeded = await stm.loadGraph(new Assets("/my/gateway/"));
|
||||
const {getState, stm, loadTimelineCredMock} = example(readyToLoadGraph());
|
||||
|
||||
const timelineCred = new TimelineCred(
|
||||
new Graph(),
|
||||
[],
|
||||
new Map(),
|
||||
defaultParams(),
|
||||
[]
|
||||
);
|
||||
loadTimelineCredMock.mockReturnValue(Promise.resolve(timelineCred));
|
||||
const succeeded = await stm.loadTimelineCred(new Assets("/my/gateway/"));
|
||||
expect(succeeded).toBe(true);
|
||||
const state = getState();
|
||||
expect(loading(state)).toBe("NOT_LOADING");
|
||||
|
@ -106,15 +127,15 @@ describe("explorer/legacy/state", () => {
|
|||
if (state.type !== "READY_TO_RUN_PAGERANK") {
|
||||
throw new Error("Impossible");
|
||||
}
|
||||
expect(state.graph).toBe(graph);
|
||||
expect(state.timelineCred).toBe(timelineCred);
|
||||
});
|
||||
it("sets loading state FAILED on reject", async () => {
|
||||
const {getState, stm, loadGraphMock} = example(readyToLoadGraph());
|
||||
const {getState, stm, loadTimelineCredMock} = example(readyToLoadGraph());
|
||||
const error = new Error("Oh no!");
|
||||
// $ExpectFlowError
|
||||
console.error = jest.fn();
|
||||
loadGraphMock.mockReturnValue(Promise.reject(error));
|
||||
const succeeded = await stm.loadGraph(new Assets("/my/gateway/"));
|
||||
loadTimelineCredMock.mockReturnValue(Promise.reject(error));
|
||||
const succeeded = await stm.loadTimelineCred(new Assets("/my/gateway/"));
|
||||
expect(succeeded).toBe(false);
|
||||
const state = getState();
|
||||
expect(loading(state)).toBe("FAILED");
|
||||
|
@ -183,69 +204,69 @@ describe("explorer/legacy/state", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("loadGraphAndRunPagerank", () => {
|
||||
describe("loadTimelineCredAndRunPagerank", () => {
|
||||
it("when READY_TO_LOAD_GRAPH, loads graph then runs pagerank", async () => {
|
||||
const {stm} = example(readyToLoadGraph());
|
||||
(stm: any).loadGraph = jest.fn();
|
||||
(stm: any).loadTimelineCred = jest.fn();
|
||||
(stm: any).runPagerank = jest.fn();
|
||||
stm.loadGraph.mockResolvedValue(true);
|
||||
stm.loadTimelineCred.mockResolvedValue(true);
|
||||
const assets = new Assets("/gateway/");
|
||||
const prefix = NodeAddress.fromParts(["bar"]);
|
||||
const types = defaultTypes();
|
||||
const wt = defaultWeights();
|
||||
await stm.loadGraphAndRunPagerank(assets, wt, types, prefix);
|
||||
expect(stm.loadGraph).toHaveBeenCalledTimes(1);
|
||||
expect(stm.loadGraph).toHaveBeenCalledWith(assets);
|
||||
await stm.loadTimelineCredAndRunPagerank(assets, wt, types, prefix);
|
||||
expect(stm.loadTimelineCred).toHaveBeenCalledTimes(1);
|
||||
expect(stm.loadTimelineCred).toHaveBeenCalledWith(assets);
|
||||
expect(stm.runPagerank).toHaveBeenCalledTimes(1);
|
||||
expect(stm.runPagerank).toHaveBeenCalledWith(wt, types, prefix);
|
||||
});
|
||||
it("does not run pagerank if loadGraph did not succeed", async () => {
|
||||
it("does not run pagerank if loadTimelineCred did not succeed", async () => {
|
||||
const {stm} = example(readyToLoadGraph());
|
||||
(stm: any).loadGraph = jest.fn();
|
||||
(stm: any).loadTimelineCred = jest.fn();
|
||||
(stm: any).runPagerank = jest.fn();
|
||||
stm.loadGraph.mockResolvedValue(false);
|
||||
stm.loadTimelineCred.mockResolvedValue(false);
|
||||
const assets = new Assets("/gateway/");
|
||||
const prefix = NodeAddress.fromParts(["bar"]);
|
||||
await stm.loadGraphAndRunPagerank(
|
||||
await stm.loadTimelineCredAndRunPagerank(
|
||||
assets,
|
||||
defaultWeights(),
|
||||
defaultTypes(),
|
||||
prefix
|
||||
);
|
||||
expect(stm.loadGraph).toHaveBeenCalledTimes(1);
|
||||
expect(stm.loadTimelineCred).toHaveBeenCalledTimes(1);
|
||||
expect(stm.runPagerank).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
it("when READY_TO_RUN_PAGERANK, runs pagerank", async () => {
|
||||
const {stm} = example(readyToRunPagerank());
|
||||
(stm: any).loadGraph = jest.fn();
|
||||
(stm: any).loadTimelineCred = jest.fn();
|
||||
(stm: any).runPagerank = jest.fn();
|
||||
const prefix = NodeAddress.fromParts(["bar"]);
|
||||
const wt = defaultWeights();
|
||||
const types = defaultTypes();
|
||||
await stm.loadGraphAndRunPagerank(
|
||||
await stm.loadTimelineCredAndRunPagerank(
|
||||
new Assets("/gateway/"),
|
||||
wt,
|
||||
types,
|
||||
prefix
|
||||
);
|
||||
expect(stm.loadGraph).toHaveBeenCalledTimes(0);
|
||||
expect(stm.loadTimelineCred).toHaveBeenCalledTimes(0);
|
||||
expect(stm.runPagerank).toHaveBeenCalledTimes(1);
|
||||
expect(stm.runPagerank).toHaveBeenCalledWith(wt, types, prefix);
|
||||
});
|
||||
it("when PAGERANK_EVALUATED, runs pagerank", async () => {
|
||||
const {stm} = example(pagerankEvaluated());
|
||||
(stm: any).loadGraph = jest.fn();
|
||||
(stm: any).loadTimelineCred = jest.fn();
|
||||
(stm: any).runPagerank = jest.fn();
|
||||
const prefix = NodeAddress.fromParts(["bar"]);
|
||||
const wt = defaultWeights();
|
||||
const types = defaultTypes();
|
||||
await stm.loadGraphAndRunPagerank(
|
||||
await stm.loadTimelineCredAndRunPagerank(
|
||||
new Assets("/gateway/"),
|
||||
wt,
|
||||
types,
|
||||
prefix
|
||||
);
|
||||
expect(stm.loadGraph).toHaveBeenCalledTimes(0);
|
||||
expect(stm.loadTimelineCred).toHaveBeenCalledTimes(0);
|
||||
expect(stm.runPagerank).toHaveBeenCalledTimes(1);
|
||||
expect(stm.runPagerank).toHaveBeenCalledWith(wt, types, prefix);
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue