Skip to content

Commit

Permalink
feat(zui): convert references to type arguments (#387)
Browse files Browse the repository at this point in the history
  • Loading branch information
franklevasseur authored Oct 1, 2024
1 parent e80a80b commit ee04385
Show file tree
Hide file tree
Showing 4 changed files with 198 additions and 77 deletions.
82 changes: 66 additions & 16 deletions zui/src/transforms/zui-to-typescript-next/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, it, expect } from 'vitest'
import { UntitledDeclarationError, toTypescript as toTs } from '.'
import { UnrepresentableGenericError, UntitledDeclarationError, toTypescript as toTs } from '.'
import z, { ZodType } from '../../z'

const toTypescript = (schema: ZodType): string => {
Expand All @@ -20,6 +20,24 @@ describe.concurrent('functions', () => {
expect(() => toTs(fn, { declaration: true })).toThrowError(UntitledDeclarationError)
})

it('type delcaration works', async () => {
const fn = z
.function()
.args(z.object({ a: z.number(), b: z.number() }))
.returns(z.number())
.title('add')
.describe('Add two numbers together.\nThis is a multiline description')

const typings = toTs(fn, { declaration: 'type' })
await expect(typings).toMatchWithoutFormatting(`
/**
* Add two numbers together.
* This is a multiline description
*/
type add = (arg0: { a: number; b: number }) => number;
`)
})

it('function with multi-line description', async () => {
const fn = z
.function()
Expand All @@ -35,7 +53,7 @@ describe.concurrent('functions', () => {
* Add two numbers together.
* This is a multiline description
*/
declare function add(arg0: { a: number; b: number }): number;
declare const add: (arg0: { a: number; b: number }) => number;
`)
})

Expand All @@ -44,15 +62,15 @@ describe.concurrent('functions', () => {

const typings = toTypescript(fn)

await expect(typings).toMatchWithoutFormatting('declare function fn(): unknown;')
await expect(typings).toMatchWithoutFormatting('declare const fn: () => unknown;')
})

it('function with no args and void return', async () => {
const fn = z.function().title('fn').returns(z.void())

const typings = toTypescript(fn)

await expect(typings).toMatchWithoutFormatting('declare function fn(): void;')
await expect(typings).toMatchWithoutFormatting('declare const fn: () => void;')
})

it('async function returning union', async () => {
Expand All @@ -63,7 +81,7 @@ describe.concurrent('functions', () => {

const typings = toTypescript(fn)

await expect(typings).toMatchWithoutFormatting('declare function fn(): Promise<number | string>;')
await expect(typings).toMatchWithoutFormatting('declare const fn: () => Promise<number | string>;')
})

it('function with multiple args', async () => {
Expand All @@ -82,7 +100,7 @@ describe.concurrent('functions', () => {
const typings = toTypescript(fn)

await expect(typings).toMatchWithoutFormatting(`
declare function fn(
declare const fn: (
arg0: {
a?: number;
/** This is B parameter */
Expand All @@ -91,14 +109,14 @@ describe.concurrent('functions', () => {
/** This is a number */
arg1: number,
arg2: [string, /** This is a number */ number]
): unknown;
) => unknown;
`)
})

it('function with optional args', async () => {
const fn = z.function().title('fn').args(z.string().optional())
const typings = toTypescript(fn)
await expect(typings).toMatchWithoutFormatting('declare function fn(arg0?: string): unknown;')
await expect(typings).toMatchWithoutFormatting('declare const fn: (arg0?: string) => unknown;')
})

it('string literals', async () => {
Expand Down Expand Up @@ -162,7 +180,7 @@ describe.concurrent('functions', () => {
it('function with named args', async () => {
const fn = z.function().title('fn').args(z.string().title('firstName').optional())
const typings = toTypescript(fn)
await expect(typings).toMatchWithoutFormatting('declare function fn(firstName?: string): unknown;')
await expect(typings).toMatchWithoutFormatting('declare const fn: (firstName?: string) => unknown;')
})

it('mix of named and unnammed params', async () => {
Expand All @@ -172,11 +190,11 @@ describe.concurrent('functions', () => {
.args(z.string().title('firstName').optional(), z.number(), z.object({ a: z.string() }).title('obj'))
const typings = toTypescript(fn)
await expect(typings).toMatchWithoutFormatting(`
declare function fn(
declare const fn: (
firstName?: string,
arg1: number,
obj: { a: string }
): unknown;
) => unknown;
`)
})

Expand All @@ -189,10 +207,10 @@ describe.concurrent('functions', () => {

const typings = toTypescript(fn)
await expect(typings).toMatchWithoutFormatting(`
declare function fn(
declare const fn: (
arg0?: string | null,
arg1?: number
): string | null | undefined;
) => string | null | undefined;
`)
})
})
Expand All @@ -203,6 +221,14 @@ describe.concurrent('objects', () => {
expect(() => toTs(obj, { declaration: true })).toThrowError()
})

it('type declaration works', async () => {
const obj = z.object({ a: z.number(), b: z.string() }).title('MyObject')

const typings = toTs(obj, { declaration: 'type' })

await expect(typings).toMatchWithoutFormatting('type MyObject = { a: number; b: string };')
})

it('normal object', async () => {
const obj = z.object({ a: z.number(), b: z.string() }).title('MyObject')

Expand Down Expand Up @@ -461,7 +487,7 @@ describe.concurrent('objects', () => {
const typings = toTypescript(fn)

await expect(typings).toMatchWithoutFormatting(
'declare function MyObject(arg0: Array<{ a: number; b: string }>): unknown;',
'declare const MyObject: (arg0: Array<{ a: number; b: string }>) => unknown;',
)
})

Expand All @@ -470,10 +496,10 @@ describe.concurrent('objects', () => {
const typings = toTypescript(fn)

await expect(typings).toMatchWithoutFormatting(`
declare function MyObject(
declare const MyObject: (
/** This is an array of numbers */
arg0: number[]
): unknown;
) => unknown;
`)
})

Expand Down Expand Up @@ -549,3 +575,27 @@ describe.concurrent('objects', () => {
await expect(typings).toMatchWithoutFormatting(expected)
})
})

describe.concurrent('generics', () => {
it("can't generate a generic type without type declaration", async () => {
const schema = z.object({ a: z.string(), b: z.ref('T') }).title('MyObject')
expect(() => toTs(schema, { declaration: true })).toThrowError(UnrepresentableGenericError)
expect(() => toTs(schema, { declaration: false })).toThrowError(UnrepresentableGenericError)
expect(() => toTs(schema, { declaration: 'variable' })).toThrowError(UnrepresentableGenericError)
expect(() => toTs(schema, { declaration: 'none' })).toThrowError(UnrepresentableGenericError)
})

it('can generate a generic type', async () => {
const schema = z.object({ a: z.string(), b: z.ref('T') }).title('MyObject')
const typings = toTs(schema, { declaration: 'type' })

await expect(typings).toMatchWithoutFormatting('type MyObject<T> = { a: string; b: T };')
})

it('can generate a generic type by formating ref Uri', async () => {
const schema = z.object({ a: z.string(), b: z.ref('#/$defs/T') }).title('MyObject')
const typings = toTs(schema, { declaration: 'type' })

await expect(typings).toMatchWithoutFormatting('type MyObject<DefsT> = { a: string; b: DefsT };')
})
})
Loading

0 comments on commit ee04385

Please sign in to comment.