Create canonical demoAdapters for testing (#735)

PluginAdapters and Node/Edge types are increasingly fundamental to the
cred explorer. Prior to this commit, we had no canonical demo
adapters/types, and we would create ad-hoc and messy adapters whenever
we needed them. This creates unnecessary repetition and lowers test
quality.

This commit creates a canonical demo adapter (loosely themed based on
the wonderful game [Factorio]) and refactors most existing test cases to
use the demo adapters. In particular, the horrible mess of pagerankTable
adapters has been removed.

[Factorio]: https://www.factorio.com/

I left `aggregate.test.js` untouched because I would have needed to
materially re-write the tests to port them over. I added a comment so
that if we ever do re-write those tests, we'll use the new demo
adapters.

Test plan: `yarn test` passes.
This commit is contained in:
Dandelion Mané 2018-08-30 15:25:42 -07:00 committed by GitHub
parent 761b0f1282
commit fc5c9ea589
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 196 additions and 288 deletions

View File

@ -1,91 +1,22 @@
// @flow
import {
NodeAddress,
EdgeAddress,
type NodeAddressT,
Graph,
} from "../../core/graph";
import type {DynamicPluginAdapter} from "./pluginAdapter";
import {NodeAddress, EdgeAddress, Graph} from "../../core/graph";
import {FactorioStaticAdapter} from "./demoAdapters";
import {StaticAdapterSet} from "./adapterSet";
import {FallbackStaticAdapter, FALLBACK_NAME} from "./fallbackAdapter";
import {Assets} from "../assets";
import {makeRepo, type Repo} from "../../core/repo";
import {makeRepo} from "../../core/repo";
describe("app/adapters/adapterSet", () => {
class TestStaticPluginAdapter {
loadingMock: Function;
constructor() {
this.loadingMock = jest.fn();
}
name() {
return "other plugin";
}
nodePrefix() {
return NodeAddress.fromParts(["other"]);
}
edgePrefix() {
return EdgeAddress.fromParts(["other"]);
}
nodeTypes() {
return [
{
name: "other1",
pluralName: "others1",
defaultWeight: 0,
prefix: NodeAddress.fromParts(["other", "1"]),
},
{
name: "other2",
pluralName: "others2",
defaultWeight: 0,
prefix: NodeAddress.fromParts(["other", "2"]),
},
];
}
edgeTypes() {
return [
{
forwardName: "others_1",
backwardName: "othered_by_1",
prefix: EdgeAddress.fromParts(["other", "1"]),
},
{
forwardName: "others_2",
backwardName: "othered_by_2",
prefix: EdgeAddress.fromParts(["other", "2"]),
},
];
}
load(assets: Assets, repo: Repo) {
return this.loadingMock(assets, repo).then(
() => new TestDynamicPluginAdapter()
);
}
}
class TestDynamicPluginAdapter implements DynamicPluginAdapter {
graph() {
return new Graph().addNode(NodeAddress.fromParts(["other1", "example"]));
}
nodeDescription(x: NodeAddressT) {
return `Node from the test plugin: ${NodeAddress.toString(x)}`;
}
static() {
return new TestStaticPluginAdapter();
}
}
describe("StaticAdapterSet", () => {
function example() {
const x = new TestStaticPluginAdapter();
const x = new FactorioStaticAdapter();
const fallback = new FallbackStaticAdapter();
const sas = new StaticAdapterSet([x]);
return {x, fallback, sas};
}
it("errors if two plugins have the same name", () => {
const x = new TestStaticPluginAdapter();
const x = new FactorioStaticAdapter();
const shouldError = () => new StaticAdapterSet([x, x]);
expect(shouldError).toThrowError("Multiple plugins with name");
});
@ -100,24 +31,30 @@ describe("app/adapters/adapterSet", () => {
it("aggregates NodeTypes across plugins", () => {
const {sas} = example();
const nodeTypes = sas.nodeTypes();
expect(nodeTypes).toHaveLength(3);
const expectedNumNodeTypes =
new FactorioStaticAdapter().nodeTypes().length +
new FallbackStaticAdapter().nodeTypes().length;
expect(nodeTypes).toHaveLength(expectedNumNodeTypes);
});
it("aggregates EdgeTypes across plugins", () => {
const {sas} = example();
const edgeTypes = sas.edgeTypes();
expect(edgeTypes).toHaveLength(3);
const expectedNumEdgeTypes =
new FactorioStaticAdapter().edgeTypes().length +
new FallbackStaticAdapter().edgeTypes().length;
expect(edgeTypes).toHaveLength(expectedNumEdgeTypes);
});
it("finds adapter matching a node", () => {
const {x, sas} = example();
const matching = sas.adapterMatchingNode(
NodeAddress.fromParts(["other", "foo"])
NodeAddress.fromParts(["factorio", "inserter"])
);
expect(matching.name()).toBe(x.name());
});
it("finds adapter matching an edge", () => {
const {x, sas} = example();
const matching = sas.adapterMatchingEdge(
EdgeAddress.fromParts(["other", "foo"])
EdgeAddress.fromParts(["factorio", "assembles"])
);
expect(matching.name()).toBe(x.name());
});
@ -134,16 +71,16 @@ describe("app/adapters/adapterSet", () => {
it("finds type matching a node", () => {
const {sas} = example();
const type = sas.typeMatchingNode(
NodeAddress.fromParts(["other", "1", "foo"])
NodeAddress.fromParts(["factorio", "inserter", "1", "foo"])
);
expect(type.name).toBe("other1");
expect(type.name).toBe("inserter");
});
it("finds type matching an edge", () => {
const {sas} = example();
const type = sas.typeMatchingEdge(
EdgeAddress.fromParts(["other", "1", "foo"])
EdgeAddress.fromParts(["factorio", "assembles", "other", "1", "foo"])
);
expect(type.forwardName).toBe("others_1");
expect(type.forwardName).toBe("assembles");
});
it("finds fallback type for unregistered node", () => {
const {sas} = example();
@ -161,6 +98,7 @@ describe("app/adapters/adapterSet", () => {
});
it("loads a dynamicAdapterSet", async () => {
const {x, sas} = example();
x.loadingMock = jest.fn();
x.loadingMock.mockResolvedValue();
expect(x.loadingMock).toHaveBeenCalledTimes(0);
const assets = new Assets("/my/gateway/");
@ -176,9 +114,8 @@ describe("app/adapters/adapterSet", () => {
describe("DynamicAdapterSet", () => {
async function example() {
const x = new TestStaticPluginAdapter();
const x = new FactorioStaticAdapter();
const sas = new StaticAdapterSet([x]);
x.loadingMock.mockResolvedValue();
const das = await sas.load(
new Assets("/my/gateway/"),
makeRepo("foo", "bar")
@ -203,14 +140,14 @@ describe("app/adapters/adapterSet", () => {
it("finds adapter matching a node", async () => {
const {x, das} = await example();
const matching = das.adapterMatchingNode(
NodeAddress.fromParts(["other", "foo"])
NodeAddress.fromParts(["factorio", "inserter"])
);
expect(matching.static().name()).toBe(x.name());
});
it("finds adapter matching an edge", async () => {
const {x, das} = await example();
const matching = das.adapterMatchingEdge(
EdgeAddress.fromParts(["other", "foo"])
EdgeAddress.fromParts(["factorio", "assembles"])
);
expect(matching.static().name()).toBe(x.name());
});

View File

@ -0,0 +1,129 @@
// @flow
import {Assets} from "../assets";
import {
Graph,
NodeAddress,
type NodeAddressT,
EdgeAddress,
} from "../../core/graph";
import type {
StaticPluginAdapter,
DynamicPluginAdapter,
EdgeType,
NodeType,
} from "./pluginAdapter";
import {StaticAdapterSet} from "./adapterSet";
import {makeRepo, type Repo} from "../../core/repo";
export const inserterNodeType: NodeType = Object.freeze({
name: "inserter",
pluralName: "inserters",
prefix: NodeAddress.fromParts(["factorio", "inserter"]),
defaultWeight: 1,
});
export const machineNodeType: NodeType = Object.freeze({
name: "machine",
pluralName: "machines",
prefix: NodeAddress.fromParts(["factorio", "machine"]),
defaultWeight: 2,
});
export const assemblesEdgeType: EdgeType = Object.freeze({
forwardName: "assembles",
backwardName: "is assembled by",
prefix: EdgeAddress.fromParts(["factorio", "assembles"]),
});
export const transportsEdgeType: EdgeType = Object.freeze({
forwardName: "transports",
backwardName: "is transported by",
prefix: EdgeAddress.fromParts(["factorio", "transports"]),
});
export class FactorioStaticAdapter implements StaticPluginAdapter {
loadingMock: Function;
name() {
return "Factorio demo adapter";
}
nodePrefix() {
return NodeAddress.fromParts(["factorio"]);
}
nodeTypes() {
return [inserterNodeType, machineNodeType];
}
edgePrefix() {
return EdgeAddress.fromParts(["factorio"]);
}
edgeTypes() {
return [assemblesEdgeType, transportsEdgeType];
}
async load(assets: Assets, repo: Repo): Promise<DynamicPluginAdapter> {
if (this.loadingMock) {
return this.loadingMock(assets, repo).then(
() => new FactorioDynamicAdapter()
);
}
return Promise.resolve(new FactorioDynamicAdapter());
}
}
export const factorioNodes = Object.freeze({
inserter1: NodeAddress.fromParts(["factorio", "inserter", "1"]),
machine1: NodeAddress.fromParts(["factorio", "machine", "1"]),
inserter2: NodeAddress.fromParts(["factorio", "inserter", "2"]),
machine2: NodeAddress.fromParts(["factorio", "machine", "2"]),
});
export const factorioEdges = Object.freeze({
transports1: Object.freeze({
src: factorioNodes.inserter1,
dst: factorioNodes.machine1,
address: EdgeAddress.fromParts(["factorio", "transports", "1"]),
}),
assembles1: Object.freeze({
src: factorioNodes.machine1,
dst: factorioNodes.inserter2,
address: EdgeAddress.fromParts(["factorio", "assembles", "1"]),
}),
transports2: Object.freeze({
src: factorioNodes.inserter2,
dst: factorioNodes.machine2,
address: EdgeAddress.fromParts(["factorio", "assembles", "2"]),
}),
});
export function factorioGraph() {
return new Graph()
.addNode(factorioNodes.inserter1)
.addNode(factorioNodes.inserter2)
.addNode(factorioNodes.machine1)
.addNode(factorioNodes.machine2)
.addEdge(factorioEdges.transports1)
.addEdge(factorioEdges.transports2)
.addEdge(factorioEdges.assembles1);
}
export class FactorioDynamicAdapter implements DynamicPluginAdapter {
graph() {
return factorioGraph();
}
nodeDescription(x: NodeAddressT) {
return NodeAddress.toString(x);
}
static() {
return new FactorioStaticAdapter();
}
}
export function staticAdapterSet() {
return new StaticAdapterSet([new FactorioStaticAdapter()]);
}
export async function dynamicAdapterSet() {
return await staticAdapterSet().load(
new Assets("/gateway/"),
makeRepo("foo", "bar")
);
}

View File

@ -2,6 +2,7 @@
import React from "react";
import {shallow} from "enzyme";
import * as NullUtil from "../../../util/null";
import {NodeAddress, EdgeAddress} from "../../../core/graph";
import type {NodeType, EdgeType} from "../../adapters/pluginAdapter";
@ -15,6 +16,7 @@ import {Badge} from "./shared";
import {example} from "./sharedTestUtils";
import {aggregateFlat, type FlatAggregation} from "./aggregate";
import {TableRow} from "./TableRow";
import {factorioNodes} from "../../adapters/demoAdapters";
require("../../testUtil").configureEnzyme();
@ -31,8 +33,8 @@ describe("app/credExplorer/pagerankTable/Aggregation", () => {
});
describe("AggregationRowList", () => {
it("instantiates AggregationRows for each aggregation", async () => {
const {adapters, pnd, nodes} = await example();
const node = nodes.bar1;
const {adapters, pnd} = await example();
const node = factorioNodes.inserter1;
const depth = 20;
const maxEntriesPerList = 50;
const sharedProps = {adapters, pnd, maxEntriesPerList};
@ -66,9 +68,9 @@ describe("app/credExplorer/pagerankTable/Aggregation", () => {
describe("AggregationRow", () => {
async function setup() {
const {pnd, adapters, nodes} = await example();
const {pnd, adapters} = await example();
const sharedProps = {adapters, pnd, maxEntriesPerList: 123};
const target = nodes.bar1;
const target = factorioNodes.inserter1;
const {scoredConnections} = NullUtil.get(pnd.get(target));
const aggregations = aggregateFlat(
scoredConnections,

View File

@ -9,6 +9,7 @@ import {ConnectionRowList, ConnectionRow, ConnectionView} from "./Connection";
import {example} from "./sharedTestUtils";
import {TableRow} from "./TableRow";
import {NodeRow} from "./Node";
import {factorioNodes} from "../../adapters/demoAdapters";
require("../../testUtil").configureEnzyme();
@ -26,9 +27,9 @@ describe("app/credExplorer/pagerankTable/Connection", () => {
describe("ConnectionRowList", () => {
async function setup(maxEntriesPerList: number = 100000) {
const {adapters, pnd, nodes} = await example();
const {adapters, pnd} = await example();
const depth = 2;
const node = nodes.bar1;
const node = factorioNodes.inserter1;
const sharedProps = {adapters, pnd, maxEntriesPerList};
const connections = NullUtil.get(sharedProps.pnd.get(node))
.scoredConnections;
@ -76,15 +77,11 @@ describe("app/credExplorer/pagerankTable/Connection", () => {
describe("ConnectionRow", () => {
async function setup() {
const {pnd, adapters, nodes} = await example();
const {pnd, adapters} = await example();
const sharedProps = {adapters, pnd, maxEntriesPerList: 123};
const target = nodes.bar1;
const target = factorioNodes.inserter1;
const {scoredConnections} = NullUtil.get(pnd.get(target));
const alphaConnections = scoredConnections.filter(
(sc) => sc.source === nodes.fooAlpha
);
expect(alphaConnections).toHaveLength(1);
const scoredConnection = alphaConnections[0];
const scoredConnection = scoredConnections[0];
const depth = 2;
const component = (
<ConnectionRow
@ -160,8 +157,8 @@ describe("app/credExplorer/pagerankTable/Connection", () => {
});
describe("ConnectionView", () => {
async function setup() {
const {pnd, adapters, nodes} = await example();
const {scoredConnections} = NullUtil.get(pnd.get(nodes.bar1));
const {pnd, adapters} = await example();
const {scoredConnections} = NullUtil.get(pnd.get(factorioNodes.machine1));
const connections = scoredConnections.map((sc) => sc.connection);
function connectionByType(t) {
return NullUtil.get(
@ -208,8 +205,10 @@ describe("app/credExplorer/pagerankTable/Connection", () => {
const outerSpan = view.find("span").first();
const badge = outerSpan.find("Badge");
const description = outerSpan.children().find("span");
expect(badge.children().text()).toEqual("is barred by");
expect(description.text()).toEqual('bar: NodeAddress["bar","a","1"]');
expect(badge.children().text()).toEqual("is transported by");
expect(description.text()).toEqual(
'NodeAddress["factorio","inserter","1"]'
);
});
it("for outward connections, renders a `Badge` and description", async () => {
const {cvForConnection, outConnection} = await setup();
@ -217,8 +216,10 @@ describe("app/credExplorer/pagerankTable/Connection", () => {
const outerSpan = view.find("span").first();
const badge = outerSpan.find("Badge");
const description = outerSpan.children().find("span");
expect(badge.children().text()).toEqual("bars");
expect(description.text()).toEqual("xox node!");
expect(badge.children().text()).toEqual("assembles");
expect(description.text()).toEqual(
'NodeAddress["factorio","inserter","2"]'
);
});
it("for synthetic connections, renders only a `Badge`", async () => {
const {cvForConnection, syntheticConnection} = await setup();

View File

@ -7,11 +7,12 @@ import * as NullUtil from "../../../util/null";
import {TableRow} from "./TableRow";
import {AggregationRowList} from "./Aggregation";
import {type NodeAddressT, NodeAddress} from "../../../core/graph";
import type {NodeAddressT} from "../../../core/graph";
import {nodeDescription} from "./shared";
import {example} from "./sharedTestUtils";
import {NodeRowList, NodeRow, type NodeRowProps} from "./Node";
import {factorioNodes} from "../../adapters/demoAdapters";
require("../../testUtil").configureEnzyme();
@ -32,14 +33,8 @@ describe("app/credExplorer/pagerankTable/Node", () => {
}
async function setup(maxEntriesPerList: number = 100000) {
const {adapters, pnd} = await example();
const nodes = sortedByScore(Array.from(pnd.keys()), pnd)
.reverse() // ascending order!
.filter((x) =>
NodeAddress.hasPrefix(x, NodeAddress.fromParts(["foo"]))
);
const nodes = Array.from(pnd.keys());
expect(nodes).not.toHaveLength(0);
expect(nodes).not.toHaveLength(1);
expect(nodes).not.toHaveLength(pnd.size);
const sharedProps = {adapters, pnd, maxEntriesPerList};
const component = <NodeRowList sharedProps={sharedProps} nodes={nodes} />;
const element = shallow(component);
@ -86,9 +81,9 @@ describe("app/credExplorer/pagerankTable/Node", () => {
describe("NodeRow", () => {
async function setup(props: $Shape<{...NodeRowProps}>) {
props = props || {};
const {pnd, adapters, nodes} = await example();
const {pnd, adapters} = await example();
const sharedProps = {adapters, pnd, maxEntriesPerList: 123};
const node = nodes.bar1;
const node = factorioNodes.inserter1;
const component = shallow(
<NodeRow
node={NullUtil.orElse(props.node, node)}

View File

@ -70,8 +70,9 @@ describe("app/credExplorer/pagerankTable/Table", () => {
});
it("with the ability to filter nodes passed to NodeRowList", async () => {
const {element, options} = await setup();
const option1 = options.at(1);
const value = option1.prop("value");
const option = options.at(2);
const value = option.prop("value");
expect(value).not.toEqual(NodeAddress.empty);
const previousNodes = element.find("NodeRowList").prop("nodes");
expect(
@ -89,7 +90,7 @@ describe("app/credExplorer/pagerankTable/Table", () => {
expect(element.state().topLevelFilter).toEqual(NodeAddress.empty);
});
it("filter defaults to defaultNodeFilter if available", async () => {
const filter = NodeAddress.fromParts(["foo", "a"]);
const filter = NodeAddress.fromParts(["factorio", "inserter"]);
const {element} = await setup(filter);
expect(element.state().topLevelFilter).toEqual(filter);
});

View File

@ -13,44 +13,18 @@ Array [
"style": Object {
"fontWeight": "bold",
},
"text": "bar",
"valueString": "NodeAddress[\\"bar\\"]",
"text": "Factorio demo adapter",
"valueString": "NodeAddress[\\"factorio\\"]",
},
Object {
"style": undefined,
"text": "alpha",
"valueString": "NodeAddress[\\"bar\\",\\"a\\"]",
},
Object {
"style": Object {
"fontWeight": "bold",
},
"text": "foo",
"valueString": "NodeAddress[\\"foo\\"]",
"text": "inserter",
"valueString": "NodeAddress[\\"factorio\\",\\"inserter\\"]",
},
Object {
"style": undefined,
"text": "alpha",
"valueString": "NodeAddress[\\"foo\\",\\"a\\"]",
},
Object {
"style": undefined,
"text": "beta",
"valueString": "NodeAddress[\\"foo\\",\\"b\\"]",
},
Object {
"style": Object {
"fontWeight": "bold",
},
"text": "unused",
"valueString": "NodeAddress[\\"unused\\"]",
},
Object {
"style": Object {
"fontWeight": "bold",
},
"text": "xox",
"valueString": "NodeAddress[\\"xox\\"]",
"text": "machine",
"valueString": "NodeAddress[\\"factorio\\",\\"machine\\"]",
},
]
`;

View File

@ -11,6 +11,9 @@ import {
} from "./aggregate";
describe("app/credExplorer/aggregate", () => {
// TODO: If making major modifications to these tests, consider switching
// from the hand-maintained connections and types, and instead use the demo
// adadpters from app/adapters/demoAdapters
function example() {
const nodes = {
root: NodeAddress.fromParts(["root"]),

View File

@ -1,151 +1,17 @@
// @flow
import {Graph, NodeAddress, EdgeAddress} from "../../../core/graph";
import {StaticAdapterSet, DynamicAdapterSet} from "../../adapters/adapterSet";
import type {DynamicPluginAdapter} from "../../adapters/pluginAdapter";
import {dynamicAdapterSet} from "../../adapters/demoAdapters";
import {pagerank} from "../../../core/attribution/pagerank";
export const COLUMNS = () => ["Description", "", "Cred"];
export async function example() {
const graph = new Graph();
const nodes = {
fooAlpha: NodeAddress.fromParts(["foo", "a", "1"]),
fooBeta: NodeAddress.fromParts(["foo", "b", "2"]),
bar1: NodeAddress.fromParts(["bar", "a", "1"]),
bar2: NodeAddress.fromParts(["bar", "2"]),
xox: NodeAddress.fromParts(["xox"]),
empty: NodeAddress.empty,
};
Object.values(nodes).forEach((n) => graph.addNode((n: any)));
function addEdge(parts, src, dst) {
const edge = {address: EdgeAddress.fromParts(parts), src, dst};
graph.addEdge(edge);
return edge;
}
const edges = {
fooA: addEdge(["foo", "a"], nodes.fooAlpha, nodes.fooBeta),
fooB: addEdge(["foo", "b"], nodes.fooAlpha, nodes.bar1),
fooC: addEdge(["foo", "c"], nodes.fooAlpha, nodes.xox),
barD: addEdge(["bar", "d"], nodes.bar1, nodes.bar1),
barE: addEdge(["bar", "e"], nodes.bar1, nodes.xox),
barF: addEdge(["bar", "f"], nodes.bar1, nodes.xox),
};
const dynamicAdapters: DynamicPluginAdapter[] = [
{
static: () => ({
name: () => "foo",
nodePrefix: () => NodeAddress.fromParts(["foo"]),
edgePrefix: () => EdgeAddress.fromParts(["foo"]),
nodeTypes: () => [
{
pluralName: "alphas",
name: "alpha",
prefix: NodeAddress.fromParts(["foo", "a"]),
defaultWeight: 1,
},
{
pluralName: "betas",
name: "beta",
prefix: NodeAddress.fromParts(["foo", "b"]),
defaultWeight: 1,
},
],
edgeTypes: () => [
{
prefix: EdgeAddress.fromParts(["foo"]),
forwardName: "foos",
backwardName: "is fooed by",
},
],
load: (_unused_repo) => {
throw new Error("unused");
},
}),
graph: () => {
throw new Error("unused");
},
nodeDescription: (x) => `foo: ${NodeAddress.toString(x)}`,
},
{
static: () => ({
name: () => "bar",
nodePrefix: () => NodeAddress.fromParts(["bar"]),
edgePrefix: () => EdgeAddress.fromParts(["bar"]),
nodeTypes: () => [
{
name: "alpha",
pluralName: "alphas",
prefix: NodeAddress.fromParts(["bar", "a"]),
defaultWeight: 1,
},
],
edgeTypes: () => [
{
prefix: EdgeAddress.fromParts(["bar"]),
forwardName: "bars",
backwardName: "is barred by",
},
],
load: (_unused_repo) => {
throw new Error("unused");
},
}),
graph: () => {
throw new Error("unused");
},
nodeDescription: (x) => `bar: ${NodeAddress.toString(x)}`,
},
{
static: () => ({
name: () => "xox",
nodePrefix: () => NodeAddress.fromParts(["xox"]),
edgePrefix: () => EdgeAddress.fromParts(["xox"]),
nodeTypes: () => [],
edgeTypes: () => [],
load: (_unused_repo) => {
throw new Error("unused");
},
}),
graph: () => {
throw new Error("unused");
},
nodeDescription: (_unused_arg) => `xox node!`,
},
{
static: () => ({
nodePrefix: () => NodeAddress.fromParts(["unused"]),
edgePrefix: () => EdgeAddress.fromParts(["unused"]),
nodeTypes: () => [],
edgeTypes: () => [],
name: () => "unused",
load: (_unused_repo) => {
throw new Error("unused");
},
}),
graph: () => {
throw new Error("unused");
},
nodeDescription: () => {
throw new Error("Unused");
},
},
];
const staticAdapters = dynamicAdapters.map((x) => x.static());
const adapters = new DynamicAdapterSet(
new StaticAdapterSet(staticAdapters),
dynamicAdapters
);
const adapters = await dynamicAdapterSet();
const graph = adapters.graph();
const pnd = await pagerank(graph, (_unused_Edge) => ({
toWeight: 1,
froWeight: 1,
}));
return {adapters, nodes, edges, graph, pnd};
return {adapters, pnd};
}