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:
parent
7ea8bdd964
commit
fb00c35823
|
@ -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());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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());
|
Loading…
Reference in New Issue