From 665511f2f09b739ceffda0697dcde16da5c8b6a8 Mon Sep 17 00:00:00 2001 From: Jackson Morgan Date: Mon, 16 Dec 2024 15:57:48 -0500 Subject: [PATCH] Graph creation works --- packages/type-traverser/example/example.ts | 408 ++++++++---------- .../src/ReverseRelationshipTypes.ts | 7 +- .../type-traverser/src/TraverserDefinition.ts | 25 +- .../src/instanceGraph/InstanceGraph.ts | 20 +- .../src/instanceGraph/nodes/InstanceNode.ts | 17 +- .../nodes/InterfaceInstanceNode.ts | 28 +- .../nodes/PrimitiveInstanceNode.ts | 2 +- .../instanceGraph/nodes/UnionInstanceNode.ts | 13 +- .../test/integration/avatar/avatar.test.ts | 161 ++++++- .../AvatarBrokenTransformer.ts | 0 .../AvatarErroringTransformer.ts | 0 .../AvatarTraverserDefinition.ts | 0 .../AvatarTraverserTypes.ts | 0 .../test/integration/avatar_old/avatar.old.ts | 19 + .../{avatar => avatar_old}/sampleData.ts | 0 packages/type-traverser/tsconfig.build.json | 2 +- tsconfig.base.json | 1 + 17 files changed, 438 insertions(+), 265 deletions(-) rename packages/type-traverser/test/integration/{avatar => avatar_old}/AvatarBrokenTransformer.ts (100%) rename packages/type-traverser/test/integration/{avatar => avatar_old}/AvatarErroringTransformer.ts (100%) rename packages/type-traverser/test/integration/{avatar => avatar_old}/AvatarTraverserDefinition.ts (100%) rename packages/type-traverser/test/integration/{avatar => avatar_old}/AvatarTraverserTypes.ts (100%) create mode 100644 packages/type-traverser/test/integration/avatar_old/avatar.old.ts rename packages/type-traverser/test/integration/{avatar => avatar_old}/sampleData.ts (100%) diff --git a/packages/type-traverser/example/example.ts b/packages/type-traverser/example/example.ts index bbb1656..7b3398f 100644 --- a/packages/type-traverser/example/example.ts +++ b/packages/type-traverser/example/example.ts @@ -1,13 +1,10 @@ import type { - TraverserDefinition, ValidateTraverserTypes, - AssertExtends, - InterfaceType, - PrimitiveType, - UnionType, + ItemNamed, + TraverserDefinitions, } from "../src"; -import { Traverser } from "../src"; -import type { ReverseRelationshipIndentifiers } from "../src/reverseRelationshipTypes"; +import { InstanceGraph } from "../src/instanceGraph/instanceGraph"; +import type { ParentIdentifiers } from "../src/ReverseRelationshipTypes"; async function run() { /** @@ -64,7 +61,7 @@ async function run() { }; NonBender: { kind: "interface"; - type: Bender; + type: NonBender; properties: { friends: "Person"; }; @@ -76,236 +73,205 @@ async function run() { }; }>; - type AvatarReverseRelationshipIdentifiers = - ReverseRelationshipIndentifiers; - - const sample: AvatarReverseRelationshipIdentifiers = { - Element: ["Bender", "element"], - Bender: ["Person"], - } - - type KeysMatchingCondition = { - [K in keyof T]: T[K] extends Condition ? K : never; - }[keyof T]; - - // Condition: objects with `{ kind: "interface" }` - type InterfaceKeys = KeysMatchingCondition< + type PersonParentIdentifiers = ParentIdentifiers< AvatarTraverserTypes, - { kind: "interface" } + "Person" >; - - - 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 extends InterfaceType - // ? "interface" - // : T extends UnionType - // ? "union" - // : T extends PrimitiveType - // ? "primitive" - // : never; - - // // Example usage: - // type MappedUnion = MapUnion; // "a_mapped" | "b_mapped" | "c_mapped" - - interface; - type UnionType = "a" | "b" | "c"; - - type MapUnion = T extends "a" - ? "a_mapped" - : T extends "b" - ? "b_mapped" - : T extends "c" - ? "c_mapped" - : never; - - // Example usage: - type MappedUnion = MapUnion; // "a_mapped" | "b_mapped" | "c_mapped" + const sample: PersonParentIdentifiers = ["Bender", "friends"]; + console.log(sample); /** * Create the traverser definition */ - const avatarTraverserDefinition: TraverserDefinition = { - 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"; + const avatarTraverserDefinition: TraverserDefinitions = + { + Element: { + kind: "primitive", }, - }, - }; - - /** - * Instantiate the Traverser - */ - const avatarTraverser = new Traverser( - avatarTraverserDefinition, - ); - - /** - * Create a visitor - */ - const avatarVisitor = avatarTraverser.createVisitor({ - Element: async (item) => { - console.log(`Element: ${item}`); - }, - Bender: { - visitor: async (item) => { - console.log(`Bender: ${item.name}`); + Bender: { + kind: "interface", + properties: { + element: "Element", + friends: "Person", + }, }, - properties: { - element: async (item) => { - console.log(`Bender.element: ${item}`); + NonBender: { + kind: "interface", + properties: { + friends: "Person", }, }, - }, - NonBender: { - visitor: async (item) => { - console.log(`NonBender: ${item.name}`); + Person: { + kind: "union", + selector: (item) => { + return (item as Bender).element ? "Bender" : "NonBender"; + }, }, - }, - Person: async (item) => { - console.log(`Person: ${item.name}`); - }, - }); + }; - /** - * Run the visitor on data - */ - console.log( - "############################## Visitor Logs ##############################", - ); - await avatarVisitor.visit(aang, "Bender", undefined); + console.log(avatarTraverserDefinition); - /** - * Create a visitor that uses context - */ - interface AvatarCountingVisitorContext { - numberOfBenders: number; - } - const avatarCountingVisitor = - avatarTraverser.createVisitor({ - Bender: { - visitor: async (item, context) => { - context.numberOfBenders++; - }, - }, - }); + const graph = new InstanceGraph(avatarTraverserDefinition); + const aangNode = graph.getNodeFor(aang, "Bender"); + const aangeChild = aangNode.child("friends"); - /** - * 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 parent = aangNode.parent("Person"); - /** - * 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; - }, + const aangChildren = aangNode.allChildren(); + aangChildren.forEach((child) => { + child.typeName === "Element"; }); - /** - * 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(); + const aangParents = aangNode.allParents(); + + + // /** + // * Instantiate the Traverser + // */ + // const avatarTraverser = new Traverser( + // avatarTraverserDefinition, + // ); + + // /** + // * Create a visitor + // */ + // const avatarVisitor = avatarTraverser.createVisitor({ + // 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({ + // 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(); diff --git a/packages/type-traverser/src/ReverseRelationshipTypes.ts b/packages/type-traverser/src/ReverseRelationshipTypes.ts index 475a621..f7c91ec 100644 --- a/packages/type-traverser/src/ReverseRelationshipTypes.ts +++ b/packages/type-traverser/src/ReverseRelationshipTypes.ts @@ -74,4 +74,9 @@ export type BaseReverseRelationshipIndentifiers< export type ParentIdentifiers< Types extends TraverserTypes, ChildName extends keyof Types, -> = BaseReverseRelationshipIndentifiers[keyof Types]; +> = { + [CN in ChildName]: BaseReverseRelationshipIndentifiers< + Types, + CN + >[keyof Types]; +}[ChildName]; diff --git a/packages/type-traverser/src/TraverserDefinition.ts b/packages/type-traverser/src/TraverserDefinition.ts index aec169f..dfec5b4 100644 --- a/packages/type-traverser/src/TraverserDefinition.ts +++ b/packages/type-traverser/src/TraverserDefinition.ts @@ -24,15 +24,22 @@ export type PrimitiveTraverserDefinition = { export type TraverserDefinition< Types extends TraverserTypes, - TypeField extends keyof Types, -> = Types[TypeField] extends InterfaceType - ? InterfaceTraverserDefinition - : Types[TypeField] extends UnionType - ? UnionTraverserDefinition - : Types[TypeField] extends PrimitiveType - ? PrimitiveTraverserDefinition - : never; + TypeName extends keyof Types, + Type extends Types[TypeName], +> = { + [TN in TypeName]: Type extends InterfaceType + ? InterfaceTraverserDefinition + : Type extends UnionType + ? UnionTraverserDefinition + : Type extends PrimitiveType + ? PrimitiveTraverserDefinition + : never; +}[TypeName]; export type TraverserDefinitions> = { - [TypeField in keyof Types]: TraverserDefinition; + [TypeName in keyof Types]: TraverserDefinition< + Types, + TypeName, + Types[TypeName] + >; }; diff --git a/packages/type-traverser/src/instanceGraph/InstanceGraph.ts b/packages/type-traverser/src/instanceGraph/InstanceGraph.ts index ddd4ab1..04f28bc 100644 --- a/packages/type-traverser/src/instanceGraph/InstanceGraph.ts +++ b/packages/type-traverser/src/instanceGraph/InstanceGraph.ts @@ -1,7 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { MultiMap } from "../transformerSubTraversers/util/MultiMap"; import type { TraverserTypes } from "../TraverserTypes"; -import type { InstanceNode } from "./nodes/InstanceNode"; import { createInstanceNodeFor, type InstanceNodeFor, @@ -12,7 +11,7 @@ export class InstanceGraph> { protected objectMap: MultiMap< object, keyof Types, - InstanceNode + InstanceNodeFor > = new MultiMap(); public readonly traverserDefinitions: TraverserDefinitions; @@ -26,14 +25,23 @@ export class InstanceGraph> { ): InstanceNodeFor { let potentialNode; // Skip the cache for Primitive Nodes - if ( + const isCachable = this.traverserDefinitions[typeName].kind !== "primitive" && typeof instance === "object" && - instance != null - ) { + instance != null; + + if (isCachable) { potentialNode = this.objectMap.get(instance, typeName); } if (potentialNode) return potentialNode as InstanceNodeFor; - 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; } } diff --git a/packages/type-traverser/src/instanceGraph/nodes/InstanceNode.ts b/packages/type-traverser/src/instanceGraph/nodes/InstanceNode.ts index 3a547bb..de5a629 100644 --- a/packages/type-traverser/src/instanceGraph/nodes/InstanceNode.ts +++ b/packages/type-traverser/src/instanceGraph/nodes/InstanceNode.ts @@ -8,7 +8,7 @@ import type { InstanceNodeFor } from "./createInstanceNodeFor"; export abstract class InstanceNode< Types extends TraverserTypes, TypeName extends keyof Types, - _Type extends Types[TypeName], + Type extends Types[TypeName], > { readonly graph: InstanceGraph; readonly instance: Types[TypeName]["type"]; @@ -16,17 +16,16 @@ export abstract class InstanceNode< protected readonly parents: Record< string, Set[0]>> - >; + > = {}; constructor( graph: InstanceGraph, - instance: Types[TypeName]["type"], + instance: Type["type"], typeName: TypeName, ) { this.graph = graph; this.instance = instance; this.typeName = typeName; - this._recursivelyBuildChildren(); } private getParentKey( @@ -68,9 +67,11 @@ export abstract class InstanceNode< */ public abstract allChildren(): InstanceNodeFor[]; - protected abstract _recursivelyBuildChildren(): void; - - public get traverserDefinition(): TraverserDefinition { - return this.graph.traverserDefinitions[this.typeName]; + public get traverserDefinition(): TraverserDefinition { + return this.graph.traverserDefinitions[ + this.typeName + ] as unknown as TraverserDefinition; } + + public abstract _recursivelyBuildChildren(): void; } diff --git a/packages/type-traverser/src/instanceGraph/nodes/InterfaceInstanceNode.ts b/packages/type-traverser/src/instanceGraph/nodes/InterfaceInstanceNode.ts index 5ae057e..fb688f2 100644 --- a/packages/type-traverser/src/instanceGraph/nodes/InterfaceInstanceNode.ts +++ b/packages/type-traverser/src/instanceGraph/nodes/InterfaceInstanceNode.ts @@ -1,8 +1,12 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import type { InterfaceType, TraverserTypes } from "../../TraverserTypes"; +import type { InstanceGraph } from "../instanceGraph"; import type { InstanceNodeFor } from "./createInstanceNodeFor"; import { InstanceNode } from "./InstanceNode"; +/** + * Helper Function + */ type InterfacePropertyNode< Types extends TraverserTypes, Type extends InterfaceType, @@ -11,6 +15,9 @@ type InterfacePropertyNode< ? InstanceNodeFor[] : InstanceNodeFor; +/** + * Class + */ export class InterfaceInstanceNode< Types extends TraverserTypes, TypeName extends keyof Types, @@ -24,6 +31,18 @@ export class InterfaceInstanceNode< >; }; + constructor( + graph: InstanceGraph, + 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: PropertyName, child: InterfacePropertyNode, @@ -47,8 +66,15 @@ export class InterfaceInstanceNode< public _recursivelyBuildChildren() { Object.entries(this.instance).forEach( ([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 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 // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore diff --git a/packages/type-traverser/src/instanceGraph/nodes/PrimitiveInstanceNode.ts b/packages/type-traverser/src/instanceGraph/nodes/PrimitiveInstanceNode.ts index 4152c22..10004f4 100644 --- a/packages/type-traverser/src/instanceGraph/nodes/PrimitiveInstanceNode.ts +++ b/packages/type-traverser/src/instanceGraph/nodes/PrimitiveInstanceNode.ts @@ -16,7 +16,7 @@ export class PrimitiveInstanceNode< public allChildren(): [] { return []; } - protected _recursivelyBuildChildren() { + public _recursivelyBuildChildren(): void { return; } } diff --git a/packages/type-traverser/src/instanceGraph/nodes/UnionInstanceNode.ts b/packages/type-traverser/src/instanceGraph/nodes/UnionInstanceNode.ts index b28b877..1fe5e77 100644 --- a/packages/type-traverser/src/instanceGraph/nodes/UnionInstanceNode.ts +++ b/packages/type-traverser/src/instanceGraph/nodes/UnionInstanceNode.ts @@ -14,14 +14,21 @@ export class UnionInstanceNode< this.childNode = child; } - public child(): InstanceNodeFor | undefined { + public child(): InstanceNodeFor { + if (!this.childNode) throw new Error("Child node not yet set"); return this.childNode; } public allChildren(): InstanceNodeFor[] { return this.childNode ? [this.childNode] : []; } - protected _recursivelyBuildChildren() { - // TODO + public _recursivelyBuildChildren(): void { + 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); } } diff --git a/packages/type-traverser/test/integration/avatar/avatar.test.ts b/packages/type-traverser/test/integration/avatar/avatar.test.ts index 6748fc6..f2b071f 100644 --- a/packages/type-traverser/test/integration/avatar/avatar.test.ts +++ b/packages/type-traverser/test/integration/avatar/avatar.test.ts @@ -1,19 +1,152 @@ -import { BrokenAvatarTransformer } from "./AvatarBrokenTransformer"; -import { AvatarErroringTransformer } from "./AvatarErroringTransformer"; -import { aang } from "./sampleData"; +import type { + TraverserDefinitions, + ValidateTraverserTypes, +} from "../../../src"; +import { InstanceGraph } from "../../../src/instanceGraph/instanceGraph"; -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`, - ); +describe("AvatarExample", () => { + /** + * Types + */ + type Element = "Water" | "Earth" | "Fire" | "Air"; + 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 = + { + 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 () => { - await expect( - AvatarErroringTransformer.transform(aang, "Bender", undefined), - ).rejects.toThrow("No Non Benders Allowed"); + it("returns parent nodes when parent methods are called.", () => { + const graph = new InstanceGraph(avatarTraverserDefinition); + const aangBender = graph.getNodeFor(aang, "Bender"); + // 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"); }); }); diff --git a/packages/type-traverser/test/integration/avatar/AvatarBrokenTransformer.ts b/packages/type-traverser/test/integration/avatar_old/AvatarBrokenTransformer.ts similarity index 100% rename from packages/type-traverser/test/integration/avatar/AvatarBrokenTransformer.ts rename to packages/type-traverser/test/integration/avatar_old/AvatarBrokenTransformer.ts diff --git a/packages/type-traverser/test/integration/avatar/AvatarErroringTransformer.ts b/packages/type-traverser/test/integration/avatar_old/AvatarErroringTransformer.ts similarity index 100% rename from packages/type-traverser/test/integration/avatar/AvatarErroringTransformer.ts rename to packages/type-traverser/test/integration/avatar_old/AvatarErroringTransformer.ts diff --git a/packages/type-traverser/test/integration/avatar/AvatarTraverserDefinition.ts b/packages/type-traverser/test/integration/avatar_old/AvatarTraverserDefinition.ts similarity index 100% rename from packages/type-traverser/test/integration/avatar/AvatarTraverserDefinition.ts rename to packages/type-traverser/test/integration/avatar_old/AvatarTraverserDefinition.ts diff --git a/packages/type-traverser/test/integration/avatar/AvatarTraverserTypes.ts b/packages/type-traverser/test/integration/avatar_old/AvatarTraverserTypes.ts similarity index 100% rename from packages/type-traverser/test/integration/avatar/AvatarTraverserTypes.ts rename to packages/type-traverser/test/integration/avatar_old/AvatarTraverserTypes.ts diff --git a/packages/type-traverser/test/integration/avatar_old/avatar.old.ts b/packages/type-traverser/test/integration/avatar_old/avatar.old.ts new file mode 100644 index 0000000..6748fc6 --- /dev/null +++ b/packages/type-traverser/test/integration/avatar_old/avatar.old.ts @@ -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"); + }); +}); diff --git a/packages/type-traverser/test/integration/avatar/sampleData.ts b/packages/type-traverser/test/integration/avatar_old/sampleData.ts similarity index 100% rename from packages/type-traverser/test/integration/avatar/sampleData.ts rename to packages/type-traverser/test/integration/avatar_old/sampleData.ts diff --git a/packages/type-traverser/tsconfig.build.json b/packages/type-traverser/tsconfig.build.json index ce7be9c..4bd5a5e 100644 --- a/packages/type-traverser/tsconfig.build.json +++ b/packages/type-traverser/tsconfig.build.json @@ -1,7 +1,7 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { - "outDir": "./dist" + "outDir": "./dist", }, "include": ["./src"] } \ No newline at end of file diff --git a/tsconfig.base.json b/tsconfig.base.json index ded8583..f8b9696 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -13,6 +13,7 @@ "target": "ES2021", "sourceMap": true, "jsx": "react-jsx", + "noErrorTruncation": true }, "exclude": [ "node_modules",