Graph creation works

main
Jackson Morgan 9 months ago
parent 637a517b4b
commit 665511f2f0
  1. 408
      packages/type-traverser/example/example.ts
  2. 7
      packages/type-traverser/src/ReverseRelationshipTypes.ts
  3. 25
      packages/type-traverser/src/TraverserDefinition.ts
  4. 20
      packages/type-traverser/src/instanceGraph/InstanceGraph.ts
  5. 17
      packages/type-traverser/src/instanceGraph/nodes/InstanceNode.ts
  6. 28
      packages/type-traverser/src/instanceGraph/nodes/InterfaceInstanceNode.ts
  7. 2
      packages/type-traverser/src/instanceGraph/nodes/PrimitiveInstanceNode.ts
  8. 13
      packages/type-traverser/src/instanceGraph/nodes/UnionInstanceNode.ts
  9. 161
      packages/type-traverser/test/integration/avatar/avatar.test.ts
  10. 0
      packages/type-traverser/test/integration/avatar_old/AvatarBrokenTransformer.ts
  11. 0
      packages/type-traverser/test/integration/avatar_old/AvatarErroringTransformer.ts
  12. 0
      packages/type-traverser/test/integration/avatar_old/AvatarTraverserDefinition.ts
  13. 0
      packages/type-traverser/test/integration/avatar_old/AvatarTraverserTypes.ts
  14. 19
      packages/type-traverser/test/integration/avatar_old/avatar.old.ts
  15. 0
      packages/type-traverser/test/integration/avatar_old/sampleData.ts
  16. 2
      packages/type-traverser/tsconfig.build.json
  17. 1
      tsconfig.base.json

@ -1,13 +1,10 @@
import type { import type {
TraverserDefinition,
ValidateTraverserTypes, ValidateTraverserTypes,
AssertExtends, ItemNamed,
InterfaceType, TraverserDefinitions,
PrimitiveType,
UnionType,
} from "../src"; } from "../src";
import { Traverser } from "../src"; import { InstanceGraph } from "../src/instanceGraph/instanceGraph";
import type { ReverseRelationshipIndentifiers } from "../src/reverseRelationshipTypes"; import type { ParentIdentifiers } from "../src/ReverseRelationshipTypes";
async function run() { async function run() {
/** /**
@ -64,7 +61,7 @@ async function run() {
}; };
NonBender: { NonBender: {
kind: "interface"; kind: "interface";
type: Bender; type: NonBender;
properties: { properties: {
friends: "Person"; friends: "Person";
}; };
@ -76,236 +73,205 @@ async function run() {
}; };
}>; }>;
type AvatarReverseRelationshipIdentifiers = type PersonParentIdentifiers = ParentIdentifiers<
ReverseRelationshipIndentifiers<AvatarTraverserTypes>;
const sample: AvatarReverseRelationshipIdentifiers = {
Element: ["Bender", "element"],
Bender: ["Person"],
}
type KeysMatchingCondition<T, Condition> = {
[K in keyof T]: T[K] extends Condition ? K : never;
}[keyof T];
// Condition: objects with `{ kind: "interface" }`
type InterfaceKeys = KeysMatchingCondition<
AvatarTraverserTypes, AvatarTraverserTypes,
{ kind: "interface" } "Person"
>; >;
const sample: PersonParentIdentifiers = ["Bender", "friends"];
console.log(sample);
type something = AvatarTraverserTypes[keyof AvatarTraverserTypes];
type something2 = something extends PrimitiveType ? "cool" : never;
type SomeInterface = {
a: { type: "1" };
b: { type: "2" };
c: { type: "3" };
};
// type TestUnionType = AvatarTraverserTypes[keyof AvatarTraverserTypes];
// type MapUnion<T> = T extends InterfaceType<keyof AvatarTraverserTypes>
// ? "interface"
// : T extends UnionType<keyof AvatarTraverserTypes>
// ? "union"
// : T extends PrimitiveType
// ? "primitive"
// : never;
// // Example usage:
// type MappedUnion = MapUnion<TestUnionType>; // "a_mapped" | "b_mapped" | "c_mapped"
interface;
type UnionType = "a" | "b" | "c";
type MapUnion<T> = T extends "a"
? "a_mapped"
: T extends "b"
? "b_mapped"
: T extends "c"
? "c_mapped"
: never;
// Example usage:
type MappedUnion = MapUnion<UnionType>; // "a_mapped" | "b_mapped" | "c_mapped"
/** /**
* Create the traverser definition * Create the traverser definition
*/ */
const avatarTraverserDefinition: TraverserDefinition<AvatarTraverserTypes> = { const avatarTraverserDefinition: TraverserDefinitions<AvatarTraverserTypes> =
Element: { {
kind: "primitive", Element: {
}, kind: "primitive",
Bender: {
kind: "interface",
properties: {
element: "Element",
friends: "Person",
},
},
NonBender: {
kind: "interface",
properties: {
friends: "Person",
},
},
Person: {
kind: "union",
selector: (item) => {
return (item as Bender).element ? "Bender" : "NonBender";
}, },
}, Bender: {
}; kind: "interface",
properties: {
/** element: "Element",
* Instantiate the Traverser friends: "Person",
*/ },
const avatarTraverser = new Traverser<AvatarTraverserTypes>(
avatarTraverserDefinition,
);
/**
* Create a visitor
*/
const avatarVisitor = avatarTraverser.createVisitor<undefined>({
Element: async (item) => {
console.log(`Element: ${item}`);
},
Bender: {
visitor: async (item) => {
console.log(`Bender: ${item.name}`);
}, },
properties: { NonBender: {
element: async (item) => { kind: "interface",
console.log(`Bender.element: ${item}`); properties: {
friends: "Person",
}, },
}, },
}, Person: {
NonBender: { kind: "union",
visitor: async (item) => { selector: (item) => {
console.log(`NonBender: ${item.name}`); return (item as Bender).element ? "Bender" : "NonBender";
},
}, },
}, };
Person: async (item) => {
console.log(`Person: ${item.name}`);
},
});
/** console.log(avatarTraverserDefinition);
* Run the visitor on data
*/
console.log(
"############################## Visitor Logs ##############################",
);
await avatarVisitor.visit(aang, "Bender", undefined);
/** const graph = new InstanceGraph(avatarTraverserDefinition);
* Create a visitor that uses context const aangNode = graph.getNodeFor(aang, "Bender");
*/ const aangeChild = aangNode.child("friends");
interface AvatarCountingVisitorContext {
numberOfBenders: number;
}
const avatarCountingVisitor =
avatarTraverser.createVisitor<AvatarCountingVisitorContext>({
Bender: {
visitor: async (item, context) => {
context.numberOfBenders++;
},
},
});
/** const parent = aangNode.parent("Person");
* Run the counting visitor
*/
console.log(
"############################## Found Number of Benders Using Visitor ##############################",
);
const countContext: AvatarCountingVisitorContext = { numberOfBenders: 0 };
await avatarCountingVisitor.visit(aang, "Bender", countContext);
console.log(countContext.numberOfBenders);
/** const aangChildren = aangNode.allChildren();
* Set up a transformer aangChildren.forEach((child) => {
*/ child.typeName === "Element";
interface ActionablePerson {
doAction(): void;
friends: ActionablePerson[];
}
const avatarTransformer = avatarTraverser.createTransformer<
{
Element: {
return: string;
};
Bender: {
return: ActionablePerson;
properties: {
element: string;
};
};
NonBender: {
return: ActionablePerson;
};
},
undefined
>({
Element: async (item) => {
return item.toUpperCase();
},
Bender: {
transformer: async (item, getTransformedChildren) => {
const transformedChildren = await getTransformedChildren();
return {
doAction: () => {
console.log(`I can bend ${transformedChildren.element}`);
},
friends: transformedChildren.friends,
};
},
properties: {
element: async (item, getTransformedChildren) => {
const transformedChildren = await getTransformedChildren();
return `the element of ${transformedChildren}`;
},
},
},
NonBender: {
transformer: async (item, getTransformedChildren) => {
const transformedChildren = await getTransformedChildren();
return {
doAction: () => {
console.log(`I can't bend.`);
},
friends: transformedChildren.friends,
};
},
},
Person: async (
item,
getTransformedChildren,
setReturnPointer,
_context,
) => {
const personToReturn: ActionablePerson = {} as ActionablePerson;
setReturnPointer(personToReturn);
const transformedChildren = await getTransformedChildren();
personToReturn.doAction = transformedChildren.doAction;
personToReturn.friends = transformedChildren.friends;
return personToReturn;
},
}); });
/** const aangParents = aangNode.allParents();
* Run the Transformer
*/
console.log( // /**
"############################## AvatarTraverser DoAction ##############################", // * Instantiate the Traverser
); // */
const result = await avatarTransformer.transform(aang, "Bender", undefined); // const avatarTraverser = new Traverser<AvatarTraverserTypes>(
result.doAction(); // avatarTraverserDefinition,
result.friends[0].doAction(); // );
result.friends[1].doAction();
// /**
// * Create a visitor
// */
// const avatarVisitor = avatarTraverser.createVisitor<undefined>({
// Element: async (item) => {
// console.log(`Element: ${item}`);
// },
// Bender: {
// visitor: async (item) => {
// console.log(`Bender: ${item.name}`);
// },
// properties: {
// element: async (item) => {
// console.log(`Bender.element: ${item}`);
// },
// },
// },
// NonBender: {
// visitor: async (item) => {
// console.log(`NonBender: ${item.name}`);
// },
// },
// Person: async (item) => {
// console.log(`Person: ${item.name}`);
// },
// });
// /**
// * Run the visitor on data
// */
// console.log(
// "############################## Visitor Logs ##############################",
// );
// await avatarVisitor.visit(aang, "Bender", undefined);
// /**
// * Create a visitor that uses context
// */
// interface AvatarCountingVisitorContext {
// numberOfBenders: number;
// }
// const avatarCountingVisitor =
// avatarTraverser.createVisitor<AvatarCountingVisitorContext>({
// Bender: {
// visitor: async (item, context) => {
// context.numberOfBenders++;
// },
// },
// });
// /**
// * Run the counting visitor
// */
// console.log(
// "############################## Found Number of Benders Using Visitor ##############################",
// );
// const countContext: AvatarCountingVisitorContext = { numberOfBenders: 0 };
// await avatarCountingVisitor.visit(aang, "Bender", countContext);
// console.log(countContext.numberOfBenders);
// /**
// * Set up a transformer
// */
// interface ActionablePerson {
// doAction(): void;
// friends: ActionablePerson[];
// }
// const avatarTransformer = avatarTraverser.createTransformer<
// {
// Element: {
// return: string;
// };
// Bender: {
// return: ActionablePerson;
// properties: {
// element: string;
// };
// };
// NonBender: {
// return: ActionablePerson;
// };
// },
// undefined
// >({
// Element: async (item) => {
// return item.toUpperCase();
// },
// Bender: {
// transformer: async (item, getTransformedChildren) => {
// const transformedChildren = await getTransformedChildren();
// return {
// doAction: () => {
// console.log(`I can bend ${transformedChildren.element}`);
// },
// friends: transformedChildren.friends,
// };
// },
// properties: {
// element: async (item, getTransformedChildren) => {
// const transformedChildren = await getTransformedChildren();
// return `the element of ${transformedChildren}`;
// },
// },
// },
// NonBender: {
// transformer: async (item, getTransformedChildren) => {
// const transformedChildren = await getTransformedChildren();
// return {
// doAction: () => {
// console.log(`I can't bend.`);
// },
// friends: transformedChildren.friends,
// };
// },
// },
// Person: async (
// item,
// getTransformedChildren,
// setReturnPointer,
// _context,
// ) => {
// const personToReturn: ActionablePerson = {} as ActionablePerson;
// setReturnPointer(personToReturn);
// const transformedChildren = await getTransformedChildren();
// personToReturn.doAction = transformedChildren.doAction;
// personToReturn.friends = transformedChildren.friends;
// return personToReturn;
// },
// });
// /**
// * Run the Transformer
// */
// console.log(
// "############################## AvatarTraverser DoAction ##############################",
// );
// const result = await avatarTransformer.transform(aang, "Bender", undefined);
// result.doAction();
// result.friends[0].doAction();
// result.friends[1].doAction();
} }
run(); run();

@ -74,4 +74,9 @@ export type BaseReverseRelationshipIndentifiers<
export type ParentIdentifiers< export type ParentIdentifiers<
Types extends TraverserTypes<any>, Types extends TraverserTypes<any>,
ChildName extends keyof Types, ChildName extends keyof Types,
> = BaseReverseRelationshipIndentifiers<Types, ChildName>[keyof Types]; > = {
[CN in ChildName]: BaseReverseRelationshipIndentifiers<
Types,
CN
>[keyof Types];
}[ChildName];

@ -24,15 +24,22 @@ export type PrimitiveTraverserDefinition = {
export type TraverserDefinition< export type TraverserDefinition<
Types extends TraverserTypes<any>, Types extends TraverserTypes<any>,
TypeField extends keyof Types, TypeName extends keyof Types,
> = Types[TypeField] extends InterfaceType<keyof Types> Type extends Types[TypeName],
? InterfaceTraverserDefinition<Types[TypeField]> > = {
: Types[TypeField] extends UnionType<keyof Types> [TN in TypeName]: Type extends InterfaceType<keyof Types>
? UnionTraverserDefinition<Types[TypeField]> ? InterfaceTraverserDefinition<Type>
: Types[TypeField] extends PrimitiveType : Type extends UnionType<keyof Types>
? PrimitiveTraverserDefinition ? UnionTraverserDefinition<Type>
: never; : Type extends PrimitiveType
? PrimitiveTraverserDefinition
: never;
}[TypeName];
export type TraverserDefinitions<Types extends TraverserTypes<any>> = { export type TraverserDefinitions<Types extends TraverserTypes<any>> = {
[TypeField in keyof Types]: TraverserDefinition<Types, TypeField>; [TypeName in keyof Types]: TraverserDefinition<
Types,
TypeName,
Types[TypeName]
>;
}; };

@ -1,7 +1,6 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
import { MultiMap } from "../transformerSubTraversers/util/MultiMap"; import { MultiMap } from "../transformerSubTraversers/util/MultiMap";
import type { TraverserTypes } from "../TraverserTypes"; import type { TraverserTypes } from "../TraverserTypes";
import type { InstanceNode } from "./nodes/InstanceNode";
import { import {
createInstanceNodeFor, createInstanceNodeFor,
type InstanceNodeFor, type InstanceNodeFor,
@ -12,7 +11,7 @@ export class InstanceGraph<Types extends TraverserTypes<any>> {
protected objectMap: MultiMap< protected objectMap: MultiMap<
object, object,
keyof Types, keyof Types,
InstanceNode<Types, keyof Types, Types[keyof Types]> InstanceNodeFor<Types, keyof Types>
> = new MultiMap(); > = new MultiMap();
public readonly traverserDefinitions: TraverserDefinitions<Types>; public readonly traverserDefinitions: TraverserDefinitions<Types>;
@ -26,14 +25,23 @@ export class InstanceGraph<Types extends TraverserTypes<any>> {
): InstanceNodeFor<Types, TypeName> { ): InstanceNodeFor<Types, TypeName> {
let potentialNode; let potentialNode;
// Skip the cache for Primitive Nodes // Skip the cache for Primitive Nodes
if ( const isCachable =
this.traverserDefinitions[typeName].kind !== "primitive" && this.traverserDefinitions[typeName].kind !== "primitive" &&
typeof instance === "object" && typeof instance === "object" &&
instance != null instance != null;
) {
if (isCachable) {
potentialNode = this.objectMap.get(instance, typeName); potentialNode = this.objectMap.get(instance, typeName);
} }
if (potentialNode) return potentialNode as InstanceNodeFor<Types, TypeName>; if (potentialNode) return potentialNode as InstanceNodeFor<Types, TypeName>;
return createInstanceNodeFor(instance, typeName, this); const newNode = createInstanceNodeFor(instance, typeName, this);
if (isCachable) {
// TODO: Figure out why this is a ts error
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
this.objectMap.set(instance, typeName, newNode);
}
newNode._recursivelyBuildChildren();
return newNode;
} }
} }

@ -8,7 +8,7 @@ import type { InstanceNodeFor } from "./createInstanceNodeFor";
export abstract class InstanceNode< export abstract class InstanceNode<
Types extends TraverserTypes<any>, Types extends TraverserTypes<any>,
TypeName extends keyof Types, TypeName extends keyof Types,
_Type extends Types[TypeName], Type extends Types[TypeName],
> { > {
readonly graph: InstanceGraph<Types>; readonly graph: InstanceGraph<Types>;
readonly instance: Types[TypeName]["type"]; readonly instance: Types[TypeName]["type"];
@ -16,17 +16,16 @@ export abstract class InstanceNode<
protected readonly parents: Record< protected readonly parents: Record<
string, string,
Set<InstanceNodeFor<Types, ParentIdentifiers<Types, TypeName>[0]>> Set<InstanceNodeFor<Types, ParentIdentifiers<Types, TypeName>[0]>>
>; > = {};
constructor( constructor(
graph: InstanceGraph<Types>, graph: InstanceGraph<Types>,
instance: Types[TypeName]["type"], instance: Type["type"],
typeName: TypeName, typeName: TypeName,
) { ) {
this.graph = graph; this.graph = graph;
this.instance = instance; this.instance = instance;
this.typeName = typeName; this.typeName = typeName;
this._recursivelyBuildChildren();
} }
private getParentKey( private getParentKey(
@ -68,9 +67,11 @@ export abstract class InstanceNode<
*/ */
public abstract allChildren(): InstanceNodeFor<Types, any>[]; public abstract allChildren(): InstanceNodeFor<Types, any>[];
protected abstract _recursivelyBuildChildren(): void; public get traverserDefinition(): TraverserDefinition<Types, TypeName, Type> {
return this.graph.traverserDefinitions[
public get traverserDefinition(): TraverserDefinition<Types, TypeName> { this.typeName
return this.graph.traverserDefinitions[this.typeName]; ] as unknown as TraverserDefinition<Types, TypeName, Type>;
} }
public abstract _recursivelyBuildChildren(): void;
} }

@ -1,8 +1,12 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
import type { InterfaceType, TraverserTypes } from "../../TraverserTypes"; import type { InterfaceType, TraverserTypes } from "../../TraverserTypes";
import type { InstanceGraph } from "../instanceGraph";
import type { InstanceNodeFor } from "./createInstanceNodeFor"; import type { InstanceNodeFor } from "./createInstanceNodeFor";
import { InstanceNode } from "./InstanceNode"; import { InstanceNode } from "./InstanceNode";
/**
* Helper Function
*/
type InterfacePropertyNode< type InterfacePropertyNode<
Types extends TraverserTypes<any>, Types extends TraverserTypes<any>,
Type extends InterfaceType<keyof Types>, Type extends InterfaceType<keyof Types>,
@ -11,6 +15,9 @@ type InterfacePropertyNode<
? InstanceNodeFor<Types, Type["properties"][PropertyName]>[] ? InstanceNodeFor<Types, Type["properties"][PropertyName]>[]
: InstanceNodeFor<Types, Type["properties"][PropertyName]>; : InstanceNodeFor<Types, Type["properties"][PropertyName]>;
/**
* Class
*/
export class InterfaceInstanceNode< export class InterfaceInstanceNode<
Types extends TraverserTypes<any>, Types extends TraverserTypes<any>,
TypeName extends keyof Types, TypeName extends keyof Types,
@ -24,6 +31,18 @@ export class InterfaceInstanceNode<
>; >;
}; };
constructor(
graph: InstanceGraph<Types>,
instance: Type["type"],
typeName: TypeName,
) {
super(graph, instance, typeName);
// This will eventually be filled out by the recursivelyBuildChildren method
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
this.children = {};
}
public _setChild<PropertyName extends keyof Type["properties"]>( public _setChild<PropertyName extends keyof Type["properties"]>(
propertyName: PropertyName, propertyName: PropertyName,
child: InterfacePropertyNode<Types, Type, PropertyName>, child: InterfacePropertyNode<Types, Type, PropertyName>,
@ -47,8 +66,15 @@ export class InterfaceInstanceNode<
public _recursivelyBuildChildren() { public _recursivelyBuildChildren() {
Object.entries(this.instance).forEach( Object.entries(this.instance).forEach(
([propertyName, value]: [keyof Type["properties"], unknown]) => { ([propertyName, value]: [keyof Type["properties"], unknown]) => {
const propertyTypeName =
// Fancy typescript doesn't work until you actually give it a type
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
this.traverserDefinition.properties[propertyName];
if (!propertyTypeName) return;
const initChildNode = (val: unknown) => { const initChildNode = (val: unknown) => {
const node = this.graph.getNodeFor(val, this.typeName); const node = this.graph.getNodeFor(val, propertyTypeName);
// Fancy typescript doesn't work until you actually give it a type // Fancy typescript doesn't work until you actually give it a type
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore // @ts-ignore

@ -16,7 +16,7 @@ export class PrimitiveInstanceNode<
public allChildren(): [] { public allChildren(): [] {
return []; return [];
} }
protected _recursivelyBuildChildren() { public _recursivelyBuildChildren(): void {
return; return;
} }
} }

@ -14,14 +14,21 @@ export class UnionInstanceNode<
this.childNode = child; this.childNode = child;
} }
public child(): InstanceNodeFor<Types, Type["typeNames"]> | undefined { public child(): InstanceNodeFor<Types, Type["typeNames"]> {
if (!this.childNode) throw new Error("Child node not yet set");
return this.childNode; return this.childNode;
} }
public allChildren(): InstanceNodeFor<Types, Type["typeNames"]>[] { public allChildren(): InstanceNodeFor<Types, Type["typeNames"]>[] {
return this.childNode ? [this.childNode] : []; return this.childNode ? [this.childNode] : [];
} }
protected _recursivelyBuildChildren() { public _recursivelyBuildChildren(): void {
// TODO const childType = this.traverserDefinition.selector(this.instance);
const childNode = this.graph.getNodeFor(this.instance, childType);
this._setChild(childNode);
// Fancy typescript only works once the type is provided
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
childNode._setParent([this.typeName], this);
} }
} }

@ -1,19 +1,152 @@
import { BrokenAvatarTransformer } from "./AvatarBrokenTransformer"; import type {
import { AvatarErroringTransformer } from "./AvatarErroringTransformer"; TraverserDefinitions,
import { aang } from "./sampleData"; ValidateTraverserTypes,
} from "../../../src";
import { InstanceGraph } from "../../../src/instanceGraph/instanceGraph";
describe("Avatar", () => { describe("AvatarExample", () => {
it("Throws an error before entering an infinite loop", async () => { /**
await expect( * Types
BrokenAvatarTransformer.transform(aang, "Bender", undefined), */
).rejects.toThrow( type Element = "Water" | "Earth" | "Fire" | "Air";
`Circular dependency found. Use the 'setReturnPointer' function. The loop includes the 'Bender' type`, interface Bender {
); name: string;
element: Element;
friends: Person[];
}
interface NonBender {
name: string;
friends: Person[];
}
type Person = Bender | NonBender;
/**
* Raw Data to Traverse
*/
const aang: Bender = {
name: "Aang",
element: "Air",
friends: [],
};
const sokka: NonBender = {
name: "Sokka",
friends: [],
};
const katara: Bender = {
name: "Katara",
element: "Water",
friends: [],
};
aang.friends.push(sokka, katara);
sokka.friends.push(aang, katara);
katara.friends.push(aang, sokka);
/**
* Traverser Types
*/
type AvatarTraverserTypes = ValidateTraverserTypes<{
Element: {
kind: "primitive";
type: Element;
};
Bender: {
kind: "interface";
type: Bender;
properties: {
element: "Element";
friends: "Person";
};
};
NonBender: {
kind: "interface";
type: NonBender;
properties: {
friends: "Person";
};
};
Person: {
kind: "union";
type: Person;
typeNames: "Bender" | "NonBender";
};
}>;
/**
* Create the traverser definition
*/
const avatarTraverserDefinition: TraverserDefinitions<AvatarTraverserTypes> =
{
Element: {
kind: "primitive",
},
Bender: {
kind: "interface",
properties: {
element: "Element",
friends: "Person",
},
},
NonBender: {
kind: "interface",
properties: {
friends: "Person",
},
},
Person: {
kind: "union",
selector: (item) => {
return (item as Bender).element ? "Bender" : "NonBender";
},
},
};
it("returns child nodes when child methods are called.", () => {
const graph = new InstanceGraph(avatarTraverserDefinition);
const aangBender = graph.getNodeFor(aang, "Bender");
expect(aangBender.typeName).toBe("Bender");
expect(aangBender.instance.name).toBe("Aang");
// child
const aangElement = aangBender.child("element");
expect(aangElement.instance).toBe("Air");
expect(aangElement.typeName).toBe("Element");
const aangFriends = aangBender.child("friends");
expect(aangFriends.length).toBe(2);
const sokkaPerson = aangFriends[0];
const kataraPerson = aangFriends[1];
expect(sokkaPerson.instance.name).toBe("Sokka");
expect(kataraPerson.instance.name).toBe("Katara");
expect(sokkaPerson.typeName).toBe("Person");
expect(kataraPerson.typeName).toBe("Person");
const sokkaNonBender = sokkaPerson.child();
expect(sokkaNonBender.instance.name).toBe("Sokka");
expect(sokkaNonBender.typeName).toBe("NonBender");
if (sokkaNonBender.typeName === "NonBender") {
const aangPerson = sokkaNonBender.child("friends")[0];
const aangBender2 = aangPerson.child();
expect(aangBender2).toBe(aangBender);
}
// allChildren
const [childElemement, childSokka, childKatara] = aangBender.allChildren();
expect(childElemement.instance).toBe("Air");
expect((childSokka.instance as NonBender).name).toBe("Sokka");
expect((childKatara.instance as Bender).name).toBe("Katara");
const childOfSokkaPerson = sokkaPerson.allChildren();
expect(childOfSokkaPerson.length).toBe(1);
expect(childOfSokkaPerson[0].instance.name).toBe("Sokka");
}); });
it("Bubbles errors", async () => { it("returns parent nodes when parent methods are called.", () => {
await expect( const graph = new InstanceGraph(avatarTraverserDefinition);
AvatarErroringTransformer.transform(aang, "Bender", undefined), const aangBender = graph.getNodeFor(aang, "Bender");
).rejects.toThrow("No Non Benders Allowed"); // parent
const [aangPerson] = aangBender.parent("Person");
expect(aangPerson.instance.name).toBe("Aang");
expect(aangPerson.typeName).toBe("Person");
const [sokkaNonBender] = aangPerson.parent("NonBender", "friends");
const [kataraBender] = aangPerson.parent("Bender", "friends");
expect(sokkaNonBender.typeName).toBe("NonBender");
expect(sokkaNonBender.instance.name).toBe("Sokka");
expect(kataraBender.typeName).toBe("Bender");
expect(kataraBender.instance.name).toBe("Katara");
}); });
}); });

@ -0,0 +1,19 @@
import { BrokenAvatarTransformer } from "./AvatarBrokenTransformer";
import { AvatarErroringTransformer } from "./AvatarErroringTransformer";
import { aang } from "./sampleData";
describe("Avatar", () => {
it("Throws an error before entering an infinite loop", async () => {
await expect(
BrokenAvatarTransformer.transform(aang, "Bender", undefined),
).rejects.toThrow(
`Circular dependency found. Use the 'setReturnPointer' function. The loop includes the 'Bender' type`,
);
});
it("Bubbles errors", async () => {
await expect(
AvatarErroringTransformer.transform(aang, "Bender", undefined),
).rejects.toThrow("No Non Benders Allowed");
});
});

@ -1,7 +1,7 @@
{ {
"extends": "../../tsconfig.base.json", "extends": "../../tsconfig.base.json",
"compilerOptions": { "compilerOptions": {
"outDir": "./dist" "outDir": "./dist",
}, },
"include": ["./src"] "include": ["./src"]
} }

@ -13,6 +13,7 @@
"target": "ES2021", "target": "ES2021",
"sourceMap": true, "sourceMap": true,
"jsx": "react-jsx", "jsx": "react-jsx",
"noErrorTruncation": true
}, },
"exclude": [ "exclude": [
"node_modules", "node_modules",

Loading…
Cancel
Save