Factor eventide graph demo data to a new module (#71)

* Factor evertide graph demo data to a new module

It would be helpful to make our standard tiny graph available to other
test and demo instances, outside of just graph.test.js. This way we can
use it as a test case for the Graph Explorer.
This commit is contained in:
Dandelion Mané 2018-03-05 20:58:47 -08:00 committed by GitHub
parent 7ea8bdd964
commit fb00c35823
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 275 additions and 230 deletions

View File

@ -3,6 +3,7 @@
import type {Address, Addressable} from "./address"; import type {Address, Addressable} from "./address";
import {sortedByAddress} from "./address"; import {sortedByAddress} from "./address";
import {Graph} from "./graph"; import {Graph} from "./graph";
import * as demoData from "./graphDemoData";
describe("graph", () => { describe("graph", () => {
describe("#Graph", () => { describe("#Graph", () => {
@ -13,127 +14,23 @@ describe("graph", () => {
expect(sortedByAddress(xs)).toEqual(sortedByAddress(ys)); expect(sortedByAddress(xs)).toEqual(sortedByAddress(ys));
} }
// A Seafood Fruit Mix is made by cooking Mighty Bananas (picked
// from a tree) and a Razorclaw Crab (grabbed from the beach). In
// this graph, an edge from `u` to `v` means that `u` thanks `v` for
// a particular contribution. For example, the meal thanks the hero
// for cooking it, as well as thanking the bananas and the crab for
// composing it.
function makeAddress(id: string): Address {
return {
repositoryName: "sourcecred/eventide",
pluginName: "hill_cooking_pot",
id,
};
}
const heroNode = () => ({
address: makeAddress("hero_of_time#0"),
payload: {},
});
const bananasNode = () => ({
address: makeAddress("mighty_bananas#1"),
payload: {},
});
const crabNode = () => ({
address: makeAddress("razorclaw_crab#2"),
payload: {},
});
const mealNode = () => ({
address: makeAddress("seafood_fruit_mix#3"),
payload: {
effect: ["attack_power", 1],
},
});
const pickEdge = () => ({
address: makeAddress("hero_of_time#0@picks@mighty_bananas#1"),
src: bananasNode().address,
dst: heroNode().address,
payload: {},
});
const grabEdge = () => ({
address: makeAddress("hero_of_time#0@grabs@razorclaw_crab#2"),
src: crabNode().address,
dst: heroNode().address,
payload: {},
});
const cookEdge = () => ({
address: makeAddress("hero_of_time#0@cooks@seafood_fruit_mix#3"),
src: mealNode().address,
dst: heroNode().address,
payload: {
crit: false,
},
});
const bananasIngredientEdge = () => ({
address: makeAddress("mighty_bananas#1@included_in@seafood_fruit_mix#3"),
src: mealNode().address,
dst: bananasNode().address,
payload: {},
});
const crabIngredientEdge = () => ({
address: makeAddress("razorclaw_crab#2@included_in@seafood_fruit_mix#3"),
src: mealNode().address,
dst: crabNode().address,
payload: {},
});
const eatEdge = () => ({
address: makeAddress("hero_of_time#0@eats@seafood_fruit_mix#3"),
src: heroNode().address,
dst: mealNode().address,
payload: {},
});
const simpleMealGraph = () =>
new Graph()
.addNode(heroNode())
.addNode(bananasNode())
.addNode(crabNode())
.addNode(mealNode())
.addEdge(pickEdge())
.addEdge(grabEdge())
.addEdge(cookEdge())
.addEdge(bananasIngredientEdge())
.addEdge(crabIngredientEdge())
.addEdge(eatEdge());
const crabLoopEdge = () => ({
address: makeAddress("crab-self-assessment"),
src: crabNode().address,
dst: crabNode().address,
payload: {evaluation: "not effective at avoiding hero"},
});
const duplicateCookEdge = () => ({
address: makeAddress("hero_of_time#0@again_cooks@seafood_fruit_mix#3"),
src: mealNode().address,
dst: heroNode().address,
payload: {
crit: true,
saveScummed: true,
},
});
const advancedMealGraph = () =>
simpleMealGraph()
.addEdge(crabLoopEdge())
.addEdge(duplicateCookEdge());
describe("construction", () => { describe("construction", () => {
it("works for a simple graph", () => { it("works for a simple graph", () => {
simpleMealGraph(); demoData.simpleMealGraph();
}); });
it("works for an advanced graph", () => { it("works for an advanced graph", () => {
advancedMealGraph(); demoData.advancedMealGraph();
}); });
it("forbids adding an edge with dangling `dst`", () => { it("forbids adding an edge with dangling `dst`", () => {
expect(() => { expect(() => {
simpleMealGraph().addEdge({ demoData.simpleMealGraph().addEdge({
address: makeAddress( address: demoData.makeAddress(
"treasure_octorok#5@helps_cook@seafood_fruit_mix#3" "treasure_octorok#5@helps_cook@seafood_fruit_mix#3"
), ),
src: mealNode().address, src: demoData.mealNode().address,
dst: makeAddress("treasure_octorok#5"), dst: demoData.makeAddress("treasure_octorok#5"),
payload: {}, payload: {},
}); });
}).toThrow(/does not exist/); }).toThrow(/does not exist/);
@ -141,10 +38,12 @@ describe("graph", () => {
it("forbids adding an edge with dangling `src`", () => { it("forbids adding an edge with dangling `src`", () => {
expect(() => { expect(() => {
simpleMealGraph().addEdge({ demoData.simpleMealGraph().addEdge({
address: makeAddress("health_bar#6@healed_by@seafood_fruit_mix#3"), address: demoData.makeAddress(
src: makeAddress("health_bar#6"), "health_bar#6@healed_by@seafood_fruit_mix#3"
dst: mealNode().address, ),
src: demoData.makeAddress("health_bar#6"),
dst: demoData.mealNode().address,
payload: {}, payload: {},
}); });
}).toThrow(/does not exist/); }).toThrow(/does not exist/);
@ -192,44 +91,54 @@ describe("graph", () => {
describe("getting nodes and edges", () => { describe("getting nodes and edges", () => {
it("correctly gets nodes in the simple graph", () => { it("correctly gets nodes in the simple graph", () => {
const g = simpleMealGraph(); const g = demoData.simpleMealGraph();
[heroNode(), bananasNode(), crabNode(), mealNode()].forEach((x) => { [
demoData.heroNode(),
demoData.bananasNode(),
demoData.crabNode(),
demoData.mealNode(),
].forEach((x) => {
expect(g.getNode(x.address)).toEqual(x); expect(g.getNode(x.address)).toEqual(x);
}); });
}); });
it("correctly gets nodes in the advanced graph", () => { it("correctly gets nodes in the advanced graph", () => {
const g = advancedMealGraph(); const g = demoData.advancedMealGraph();
[heroNode(), bananasNode(), crabNode(), mealNode()].forEach((x) => { [
demoData.heroNode(),
demoData.bananasNode(),
demoData.crabNode(),
demoData.mealNode(),
].forEach((x) => {
expect(g.getNode(x.address)).toEqual(x); expect(g.getNode(x.address)).toEqual(x);
}); });
}); });
it("correctly gets edges in the simple graph", () => { it("correctly gets edges in the simple graph", () => {
const g = simpleMealGraph(); const g = demoData.simpleMealGraph();
[ [
pickEdge(), demoData.pickEdge(),
grabEdge(), demoData.grabEdge(),
cookEdge(), demoData.cookEdge(),
bananasIngredientEdge(), demoData.bananasIngredientEdge(),
crabIngredientEdge(), demoData.crabIngredientEdge(),
eatEdge(), demoData.eatEdge(),
].forEach((x) => { ].forEach((x) => {
expect(g.getEdge(x.address)).toEqual(x); expect(g.getEdge(x.address)).toEqual(x);
}); });
}); });
it("correctly gets edges in the advanced graph", () => { it("correctly gets edges in the advanced graph", () => {
const g = advancedMealGraph(); const g = demoData.advancedMealGraph();
[ [
pickEdge(), demoData.pickEdge(),
grabEdge(), demoData.grabEdge(),
cookEdge(), demoData.cookEdge(),
bananasIngredientEdge(), demoData.bananasIngredientEdge(),
crabIngredientEdge(), demoData.crabIngredientEdge(),
eatEdge(), demoData.eatEdge(),
crabLoopEdge(), demoData.crabLoopEdge(),
duplicateCookEdge(), demoData.duplicateCookEdge(),
].forEach((x) => { ].forEach((x) => {
expect(g.getEdge(x.address)).toEqual(x); expect(g.getEdge(x.address)).toEqual(x);
}); });
@ -237,36 +146,47 @@ describe("graph", () => {
it("returns `undefined` for nodes that do not exist", () => { it("returns `undefined` for nodes that do not exist", () => {
expect( expect(
simpleMealGraph().getNode(makeAddress("treasure_octorok#5")) demoData
.simpleMealGraph()
.getNode(demoData.makeAddress("treasure_octorok#5"))
).toBeUndefined(); ).toBeUndefined();
}); });
it("returns `undefined` for edges that do not exist", () => { it("returns `undefined` for edges that do not exist", () => {
expect( expect(
simpleMealGraph().getNode( demoData
makeAddress("treasure_octorok#5@helps_cook@seafood_fruit_mix#3") .simpleMealGraph()
) .getNode(
demoData.makeAddress(
"treasure_octorok#5@helps_cook@seafood_fruit_mix#3"
)
)
).toBeUndefined(); ).toBeUndefined();
}); });
it("gets all nodes", () => { it("gets all nodes", () => {
const expected = [heroNode(), bananasNode(), crabNode(), mealNode()]; const expected = [
const actual = advancedMealGraph().getAllNodes(); demoData.heroNode(),
demoData.bananasNode(),
demoData.crabNode(),
demoData.mealNode(),
];
const actual = demoData.advancedMealGraph().getAllNodes();
expectSameSorted(expected, actual); expectSameSorted(expected, actual);
}); });
it("gets all edges", () => { it("gets all edges", () => {
const expected = [ const expected = [
pickEdge(), demoData.pickEdge(),
grabEdge(), demoData.grabEdge(),
cookEdge(), demoData.cookEdge(),
bananasIngredientEdge(), demoData.bananasIngredientEdge(),
crabIngredientEdge(), demoData.crabIngredientEdge(),
eatEdge(), demoData.eatEdge(),
crabLoopEdge(), demoData.crabLoopEdge(),
duplicateCookEdge(), demoData.duplicateCookEdge(),
]; ];
const actual = advancedMealGraph().getAllEdges(); const actual = demoData.advancedMealGraph().getAllEdges();
expectSameSorted(expected, actual); expectSameSorted(expected, actual);
}); });
}); });
@ -274,8 +194,8 @@ describe("graph", () => {
describe("creating nodes and edges", () => { describe("creating nodes and edges", () => {
it("forbids adding a node with existing address", () => { it("forbids adding a node with existing address", () => {
expect(() => expect(() =>
simpleMealGraph().addNode({ demoData.simpleMealGraph().addNode({
address: crabNode().address, address: demoData.crabNode().address,
payload: {anotherCrab: true}, payload: {anotherCrab: true},
}) })
).toThrow(/already exists/); ).toThrow(/already exists/);
@ -283,29 +203,31 @@ describe("graph", () => {
it("forbids adding an edge with existing address", () => { it("forbids adding an edge with existing address", () => {
expect(() => expect(() =>
simpleMealGraph().addEdge({ demoData.simpleMealGraph().addEdge({
address: cookEdge().address, address: demoData.cookEdge().address,
src: crabNode().address, src: demoData.crabNode().address,
dst: crabNode().address, dst: demoData.crabNode().address,
payload: {}, payload: {},
}) })
).toThrow(/already exists/); ).toThrow(/already exists/);
}); });
it("allows creating self-loops", () => { it("allows creating self-loops", () => {
const g = simpleMealGraph(); const g = demoData.simpleMealGraph();
g.addEdge(crabLoopEdge()); g.addEdge(demoData.crabLoopEdge());
expect(g.getOutEdges(crabNode().address)).toContainEqual( expect(g.getOutEdges(demoData.crabNode().address)).toContainEqual(
crabLoopEdge() demoData.crabLoopEdge()
);
expect(g.getInEdges(demoData.crabNode().address)).toContainEqual(
demoData.crabLoopEdge()
); );
expect(g.getInEdges(crabNode().address)).toContainEqual(crabLoopEdge());
}); });
it("allows creating multiple edges between the same nodes", () => { it("allows creating multiple edges between the same nodes", () => {
const g = simpleMealGraph(); const g = demoData.simpleMealGraph();
g.addEdge(duplicateCookEdge()); g.addEdge(demoData.duplicateCookEdge());
[cookEdge(), duplicateCookEdge()].forEach((e) => { [demoData.cookEdge(), demoData.duplicateCookEdge()].forEach((e) => {
expect(g.getOutEdges(mealNode().address)).toContainEqual(e); expect(g.getOutEdges(demoData.mealNode().address)).toContainEqual(e);
expect(g.getEdge(e.address)).toEqual(e); expect(g.getEdge(e.address)).toEqual(e);
}); });
}); });
@ -315,16 +237,16 @@ describe("graph", () => {
// the namespaces to be forced to be disjoint. In that case, we can // the namespaces to be forced to be disjoint. In that case, we can
// certainly change these tests. // certainly change these tests.
it("allows adding an edge with an existing node's address", () => { it("allows adding an edge with an existing node's address", () => {
simpleMealGraph().addEdge({ demoData.simpleMealGraph().addEdge({
address: crabNode().address, address: demoData.crabNode().address,
src: crabNode().address, src: demoData.crabNode().address,
dst: crabNode().address, dst: demoData.crabNode().address,
payload: {message: "thanks for being you"}, payload: {message: "thanks for being you"},
}); });
}); });
it("allows adding a node with an existing edge's address", () => { it("allows adding a node with an existing edge's address", () => {
simpleMealGraph().addNode({ demoData.simpleMealGraph().addNode({
address: cookEdge().address, address: demoData.cookEdge().address,
payload: {}, payload: {},
}); });
}); });
@ -333,21 +255,21 @@ describe("graph", () => {
describe("in- and out-edges", () => { describe("in- and out-edges", () => {
it("gets out-edges", () => { it("gets out-edges", () => {
const nodeAndExpectedEdgePairs = [ const nodeAndExpectedEdgePairs = [
[heroNode(), [eatEdge()]], [demoData.heroNode(), [demoData.eatEdge()]],
[bananasNode(), [pickEdge()]], [demoData.bananasNode(), [demoData.pickEdge()]],
[crabNode(), [grabEdge(), crabLoopEdge()]], [demoData.crabNode(), [demoData.grabEdge(), demoData.crabLoopEdge()]],
[ [
mealNode(), demoData.mealNode(),
[ [
bananasIngredientEdge(), demoData.bananasIngredientEdge(),
crabIngredientEdge(), demoData.crabIngredientEdge(),
cookEdge(), demoData.cookEdge(),
duplicateCookEdge(), demoData.duplicateCookEdge(),
], ],
], ],
]; ];
nodeAndExpectedEdgePairs.forEach(([node, expectedEdges]) => { nodeAndExpectedEdgePairs.forEach(([node, expectedEdges]) => {
const actual = advancedMealGraph().getOutEdges(node.address); const actual = demoData.advancedMealGraph().getOutEdges(node.address);
expectSameSorted(actual, expectedEdges); expectSameSorted(actual, expectedEdges);
}); });
}); });
@ -355,62 +277,76 @@ describe("graph", () => {
it("gets in-edges", () => { it("gets in-edges", () => {
const nodeAndExpectedEdgePairs = [ const nodeAndExpectedEdgePairs = [
[ [
heroNode(), demoData.heroNode(),
[pickEdge(), grabEdge(), cookEdge(), duplicateCookEdge()], [
demoData.pickEdge(),
demoData.grabEdge(),
demoData.cookEdge(),
demoData.duplicateCookEdge(),
],
], ],
[bananasNode(), [bananasIngredientEdge()]], [demoData.bananasNode(), [demoData.bananasIngredientEdge()]],
[crabNode(), [crabIngredientEdge(), crabLoopEdge()]], [
[mealNode(), [eatEdge()]], demoData.crabNode(),
[demoData.crabIngredientEdge(), demoData.crabLoopEdge()],
],
[demoData.mealNode(), [demoData.eatEdge()]],
]; ];
nodeAndExpectedEdgePairs.forEach(([node, expectedEdges]) => { nodeAndExpectedEdgePairs.forEach(([node, expectedEdges]) => {
const actual = advancedMealGraph().getInEdges(node.address); const actual = demoData.advancedMealGraph().getInEdges(node.address);
expectSameSorted(actual, expectedEdges); expectSameSorted(actual, expectedEdges);
}); });
}); });
it("fails to get out-edges for a nonexistent node", () => { it("fails to get out-edges for a nonexistent node", () => {
expect(() => { expect(() => {
simpleMealGraph().getOutEdges(makeAddress("hinox")); demoData.simpleMealGraph().getOutEdges(demoData.makeAddress("hinox"));
}).toThrow(/no node for address/); }).toThrow(/no node for address/);
}); });
it("fails to get in-edges for a nonexistent node", () => { it("fails to get in-edges for a nonexistent node", () => {
expect(() => { expect(() => {
simpleMealGraph().getInEdges(makeAddress("hinox")); demoData.simpleMealGraph().getInEdges(demoData.makeAddress("hinox"));
}).toThrow(/no node for address/); }).toThrow(/no node for address/);
}); });
}); });
describe("#equals", () => { describe("#equals", () => {
it("returns true for identity-equal graphs", () => { it("returns true for identity-equal graphs", () => {
const g = advancedMealGraph(); const g = demoData.advancedMealGraph();
expect(g.equals(g)).toBe(true); expect(g.equals(g)).toBe(true);
}); });
it("returns true for deep-equal graphs", () => { it("returns true for deep-equal graphs", () => {
expect(advancedMealGraph().equals(advancedMealGraph())).toBe(true); expect(
demoData.advancedMealGraph().equals(demoData.advancedMealGraph())
).toBe(true);
}); });
it("returns false when the LHS has nodes missing in the RHS", () => { it("returns false when the LHS has nodes missing in the RHS", () => {
expect(advancedMealGraph().equals(simpleMealGraph())).toBe(false); expect(
demoData.advancedMealGraph().equals(demoData.simpleMealGraph())
).toBe(false);
}); });
it("returns false when the RHS has nodes missing in the LHS", () => { it("returns false when the RHS has nodes missing in the LHS", () => {
expect(simpleMealGraph().equals(advancedMealGraph())).toBe(false); expect(
demoData.simpleMealGraph().equals(demoData.advancedMealGraph())
).toBe(false);
}); });
const extraNode1 = () => ({ const extraNode1 = () => ({
address: makeAddress("octorok"), address: demoData.makeAddress("octorok"),
payload: {}, payload: {},
}); });
const extraNode2 = () => ({ const extraNode2 = () => ({
address: makeAddress("hinox"), address: demoData.makeAddress("hinox"),
payload: {status: "sleeping"}, payload: {status: "sleeping"},
}); });
it("returns false when the LHS has edges missing in the RHS", () => { it("returns false when the LHS has edges missing in the RHS", () => {
const g1 = advancedMealGraph(); const g1 = demoData.advancedMealGraph();
const g2 = advancedMealGraph().addNode(extraNode1()); const g2 = demoData.advancedMealGraph().addNode(extraNode1());
expect(g1.equals(g2)).toBe(false); expect(g1.equals(g2)).toBe(false);
}); });
it("returns false when the LHS has edges missing in the RHS", () => { it("returns false when the LHS has edges missing in the RHS", () => {
const g1 = advancedMealGraph().addNode(extraNode1()); const g1 = demoData.advancedMealGraph().addNode(extraNode1());
const g2 = advancedMealGraph(); const g2 = demoData.advancedMealGraph();
expect(g1.equals(g2)).toBe(false); expect(g1.equals(g2)).toBe(false);
}); });
it("returns true when nodes are added in different orders", () => { it("returns true when nodes are added in different orders", () => {
@ -468,33 +404,32 @@ describe("graph", () => {
} }
it("conservatively recomposes a neighborhood decomposition", () => { it("conservatively recomposes a neighborhood decomposition", () => {
const result = neighborhoodDecomposition(advancedMealGraph()).reduce( const result = neighborhoodDecomposition(
(g1, g2) => Graph.mergeConservative(g1, g2), demoData.advancedMealGraph()
new Graph() ).reduce((g1, g2) => Graph.mergeConservative(g1, g2), new Graph());
); expect(result.equals(demoData.advancedMealGraph())).toBe(true);
expect(result.equals(advancedMealGraph())).toBe(true);
}); });
it("conservatively recomposes an edge decomposition", () => { it("conservatively recomposes an edge decomposition", () => {
const result = edgeDecomposition(advancedMealGraph()).reduce( const result = edgeDecomposition(demoData.advancedMealGraph()).reduce(
(g1, g2) => Graph.mergeConservative(g1, g2), (g1, g2) => Graph.mergeConservative(g1, g2),
new Graph() new Graph()
); );
expect(result.equals(advancedMealGraph())).toBe(true); expect(result.equals(demoData.advancedMealGraph())).toBe(true);
}); });
it("conservatively merges a graph with itself", () => { it("conservatively merges a graph with itself", () => {
const result = Graph.mergeConservative( const result = Graph.mergeConservative(
advancedMealGraph(), demoData.advancedMealGraph(),
advancedMealGraph() demoData.advancedMealGraph()
); );
expect(result.equals(advancedMealGraph())).toBe(true); expect(result.equals(demoData.advancedMealGraph())).toBe(true);
}); });
it("conservatively rejects a graph with conflicting nodes", () => { it("conservatively rejects a graph with conflicting nodes", () => {
const makeGraph: (nodePayload: string) => Graph = (nodePayload) => const makeGraph: (nodePayload: string) => Graph = (nodePayload) =>
new Graph().addNode({ new Graph().addNode({
address: makeAddress("conflicting-node"), address: demoData.makeAddress("conflicting-node"),
payload: nodePayload, payload: nodePayload,
}); });
const g1 = makeGraph("one"); const g1 = makeGraph("one");
@ -505,14 +440,14 @@ describe("graph", () => {
}); });
it("conservatively rejects a graph with conflicting edges", () => { it("conservatively rejects a graph with conflicting edges", () => {
const srcAddress = makeAddress("src"); const srcAddress = demoData.makeAddress("src");
const dstAddress = makeAddress("dst"); const dstAddress = demoData.makeAddress("dst");
const makeGraph: (edgePayload: string) => Graph = (edgePayload) => const makeGraph: (edgePayload: string) => Graph = (edgePayload) =>
new Graph() new Graph()
.addNode({address: srcAddress, payload: {}}) .addNode({address: srcAddress, payload: {}})
.addNode({address: dstAddress, payload: {}}) .addNode({address: dstAddress, payload: {}})
.addEdge({ .addEdge({
address: makeAddress("conflicting-edge"), address: demoData.makeAddress("conflicting-edge"),
src: srcAddress, src: srcAddress,
dst: dstAddress, dst: dstAddress,
payload: edgePayload, payload: edgePayload,
@ -530,20 +465,20 @@ describe("graph", () => {
it("has the empty graph as a left identity", () => { it("has the empty graph as a left identity", () => {
const merged = Graph.merge( const merged = Graph.merge(
new Graph(), new Graph(),
advancedMealGraph(), demoData.advancedMealGraph(),
assertNotCalled, assertNotCalled,
assertNotCalled assertNotCalled
); );
expect(merged.equals(advancedMealGraph())).toBe(true); expect(merged.equals(demoData.advancedMealGraph())).toBe(true);
}); });
it("has the empty graph as a right identity", () => { it("has the empty graph as a right identity", () => {
const merged = Graph.merge( const merged = Graph.merge(
advancedMealGraph(), demoData.advancedMealGraph(),
new Graph(), new Graph(),
assertNotCalled, assertNotCalled,
assertNotCalled assertNotCalled
); );
expect(merged.equals(advancedMealGraph())).toBe(true); expect(merged.equals(demoData.advancedMealGraph())).toBe(true);
}); });
it("trivially merges the empty graph with itself", () => { it("trivially merges the empty graph with itself", () => {
const merged = Graph.merge( const merged = Graph.merge(
@ -558,39 +493,43 @@ describe("graph", () => {
describe("JSON functions", () => { describe("JSON functions", () => {
it("should serialize a simple graph", () => { it("should serialize a simple graph", () => {
expect(advancedMealGraph().toJSON()).toMatchSnapshot(); expect(demoData.advancedMealGraph().toJSON()).toMatchSnapshot();
}); });
it("should work transparently with JSON.stringify", () => { it("should work transparently with JSON.stringify", () => {
// (This is guaranteed by the `JSON.stringify` API, and is more // (This is guaranteed by the `JSON.stringify` API, and is more
// as documentation than actual test.) // as documentation than actual test.)
expect(JSON.stringify(advancedMealGraph())).toEqual( expect(JSON.stringify(demoData.advancedMealGraph())).toEqual(
JSON.stringify(advancedMealGraph().toJSON()) JSON.stringify(demoData.advancedMealGraph().toJSON())
); );
}); });
it("should canonicalize away node insertion order", () => { it("should canonicalize away node insertion order", () => {
const g1 = new Graph().addNode(heroNode()).addNode(mealNode()); const g1 = new Graph()
const g2 = new Graph().addNode(mealNode()).addNode(heroNode()); .addNode(demoData.heroNode())
.addNode(demoData.mealNode());
const g2 = new Graph()
.addNode(demoData.mealNode())
.addNode(demoData.heroNode());
expect(g1.toJSON()).toEqual(g2.toJSON()); expect(g1.toJSON()).toEqual(g2.toJSON());
}); });
it("should canonicalize away edge insertion order", () => { it("should canonicalize away edge insertion order", () => {
const g1 = new Graph() const g1 = new Graph()
.addNode(heroNode()) .addNode(demoData.heroNode())
.addNode(mealNode()) .addNode(demoData.mealNode())
.addEdge(cookEdge()) .addEdge(demoData.cookEdge())
.addEdge(duplicateCookEdge()); .addEdge(demoData.duplicateCookEdge());
const g2 = new Graph() const g2 = new Graph()
.addNode(heroNode()) .addNode(demoData.heroNode())
.addNode(mealNode()) .addNode(demoData.mealNode())
.addEdge(duplicateCookEdge()) .addEdge(demoData.duplicateCookEdge())
.addEdge(cookEdge()); .addEdge(demoData.cookEdge());
expect(g1.toJSON()).toEqual(g2.toJSON()); expect(g1.toJSON()).toEqual(g2.toJSON());
}); });
it("should no-op on a serialization--deserialization roundtrip", () => { it("should no-op on a serialization--deserialization roundtrip", () => {
const g = () => advancedMealGraph(); const g = () => demoData.advancedMealGraph();
expect(Graph.fromJSON(g().toJSON()).equals(g())).toBe(true); expect(Graph.fromJSON(g().toJSON()).equals(g())).toBe(true);
}); });
it("should no-op on a deserialization--serialization roundtrip", () => { it("should no-op on a deserialization--serialization roundtrip", () => {
const json = () => advancedMealGraph().toJSON(); const json = () => demoData.advancedMealGraph().toJSON();
expect(Graph.fromJSON(json()).toJSON()).toEqual(json()); expect(Graph.fromJSON(json()).toJSON()).toEqual(json());
}); });
}); });

View File

@ -0,0 +1,106 @@
// @flow
// This module provides some small demo graphs, which report
// on a hero's adventures in cooking a seafood fruit mix.
// It is factored as its own module so that it may be depended on by
// multiple test and demo consumers.
import type {Address} from "./address";
import {Graph} from "./graph";
export function makeAddress(id: string): Address {
return {
repositoryName: "sourcecred/eventide",
pluginName: "hill_cooking_pot",
id,
};
}
export const heroNode = () => ({
address: makeAddress("hero_of_time#0"),
payload: {},
});
export const bananasNode = () => ({
address: makeAddress("mighty_bananas#1"),
payload: {},
});
export const crabNode = () => ({
address: makeAddress("razorclaw_crab#2"),
payload: {},
});
export const mealNode = () => ({
address: makeAddress("seafood_fruit_mix#3"),
payload: {
effect: ["attack_power", 1],
},
});
export const pickEdge = () => ({
address: makeAddress("hero_of_time#0@picks@mighty_bananas#1"),
src: bananasNode().address,
dst: heroNode().address,
payload: {},
});
export const grabEdge = () => ({
address: makeAddress("hero_of_time#0@grabs@razorclaw_crab#2"),
src: crabNode().address,
dst: heroNode().address,
payload: {},
});
export const cookEdge = () => ({
address: makeAddress("hero_of_time#0@cooks@seafood_fruit_mix#3"),
src: mealNode().address,
dst: heroNode().address,
payload: {
crit: false,
},
});
export const bananasIngredientEdge = () => ({
address: makeAddress("mighty_bananas#1@included_in@seafood_fruit_mix#3"),
src: mealNode().address,
dst: bananasNode().address,
payload: {},
});
export const crabIngredientEdge = () => ({
address: makeAddress("razorclaw_crab#2@included_in@seafood_fruit_mix#3"),
src: mealNode().address,
dst: crabNode().address,
payload: {},
});
export const eatEdge = () => ({
address: makeAddress("hero_of_time#0@eats@seafood_fruit_mix#3"),
src: heroNode().address,
dst: mealNode().address,
payload: {},
});
export const simpleMealGraph = () =>
new Graph()
.addNode(heroNode())
.addNode(bananasNode())
.addNode(crabNode())
.addNode(mealNode())
.addEdge(pickEdge())
.addEdge(grabEdge())
.addEdge(cookEdge())
.addEdge(bananasIngredientEdge())
.addEdge(crabIngredientEdge())
.addEdge(eatEdge());
export const crabLoopEdge = () => ({
address: makeAddress("crab-self-assessment"),
src: crabNode().address,
dst: crabNode().address,
payload: {evaluation: "not effective at avoiding hero"},
});
export const duplicateCookEdge = () => ({
address: makeAddress("hero_of_time#0@again_cooks@seafood_fruit_mix#3"),
src: mealNode().address,
dst: heroNode().address,
payload: {
crit: true,
saveScummed: true,
},
});
export const advancedMealGraph = () =>
simpleMealGraph()
.addEdge(crabLoopEdge())
.addEdge(duplicateCookEdge());