Repository: RobinTail/express-zod-api
Pull request #876 (2023-03-12 17:05:26)

Feat: Supporting `z.lazy()` and circular schemas in Client
Similar to #856 , but for Client


Patch:
diff --git a/example/example.client.ts b/example/example.client.ts
index 5c2f3fd6..740fe778 100644
--- a/example/example.client.ts
+++ b/example/example.client.ts
@@ -1,3 +1,8 @@
+type Type2048581c137c5b2130eb860e3ae37da196dfc25b = {
+  title: string;
+  features: Type2048581c137c5b2130eb860e3ae37da196dfc25b;
+}[];
+
 type GetV1UserRetrieveInput = {} & {
   /** a numeric string containing the id of the user */
   id: string;
@@ -11,7 +16,7 @@ type GetV1UserRetrieveResponse =
         name: string;
         features: {
           title: string;
-          features: any;
+          features: Type2048581c137c5b2130eb860e3ae37da196dfc25b;
         }[];
       };
     }
diff --git a/src/client.ts b/src/client.ts
index 69b57dbd..1c57c1ac 100644
--- a/src/client.ts
+++ b/src/client.ts
@@ -1,4 +1,5 @@
 import ts from "typescript";
+import { z } from "zod";
 import {
   exportModifier,
   f,
@@ -22,7 +23,7 @@ import {
   parametricIndexNode,
   protectedReadonlyModifier,
 } from "./client-helpers";
-import { makeCleanId } from "./common-helpers";
+import { defaultSerializer, makeCleanId } from "./common-helpers";
 import { methods } from "./method";
 import { mimeJson } from "./mime";
 import { Routing } from "./routing";
@@ -34,31 +35,53 @@ interface Registry {
   [METHOD_PATH: string]: Record<"in" | "out", string> & { isJson: boolean };
 }
 
+interface GeneratorParams {
+  routing: Routing;
+  serializer?: (schema: z.ZodTypeAny) => string;
+}
+
 export class Client {
   protected agg: ts.Node[] = [];
   protected registry: Registry = {};
   protected paths: string[] = [];
+  protected aliases: Record<string, ts.TypeAliasDeclaration> = {};
+
+  protected getAlias(name: string): ts.TypeReferenceNode | undefined {
+    return name in this.aliases ? f.createTypeReferenceNode(name) : undefined;
+  }
 
-  constructor({ routing }: { routing: Routing }) {
+  protected makeAlias(name: string, type: ts.TypeNode): ts.TypeReferenceNode {
+    this.aliases[name] = createTypeAlias(type, name);
+    return this.getAlias(name)!;
+  }
+
+  constructor({ routing, serializer = defaultSerializer }: GeneratorParams) {
     walkRouting({
       routing,
       onEndpoint: (endpoint, path, method) => {
         const inputId = makeCleanId(path, method, "input");
         const responseId = makeCleanId(path, method, "response");
+        const commons = {
+          serializer,
+          getAlias: this.getAlias.bind(this),
+          makeAlias: this.makeAlias.bind(this),
+        };
         const input = zodToTs({
+          ...commons,
           schema: endpoint.getSchema("input"),
           isResponse: false,
         });
         const response = zodToTs({
+          ...commons,
           isResponse: true,
           schema: endpoint
             .getSchema("positive")
             .or(endpoint.getSchema("negative")),
         });
-        const inputAlias = createTypeAlias(input, inputId);
-        const responseAlias = createTypeAlias(response, responseId);
-        this.agg.push(inputAlias);
-        this.agg.push(responseAlias);
+        this.agg.push(
+          createTypeAlias(input, inputId),
+          createTypeAlias(response, responseId)
+        );
         if (method !== "options") {
           this.paths.push(path);
           this.registry[`${method} ${path}`] = {
@@ -70,6 +93,8 @@ export class Client {
       },
     });
 
+    this.agg = Object.values<ts.Node>(this.aliases).concat(this.agg);
+
     const pathNode = makePublicLiteralType("Path", this.paths);
     const methodNode = makePublicLiteralType("Method", methods);
 
diff --git a/src/common-helpers.ts b/src/common-helpers.ts
index ade090e3..d5e4a452 100644
--- a/src/common-helpers.ts
+++ b/src/common-helpers.ts
@@ -1,3 +1,4 @@
+import { createHash } from "crypto";
 import { Request } from "express";
 import { HttpError } from "http-errors";
 import { z } from "zod";
@@ -227,6 +228,9 @@ export const makeCleanId = (path: string, method: string, suffix?: string) => {
     .join("");
 };
 
+export const defaultSerializer = (schema: z.ZodTypeAny): string =>
+  createHash("sha1").update(JSON.stringify(schema), "utf8").digest("hex");
+
 export const tryToTransform = ({
   effect,
   sample,
diff --git a/src/open-api-helpers.ts b/src/open-api-helpers.ts
index 2e6a978a..5300ec4d 100644
--- a/src/open-api-helpers.ts
+++ b/src/open-api-helpers.ts
@@ -1,4 +1,3 @@
-import { createHash } from "crypto";
 import {
   ContentObject,
   ExampleObject,
@@ -56,7 +55,7 @@ type MediaExamples = Pick<MediaTypeObject, "examples">;
 export interface OpenAPIContext {
   isResponse: boolean;
   serializer: (schema: z.ZodTypeAny) => string;
-  hasRef: (name: string) => boolean;
+  getRef: (name: string) => ReferenceObject | undefined;
   makeRef: (
     name: string,
     schema: SchemaObject | ReferenceObject
@@ -69,7 +68,7 @@ type Depicter<
 > = SchemaHandler<T, SchemaObject | ReferenceObject, OpenAPIContext, Variant>;
 
 interface ReqResDepictHelperCommonProps
-  extends Pick<OpenAPIContext, "serializer" | "hasRef" | "makeRef"> {
+  extends Pick<OpenAPIContext, "serializer" | "getRef" | "makeRef"> {
   method: Method;
   path: string;
   endpoint: AbstractEndpoint;
@@ -514,23 +513,21 @@ export const depictBranded: Depicter<z.ZodBranded<z.ZodTypeAny, any>> = ({
   next,
 }) => next({ schema: schema.unwrap() });
 
-export const defaultSerializer = (schema: z.ZodTypeAny): string =>
-  createHash("sha1").update(JSON.stringify(schema), "utf8").digest("hex");
-
 export const depictLazy: Depicter<z.ZodLazy<z.ZodTypeAny>> = ({
   next,
   schema: lazy,
   serializer: serialize,
-  hasRef,
+  getRef,
   makeRef,
 }): ReferenceObject => {
   const hash = serialize(lazy.schema);
-  if (hasRef(hash)) {
-    return { $ref: `#/components/schemas/${hash}` }; // @todo consider changing it to getRef that return ReferenceObject
-  }
-  const ref = makeRef(hash, {}); // make empty ref first
-  makeRef(hash, next({ schema: lazy.schema })); // update
-  return ref;
+  return (
+    getRef(hash) ||
+    (() => {
+      makeRef(hash, {}); // make empty ref first
+      return makeRef(hash, next({ schema: lazy.schema })); // update
+    })()
+  );
 };
 
 export const depictExamples = (
@@ -614,7 +611,7 @@ export const depictRequestParams = ({
   endpoint,
   inputSources,
   serializer,
-  hasRef,
+  getRef,
   makeRef,
   composition,
   clue = "parameter",
@@ -638,7 +635,7 @@ export const depictRequestParams = ({
         onEach,
         onMissing,
         serializer,
-        hasRef,
+        getRef,
         makeRef,
       });
       const result =
@@ -764,7 +761,7 @@ export const depictResponse = ({
   endpoint,
   isPositive,
   serializer,
-  hasRef,
+  getRef,
   makeRef,
   composition,
   clue = "response",
@@ -781,7 +778,7 @@ export const depictResponse = ({
       onEach,
       onMissing,
       serializer,
-      hasRef,
+      getRef,
       makeRef,
     })
   );
@@ -901,7 +898,7 @@ export const depictRequest = ({
   path,
   endpoint,
   serializer,
-  hasRef,
+  getRef,
   makeRef,
   composition,
   clue = "request body",
@@ -916,7 +913,7 @@ export const depictRequest = ({
         onEach,
         onMissing,
         serializer,
-        hasRef,
+        getRef,
         makeRef,
       }),
       pathParams
diff --git a/src/open-api.ts b/src/open-api.ts
index a1a69109..a9132e92 100644
--- a/src/open-api.ts
+++ b/src/open-api.ts
@@ -7,12 +7,15 @@ import {
   SecuritySchemeType,
 } from "openapi3-ts";
 import { z } from "zod";
-import { defaultInputSources, makeCleanId } from "./common-helpers";
+import {
+  defaultInputSources,
+  defaultSerializer,
+  makeCleanId,
+} from "./common-helpers";
 import { CommonConfig } from "./config-type";
 import { mapLogicalContainer } from "./logical-container";
 import { Method } from "./method";
 import {
-  defaultSerializer,
   depictRequest,
   depictRequestParams,
   depictResponse,
@@ -56,11 +59,13 @@ export class OpenAPI extends OpenApiBuilder {
     schema: SchemaObject | ReferenceObject
   ): ReferenceObject {
     this.addSchema(name, schema);
-    return { $ref: `#/components/schemas/${name}` };
+    return this.getRef(name)!;
   }
 
-  protected hasRef(name: string): boolean {
-    return name in (this.rootDoc.components?.schemas || {});
+  protected getRef(name: string): ReferenceObject | undefined {
+    return name in (this.rootDoc.components?.schemas || {})
+      ? { $ref: `#/components/schemas/${name}` }
+      : undefined;
   }
 
   protected ensureUniqOperationId(path: string, method: Method) {
@@ -116,7 +121,7 @@ export class OpenAPI extends OpenApiBuilder {
         endpoint,
         composition,
         serializer,
-        hasRef: this.hasRef.bind(this),
+        getRef: this.getRef.bind(this),
         makeRef: this.makeRef.bind(this),
       };
       const [shortDesc, longDesc] = (["short", "long"] as const).map(
diff --git a/src/zts-helpers.ts b/src/zts-helpers.ts
index 735865c7..a9bb4ce9 100644
--- a/src/zts-helpers.ts
+++ b/src/zts-helpers.ts
@@ -32,7 +32,12 @@ const { factory: f } = ts;
 
 export type LiteralType = string | number | boolean;
 
-export type ZTSContext = { isResponse?: boolean };
+export type ZTSContext = {
+  isResponse: boolean;
+  getAlias: (name: string) => ts.TypeReferenceNode | undefined;
+  makeAlias: (name: string, type: ts.TypeNode) => ts.TypeReferenceNode;
+  serializer: (schema: z.ZodTypeAny) => string;
+};
 
 export type Producer<T extends z.ZodTypeAny> = SchemaHandler<
   T,
diff --git a/src/zts.ts b/src/zts.ts
index 1b4b3895..36711b14 100644
--- a/src/zts.ts
+++ b/src/zts.ts
@@ -203,6 +203,23 @@ const onPipeline: Producer<z.ZodPipeline<z.ZodTypeAny, z.ZodTypeAny>> = ({
 const onNull: Producer<z.ZodNull> = () =>
   f.createLiteralTypeNode(f.createNull());
 
+const onLazy: Producer<z.ZodLazy<z.ZodTypeAny>> = ({
+  getAlias,
+  makeAlias,
+  next,
+  serializer: serialize,
+  schema: lazy,
+}) => {
+  const name = `Type${serialize(lazy.schema)}`;
+  return (
+    getAlias(name) ||
+    (() => {
+      makeAlias(name, f.createLiteralTypeNode(f.createNull())); // make empty type first
+      return makeAlias(name, next({ schema: lazy.schema })); // update
+    })()
+  );
+};
+
 const producers: HandlingRules<ts.TypeNode, ZTSContext> = {
   ZodString: onPrimitive(ts.SyntaxKind.StringKeyword),
   ZodNumber: onPrimitive(ts.SyntaxKind.NumberKeyword),
@@ -231,6 +248,7 @@ const producers: HandlingRules<ts.TypeNode, ZTSContext> = {
   ZodBranded: onBranded,
   ZodCatch: onCatch,
   ZodPipeline: onPipeline,
+  ZodLazy: onLazy,
 };
 
 export const zodToTs = ({


Test patch:
diff --git a/tests/unit/__snapshots__/client.spec.ts.snap b/tests/unit/__snapshots__/client.spec.ts.snap
index e6eff780..0c108e53 100644
--- a/tests/unit/__snapshots__/client.spec.ts.snap
+++ b/tests/unit/__snapshots__/client.spec.ts.snap
@@ -1,7 +1,12 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
 exports[`API Client Generator Should generate a client for example API 1`] = `
-"type GetV1UserRetrieveInput = {} & {
+"type Type2048581c137c5b2130eb860e3ae37da196dfc25b = {
+    title: string;
+    features: Type2048581c137c5b2130eb860e3ae37da196dfc25b;
+}[];
+
+type GetV1UserRetrieveInput = {} & {
     /** a numeric string containing the id of the user */
     id: string;
 };
@@ -13,7 +18,7 @@ type GetV1UserRetrieveResponse = {
         name: string;
         features: {
             title: string;
-            features: any;
+            features: Type2048581c137c5b2130eb860e3ae37da196dfc25b;
         }[];
     };
 } | {
diff --git a/tests/unit/__snapshots__/open-api-helpers.spec.ts.snap b/tests/unit/__snapshots__/open-api-helpers.spec.ts.snap
index fd46cd11..baa29bc9 100644
--- a/tests/unit/__snapshots__/open-api-helpers.spec.ts.snap
+++ b/tests/unit/__snapshots__/open-api-helpers.spec.ts.snap
@@ -301,7 +301,7 @@ exports[`Open API helpers depictIntersection() should wrap next depicters in all
 
 exports[`Open API helpers depictLazy should handle circular references 0 1`] = `
 {
-  "$ref": "6cbbd837811754902ea1e68d3e5c75e36250b880",
+  "$ref": "#/components/schemas/6cbbd837811754902ea1e68d3e5c75e36250b880",
 }
 `;
 
@@ -316,7 +316,7 @@ exports[`Open API helpers depictLazy should handle circular references 0 2`] = `
 
 exports[`Open API helpers depictLazy should handle circular references 1 1`] = `
 {
-  "$ref": "7a225c55e65ab4a2fd3ce390265b255ee6747049",
+  "$ref": "#/components/schemas/7a225c55e65ab4a2fd3ce390265b255ee6747049",
 }
 `;
 
@@ -328,7 +328,7 @@ exports[`Open API helpers depictLazy should handle circular references 1 2`] = `
 
 exports[`Open API helpers depictLazy should handle circular references 2 1`] = `
 {
-  "$ref": "118cb3b11b8a1f3b6b1e60a89f96a8be9da32a0f",
+  "$ref": "#/components/schemas/118cb3b11b8a1f3b6b1e60a89f96a8be9da32a0f",
 }
 `;
 
diff --git a/tests/unit/__snapshots__/zts.spec.ts.snap b/tests/unit/__snapshots__/zts.spec.ts.snap
index 90567496..f3acfdd8 100644
--- a/tests/unit/__snapshots__/zts.spec.ts.snap
+++ b/tests/unit/__snapshots__/zts.spec.ts.snap
@@ -8,7 +8,7 @@ exports[`zod-to-ts Example should produce the expected results 1`] = `
         string: string;
     }[];
     boolean: boolean;
-    circular: any;
+    circular: Type118cb3b11b8a1f3b6b1e60a89f96a8be9da32a0f;
     union: {
         number: number;
     } | "hi";
@@ -55,7 +55,7 @@ exports[`zod-to-ts Example should produce the expected results 1`] = `
     optDefaultString?: string | undefined;
     refinedStringWithSomeBullshit: (string | number) & ((bigint | null) | undefined);
     nativeEnum: "A" | "apple" | "banana" | "cantaloupe" | 5;
-    lazy: any;
+    lazy: Type51497f7e879bae48c3fbad2fa68050d1e08bbf82;
     discUnion: {
         kind: "circle";
         radius: number;
diff --git a/tests/unit/open-api-helpers.spec.ts b/tests/unit/open-api-helpers.spec.ts
index 58aca0f6..3ab58dc2 100644
--- a/tests/unit/open-api-helpers.spec.ts
+++ b/tests/unit/open-api-helpers.spec.ts
@@ -1,11 +1,11 @@
 import { ReferenceObject, SchemaObject } from "openapi3-ts";
 import { z } from "zod";
+import { defaultSerializer } from "../../src/common-helpers";
 import { IOSchemaError } from "../../src/errors";
 import { OpenAPIError, defaultEndpointsFactory, ez, withMeta } from "../../src";
 import { getMeta } from "../../src/metadata";
 import {
   OpenAPIContext,
-  defaultSerializer,
   depictAny,
   depictArray,
   depictBigInt,
@@ -54,21 +54,21 @@ import { SchemaHandler, walkSchema } from "../../src/schema-walker";
 import { serializeSchemaForTest } from "../helpers";
 
 describe("Open API helpers", () => {
-  const hasRefMock = jest.fn();
+  const getRefMock = jest.fn();
   const makeRefMock = jest.fn(
     (name: string, {}: SchemaObject | ReferenceObject): ReferenceObject => ({
-      $ref: name,
+      $ref: `#/components/schemas/${name}`,
     })
   );
   const requestContext: OpenAPIContext = {
     isResponse: false,
-    hasRef: hasRefMock,
+    getRef: getRefMock,
     makeRef: makeRefMock,
     serializer: defaultSerializer,
   };
   const responseContext: OpenAPIContext = {
     isResponse: true,
-    hasRef: hasRefMock,
+    getRef: getRefMock,
     makeRef: makeRefMock,
     serializer: defaultSerializer,
   };
@@ -91,7 +91,7 @@ describe("Open API helpers", () => {
       });
 
   beforeEach(() => {
-    hasRefMock.mockClear();
+    getRefMock.mockClear();
     makeRefMock.mockClear();
   });
 
@@ -742,7 +742,7 @@ describe("Open API helpers", () => {
           }),
           inputSources: ["query", "params"],
           composition: "inline",
-          hasRef: hasRefMock,
+          getRef: getRefMock,
           makeRef: makeRefMock,
           serializer: defaultSerializer,
         })
@@ -765,7 +765,7 @@ describe("Open API helpers", () => {
           }),
           inputSources: ["body", "params"],
           composition: "inline",
-          hasRef: hasRefMock,
+          getRef: getRefMock,
           makeRef: makeRefMock,
           serializer: defaultSerializer,
         })
@@ -788,7 +788,7 @@ describe("Open API helpers", () => {
           }),
           inputSources: ["body"],
           composition: "inline",
-          hasRef: hasRefMock,
+          getRef: getRefMock,
           makeRef: makeRefMock,
           serializer: defaultSerializer,
         })
@@ -912,10 +912,14 @@ describe("Open API helpers", () => {
         hash: "118cb3b11b8a1f3b6b1e60a89f96a8be9da32a0f",
       },
     ])("should handle circular references %#", ({ schema, hash }) => {
-      hasRefMock
-        .mockImplementationOnce(() => false)
-        .mockImplementationOnce(() => true);
-      expect(hasRefMock.mock.calls.length).toBe(0);
+      getRefMock
+        .mockImplementationOnce(() => undefined)
+        .mockImplementationOnce(
+          (name: string): ReferenceObject => ({
+            $ref: `#/components/schemas/${name}`,
+          })
+        );
+      expect(getRefMock.mock.calls.length).toBe(0);
       expect(
         depictLazy({
           schema,
@@ -923,8 +927,8 @@ describe("Open API helpers", () => {
           next: makeNext(responseContext),
         })
       ).toMatchSnapshot();
-      expect(hasRefMock).toHaveBeenCalledTimes(2);
-      for (const call of hasRefMock.mock.calls) {
+      expect(getRefMock).toHaveBeenCalledTimes(2);
+      for (const call of getRefMock.mock.calls) {
         expect(call[0]).toBe(hash);
       }
       expect(makeRefMock).toHaveBeenCalledTimes(2);
diff --git a/tests/unit/zts.spec.ts b/tests/unit/zts.spec.ts
index 5795b392..13ea6342 100644
--- a/tests/unit/zts.spec.ts
+++ b/tests/unit/zts.spec.ts
@@ -26,17 +26,26 @@
 
 import ts from "typescript";
 import { z } from "zod";
+import { f } from "../../src/client-helpers";
+import { defaultSerializer } from "../../src/common-helpers";
 import { zodToTs } from "../../src/zts";
 import { createTypeAlias, printNode } from "../../src/zts-helpers";
 
 describe("zod-to-ts", () => {
   const printNodeTest = (node: ts.Node) =>
     printNode(node, { newLine: ts.NewLineKind.LineFeed });
+  const defaultCtx = {
+    isResponse: false,
+    getAlias: jest.fn((name: string) => f.createTypeReferenceNode(name)),
+    makeAlias: jest.fn(),
+    serializer: defaultSerializer,
+  };
 
   describe("z.array()", () => {
     it("outputs correct typescript", () => {
       const node = zodToTs({
         schema: z.object({ id: z.number(), value: z.string() }).array(),
+        ...defaultCtx,
       });
       expect(printNodeTest(node)).toMatchSnapshot();
     });
@@ -46,6 +55,7 @@ describe("zod-to-ts", () => {
     const identifier = "User";
     const node = zodToTs({
       schema: z.object({ username: z.string(), age: z.number() }),
+      ...defaultCtx,
     });
 
     it("outputs correct typescript", () => {
@@ -86,7 +96,9 @@ describe("zod-to-ts", () => {
       { schema: z.nativeEnum(Fruit), feature: "string" },
       { schema: z.nativeEnum(StringLiteral), feature: "quoted string" },
     ])("handles $feature literals", ({ schema }) => {
-      expect(printNodeTest(zodToTs({ schema }))).toMatchSnapshot();
+      expect(
+        printNodeTest(zodToTs({ schema, ...defaultCtx }))
+      ).toMatchSnapshot();
     });
   });
 
@@ -189,6 +201,7 @@ describe("zod-to-ts", () => {
     it("should produce the expected results", () => {
       const node = zodToTs({
         schema: example,
+        ...defaultCtx,
       });
       expect(printNode(node)).toMatchSnapshot();
     });
@@ -217,12 +230,12 @@ describe("zod-to-ts", () => {
     });
 
     it("outputs correct typescript", () => {
-      const node = zodToTs({ schema: optionalStringSchema });
+      const node = zodToTs({ schema: optionalStringSchema, ...defaultCtx });
       expect(printNodeTest(node)).toMatchSnapshot();
     });
 
     it("should output `?:` and undefined union for optional properties", () => {
-      const node = zodToTs({ schema: objectWithOptionals });
+      const node = zodToTs({ schema: objectWithOptionals, ...defaultCtx });
       expect(printNodeTest(node)).toMatchSnapshot();
     });
   });
@@ -231,7 +244,7 @@ describe("zod-to-ts", () => {
     const nullableUsernameSchema = z.object({
       username: z.string().nullable(),
     });
-    const node = zodToTs({ schema: nullableUsernameSchema });
+    const node = zodToTs({ schema: nullableUsernameSchema, ...defaultCtx });
 
     it("outputs correct typescript", () => {
       expect(printNodeTest(node)).toMatchSnapshot();
@@ -244,7 +257,7 @@ describe("zod-to-ts", () => {
         "string-literal": z.string(),
         5: z.number(),
       });
-      const node = zodToTs({ schema });
+      const node = zodToTs({ schema, ...defaultCtx });
       expect(printNodeTest(node)).toMatchSnapshot();
     });
 
@@ -254,7 +267,7 @@ describe("zod-to-ts", () => {
         name: z.string(),
         countryOfOrigin: z.string(),
       });
-      const node = zodToTs({ schema });
+      const node = zodToTs({ schema, ...defaultCtx });
       expect(printNodeTest(node)).toMatchSnapshot();
     });
 
@@ -270,7 +283,7 @@ describe("zod-to-ts", () => {
         _r: z.any(),
         "-r": z.undefined(),
       });
-      const node = zodToTs({ schema });
+      const node = zodToTs({ schema, ...defaultCtx });
       expect(printNodeTest(node)).toMatchSnapshot();
     });
 
@@ -279,7 +292,7 @@ describe("zod-to-ts", () => {
         name: z.string().describe("The name of the item"),
         price: z.number().describe("The price of the item"),
       });
-      const node = zodToTs({ schema });
+      const node = zodToTs({ schema, ...defaultCtx });
       expect(printNodeTest(node)).toMatchSnapshot();
     });
   });
@@ -297,7 +310,7 @@ describe("zod-to-ts", () => {
       unknown: z.unknown(),
       never: z.never(),
     });
-    const node = zodToTs({ schema: primitiveSchema });
+    const node = zodToTs({ schema: primitiveSchema, ...defaultCtx });
 
     it("outputs correct typescript", () => {
       expect(printNodeTest(node)).toMatchSnapshot();
@@ -310,7 +323,7 @@ describe("zod-to-ts", () => {
       z.object({ kind: z.literal("square"), x: z.number() }),
       z.object({ kind: z.literal("triangle"), x: z.number(), y: z.number() }),
     ]);
-    const node = zodToTs({ schema: shapeSchema });
+    const node = zodToTs({ schema: shapeSchema, ...defaultCtx });
 
     it("outputs correct typescript", () => {
       expect(printNodeTest(node)).toMatchSnapshot();
@@ -324,7 +337,9 @@ describe("zod-to-ts", () => {
       z.literal(false),
       z.literal(123),
     ])("Should produce the correct typescript %#", (schema) => {
-      expect(printNodeTest(zodToTs({ schema }))).toMatchSnapshot();
+      expect(
+        printNodeTest(zodToTs({ schema, ...defaultCtx }))
+      ).toMatchSnapshot();
     });
   });
 
@@ -336,14 +351,14 @@ describe("zod-to-ts", () => {
       ])("should produce the schema type $expected", ({ isResponse }) => {
         const schema = z.number().transform((num) => `${num}`);
         expect(
-          printNodeTest(zodToTs({ schema, isResponse }))
+          printNodeTest(zodToTs({ schema, ...defaultCtx, isResponse }))
         ).toMatchSnapshot();
       });
 
       test("should handle unsupported transformation in response", () => {
         const schema = z.number().transform((num) => () => num);
         expect(
-          printNodeTest(zodToTs({ schema, isResponse: true }))
+          printNodeTest(zodToTs({ schema, ...defaultCtx, isResponse: true }))
         ).toMatchSnapshot();
       });
 
@@ -352,7 +367,7 @@ describe("zod-to-ts", () => {
           throw new Error("this should be handled");
         });
         expect(
-          printNodeTest(zodToTs({ schema, isResponse: true }))
+          printNodeTest(zodToTs({ schema, ...defaultCtx, isResponse: true }))
         ).toMatchSnapshot();
       });
     });