Add an `advancedGraph` test case (#377)
The `advancedGraph` is an example graph defined in `graph.test.js`. It shows off many tricksy features, like having loop edges, multiple edges from the same src to same dst, etc. We also provide two ways of constructing it: `graph1` is straightforward, `graph2` adds tons of spurious adds, removes, and odd ordering. This way we can ensure that our functions treat `graph1` and `graph2` equivalently. Test plan: New unit tests are added verifying that `equals`, `merge`, and `to/fromJSON` handle the advanced graph appropriately.
This commit is contained in:
parent
5fc0d42c1f
commit
c352b5b8d6
|
@ -1,6 +1,6 @@
|
||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`core/graph toJSON / fromJSON toJSON matches snapshot 1`] = `
|
exports[`core/graph toJSON / fromJSON snapshot testing a trivial graph 1`] = `
|
||||||
Array [
|
Array [
|
||||||
Object {
|
Object {
|
||||||
"type": "sourcecred/graph",
|
"type": "sourcecred/graph",
|
||||||
|
@ -35,3 +35,53 @@ Array [
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`core/graph toJSON / fromJSON snapshot testing an advanced graph 1`] = `
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"type": "sourcecred/graph",
|
||||||
|
"version": "0.4.0",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"edges": Array [
|
||||||
|
Object {
|
||||||
|
"address": Array [
|
||||||
|
"hom",
|
||||||
|
"1",
|
||||||
|
],
|
||||||
|
"dstIndex": 0,
|
||||||
|
"srcIndex": 3,
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"address": Array [
|
||||||
|
"hom",
|
||||||
|
"2",
|
||||||
|
],
|
||||||
|
"dstIndex": 0,
|
||||||
|
"srcIndex": 3,
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"address": Array [
|
||||||
|
"loop",
|
||||||
|
],
|
||||||
|
"dstIndex": 2,
|
||||||
|
"srcIndex": 2,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"nodes": Array [
|
||||||
|
Array [
|
||||||
|
"dst",
|
||||||
|
],
|
||||||
|
Array [
|
||||||
|
"isolated",
|
||||||
|
],
|
||||||
|
Array [
|
||||||
|
"loop",
|
||||||
|
],
|
||||||
|
Array [
|
||||||
|
"src",
|
||||||
|
],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
|
@ -31,6 +31,105 @@ describe("core/graph", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function advancedGraph() {
|
||||||
|
// The advanced graph has the following features:
|
||||||
|
// - Multiple edges of same hom, from `src` to `dst`
|
||||||
|
// - An isolated node, `isolated`
|
||||||
|
// - A loop
|
||||||
|
// - A node and edge with the same toParts representation
|
||||||
|
// This function exposes all of the pieces of the advanced graph.
|
||||||
|
// It also returns two different versions of the graph, which are
|
||||||
|
// logically equivalent but very different history
|
||||||
|
// To avoid contamination, every piece is exposed as a function
|
||||||
|
// which generates a clean copy of that piece.
|
||||||
|
const src = () => NodeAddress.fromParts(["src"]);
|
||||||
|
const dst = () => NodeAddress.fromParts(["dst"]);
|
||||||
|
const hom1 = () => ({
|
||||||
|
src: src(),
|
||||||
|
dst: dst(),
|
||||||
|
address: EdgeAddress.fromParts(["hom", "1"]),
|
||||||
|
});
|
||||||
|
const hom2 = () => ({
|
||||||
|
src: src(),
|
||||||
|
dst: dst(),
|
||||||
|
address: EdgeAddress.fromParts(["hom", "2"]),
|
||||||
|
});
|
||||||
|
const loop = () => NodeAddress.fromParts(["loop"]);
|
||||||
|
const loop_loop = () => ({
|
||||||
|
src: loop(),
|
||||||
|
dst: loop(),
|
||||||
|
address: EdgeAddress.fromParts(["loop"]),
|
||||||
|
});
|
||||||
|
const isolated = () => NodeAddress.fromParts(["isolated"]);
|
||||||
|
const graph1 = () =>
|
||||||
|
new Graph()
|
||||||
|
.addNode(src())
|
||||||
|
.addNode(dst())
|
||||||
|
.addNode(loop())
|
||||||
|
.addNode(isolated())
|
||||||
|
.addEdge(hom1())
|
||||||
|
.addEdge(hom2())
|
||||||
|
.addEdge(loop_loop());
|
||||||
|
|
||||||
|
// graph2 is logically equivalent to graph1, but is constructed with very
|
||||||
|
// different history.
|
||||||
|
// Use this to check that logically equivalent graphs are treated
|
||||||
|
// equivalently, regardless of their history.
|
||||||
|
const phantomNode = () => NodeAddress.fromParts(["phantom"]);
|
||||||
|
const phantomEdge1 = () => ({
|
||||||
|
src: src(),
|
||||||
|
dst: phantomNode(),
|
||||||
|
address: EdgeAddress.fromParts(["phantom"]),
|
||||||
|
});
|
||||||
|
const phantomEdge2 = () => ({
|
||||||
|
src: src(),
|
||||||
|
dst: isolated(),
|
||||||
|
address: EdgeAddress.fromParts(["not", "so", "isolated"]),
|
||||||
|
});
|
||||||
|
// To verify that the graphs are equivalent, every mutation is preceded
|
||||||
|
// by a comment stating what the set of nodes and edges are prior to that mutation
|
||||||
|
const graph2 = () =>
|
||||||
|
new Graph()
|
||||||
|
// N: [], E: []
|
||||||
|
.addNode(phantomNode())
|
||||||
|
// N: [phantomNode], E: []
|
||||||
|
.addNode(src())
|
||||||
|
// N: [phantomNode, src], E: []
|
||||||
|
.addEdge(phantomEdge1())
|
||||||
|
// N: [phantomNode, src], E: [phantomEdge1]
|
||||||
|
.addNode(isolated())
|
||||||
|
// N: [phantomNode, src, isolated], E: [phantomEdge1]
|
||||||
|
.removeEdge(phantomEdge1().address)
|
||||||
|
// N: [phantomNode, src, isolated], E: []
|
||||||
|
.addNode(dst())
|
||||||
|
// N: [phantomNode, src, isolated, dst], E: []
|
||||||
|
.addEdge(hom1())
|
||||||
|
// N: [phantomNode, src, isolated, dst], E: [hom1]
|
||||||
|
.addEdge(phantomEdge2())
|
||||||
|
// N: [phantomNode, src, isolated, dst], E: [hom1, phantomEdge2]
|
||||||
|
.addEdge(hom2())
|
||||||
|
// N: [phantomNode, src, isolated, dst], E: [hom1, phantomEdge2, hom2]
|
||||||
|
.removeEdge(hom1().address)
|
||||||
|
// N: [phantomNode, src, isolated, dst], E: [phantomEdge2, hom2]
|
||||||
|
.removeNode(phantomNode())
|
||||||
|
// N: [src, isolated, dst], E: [phantomEdge2, hom2]
|
||||||
|
.removeEdge(phantomEdge2().address)
|
||||||
|
// N: [src, isolated, dst], E: [hom2]
|
||||||
|
.removeNode(isolated())
|
||||||
|
// N: [src, dst], E: [hom2]
|
||||||
|
.addNode(isolated())
|
||||||
|
// N: [src, dst, isolated], E: [hom2]
|
||||||
|
.addNode(loop())
|
||||||
|
// N: [src, dst, isolated, loop], E: [hom2]
|
||||||
|
.addEdge(loop_loop())
|
||||||
|
// N: [src, dst, isolated, loop], E: [hom2, loop_loop]
|
||||||
|
.addEdge(hom1());
|
||||||
|
// N: [src, dst, isolated, loop], E: [hom2, loop_loop, hom1]
|
||||||
|
const nodes = {src, dst, loop, isolated, phantomNode};
|
||||||
|
const edges = {hom1, hom2, loop_loop, phantomEdge1, phantomEdge2};
|
||||||
|
return {nodes, edges, graph1, graph2};
|
||||||
|
}
|
||||||
|
|
||||||
describe("Graph class", () => {
|
describe("Graph class", () => {
|
||||||
it("can be constructed", () => {
|
it("can be constructed", () => {
|
||||||
const x = new Graph();
|
const x = new Graph();
|
||||||
|
@ -784,6 +883,10 @@ describe("core/graph", () => {
|
||||||
.removeEdge(edge1().address);
|
.removeEdge(edge1().address);
|
||||||
expectEquality(g1, g2, true);
|
expectEquality(g1, g2, true);
|
||||||
});
|
});
|
||||||
|
it("the logically-equivalent advanced graphs are equal", () => {
|
||||||
|
const {graph1, graph2} = advancedGraph();
|
||||||
|
expect(graph1().equals(graph2())).toBe(true);
|
||||||
|
});
|
||||||
it("throws error on null", () => {
|
it("throws error on null", () => {
|
||||||
// $ExpectFlowError
|
// $ExpectFlowError
|
||||||
expect(() => new Graph().equals(null)).toThrow("null");
|
expect(() => new Graph().equals(null)).toThrow("null");
|
||||||
|
@ -934,6 +1037,12 @@ describe("core/graph", () => {
|
||||||
const actual = Graph.merge(graphs);
|
const actual = Graph.merge(graphs);
|
||||||
expect(actual.equals(expected)).toBe(true);
|
expect(actual.equals(expected)).toBe(true);
|
||||||
});
|
});
|
||||||
|
it("merges the advanced graphs together", () => {
|
||||||
|
const {graph1, graph2} = advancedGraph();
|
||||||
|
const graph3 = Graph.merge([graph1(), graph2()]);
|
||||||
|
expect(graph1().equals(graph3)).toBe(true);
|
||||||
|
expect(graph2().equals(graph3)).toBe(true);
|
||||||
|
});
|
||||||
it("rejects graphs with conflicting edges", () => {
|
it("rejects graphs with conflicting edges", () => {
|
||||||
const g1 = new Graph()
|
const g1 = new Graph()
|
||||||
.addNode(foo)
|
.addNode(foo)
|
||||||
|
@ -960,13 +1069,19 @@ describe("core/graph", () => {
|
||||||
dst: src,
|
dst: src,
|
||||||
address: EdgeAddress.fromParts(["edge", "2"]),
|
address: EdgeAddress.fromParts(["edge", "2"]),
|
||||||
});
|
});
|
||||||
it("toJSON matches snapshot", () => {
|
describe("snapshot testing", () => {
|
||||||
const graph = new Graph()
|
it("a trivial graph", () => {
|
||||||
.addNode(src)
|
const graph = new Graph()
|
||||||
.addNode(dst)
|
.addNode(src)
|
||||||
.addEdge(edge1())
|
.addNode(dst)
|
||||||
.addEdge(edge2());
|
.addEdge(edge1())
|
||||||
expect(graph.toJSON()).toMatchSnapshot();
|
.addEdge(edge2());
|
||||||
|
expect(graph.toJSON()).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
it("an advanced graph", () => {
|
||||||
|
const graph = advancedGraph().graph1();
|
||||||
|
expect(graph.toJSON()).toMatchSnapshot();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("compose to identity", () => {
|
describe("compose to identity", () => {
|
||||||
|
@ -999,6 +1114,10 @@ describe("core/graph", () => {
|
||||||
.addEdge(edge2());
|
.addEdge(edge2());
|
||||||
expectCompose(g);
|
expectCompose(g);
|
||||||
});
|
});
|
||||||
|
it("for the advanced graph", () => {
|
||||||
|
const g = advancedGraph().graph1();
|
||||||
|
expectCompose(g);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("toJSON representation is canonical", () => {
|
describe("toJSON representation is canonical", () => {
|
||||||
|
@ -1034,6 +1153,10 @@ describe("core/graph", () => {
|
||||||
.addEdge(edge2());
|
.addEdge(edge2());
|
||||||
expectCanonicity(g1, g2);
|
expectCanonicity(g1, g2);
|
||||||
});
|
});
|
||||||
|
it("for the advanced graph", () => {
|
||||||
|
const {graph1, graph2} = advancedGraph();
|
||||||
|
expectCanonicity(graph1(), graph2());
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue