Skip to content

Commit ddd20a3

Browse files
committed
test: align optional property assertions with actual inferred types
TypeScript 6.0 tightened isTypeIdenticalTo under exactOptionalPropertyTypes so that {a?: T} and {a?: T | undefined} are no longer treated as identical (microsoft/TypeScript#61682). expect-type's toEqualTypeOf, which piggybacks on that internal check, now correctly distinguishes them. Zod's .optional()/.partial()/.pick()/.merge() and tuple optionals all emit {a?: T | undefined} (and tuple equivalents). The test assertions were written without `| undefined`; TS 5.x's lenient identity check let it slide. Make the assertions match what Zod actually emits so the suite passes on TS 5.5, 5.9, and 6.0. No source changes; only test assertions.
1 parent 45dd421 commit ddd20a3

9 files changed

Lines changed: 37 additions & 32 deletions

File tree

packages/zod/src/v3/tests/all-errors.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ type TestFormErrors = z.inferFlattenedErrors<typeof Test>;
1616
test("default flattened errors type inference", () => {
1717
type TestTypeErrors = {
1818
formErrors: string[];
19-
fieldErrors: { [P in keyof z.TypeOf<typeof Test>]?: string[] | undefined };
19+
fieldErrors: { [P in keyof z.TypeOf<typeof Test>]?: string[] };
2020
};
2121

2222
util.assertEqual<z.inferFlattenedErrors<typeof Test>, TestTypeErrors>(true);
@@ -28,7 +28,7 @@ test("custom flattened errors type inference", () => {
2828
type TestTypeErrors = {
2929
formErrors: ErrorType[];
3030
fieldErrors: {
31-
[P in keyof z.TypeOf<typeof Test>]?: ErrorType[] | undefined;
31+
[P in keyof z.TypeOf<typeof Test>]?: ErrorType[];
3232
};
3333
};
3434

@@ -40,7 +40,7 @@ test("custom flattened errors type inference", () => {
4040
test("form errors type inference", () => {
4141
type TestTypeErrors = {
4242
formErrors: string[];
43-
fieldErrors: { [P in keyof z.TypeOf<typeof Test>]?: string[] | undefined };
43+
fieldErrors: { [P in keyof z.TypeOf<typeof Test>]?: string[] };
4444
};
4545

4646
util.assertEqual<z.inferFlattenedErrors<typeof Test>, TestTypeErrors>(true);

packages/zod/src/v3/tests/object.test.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -212,9 +212,9 @@ test("inferred merged object type with optional properties", async () => {
212212
.object({ a: z.string(), b: z.string().optional() })
213213
.merge(z.object({ a: z.string().optional(), b: z.string() }));
214214
type Merged = z.infer<typeof Merged>;
215-
util.assertEqual<Merged, { a?: string; b: string }>(true);
215+
util.assertEqual<Merged, { a?: string | undefined; b: string }>(true);
216216
// todo
217-
// util.assertEqual<Merged, { a?: string; b: string }>(true);
217+
// util.assertEqual<Merged, { a?: string | undefined; b: string }>(true);
218218
});
219219

220220
test("inferred unioned object type with optional properties", async () => {
@@ -223,7 +223,7 @@ test("inferred unioned object type with optional properties", async () => {
223223
z.object({ a: z.string().optional(), b: z.string() }),
224224
]);
225225
type Unioned = z.infer<typeof Unioned>;
226-
util.assertEqual<Unioned, { a: string; b?: string } | { a?: string; b: string }>(true);
226+
util.assertEqual<Unioned, { a: string; b?: string | undefined } | { a?: string | undefined; b: string }>(true);
227227
});
228228

229229
test("inferred enum type", async () => {
@@ -245,13 +245,13 @@ test("inferred enum type", async () => {
245245
test("inferred partial object type with optional properties", async () => {
246246
const Partial = z.object({ a: z.string(), b: z.string().optional() }).partial();
247247
type Partial = z.infer<typeof Partial>;
248-
util.assertEqual<Partial, { a?: string; b?: string }>(true);
248+
util.assertEqual<Partial, { a?: string | undefined; b?: string | undefined }>(true);
249249
});
250250

251251
test("inferred picked object type with optional properties", async () => {
252252
const Picked = z.object({ a: z.string(), b: z.string().optional() }).pick({ b: true });
253253
type Picked = z.infer<typeof Picked>;
254-
util.assertEqual<Picked, { b?: string }>(true);
254+
util.assertEqual<Picked, { b?: string | undefined }>(true);
255255
});
256256

257257
test("inferred type for unknown/any keys", () => {

packages/zod/src/v3/tests/partials.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ test("shallow inference", () => {
2121
name?: string | undefined;
2222
age?: number | undefined;
2323
outer?: { inner: string } | undefined;
24-
array?: { asdf: string }[];
24+
array?: { asdf: string }[] | undefined;
2525
};
2626
util.assertEqual<shallow, correct>(true);
2727
});
@@ -41,7 +41,7 @@ test("deep partial inference", () => {
4141
asdf.parse("asdf");
4242
type deep = z.infer<typeof deep>;
4343
type correct = {
44-
array?: { asdf?: string }[];
44+
array?: { asdf?: string | undefined }[] | undefined;
4545
name?: string | undefined;
4646
age?: number | undefined;
4747
outer?: { inner?: string | undefined } | undefined;
@@ -118,7 +118,7 @@ test("deep partial inference", () => {
118118
asdf?: string | undefined;
119119
}[]
120120
| undefined;
121-
tuple?: [{ value?: string }] | undefined;
121+
tuple?: [{ value?: string | undefined }] | undefined;
122122
};
123123
util.assertEqual<expected, partialed>(true);
124124
});

packages/zod/src/v4/classic/tests/index.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ test("z.tuple", () => {
247247
const b = z.tuple([z.string(), z.number(), z.optional(z.string())], z.boolean());
248248
type b = z.output<typeof b>;
249249

250-
expectTypeOf<b>().toEqualTypeOf<[string, number, string?, ...boolean[]]>();
250+
expectTypeOf<b>().toEqualTypeOf<[string, number, (string | undefined)?, ...boolean[]]>();
251251
const datas = [
252252
["hello", 123],
253253
["hello", 123, "world"],
@@ -265,7 +265,7 @@ test("z.tuple", () => {
265265
const cArgs = [z.string(), z.number(), z.optional(z.string())] as const;
266266
const c = z.tuple(cArgs, z.boolean());
267267
type c = z.output<typeof c>;
268-
expectTypeOf<c>().toEqualTypeOf<[string, number, string?, ...boolean[]]>();
268+
expectTypeOf<c>().toEqualTypeOf<[string, number, (string | undefined)?, ...boolean[]]>();
269269
// type c = z.output<typeof c>;
270270
});
271271

packages/zod/src/v4/classic/tests/object.test.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -232,8 +232,7 @@ test("inferred merged object type with optional properties", async () => {
232232
.object({ a: z.string(), b: z.string().optional() })
233233
.merge(z.object({ a: z.string().optional(), b: z.string() }));
234234
type Merged = z.infer<typeof Merged>;
235-
expectTypeOf<Merged>().toEqualTypeOf<{ a?: string; b: string }>();
236-
expectTypeOf<Merged>().toEqualTypeOf<{ a?: string; b: string }>();
235+
expectTypeOf<Merged>().toEqualTypeOf<{ a?: string | undefined; b: string }>();
237236
});
238237

239238
test("inferred unioned object type with optional properties", async () => {
@@ -242,7 +241,9 @@ test("inferred unioned object type with optional properties", async () => {
242241
z.object({ a: z.string().optional(), b: z.string() }),
243242
]);
244243
type Unioned = z.infer<typeof Unioned>;
245-
expectTypeOf<Unioned>().toEqualTypeOf<{ a: string; b?: string } | { a?: string; b: string }>();
244+
expectTypeOf<Unioned>().toEqualTypeOf<
245+
{ a: string; b?: string | undefined } | { a?: string | undefined; b: string }
246+
>();
246247
});
247248

248249
test("inferred enum type", async () => {
@@ -279,13 +280,13 @@ test("z.keyof returns enum", () => {
279280
test("inferred partial object type with optional properties", async () => {
280281
const Partial = z.object({ a: z.string(), b: z.string().optional() }).partial();
281282
type Partial = z.infer<typeof Partial>;
282-
expectTypeOf<Partial>().toEqualTypeOf<{ a?: string; b?: string }>();
283+
expectTypeOf<Partial>().toEqualTypeOf<{ a?: string | undefined; b?: string | undefined }>();
283284
});
284285

285286
test("inferred picked object type with optional properties", async () => {
286287
const Picked = z.object({ a: z.string(), b: z.string().optional() }).pick({ b: true });
287288
type Picked = z.infer<typeof Picked>;
288-
expectTypeOf<Picked>().toEqualTypeOf<{ b?: string }>();
289+
expectTypeOf<Picked>().toEqualTypeOf<{ b?: string | undefined }>();
289290
});
290291

291292
test("inferred type for unknown/any keys", () => {

packages/zod/src/v4/classic/tests/tuple.test.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,9 @@ test("async validation", async () => {
107107

108108
test("tuple with optional elements", () => {
109109
const myTuple = z.tuple([z.string(), z.number().optional(), z.string().optional()]).rest(z.boolean());
110-
expectTypeOf<typeof myTuple._output>().toEqualTypeOf<[string, number?, string?, ...boolean[]]>();
110+
expectTypeOf<typeof myTuple._output>().toEqualTypeOf<
111+
[string, (number | undefined)?, (string | undefined)?, ...boolean[]]
112+
>();
111113

112114
const goodData = [["asdf"], ["asdf", 1234], ["asdf", 1234, "asdf"], ["asdf", 1234, "asdf", true, false, true]];
113115
for (const data of goodData) {
@@ -149,7 +151,9 @@ test("tuple with optional elements followed by required", () => {
149151

150152
test("tuple with all optional elements", () => {
151153
const allOptionalTuple = z.tuple([z.string().optional(), z.number().optional(), z.boolean().optional()]);
152-
expectTypeOf<typeof allOptionalTuple._output>().toEqualTypeOf<[string?, number?, boolean?]>();
154+
expectTypeOf<typeof allOptionalTuple._output>().toEqualTypeOf<
155+
[(string | undefined)?, (number | undefined)?, (boolean | undefined)?]
156+
>();
153157

154158
// Empty array should be valid (all items optional)
155159
expect(allOptionalTuple.parse([])).toEqual([]);

packages/zod/src/v4/mini/tests/index.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ test("z.tuple", () => {
247247
const b = z.tuple([z.string(), z.number(), z.optional(z.string())], z.boolean());
248248
type b = z.output<typeof b>;
249249

250-
expectTypeOf<b>().toEqualTypeOf<[string, number, string?, ...boolean[]]>();
250+
expectTypeOf<b>().toEqualTypeOf<[string, number, (string | undefined)?, ...boolean[]]>();
251251
const datas = [
252252
["hello", 123],
253253
["hello", 123, "world"],
@@ -265,7 +265,7 @@ test("z.tuple", () => {
265265
const cArgs = [z.string(), z.number(), z.optional(z.string())] as const;
266266
const c = z.tuple(cArgs, z.boolean());
267267
type c = z.output<typeof c>;
268-
expectTypeOf<c>().toEqualTypeOf<[string, number, string?, ...boolean[]]>();
268+
expectTypeOf<c>().toEqualTypeOf<[string, number, (string | undefined)?, ...boolean[]]>();
269269
});
270270

271271
test("z.record", () => {

packages/zod/src/v4/mini/tests/object.test.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ test("z.object", () => {
1717
expectTypeOf<a>().toEqualTypeOf<{
1818
name: string;
1919
age: number;
20-
points?: number;
20+
points?: number | undefined;
2121
"test?": boolean;
2222
}>();
2323
expect(z.parse(a, { name: "john", age: 30, "test?": true })).toEqual({
@@ -109,7 +109,7 @@ test("z.extend", () => {
109109
expectTypeOf<ExtendedUser>().toEqualTypeOf<{
110110
name: string;
111111
age: number;
112-
email?: string;
112+
email?: string | undefined;
113113
isAdmin: boolean;
114114
}>();
115115
expect(extendedSchema).toBeDefined();
@@ -120,15 +120,15 @@ test("z.safeExtend", () => {
120120
const extended = z.safeExtend(userSchema, { name: z.string() });
121121
expect(z.safeParse(extended, { name: "John", age: 30 }).success).toBe(true);
122122
type Extended = z.infer<typeof extended>;
123-
expectTypeOf<Extended>().toEqualTypeOf<{ name: string; age: number; email?: string }>();
123+
expectTypeOf<Extended>().toEqualTypeOf<{ name: string; age: number; email?: string | undefined }>();
124124
// @ts-expect-error
125125
z.safeExtend(userSchema, { name: z.number() });
126126
});
127127

128128
test("z.pick", () => {
129129
const pickedSchema = z.pick(userSchema, { name: true, email: true });
130130
type PickedUser = z.infer<typeof pickedSchema>;
131-
expectTypeOf<PickedUser>().toEqualTypeOf<{ name: string; email?: string }>();
131+
expectTypeOf<PickedUser>().toEqualTypeOf<{ name: string; email?: string | undefined }>();
132132
expect(pickedSchema).toBeDefined();
133133
expect(z.safeParse(pickedSchema, { name: "John", email: "[email protected]" }).success).toBe(true);
134134
});
@@ -149,9 +149,9 @@ test("z.partial", () => {
149149
const partialSchema = z.partial(userSchema);
150150
type PartialUser = z.infer<typeof partialSchema>;
151151
expectTypeOf<PartialUser>().toEqualTypeOf<{
152-
name?: string;
153-
age?: number;
154-
email?: string;
152+
name?: string | undefined;
153+
age?: number | undefined;
154+
email?: string | undefined;
155155
}>();
156156
expect(z.safeParse(partialSchema, { name: "John" }).success).toBe(true);
157157
});
@@ -160,9 +160,9 @@ test("z.partial with mask", () => {
160160
const partialSchemaWithMask = z.partial(userSchema, { name: true });
161161
type PartialUserWithMask = z.infer<typeof partialSchemaWithMask>;
162162
expectTypeOf<PartialUserWithMask>().toEqualTypeOf<{
163-
name?: string;
163+
name?: string | undefined;
164164
age: number;
165-
email?: string;
165+
email?: string | undefined;
166166
}>();
167167
expect(z.safeParse(partialSchemaWithMask, { age: 30 }).success).toBe(true);
168168
expect(z.safeParse(partialSchemaWithMask, { name: "John" }).success).toBe(false);

packages/zod/src/v4/mini/tests/recursive-types.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ test("recursion with z.lazy", () => {
3333
type Category = z.infer<typeof Category>;
3434
interface _Category {
3535
name: string;
36-
subcategories?: _Category[];
36+
subcategories?: _Category[] | undefined;
3737
}
3838
expectTypeOf<Category>().toEqualTypeOf<_Category>();
3939
});

0 commit comments

Comments
 (0)