Refactor Git plugin to use NodeReference (#288)

See #286 for context.

I also upgraded client code in src/app.

Test plan:
Unit tests are extensive, including testing that `get`, `ref`, and
constructors are overriden on every `GitReference` and `GitPorcelain`
type that is exposed to clients.

Paired with @wchargin
This commit is contained in:
Dandelion Mané 2018-05-15 19:10:30 -07:00 committed by GitHub
parent bb77c36626
commit 1bd444a33b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 366 additions and 186 deletions

View File

@ -31,7 +31,7 @@ function nodeDescription(ref) {
return githubNodeDescription(ref);
}
case GIT_PLUGIN_NAME: {
return gitNodeDescription(ref.graph(), ref.address());
return gitNodeDescription(ref);
}
default: {
return stringify(ref.address());

View File

@ -21,7 +21,8 @@
import stringify from "json-stable-stringify";
import {Graph} from "../../core/graph";
import type {Node} from "../../core/graph";
import {NodeReference, NodePorcelain} from "../../core/porcelain";
import type {Edge} from "../../core/graph";
import type {Address} from "../../core/address";
import type {
@ -30,214 +31,320 @@ import type {
BlobNodePayload,
TreeNodePayload,
CommitNodePayload,
IncludesEdgePayload,
NodePayload,
NodeType,
Hash,
} from "./types";
import {GIT_PLUGIN_NAME} from "./types";
import {
BECOMES_EDGE_TYPE,
BLOB_NODE_TYPE,
COMMIT_NODE_TYPE,
GIT_PLUGIN_NAME,
HAS_CONTENTS_EDGE_TYPE,
HAS_PARENT_EDGE_TYPE,
HAS_TREE_EDGE_TYPE,
INCLUDES_EDGE_TYPE,
SUBMODULE_COMMIT_NODE_TYPE,
TREE_ENTRY_NODE_TYPE,
TREE_NODE_TYPE,
} from "./types";
import {commitAddress} from "./address";
export class PorcelainGraph {
function assertAddressType(address: Address, t: NodeType) {
if (address.type !== t) {
throw new Error(
`Expected entity at ${stringify(address)} to have type ${t}`
);
}
}
export class GraphPorcelain {
graph: Graph<any, any>;
constructor(graph: Graph<any, any>) {
this.graph = graph;
}
// Note that this method is presently unsafe, as the hash may not exist.
// In the future, we will come up with a general case solution to have
// the type system verify that returned porcelains must be existence-tested
// before their properties are usable.
commitByHash(h: Hash): Commit {
commitByHash(h: Hash): CommitReference {
const addr = commitAddress(h);
return new Commit(this.graph, addr);
const nodeReference = new NodeReference(this.graph, addr);
return new CommitReference(nodeReference);
}
}
export type GitPorcelain = Commit | Blob | Tree | TreeEntry | SubmoduleCommit;
class BaseGitPorcelain<T: NodePayload> {
graph: Graph<any, any>;
nodeAddress: Address;
constructor(graph: Graph<any, any>, nodeAddress: Address) {
if (nodeAddress.pluginName !== GIT_PLUGIN_NAME) {
throw new Error(
`Tried to create Git porcelain for node from plugin: ${
nodeAddress.pluginName
}`
);
export class GitReference<+T: NodePayload> extends NodeReference<T> {
constructor(ref: NodeReference<any>) {
const addr = ref.address();
if (addr.pluginName !== GIT_PLUGIN_NAME) {
throw new Error(`Wrong plugin name ${addr.pluginName} for Git plugin!`);
}
this.graph = graph;
this.nodeAddress = nodeAddress;
super(ref.graph(), addr);
}
type(): NodeType {
return (this.address().type: any);
return ((super.type(): string): any);
}
node(): Node<T> {
return this.graph.node(this.nodeAddress);
}
address(): Address {
return this.nodeAddress;
get(): ?GitPorcelain<T> {
const nodePorcelain = super.get();
if (nodePorcelain != null) {
return new GitPorcelain(nodePorcelain);
}
}
}
export class Commit extends BaseGitPorcelain<CommitNodePayload> {
static from(n: BaseGitPorcelain<any>): Commit {
if (n.type() !== "COMMIT") {
throw new Error(`Unable to cast ${n.type()} to Commit`);
export class GitPorcelain<+T: NodePayload> extends NodePorcelain<T> {
constructor(nodePorcelain: NodePorcelain<any>) {
if (nodePorcelain.ref().address().pluginName !== GIT_PLUGIN_NAME) {
throw new Error(
`Wrong plugin name ${
nodePorcelain.ref().address().pluginName
} for Git plugin!`
);
}
return new Commit(n.graph, n.nodeAddress);
super(nodePorcelain.ref(), nodePorcelain.node());
}
}
export class CommitReference extends GitReference<CommitNodePayload> {
constructor(ref: NodeReference<any>) {
super(ref);
assertAddressType(ref.address(), COMMIT_NODE_TYPE);
}
hash(): Hash {
return this.address().id;
}
parents(): Commit[] {
return this.graph
.neighborhood(this.nodeAddress, {
nodeType: "COMMIT",
edgeType: "HAS_PARENT",
direction: "OUT",
})
.map(({neighbor}) => new Commit(this.graph, neighbor));
parents(): CommitReference[] {
return this.neighbors({
nodeType: COMMIT_NODE_TYPE,
edgeType: HAS_PARENT_EDGE_TYPE,
direction: "OUT",
}).map(({ref}) => new CommitReference(ref));
}
tree(): Tree {
const trees = this.graph
.neighborhood(this.nodeAddress, {
nodeType: "TREE",
edgeType: "HAS_TREE",
direction: "OUT",
})
.map(({neighbor}) => new Tree(this.graph, neighbor));
tree(): TreeReference {
const trees = this.neighbors({
nodeType: TREE_NODE_TYPE,
edgeType: HAS_TREE_EDGE_TYPE,
direction: "OUT",
}).map(({ref}) => new TreeReference(ref));
if (trees.length !== 1) {
throw new Error(
`Commit ${stringify(this.nodeAddress)} has wrong number of trees`
`Commit ${stringify(this.address())} has wrong number of trees`
);
}
return trees[0];
}
get(): ?CommitPorcelain {
const x = super.get();
if (x != null) {
return new CommitPorcelain(x);
}
}
}
export class Tree extends BaseGitPorcelain<TreeNodePayload> {
static from(n: BaseGitPorcelain<any>): Tree {
if (n.type() !== "TREE") {
throw new Error(`Unable to cast ${n.type()} to Tree`);
}
return new Tree(n.graph, n.nodeAddress);
export class CommitPorcelain extends GitPorcelain<CommitNodePayload> {
constructor(nodePorcelain: NodePorcelain<any>) {
assertAddressType(nodePorcelain.ref().address(), COMMIT_NODE_TYPE);
super(nodePorcelain);
}
ref(): CommitReference {
return new CommitReference(super.ref());
}
}
export class TreeReference extends GitReference<TreeNodePayload> {
constructor(ref: NodeReference<any>) {
super(ref);
assertAddressType(ref.address(), TREE_NODE_TYPE);
}
hash(): Hash {
return this.address().id;
}
entries(): TreeEntry[] {
return this.graph
.neighborhood(this.nodeAddress, {
nodeType: "TREE_ENTRY",
edgeType: "INCLUDES",
direction: "OUT",
})
.map(({neighbor}) => new TreeEntry(this.graph, neighbor));
entries(): TreeEntryReference[] {
return this.neighbors({
nodeType: TREE_ENTRY_NODE_TYPE,
edgeType: INCLUDES_EDGE_TYPE,
direction: "OUT",
}).map(({ref}) => new TreeEntryReference(ref));
}
entry(name: string): ?TreeEntry {
return this.entries().filter((te) => te.name() === name)[0];
entry(name: string): ?TreeEntryReference {
return this.neighbors({
nodeType: TREE_ENTRY_NODE_TYPE,
edgeType: INCLUDES_EDGE_TYPE,
direction: "OUT",
})
.filter(
({edge}) => (edge: Edge<IncludesEdgePayload>).payload.name === name
)
.map(({ref}) => new TreeEntryReference(ref))[0];
}
get(): ?TreePorcelain {
const x = super.get();
if (x != null) {
return new TreePorcelain(x);
}
}
}
export class TreeEntry extends BaseGitPorcelain<TreeEntryNodePayload> {
static from(n: BaseGitPorcelain<any>): TreeEntry {
if (n.type() !== "TREE_ENTRY") {
throw new Error(`Unable to cast ${n.type()} to TreeEntry`);
}
return new TreeEntry(n.graph, n.nodeAddress);
export class TreePorcelain extends GitPorcelain<TreeNodePayload> {
constructor(nodePorcelain: NodePorcelain<any>) {
assertAddressType(nodePorcelain.ref().address(), TREE_NODE_TYPE);
super(nodePorcelain);
}
ref(): TreeReference {
return new TreeReference(super.ref());
}
}
export class TreeEntryReference extends GitReference<TreeEntryNodePayload> {
constructor(ref: NodeReference<any>) {
super(ref);
assertAddressType(ref.address(), TREE_ENTRY_NODE_TYPE);
}
name(): string {
return this.node().payload.name;
const includesEdges = this.neighbors({
nodeType: TREE_NODE_TYPE,
edgeType: INCLUDES_EDGE_TYPE,
direction: "IN",
}).map(({edge}) => edge);
if (includesEdges.length !== 1) {
throw new Error(
`Malformed tree structure at ${stringify(this.address())}`
);
}
return (includesEdges[0]: Edge<IncludesEdgePayload>).payload.name;
}
evolvesTo(): TreeEntry[] {
return this.graph
.neighborhood(this.nodeAddress, {
nodeType: "TREE_ENTRY",
edgeType: "BECOMES",
direction: "OUT",
})
.map(({neighbor}) => new TreeEntry(this.graph, neighbor));
evolvesTo(): TreeEntryReference[] {
return this.neighbors({
nodeType: TREE_ENTRY_NODE_TYPE,
edgeType: BECOMES_EDGE_TYPE,
direction: "OUT",
}).map(({ref}) => new TreeEntryReference(ref));
}
evolvesFrom(): TreeEntry[] {
return this.graph
.neighborhood(this.nodeAddress, {
nodeType: "TREE_ENTRY",
edgeType: "BECOMES",
direction: "IN",
})
.map(({neighbor}) => new TreeEntry(this.graph, neighbor));
evolvesFrom(): TreeEntryReference[] {
return this.neighbors({
nodeType: TREE_ENTRY_NODE_TYPE,
edgeType: BECOMES_EDGE_TYPE,
direction: "IN",
}).map(({ref}) => new TreeEntryReference(ref));
}
/*
* May be a single Tree, single Blob, or zero or more
* SubmoduleCommits. The Tree or Blob are put in an array for
* consistency.
*
*/
contents(): Tree[] | Blob[] | SubmoduleCommit[] {
// Note: the function has the correct type signature,
// but as-implemented it should be a flow error.
// When flow fixes this, maintain the current method signature.
return this.graph
.neighborhood(this.nodeAddress, {
edgeType: "HAS_CONTENTS",
direction: "OUT",
})
.map(({neighbor}) => {
switch (neighbor.type) {
case "BLOB":
return new Blob(this.graph, neighbor);
case "TREE":
return new Tree(this.graph, neighbor);
case "SUBMODULE_COMMIT":
return new SubmoduleCommit(this.graph, neighbor);
default:
throw new Error(`Neighbor had invalid type ${neighbor.type}`);
}
});
blob(): ?BlobReference {
return this.neighbors({
edgeType: HAS_CONTENTS_EDGE_TYPE,
nodeType: BLOB_NODE_TYPE,
direction: "OUT",
}).map(({ref}) => new BlobReference(ref))[0];
}
tree(): ?TreeReference {
return this.neighbors({
edgeType: HAS_CONTENTS_EDGE_TYPE,
nodeType: TREE_NODE_TYPE,
direction: "OUT",
}).map(({ref}) => new TreeReference(ref))[0];
}
submoduleCommits(): SubmoduleCommitReference[] {
return this.neighbors({
edgeType: HAS_CONTENTS_EDGE_TYPE,
nodeType: SUBMODULE_COMMIT_NODE_TYPE,
direction: "OUT",
}).map(({ref}) => new SubmoduleCommitReference(ref));
}
get(): ?TreeEntryPorcelain {
const x = super.get();
if (x != null) {
return new TreeEntryPorcelain(x);
}
}
}
export class Blob extends BaseGitPorcelain<BlobNodePayload> {
static from(n: BaseGitPorcelain<any>): Blob {
if (n.type() !== "BLOB") {
throw new Error(`Unable to cast ${n.type()} to Blob`);
}
return new Blob(n.graph, n.nodeAddress);
export class TreeEntryPorcelain extends GitPorcelain<TreeEntryNodePayload> {
constructor(nodePorcelain: NodePorcelain<any>) {
assertAddressType(nodePorcelain.ref().address(), TREE_ENTRY_NODE_TYPE);
super(nodePorcelain);
}
ref(): TreeEntryReference {
return new TreeEntryReference(super.ref());
}
}
export class BlobReference extends GitReference<BlobNodePayload> {
constructor(ref: NodeReference<any>) {
super(ref);
assertAddressType(ref.address(), BLOB_NODE_TYPE);
}
hash(): Hash {
return this.nodeAddress.id;
return this.address().id;
}
get(): ?BlobPorcelain {
const x = super.get();
if (x != null) {
return new BlobPorcelain(x);
}
}
}
export class SubmoduleCommit extends BaseGitPorcelain<SubmoduleCommitPayload> {
static from(n: BaseGitPorcelain<any>): SubmoduleCommit {
if (n.type() !== "SUBMODULE_COMMIT") {
throw new Error(`Unable to cast ${n.type()} to SubmoduleCommit`);
export class BlobPorcelain extends GitPorcelain<BlobNodePayload> {
constructor(nodePorcelain: NodePorcelain<any>) {
assertAddressType(nodePorcelain.ref().address(), BLOB_NODE_TYPE);
super(nodePorcelain);
}
ref(): BlobReference {
return new BlobReference(super.ref());
}
}
export class SubmoduleCommitReference extends GitReference<
SubmoduleCommitPayload
> {
constructor(ref: NodeReference<any>) {
super(ref);
assertAddressType(ref.address(), SUBMODULE_COMMIT_NODE_TYPE);
}
get(): ?SubmoduleCommitPorcelain {
const x = super.get();
if (x != null) {
return new SubmoduleCommitPorcelain(x);
}
return new SubmoduleCommit(n.graph, n.nodeAddress);
}
}
export class SubmoduleCommitPorcelain extends GitPorcelain<
SubmoduleCommitPayload
> {
constructor(nodePorcelain: NodePorcelain<any>) {
assertAddressType(
nodePorcelain.ref().address(),
SUBMODULE_COMMIT_NODE_TYPE
);
super(nodePorcelain);
}
url(): string {
return this.node().payload.url;
return this.payload().url;
}
hash(): Hash {
return this.node().payload.hash;
return this.payload().hash;
}
ref(): SubmoduleCommitReference {
return new SubmoduleCommitReference(super.ref());
}
}

View File

@ -1,14 +1,32 @@
// @flow
import cloneDeep from "lodash.clonedeep";
import {PorcelainGraph, Blob, Tree, SubmoduleCommit} from "./porcelain";
import {
COMMIT_NODE_TYPE,
TREE_NODE_TYPE,
SUBMODULE_COMMIT_NODE_TYPE,
BLOB_NODE_TYPE,
} from "./types";
import {
GraphPorcelain,
BlobReference,
TreeReference,
BlobPorcelain,
TreePorcelain,
TreeEntryReference,
TreeEntryPorcelain,
SubmoduleCommitPorcelain,
SubmoduleCommitReference,
CommitReference,
CommitPorcelain,
} from "./porcelain";
import {createGraph} from "./createGraph";
const makePorcelainGraph = () =>
new PorcelainGraph(createGraph(cloneDeep(require("./demoData/example-git"))));
const makeGraphPorcelain = () =>
new GraphPorcelain(createGraph(cloneDeep(require("./demoData/example-git"))));
const getCommit = () => {
const graph = makePorcelainGraph();
const graph = makeGraphPorcelain();
const commitHash = "3715ddfb8d4c4fd2a6f6af75488c82f84c92ec2f";
const commit = graph.commitByHash(commitHash);
if (commit.hash() !== commitHash) {
@ -18,6 +36,30 @@ const getCommit = () => {
}
return commit;
};
function really<T>(x: ?T): T {
if (x == null) {
throw new Error(`It wasn't, really`);
}
return x;
}
const getTree = () => {
return getCommit().tree();
};
const getTreeEntry = (name: string) => {
return really(getTree().entry(name));
};
const getBlob = () => {
return really(getTreeEntry("science.txt").blob());
};
const getSubmoduleCommit = () => {
return getTreeEntry("pygravitydefier").submoduleCommits()[0];
};
describe("Git porcelain", () => {
it("commits have hashes", () => {
const commit = getCommit();
@ -34,18 +76,17 @@ describe("Git porcelain", () => {
it("some commits have no parents", () => {
const commitHash = "c2b51945e7457546912a8ce158ed9d294558d294";
const commit = makePorcelainGraph().commitByHash(commitHash);
const commit = makeGraphPorcelain().commitByHash(commitHash);
expect(commit.parents()).toEqual([]);
});
it("Commits have a unique, hash-identified tree", () => {
const tree = getCommit().tree();
const tree = getTree();
expect(tree.hash()).toEqual("7be3ecfee5314ffa9b2d93fc4377792b2d6d70ed");
});
it("Trees have tree entries", () => {
const tree = getCommit().tree();
const entries = tree.entries();
const entries = getTree().entries();
expect(entries).toHaveLength(5);
const entryNames = entries.map((x) => x.name());
expect(entryNames).toEqual(
@ -59,35 +100,18 @@ describe("Git porcelain", () => {
);
});
it("Tree entries can have blobs", () => {
const entry = getCommit()
.tree()
.entry("science.txt");
if (entry == null) {
throw new Error("Where is science?!");
}
const blob: Blob = Blob.from(entry.contents()[0]);
const blob = getBlob();
expect(blob.hash()).toEqual("f1f2514ca6d7a6a1a0511957021b1995bf9ace1c");
});
it("Tree entries can have trees", () => {
const entry = getCommit()
.tree()
.entry("src");
if (entry == null) {
throw new Error("Where is src?!");
}
const tree: Tree = Tree.from(entry.contents()[0]);
const treeEntry = getTreeEntry("src");
const tree = really(treeEntry.tree());
expect(tree.hash()).toEqual("78fc9c83023386854c6bfdc5761c0e58f68e226f");
});
it("Tree entries can have submodule commits", () => {
const entry = getCommit()
.tree()
.entry("pygravitydefier");
if (entry == null) {
throw new Error("We've stopped defying gravity :(");
}
const sc: SubmoduleCommit = SubmoduleCommit.from(entry.contents()[0]);
const sc = really(getSubmoduleCommit().get());
expect(sc.hash()).toEqual("29ef158bc982733e2ba429fcf73e2f7562244188");
expect(sc.url()).toEqual(
"https://github.com/sourcecred/example-git-submodule.git"
@ -97,7 +121,7 @@ describe("Git porcelain", () => {
it("Tree entries can evolve to/from other tree entries", () => {
const parentCommitHash = "e8b7a8f19701cd5a25e4a097d513ead60e5f8bcc";
const childCommitHash = "69c5aad50eec8f2a0a07c988c3b283a6490eb45b";
const graph = makePorcelainGraph();
const graph = makeGraphPorcelain();
const parentEntry = graph
.commitByHash(parentCommitHash)
.tree()
@ -112,4 +136,51 @@ describe("Git porcelain", () => {
expect(parentEntry.evolvesTo()).toEqual([childEntry]);
expect(childEntry.evolvesFrom()).toEqual([parentEntry]);
});
describe("flow and runtime type verification", () => {
it("for Commit", () => {
const c: CommitReference = getCommit();
const p: CommitPorcelain = really(c.get());
const _: CommitReference = p.ref();
const err = `to have type ${COMMIT_NODE_TYPE}`;
expect(() => new CommitReference(getTree())).toThrow(err);
expect(() => new CommitPorcelain(really(getTree().get()))).toThrow(err);
});
it("for Tree", () => {
const c: TreeReference = getTree();
const p: TreePorcelain = really(c.get());
const _: TreeReference = p.ref();
const err = `to have type ${TREE_NODE_TYPE}`;
expect(() => new TreeReference(getCommit())).toThrow(err);
expect(() => new TreePorcelain(really(getCommit().get()))).toThrow(err);
});
it("for TreeEntry", () => {
const c: TreeEntryReference = getTreeEntry("src");
const p: TreeEntryPorcelain = really(c.get());
const _: TreeEntryReference = p.ref();
const err = `to have type ${TREE_NODE_TYPE}`;
expect(() => new TreeEntryReference(getCommit())).toThrow(err);
expect(() => new TreeEntryPorcelain(really(getTree().get()))).toThrow(
err
);
});
it("for Blob", () => {
const c: BlobReference = getBlob();
const p: BlobPorcelain = really(c.get());
const _: BlobReference = p.ref();
const err = `to have type ${BLOB_NODE_TYPE}`;
expect(() => new BlobReference(getCommit())).toThrow(err);
expect(() => new BlobPorcelain(really(getTree().get()))).toThrow(err);
});
it("for SubmoduleCommit", () => {
const c: SubmoduleCommitReference = getSubmoduleCommit();
const p: SubmoduleCommitPorcelain = really(c.get());
const _: SubmoduleCommitReference = p.ref();
const err = `to have type ${SUBMODULE_COMMIT_NODE_TYPE}`;
expect(() => new SubmoduleCommitReference(getCommit())).toThrow(err);
expect(
() => new SubmoduleCommitPorcelain(really(getTree().get()))
).toThrow(err);
});
});
});

View File

@ -1,14 +1,15 @@
// @flow
import type {Address} from "../../core/address";
import type {Graph} from "../../core/graph";
import type {NodeType, SubmoduleCommitPayload} from "./types";
import {NodeReference} from "../../core/porcelain";
import {SubmoduleCommitReference, GitReference} from "./porcelain";
/**
* Describe a node provided by the Git plugin.
*/
export function nodeDescription(graph: Graph<any, any>, address: Address) {
const type: NodeType = (address.type: any);
export function nodeDescription(nodeReference: NodeReference<any>) {
const gReference = new GitReference(nodeReference);
const type = gReference.type();
const address = gReference.address();
switch (type) {
case "COMMIT":
return `commit ${address.id}`;
@ -17,8 +18,13 @@ export function nodeDescription(graph: Graph<any, any>, address: Address) {
case "BLOB":
return `blob ${address.id}`;
case "SUBMODULE_COMMIT": {
const payload: SubmoduleCommitPayload = graph.node(address).payload;
return `submodule commit ${payload.hash} in ${payload.url}`;
const scRef = new SubmoduleCommitReference(gReference);
const scPorcelain = scRef.get();
if (scPorcelain != null) {
return `submodule commit ${scPorcelain.hash()} in ${scPorcelain.url()}`;
} else {
return `submodule commit [unknown]`;
}
}
case "TREE_ENTRY":
return `entry ${address.id}`;

View File

@ -9,6 +9,7 @@ import type {
} from "./types";
import type {Node} from "../../core/graph";
import {Graph} from "../../core/graph";
import {NodeReference} from "../../core/porcelain";
import {_makeAddress, commitAddress} from "./address";
import {nodeDescription} from "./render";
import {submoduleCommitId, treeEntryId} from "./types";
@ -19,9 +20,8 @@ describe("nodeDescription", () => {
address: commitAddress("cafebabe"),
payload: (({}: any): {||}),
};
expect(nodeDescription(new Graph().addNode(node), node.address)).toEqual(
"commit cafebabe"
);
const ref = new NodeReference(new Graph().addNode(node), node.address);
expect(nodeDescription(ref)).toEqual("commit cafebabe");
});
it("describes trees", () => {
@ -29,9 +29,8 @@ describe("nodeDescription", () => {
address: _makeAddress("TREE", "deadbeef"),
payload: (({}: any): {||}),
};
expect(nodeDescription(new Graph().addNode(node), node.address)).toEqual(
"tree deadbeef"
);
const ref = new NodeReference(new Graph().addNode(node), node.address);
expect(nodeDescription(ref)).toEqual("tree deadbeef");
});
it("describes blobs", () => {
@ -39,9 +38,8 @@ describe("nodeDescription", () => {
address: _makeAddress("BLOB", "01010101"),
payload: (({}: any): {||}),
};
expect(nodeDescription(new Graph().addNode(node), node.address)).toEqual(
"blob 01010101"
);
const ref = new NodeReference(new Graph().addNode(node), node.address);
expect(nodeDescription(ref)).toEqual("blob 01010101");
});
it("describes submodule commits", () => {
@ -51,9 +49,8 @@ describe("nodeDescription", () => {
address: _makeAddress("SUBMODULE_COMMIT", submoduleCommitId(hash, url)),
payload: {hash, url},
};
expect(nodeDescription(new Graph().addNode(node), node.address)).toEqual(
`submodule commit ${hash} in ${url}`
);
const ref = new NodeReference(new Graph().addNode(node), node.address);
expect(nodeDescription(ref)).toEqual(`submodule commit ${hash} in ${url}`);
});
it("describes tree entries", () => {
@ -63,8 +60,7 @@ describe("nodeDescription", () => {
address: _makeAddress("TREE_ENTRY", treeEntryId(tree, name)),
payload: {name},
};
expect(nodeDescription(new Graph().addNode(node), node.address)).toEqual(
`entry ${tree}:${name}`
);
const ref = new NodeReference(new Graph().addNode(node), node.address);
expect(nodeDescription(ref)).toEqual(`entry ${tree}:${name}`);
});
});