typescript-runtime-schemas
Version:
A TypeScript schema generation tool that extracts Zod schemas from TypeScript source files with runtime validation support. Generate validation schemas directly from your existing TypeScript types with support for computed types and constraint-based valid
729 lines (720 loc) • 36.5 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
const type_resolver_1 = require("./type-resolver");
const type_resolver_factory_1 = require("./type-resolver-factory");
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
// Create test files for testing
const testDir = path.join(__dirname, "../test-files");
beforeAll(async () => {
// Create test directory
await fs.promises.mkdir(testDir, { recursive: true });
// Create test files
await fs.promises.writeFile(path.join(testDir, "types.ts"), `
export interface User {
id: number;
name: string;
email: string;
age?: number;
}
export type Role = 'admin' | 'user' | 'guest';
`);
await fs.promises.writeFile(path.join(testDir, "models.ts"), `
import { User, Role } from './types';
export type UserWithRole = User & {
role: Role;
};
export type AdminUser = Pick<UserWithRole, 'id' | 'name'> & {
role: 'admin';
};
export type UserProfile = Pick<User, 'name' | 'email'>;
export type PartialUser = Partial<User>;
`);
await fs.promises.writeFile(path.join(testDir, "utils.ts"), `
export type StringMap = Record<string, string>;
export type NumberArray = Array<number>;
`);
// Create a subdirectory with more files
const subDir = path.join(testDir, "nested");
await fs.promises.mkdir(subDir, { recursive: true });
await fs.promises.writeFile(path.join(subDir, "nested-types.ts"), `
export type NestedType = {
value: string;
count: number;
};
`);
});
afterAll(async () => {
// Clean up test files
await fs.promises.rm(testDir, { recursive: true, force: true });
});
describe("TypeResolver", () => {
describe("Basic Utility Types", () => {
const basicSource = `
type User = { id: number; name: string; email: string; age?: number; }
type UserProfile = Pick<User, 'name' | 'email'>
type UserWithoutEmail = Omit<User, 'email'>
type PartialUser = Partial<User>
type RequiredUser = Required<User>
type ReadonlyUser = Readonly<User>
`;
let resolver;
beforeEach(() => {
resolver = new type_resolver_1.TypeResolver(basicSource);
});
test("should resolve Pick utility type", () => {
const result = resolver.getResolvedType("UserProfile");
expect(result).toBe("{ name: string; email: string }");
});
test("should resolve Omit utility type", () => {
const result = resolver.getResolvedType("UserWithoutEmail");
expect(result).toBe("{ id: number; name: string; age?: number }");
});
test("should resolve Partial utility type", () => {
const result = resolver.getResolvedType("PartialUser");
expect(result).toBe("{ id?: number; name?: string; email?: string; age?: number }");
});
test("should resolve Required utility type", () => {
const result = resolver.getResolvedType("RequiredUser");
expect(result).toBe("{ id: number; name: string; email: string; age: number }");
});
test("should resolve Readonly utility type", () => {
const result = resolver.getResolvedType("ReadonlyUser");
expect(result).toBe("{ readonly id: number; readonly name: string; readonly email: string; readonly age?: number }");
});
test("should resolve base type without utility", () => {
const result = resolver.getResolvedType("User");
expect(result).toBe("{ id: number; name: string; email: string; age?: number }");
});
});
describe("Record and Union Types", () => {
const recordSource = `
type Role = 'admin' | 'user' | 'guest'
type User = { id: number; name: string; }
type RolePermissions = Record<Role, boolean>
type UsersByRole = Record<Role, User>
type StringMap = Record<string, number>
`;
let resolver;
beforeEach(() => {
resolver = new type_resolver_1.TypeResolver(recordSource);
});
test("should resolve Record with union keys", () => {
const result = resolver.getResolvedType("RolePermissions");
expect(result).toBe("{ [key: 'admin' | 'user' | 'guest']: boolean }");
});
test("should resolve Record with complex value type", () => {
const result = resolver.getResolvedType("UsersByRole");
expect(result).toBe("{ [key: 'admin' | 'user' | 'guest']: { id: number; name: string } }");
});
test("should resolve Record with string index", () => {
const result = resolver.getResolvedType("StringMap");
expect(result).toBe("{ [key: string]: number }");
});
});
describe("Union Manipulation Types", () => {
const unionSource = `
type Status = 'active' | 'inactive' | 'pending' | null | undefined
type StringOrNumber = string | number | null
type NonNullableStatus = NonNullable<Status>
type NonNullableStringOrNumber = NonNullable<StringOrNumber>
type ActiveStatuses = Exclude<Status, null | undefined>
type NullishStatuses = Extract<Status, null | undefined>
type ExcludeString = Exclude<StringOrNumber, string>
type ExtractString = Extract<StringOrNumber, string>
`;
let resolver;
beforeEach(() => {
resolver = new type_resolver_1.TypeResolver(unionSource);
});
test("should resolve NonNullable utility type", () => {
const result = resolver.getResolvedType("NonNullableStatus");
expect(result).toBe("'active' | 'inactive' | 'pending'");
});
test("should resolve NonNullable with mixed types", () => {
const result = resolver.getResolvedType("NonNullableStringOrNumber");
expect(result).toBe("string | number");
});
test("should resolve Exclude utility type", () => {
const result = resolver.getResolvedType("ActiveStatuses");
expect(result).toBe("'active' | 'inactive' | 'pending'");
});
test("should resolve Extract utility type", () => {
const result = resolver.getResolvedType("NullishStatuses");
expect(result).toBe("null | undefined");
});
test("should resolve Exclude with single type", () => {
const result = resolver.getResolvedType("ExcludeString");
expect(result).toBe("number | null");
});
test("should resolve Extract with single type", () => {
const result = resolver.getResolvedType("ExtractString");
expect(result).toBe("string");
});
});
describe("Function Types", () => {
const functionSource = `
type SimpleFunction = () => string
type ComplexFunction = (a: string, b: number) => boolean
type AsyncFunction = (id: number) => Promise<User>
type User = { id: number; name: string; }
type SimpleFunctionReturn = ReturnType<SimpleFunction>
type ComplexFunctionReturn = ReturnType<ComplexFunction>
type AsyncFunctionReturn = ReturnType<AsyncFunction>
type SimpleFunctionParams = Parameters<SimpleFunction>
type ComplexFunctionParams = Parameters<ComplexFunction>
type AsyncFunctionParams = Parameters<AsyncFunction>
`;
let resolver;
beforeEach(() => {
resolver = new type_resolver_1.TypeResolver(functionSource);
});
test("should resolve ReturnType for simple function", () => {
const result = resolver.getResolvedType("SimpleFunctionReturn");
expect(result).toBe("string");
});
test("should resolve ReturnType for complex function", () => {
const result = resolver.getResolvedType("ComplexFunctionReturn");
expect(result).toBe("boolean");
});
test("should resolve ReturnType for async function", () => {
const result = resolver.getResolvedType("AsyncFunctionReturn");
expect(result).toBe("Promise<User>");
});
test("should resolve Parameters for simple function", () => {
const result = resolver.getResolvedType("SimpleFunctionParams");
expect(result).toBe("[]");
});
test("should resolve Parameters for complex function", () => {
const result = resolver.getResolvedType("ComplexFunctionParams");
expect(result).toBe("[string, number]");
});
test("should resolve Parameters for async function", () => {
const result = resolver.getResolvedType("AsyncFunctionParams");
expect(result).toBe("[number]");
});
});
describe("Constructor Types", () => {
const constructorSource = `
type User = { id: number; name: string; }
type UserConstructor = new (name: string, email: string) => User
type SimpleConstructor = new () => string
type UserConstructorParams = ConstructorParameters<UserConstructor>
type SimpleConstructorParams = ConstructorParameters<SimpleConstructor>
type UserInstance = InstanceType<UserConstructor>
type SimpleInstance = InstanceType<SimpleConstructor>
`;
let resolver;
beforeEach(() => {
resolver = new type_resolver_1.TypeResolver(constructorSource);
});
test("should resolve ConstructorParameters with parameters", () => {
const result = resolver.getResolvedType("UserConstructorParams");
expect(result).toBe("[string, string]");
});
test("should resolve ConstructorParameters without parameters", () => {
const result = resolver.getResolvedType("SimpleConstructorParams");
expect(result).toBe("[]");
});
test("should resolve InstanceType", () => {
const result = resolver.getResolvedType("UserInstance");
expect(result).toContain("InstanceType<");
});
});
describe("Generic Types", () => {
const genericSource = `
type Container<T> = { value: T; label: string }
type ApiResponse<T, E = Error> = { data: T; error?: E; status: number }
type User = { id: number; name: string; }
type StringContainer = Container<string>
type NumberContainer = Container<number>
type UserContainer = Container<User>
type UserResponse = ApiResponse<User>
type UserListResponse = ApiResponse<User[]>
`;
let resolver;
beforeEach(() => {
resolver = new type_resolver_1.TypeResolver(genericSource);
});
test("should resolve simple generic type", () => {
const result = resolver.getResolvedType("StringContainer");
expect(result).toBe("{ value: string; label: string }");
});
test("should resolve generic type with number", () => {
const result = resolver.getResolvedType("NumberContainer");
expect(result).toBe("{ value: number; label: string }");
});
test("should resolve generic type with complex type", () => {
const result = resolver.getResolvedType("UserContainer");
expect(result).toBe("{ value: { id: number; name: string }; label: string }");
});
test("should resolve generic type with default parameter", () => {
const result = resolver.getResolvedType("UserResponse");
expect(result).toBe("{ data: { id: number; name: string }; error?: E; status: number }");
});
test("should resolve generic type with array parameter", () => {
const result = resolver.getResolvedType("UserListResponse");
expect(result).toBe("{ data: User[]; error?: E; status: number }");
});
});
describe("Array and Promise Types", () => {
const arrayPromiseSource = `
type User = { id: number; name: string; }
type UserArray = Array<User>
type StringArray = Array<string>
type UserPromise = Promise<User>
type StringPromise = Promise<string>
type NestedPromise = Promise<Promise<User>>
`;
let resolver;
beforeEach(() => {
resolver = new type_resolver_1.TypeResolver(arrayPromiseSource);
});
test("should resolve Array type with complex element", () => {
const result = resolver.getResolvedType("UserArray");
expect(result).toBe("{ id: number; name: string }[]");
});
test("should resolve Array type with primitive element", () => {
const result = resolver.getResolvedType("StringArray");
expect(result).toBe("string[]");
});
test("should resolve Promise type with complex value", () => {
const result = resolver.getResolvedType("UserPromise");
expect(result).toBe("Promise<{ id: number; name: string }>");
});
test("should resolve Promise type with primitive value", () => {
const result = resolver.getResolvedType("StringPromise");
expect(result).toBe("Promise<string>");
});
test("should resolve nested Promise type", () => {
const result = resolver.getResolvedType("NestedPromise");
expect(result).toBe("Promise<Promise<{ id: number; name: string }>>");
});
});
describe("Complex Nested Types", () => {
const complexSource = `
type Address = { street: string; city: string; country: string; zipCode?: string; }
type Person = { id: number; name: string; email: string; address: Address; roles: string[]; }
type PersonSummary = Pick<Person, 'name' | 'email'>
type PersonWithoutAddress = Omit<Person, 'address'>
type PartialPerson = Partial<Person>
type ReadonlyPerson = Readonly<Person>
type RequiredAddress = Required<Address>
type PersonRecord = Record<'primary' | 'secondary', Person>
`;
let resolver;
beforeEach(() => {
resolver = new type_resolver_1.TypeResolver(complexSource);
});
test("should resolve Pick with nested types", () => {
const result = resolver.getResolvedType("PersonSummary");
expect(result).toBe("{ name: string; email: string }");
});
test("should resolve Omit preserving other complex properties", () => {
const result = resolver.getResolvedType("PersonWithoutAddress");
expect(result).toBe("{ id: number; name: string; email: string; roles: string[] }");
});
test("should resolve Partial with nested objects", () => {
const result = resolver.getResolvedType("PartialPerson");
expect(result).toBe("{ id?: number; name?: string; email?: string; address?: { street: string; city: string; country: string; zipCode?: string }; roles?: string[] }");
});
test("should resolve Readonly with nested objects", () => {
const result = resolver.getResolvedType("ReadonlyPerson");
expect(result).toBe("{ readonly id: number; readonly name: string; readonly email: string; readonly address: { street: string; city: string; country: string; zipCode?: string }; readonly roles: string[] }");
});
test("should resolve Required with optional properties", () => {
const result = resolver.getResolvedType("RequiredAddress");
expect(result).toBe("{ street: string; city: string; country: string; zipCode: string }");
});
test("should resolve Record with complex nested types", () => {
const result = resolver.getResolvedType("PersonRecord");
expect(result).toContain("primary:");
expect(result).toContain("secondary:");
expect(result).toContain("{ id: number; name: string; email: string; address: { street: string; city: string; country: string; zipCode?: string }; roles: string[] }");
});
});
describe("Error Handling", () => {
const validSource = `
type User = { id: number; name: string; }
type UserProfile = Pick<User, 'name'>
`;
let resolver;
beforeEach(() => {
resolver = new type_resolver_1.TypeResolver(validSource);
});
test("should throw error for non-existent type", () => {
expect(() => {
resolver.getResolvedType("NonExistentType");
}).toThrow("Type NonExistentType not found");
});
test("should handle empty source code", () => {
const emptyResolver = new type_resolver_1.TypeResolver("");
expect(() => {
emptyResolver.getResolvedType("AnyType");
}).toThrow("Type AnyType not found");
});
test("should handle malformed utility types gracefully", () => {
const malformedSource = `
type User = { id: number; name: string; }
type BadPick = Pick<User>
`;
const malformedResolver = new type_resolver_1.TypeResolver(malformedSource);
const result = malformedResolver.getResolvedType("BadPick");
// Should return something reasonable rather than crashing
expect(result).toBeDefined();
});
});
describe("Convenience Functions", () => {
const testSource = `
type User = { id: number; name: string; email: string; }
type UserProfile = Pick<User, 'name' | 'email'>
type PartialUser = Partial<User>
`;
test("resolveTypesFromSource should resolve all types", () => {
const result = (0, type_resolver_1.resolveTypesFromSource)(testSource);
expect(result).toContain("type User =");
expect(result).toContain("type UserProfile =");
expect(result).toContain("type PartialUser =");
expect(result).toContain("{ name: string; email: string }");
});
test("getResolvedType should resolve specific type", () => {
const result = (0, type_resolver_1.getResolvedType)(testSource, "UserProfile");
expect(result).toBe("{ name: string; email: string }");
});
test("getResolvedType should throw for non-existent type", () => {
expect(() => {
(0, type_resolver_1.getResolvedType)(testSource, "NonExistent");
}).toThrow("Type NonExistent not found");
});
});
describe("Edge Cases", () => {
test("should handle types with special characters in names", () => {
const specialSource = `
type User = { id: number; name: string; }
type User_Profile = Pick<User, 'name'>
`;
const resolver = new type_resolver_1.TypeResolver(specialSource);
const result = resolver.getResolvedType("User_Profile");
expect(result).toBe("{ name: string }");
});
test("should handle deeply nested utility types", () => {
const nestedSource = `
type User = { id: number; name: string; email: string; age?: number; }
type Step1 = Pick<User, 'name' | 'email' | 'age'>
type Step2 = Required<Step1>
type Step3 = Readonly<Step2>
`;
const resolver = new type_resolver_1.TypeResolver(nestedSource);
const step1Result = resolver.getResolvedType("Step1");
expect(step1Result).toBe("{ name: string; email: string; age?: number }");
// For deeply nested utility types, the resolver may not fully resolve them
// but should handle them gracefully without crashing
const step3Result = resolver.getResolvedType("Step3");
expect(step3Result).toBeDefined();
expect(step3Result).toContain("Readonly<");
});
test("should handle union types in Pick/Omit keys", () => {
const unionKeysSource = `
type User = { id: number; name: string; email: string; phone: string; }
type ContactInfo = 'email' | 'phone'
type UserContact = Pick<User, ContactInfo>
`;
const resolver = new type_resolver_1.TypeResolver(unionKeysSource);
const result = resolver.getResolvedType("UserContact");
// This is a complex case that might not resolve perfectly, but shouldn't crash
expect(result).toBeDefined();
});
});
describe("Predicate Filtering", () => {
const predicateTestSource = `
type User = { id: number; name: string; email: string; age?: number; }
type UserProfile = Pick<User, 'name' | 'email'>
type UserWithoutEmail = Omit<User, 'email'>
type PartialUser = Partial<User>
type RequiredUser = Required<User>
type ReadonlyUser = Readonly<User>
type SimpleType = string
type ComplexType = Record<string, User>
`;
let resolver;
beforeEach(() => {
resolver = new type_resolver_1.TypeResolver(predicateTestSource);
});
test("should resolve all types when no predicate is provided", () => {
const result = resolver.resolveTypes();
expect(result).toContain("type User =");
expect(result).toContain("type UserProfile = { name: string; email: string }");
expect(result).toContain("type UserWithoutEmail = { id: number; name: string; age?: number }");
expect(result).toContain("type PartialUser =");
expect(result).toContain("type RequiredUser =");
expect(result).toContain("type ReadonlyUser =");
expect(result).toContain("type SimpleType = string");
expect(result).toContain("type ComplexType =");
});
test("should resolve all types when predicate always returns true", () => {
const result = resolver.resolveTypes(() => true);
expect(result).toContain("type User =");
expect(result).toContain("type UserProfile = { name: string; email: string }");
expect(result).toContain("type UserWithoutEmail = { id: number; name: string; age?: number }");
expect(result).toContain("type PartialUser =");
expect(result).toContain("type RequiredUser =");
expect(result).toContain("type ReadonlyUser =");
expect(result).toContain("type SimpleType = string");
expect(result).toContain("type ComplexType =");
});
test("should return original types when predicate always returns false", () => {
const result = resolver.resolveTypes(() => false);
// When predicate returns false, types should remain unresolved
expect(result).toContain("Pick<User, 'name' | 'email'>");
expect(result).toContain("Omit<User, 'email'>");
expect(result).toContain("Partial<User>");
expect(result).toContain("Required<User>");
expect(result).toContain("Readonly<User>");
});
test("should filter types based on name pattern", () => {
// Only resolve types containing "Profile" in the name
const result = resolver.resolveTypes((name) => name.includes("Profile"));
// UserProfile should be resolved
expect(result).toContain("{ name: string; email: string }");
// Other types should remain unresolved
expect(result).toContain("Partial<User>");
expect(result).toContain("Required<User>");
});
test("should filter types based on utility type usage", () => {
// Only resolve Pick and Omit utility types
const result = resolver.resolveTypes((name, typeAlias) => {
const typeText = typeAlias.getText();
return typeText.includes("Pick<") || typeText.includes("Omit<");
});
// Pick and Omit types should be resolved
expect(result).toContain("{ name: string; email: string }");
expect(result).toContain("{ id: number; name: string; age?: number }");
// Other utility types should remain unresolved
expect(result).toContain("Partial<User>");
expect(result).toContain("Required<User>");
expect(result).toContain("Readonly<User>");
});
test("should filter types by name prefix", () => {
// Only resolve types starting with "User" but not "UserProfile"
const result = resolver.resolveTypes((name) => name.startsWith("User") && name !== "UserProfile");
// UserProfile should remain unresolved
expect(result).toContain("Pick<User, 'name' | 'email'>");
// Other User types should be resolved
expect(result).toContain("{ id: number; name: string; age?: number }");
});
test("should handle mixed resolution correctly", () => {
// Resolve only Partial and Required types
const result = resolver.resolveTypes((name, typeAlias) => {
const typeText = typeAlias.getText();
return typeText.includes("Partial<") || typeText.includes("Required<");
});
expect(result).toContain("type PartialUser = { id?: number; name?: string; email?: string; age?: number }");
expect(result).toContain("type RequiredUser = { id: number; name: string; email: string; age: number }");
expect(result).toContain("type User = { id: number; name: string; email: string; age?: number; }");
expect(result).toContain("type UserProfile = Pick<User, 'name' | 'email'>");
expect(result).toContain("type UserWithoutEmail = Omit<User, 'email'>");
expect(result).toContain("type ReadonlyUser = Readonly<User>");
expect(result).toContain("type SimpleType = string");
expect(result).toContain("type ComplexType = Record<string, User>");
});
});
describe("TypeResolverFactory", () => {
test("should create resolver from source code", async () => {
const sourceCode = `
type User = { id: number; name: string; }
type UserProfile = Pick<User, 'name'>
`;
const resolver = await type_resolver_factory_1.TypeResolverFactory.create(sourceCode);
const result = resolver.getResolvedType("UserProfile");
expect(result).toBe("{ name: string }");
});
test("should create resolver from single file", async () => {
const filePath = path.join(testDir, "types.ts");
const resolver = await type_resolver_factory_1.TypeResolverFactory.create(filePath);
const userType = resolver.getResolvedType("User");
expect(userType).toContain("id: number");
expect(userType).toContain("name: string");
expect(userType).toContain("email: string");
expect(userType).toContain("age?: number");
});
test("should create resolver from directory", async () => {
const resolver = await type_resolver_factory_1.TypeResolverFactory.create(testDir);
// Should be able to resolve types from multiple files
const userProfile = resolver.getResolvedType("UserProfile");
expect(userProfile).toBe("{ name: string; email: string }");
const adminUser = resolver.getResolvedType("AdminUser");
expect(adminUser).toContain("id: number");
expect(adminUser).toContain("name: string");
expect(adminUser).toContain("role: 'admin'");
});
test("should create resolver with glob patterns", async () => {
const resolver = await type_resolver_factory_1.TypeResolverFactory.create(testDir, {
glob: "**/*.ts",
exclude: "**/utils.ts",
});
// Should resolve types from included files
const userProfile = resolver.getResolvedType("UserProfile");
expect(userProfile).toBe("{ name: string; email: string }");
// Should not find types from excluded files
expect(() => {
resolver.getResolvedType("StringMap");
}).toThrow("Type StringMap not found");
});
test("should handle recursive directory discovery", async () => {
const resolver = await type_resolver_factory_1.TypeResolverFactory.create(testDir, {
recursive: true,
});
// Should find types in nested directories
const nestedType = resolver.getResolvedType("NestedType");
expect(nestedType).toContain("value: string");
expect(nestedType).toContain("count: number");
});
test("should handle non-recursive directory discovery", async () => {
const resolver = await type_resolver_factory_1.TypeResolverFactory.create(testDir, {
recursive: false,
});
// Should find types in root directory
const userProfile = resolver.getResolvedType("UserProfile");
expect(userProfile).toBe("{ name: string; email: string }");
// Should not find types in nested directories
expect(() => {
resolver.getResolvedType("NestedType");
}).toThrow("Type NestedType not found");
});
});
describe("Enhanced Convenience Functions", () => {
test("resolveTypesFromInput should work with source code", async () => {
const sourceCode = `
type User = { id: number; name: string; }
type UserProfile = Pick<User, 'name'>
`;
const result = await (0, type_resolver_1.resolveTypesFromInput)(sourceCode);
expect(result).toContain("type User =");
expect(result).toContain("type UserProfile = { name: string }");
});
test("resolveTypesFromInput should work with file path", async () => {
const filePath = path.join(testDir, "models.ts");
const result = await (0, type_resolver_1.resolveTypesFromInput)(filePath);
expect(result).toContain("type UserProfile = { name: string; email: string }");
expect(result).toContain("type PartialUser =");
});
test("resolveTypesFromInput should work with directory", async () => {
const result = await (0, type_resolver_1.resolveTypesFromInput)(testDir);
expect(result).toContain("type UserProfile = { name: string; email: string }");
expect(result).toContain("type AdminUser =");
});
test("getResolvedTypeFromInput should work with various inputs", async () => {
// From source code
const sourceCode = `type User = { id: number; name: string; }`;
const fromSource = await (0, type_resolver_1.getResolvedTypeFromInput)(sourceCode, "User");
expect(fromSource).toBe("{ id: number; name: string }");
// From file
const filePath = path.join(testDir, "types.ts");
const fromFile = await (0, type_resolver_1.getResolvedTypeFromInput)(filePath, "User");
expect(fromFile).toContain("id: number");
// From directory
const fromDir = await (0, type_resolver_1.getResolvedTypeFromInput)(testDir, "UserProfile");
expect(fromDir).toBe("{ name: string; email: string }");
});
test("resolveTypesFromFile should work", async () => {
const filePath = path.join(testDir, "models.ts");
const result = await (0, type_resolver_1.resolveTypesFromFile)(filePath);
expect(result).toContain("type UserProfile = { name: string; email: string }");
});
test("resolveTypesFromDirectory should work", async () => {
const result = await (0, type_resolver_1.resolveTypesFromDirectory)(testDir);
expect(result).toContain("type UserProfile = { name: string; email: string }");
expect(result).toContain("type AdminUser =");
});
test("resolveTypesFromGlob should work", async () => {
const result = await (0, type_resolver_1.resolveTypesFromGlob)(testDir, "**/*.ts", [
"**/utils.ts",
]);
expect(result).toContain("type UserProfile = { name: string; email: string }");
expect(result).not.toContain("StringMap");
});
});
describe("Import Resolution", () => {
test("should resolve types with imports", async () => {
const resolver = await type_resolver_factory_1.TypeResolverFactory.create(testDir);
// UserWithRole imports User and Role from types.ts
const userWithRole = resolver.getResolvedType("UserWithRole");
expect(userWithRole).toContain("id: number");
expect(userWithRole).toContain("name: string");
expect(userWithRole).toContain("email: string");
expect(userWithRole).toContain("role:");
// AdminUser uses Pick on UserWithRole
const adminUser = resolver.getResolvedType("AdminUser");
expect(adminUser).toContain("id: number");
expect(adminUser).toContain("name: string");
expect(adminUser).toContain("role: 'admin'");
expect(adminUser).not.toContain("email"); // Should be omitted by Pick
});
});
describe("Enhanced Error Handling", () => {
test("should throw error for non-existent file", async () => {
await expect(type_resolver_factory_1.TypeResolverFactory.create("non-existent-file.ts")).rejects.toThrow();
});
test("should throw error for non-existent directory", async () => {
await expect(type_resolver_factory_1.TypeResolverFactory.create("non-existent-directory")).rejects.toThrow();
});
test("should throw error for directory with no TypeScript files", async () => {
const emptyDir = path.join(testDir, "empty");
await fs.promises.mkdir(emptyDir, { recursive: true });
await expect(type_resolver_factory_1.TypeResolverFactory.create(emptyDir)).rejects.toThrow("No TypeScript files found");
await fs.promises.rmdir(emptyDir);
});
test("should throw error for non-existent type", async () => {
const resolver = await type_resolver_factory_1.TypeResolverFactory.create(testDir);
expect(() => {
resolver.getResolvedType("NonExistentType");
}).toThrow("Type NonExistentType not found");
});
});
describe("Backward Compatibility", () => {
test("should maintain backward compatibility with original API", async () => {
const sourceCode = `
type User = { id: number; name: string; }
type UserProfile = Pick<User, 'name'>
`;
// Original factory method should still work
const resolver = type_resolver_factory_1.TypeResolverFactory.createFromSource(sourceCode);
const result = resolver.getResolvedType("UserProfile");
expect(result).toBe("{ name: string }");
});
});
});
//# sourceMappingURL=type-resolver.test.js.map