@hy_ong/zod-kit 0.0.4 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/.claude/settings.local.json +28 -0
  2. package/LICENSE +21 -0
  3. package/README.md +465 -97
  4. package/debug.js +21 -0
  5. package/debug.ts +16 -0
  6. package/dist/index.cjs +3127 -146
  7. package/dist/index.d.cts +3021 -25
  8. package/dist/index.d.ts +3021 -25
  9. package/dist/index.js +3081 -144
  10. package/eslint.config.mts +8 -0
  11. package/package.json +10 -9
  12. package/src/config.ts +1 -1
  13. package/src/i18n/locales/en.json +161 -25
  14. package/src/i18n/locales/zh-TW.json +165 -26
  15. package/src/index.ts +17 -7
  16. package/src/validators/common/boolean.ts +191 -0
  17. package/src/validators/common/date.ts +299 -0
  18. package/src/validators/common/datetime.ts +673 -0
  19. package/src/validators/common/email.ts +313 -0
  20. package/src/validators/common/file.ts +384 -0
  21. package/src/validators/common/id.ts +471 -0
  22. package/src/validators/common/number.ts +319 -0
  23. package/src/validators/common/password.ts +386 -0
  24. package/src/validators/common/text.ts +271 -0
  25. package/src/validators/common/time.ts +600 -0
  26. package/src/validators/common/url.ts +347 -0
  27. package/src/validators/taiwan/business-id.ts +262 -0
  28. package/src/validators/taiwan/fax.ts +327 -0
  29. package/src/validators/taiwan/mobile.ts +242 -0
  30. package/src/validators/taiwan/national-id.ts +425 -0
  31. package/src/validators/taiwan/postal-code.ts +1049 -0
  32. package/src/validators/taiwan/tel.ts +330 -0
  33. package/tests/common/boolean.test.ts +340 -92
  34. package/tests/common/date.test.ts +458 -0
  35. package/tests/common/datetime.test.ts +693 -0
  36. package/tests/common/email.test.ts +232 -60
  37. package/tests/common/file.test.ts +479 -0
  38. package/tests/common/id.test.ts +535 -0
  39. package/tests/common/number.test.ts +230 -60
  40. package/tests/common/password.test.ts +271 -44
  41. package/tests/common/text.test.ts +210 -13
  42. package/tests/common/time.test.ts +528 -0
  43. package/tests/common/url.test.ts +492 -67
  44. package/tests/taiwan/business-id.test.ts +240 -0
  45. package/tests/taiwan/fax.test.ts +463 -0
  46. package/tests/taiwan/mobile.test.ts +373 -0
  47. package/tests/taiwan/national-id.test.ts +435 -0
  48. package/tests/taiwan/postal-code.test.ts +705 -0
  49. package/tests/taiwan/tel.test.ts +467 -0
  50. package/eslint.config.mjs +0 -10
  51. package/src/common/boolean.ts +0 -36
  52. package/src/common/date.ts +0 -43
  53. package/src/common/email.ts +0 -44
  54. package/src/common/integer.ts +0 -46
  55. package/src/common/number.ts +0 -37
  56. package/src/common/password.ts +0 -33
  57. package/src/common/text.ts +0 -34
  58. package/src/common/url.ts +0 -37
  59. package/tests/common/integer.test.ts +0 -90
@@ -0,0 +1,458 @@
1
+ import { describe, it, expect } from "vitest"
2
+ import { setLocale, date } from "../../src"
3
+
4
+ describe("date", () => {
5
+ describe("required (default)", () => {
6
+ it("should validate valid date string", () => {
7
+ const schema = date()
8
+ expect(schema.parse("2023-12-25")).toBe("2023-12-25")
9
+ })
10
+
11
+ it("should reject empty string", () => {
12
+ const schema = date()
13
+ expect(() => schema.parse("")).toThrow()
14
+ })
15
+
16
+ it("should reject null", () => {
17
+ const schema = date()
18
+ expect(() => schema.parse(null)).toThrow()
19
+ })
20
+
21
+ it("should reject undefined", () => {
22
+ const schema = date()
23
+ expect(() => schema.parse(undefined)).toThrow()
24
+ })
25
+
26
+ it("should reject invalid date format", () => {
27
+ const schema = date()
28
+ expect(() => schema.parse("invalid-date")).toThrow()
29
+ })
30
+
31
+ it("should reject date with wrong format", () => {
32
+ const schema = date()
33
+ expect(() => schema.parse("25/12/2023")).toThrow()
34
+ })
35
+ })
36
+
37
+ describe("optional", () => {
38
+ it("should allow null when not required", () => {
39
+ const schema = date({ required: false })
40
+ expect(schema.parse(null)).toBe(null)
41
+ })
42
+
43
+ it("should allow empty string when not required", () => {
44
+ const schema = date({ required: false })
45
+ expect(schema.parse("")).toBe(null)
46
+ })
47
+
48
+ it("should allow undefined when not required", () => {
49
+ const schema = date({ required: false })
50
+ expect(schema.parse(undefined)).toBe(null)
51
+ })
52
+
53
+ it("should validate valid date when not required", () => {
54
+ const schema = date({ required: false })
55
+ expect(schema.parse("2023-12-25")).toBe("2023-12-25")
56
+ })
57
+ })
58
+
59
+ describe("custom format", () => {
60
+ it("should validate date with DD/MM/YYYY format", () => {
61
+ const schema = date({ format: "DD/MM/YYYY" })
62
+ expect(schema.parse("25/12/2023")).toBe("25/12/2023")
63
+ })
64
+
65
+ it("should reject wrong format for custom format", () => {
66
+ const schema = date({ format: "DD/MM/YYYY" })
67
+ expect(() => schema.parse("2023-12-25")).toThrow()
68
+ })
69
+
70
+ it("should validate date with MM-DD-YYYY format", () => {
71
+ const schema = date({ format: "MM-DD-YYYY" })
72
+ expect(schema.parse("12-25-2023")).toBe("12-25-2023")
73
+ })
74
+
75
+ it("should validate date with YYYY/MM/DD format", () => {
76
+ const schema = date({ format: "YYYY/MM/DD" })
77
+ expect(schema.parse("2023/12/25")).toBe("2023/12/25")
78
+ })
79
+ })
80
+
81
+ describe("min validation", () => {
82
+ it("should accept date equal to min", () => {
83
+ const schema = date({ min: "2023-01-01" })
84
+ expect(schema.parse("2023-01-01")).toBe("2023-01-01")
85
+ })
86
+
87
+ it("should accept date after min", () => {
88
+ const schema = date({ min: "2023-01-01" })
89
+ expect(schema.parse("2023-12-31")).toBe("2023-12-31")
90
+ })
91
+
92
+ it("should reject date before min", () => {
93
+ const schema = date({ min: "2023-01-01" })
94
+ expect(() => schema.parse("2022-12-31")).toThrow()
95
+ })
96
+ })
97
+
98
+ describe("max validation", () => {
99
+ it("should accept date equal to max", () => {
100
+ const schema = date({ max: "2023-12-31" })
101
+ expect(schema.parse("2023-12-31")).toBe("2023-12-31")
102
+ })
103
+
104
+ it("should accept date before max", () => {
105
+ const schema = date({ max: "2023-12-31" })
106
+ expect(schema.parse("2023-01-01")).toBe("2023-01-01")
107
+ })
108
+
109
+ it("should reject date after max", () => {
110
+ const schema = date({ max: "2023-12-31" })
111
+ expect(() => schema.parse("2024-01-01")).toThrow()
112
+ })
113
+ })
114
+
115
+ describe("min and max validation", () => {
116
+ it("should accept date within range", () => {
117
+ const schema = date({ min: "2023-01-01", max: "2023-12-31" })
118
+ expect(schema.parse("2023-06-15")).toBe("2023-06-15")
119
+ })
120
+
121
+ it("should reject date before min in range", () => {
122
+ const schema = date({ min: "2023-01-01", max: "2023-12-31" })
123
+ expect(() => schema.parse("2022-12-31")).toThrow()
124
+ })
125
+
126
+ it("should reject date after max in range", () => {
127
+ const schema = date({ min: "2023-01-01", max: "2023-12-31" })
128
+ expect(() => schema.parse("2024-01-01")).toThrow()
129
+ })
130
+ })
131
+
132
+ describe("includes validation", () => {
133
+ it("should accept date containing required substring", () => {
134
+ const schema = date({ includes: "2023" })
135
+ expect(schema.parse("2023-12-25")).toBe("2023-12-25")
136
+ })
137
+
138
+ it("should reject date not containing required substring", () => {
139
+ const schema = date({ includes: "2023" })
140
+ expect(() => schema.parse("2022-12-25")).toThrow()
141
+ })
142
+ })
143
+
144
+ describe("default value", () => {
145
+ it("should use default value when input is empty", () => {
146
+ const schema = date({ defaultValue: "2023-01-01" })
147
+ expect(schema.parse("")).toBe("2023-01-01")
148
+ expect(schema.parse(null)).toBe("2023-01-01")
149
+ expect(schema.parse(undefined)).toBe("2023-01-01")
150
+ })
151
+
152
+ it("should use default value when optional and input is empty", () => {
153
+ const schema = date({ required: false, defaultValue: "2023-01-01" })
154
+ expect(schema.parse("")).toBe("2023-01-01")
155
+ expect(schema.parse(null)).toBe("2023-01-01")
156
+ expect(schema.parse(undefined)).toBe("2023-01-01")
157
+ })
158
+ })
159
+
160
+ describe("localization", () => {
161
+ it("should use English error messages", () => {
162
+ setLocale("en")
163
+ const schema = date()
164
+
165
+ try {
166
+ schema.parse("invalid")
167
+ } catch (error: any) {
168
+ expect(error.issues[0].message).toContain("YYYY-MM-DD")
169
+ }
170
+ })
171
+
172
+ it("should use Chinese error messages", () => {
173
+ setLocale("zh-TW")
174
+ const schema = date()
175
+
176
+ try {
177
+ schema.parse("invalid")
178
+ } catch (error: any) {
179
+ expect(error.issues[0].message).toContain("必須為 YYYY-MM-DD 格式")
180
+ }
181
+ })
182
+ })
183
+
184
+ describe("excludes validation", () => {
185
+ it("should accept date not containing excluded substring", () => {
186
+ const schema = date({ excludes: "2022" })
187
+ expect(schema.parse("2023-12-25")).toBe("2023-12-25")
188
+ })
189
+
190
+ it("should reject date containing excluded substring", () => {
191
+ const schema = date({ excludes: "2022" })
192
+ expect(() => schema.parse("2022-12-25")).toThrow()
193
+ })
194
+
195
+ it("should handle multiple excluded substrings as array", () => {
196
+ const schema = date({ excludes: ["2022", "01"] })
197
+ expect(schema.parse("2023-12-25")).toBe("2023-12-25")
198
+ expect(() => schema.parse("2022-12-25")).toThrow()
199
+ expect(() => schema.parse("2023-01-25")).toThrow()
200
+ })
201
+ })
202
+
203
+ describe("time-based validations", () => {
204
+ // Use local dates to avoid timezone issues
205
+ const today = new Date()
206
+ const yesterday = new Date(today)
207
+ yesterday.setDate(yesterday.getDate() - 1)
208
+ const tomorrow = new Date(today)
209
+ tomorrow.setDate(tomorrow.getDate() + 1)
210
+
211
+ // Format dates in local timezone
212
+ const formatLocalDate = (date: Date) => {
213
+ const year = date.getFullYear()
214
+ const month = String(date.getMonth() + 1).padStart(2, '0')
215
+ const day = String(date.getDate()).padStart(2, '0')
216
+ return `${year}-${month}-${day}`
217
+ }
218
+
219
+ const todayStr = formatLocalDate(today)
220
+ const yesterdayStr = formatLocalDate(yesterday)
221
+ const tomorrowStr = formatLocalDate(tomorrow)
222
+
223
+ describe("mustBePast", () => {
224
+ it("should accept past date", () => {
225
+ const schema = date({ mustBePast: true })
226
+ expect(schema.parse(yesterdayStr)).toBe(yesterdayStr)
227
+ })
228
+
229
+ it("should reject today when mustBePast", () => {
230
+ const schema = date({ mustBePast: true })
231
+ expect(() => schema.parse(todayStr)).toThrow()
232
+ })
233
+
234
+ it("should reject future date", () => {
235
+ const schema = date({ mustBePast: true })
236
+ expect(() => schema.parse(tomorrowStr)).toThrow()
237
+ })
238
+ })
239
+
240
+ describe("mustBeFuture", () => {
241
+ it("should accept future date", () => {
242
+ const schema = date({ mustBeFuture: true })
243
+ expect(schema.parse(tomorrowStr)).toBe(tomorrowStr)
244
+ })
245
+
246
+ it("should reject today when mustBeFuture", () => {
247
+ const schema = date({ mustBeFuture: true })
248
+ expect(() => schema.parse(todayStr)).toThrow()
249
+ })
250
+
251
+ it("should reject past date", () => {
252
+ const schema = date({ mustBeFuture: true })
253
+ expect(() => schema.parse(yesterdayStr)).toThrow()
254
+ })
255
+ })
256
+
257
+ describe("mustBeToday", () => {
258
+ it("should accept today", () => {
259
+ const schema = date({ mustBeToday: true })
260
+ expect(schema.parse(todayStr)).toBe(todayStr)
261
+ })
262
+
263
+ it("should reject past date", () => {
264
+ const schema = date({ mustBeToday: true })
265
+ expect(() => schema.parse(yesterdayStr)).toThrow()
266
+ })
267
+
268
+ it("should reject future date", () => {
269
+ const schema = date({ mustBeToday: true })
270
+ expect(() => schema.parse(tomorrowStr)).toThrow()
271
+ })
272
+ })
273
+
274
+ describe("mustNotBeToday", () => {
275
+ it("should accept past date", () => {
276
+ const schema = date({ mustNotBeToday: true })
277
+ expect(schema.parse(yesterdayStr)).toBe(yesterdayStr)
278
+ })
279
+
280
+ it("should accept future date", () => {
281
+ const schema = date({ mustNotBeToday: true })
282
+ expect(schema.parse(tomorrowStr)).toBe(tomorrowStr)
283
+ })
284
+
285
+ it("should reject today", () => {
286
+ const schema = date({ mustNotBeToday: true })
287
+ expect(() => schema.parse(todayStr)).toThrow()
288
+ })
289
+ })
290
+ })
291
+
292
+ describe("weekday/weekend validations", () => {
293
+ describe("weekdaysOnly", () => {
294
+ it("should accept Monday (weekday)", () => {
295
+ const schema = date({ weekdaysOnly: true })
296
+ expect(schema.parse("2023-12-25")).toBe("2023-12-25") // Monday
297
+ })
298
+
299
+ it("should accept Friday (weekday)", () => {
300
+ const schema = date({ weekdaysOnly: true })
301
+ expect(schema.parse("2023-12-29")).toBe("2023-12-29") // Friday
302
+ })
303
+
304
+ it("should reject Saturday (weekend)", () => {
305
+ const schema = date({ weekdaysOnly: true })
306
+ expect(() => schema.parse("2023-12-30")).toThrow() // Saturday
307
+ })
308
+
309
+ it("should reject Sunday (weekend)", () => {
310
+ const schema = date({ weekdaysOnly: true })
311
+ expect(() => schema.parse("2023-12-31")).toThrow() // Sunday
312
+ })
313
+ })
314
+
315
+ describe("weekendsOnly", () => {
316
+ it("should accept Saturday (weekend)", () => {
317
+ const schema = date({ weekendsOnly: true })
318
+ expect(schema.parse("2023-12-30")).toBe("2023-12-30") // Saturday
319
+ })
320
+
321
+ it("should accept Sunday (weekend)", () => {
322
+ const schema = date({ weekendsOnly: true })
323
+ expect(schema.parse("2023-12-31")).toBe("2023-12-31") // Sunday
324
+ })
325
+
326
+ it("should reject Monday (weekday)", () => {
327
+ const schema = date({ weekendsOnly: true })
328
+ expect(() => schema.parse("2023-12-25")).toThrow() // Monday
329
+ })
330
+
331
+ it("should reject Friday (weekday)", () => {
332
+ const schema = date({ weekendsOnly: true })
333
+ expect(() => schema.parse("2023-12-29")).toThrow() // Friday
334
+ })
335
+ })
336
+ })
337
+
338
+ describe("transform function", () => {
339
+ it("should apply custom transform function", () => {
340
+ const schema = date({
341
+ format: "YYYY/MM/DD",
342
+ transform: (val) => val.replace(/-/g, "/"),
343
+ })
344
+ expect(schema.parse("2023-12-25")).toBe("2023/12/25")
345
+ })
346
+
347
+ it("should apply transform before validation", () => {
348
+ const schema = date({
349
+ format: "YYYY/MM/DD",
350
+ transform: (val) => val.replace(/-/g, "/"),
351
+ })
352
+ expect(schema.parse("2023-12-25")).toBe("2023/12/25")
353
+ })
354
+
355
+ it("should work with other validations after transform", () => {
356
+ const schema = date({
357
+ format: "YYYY/MM/DD",
358
+ includes: "/",
359
+ transform: (val) => val.replace(/-/g, "/"),
360
+ })
361
+ expect(schema.parse("2023-12-25")).toBe("2023/12/25")
362
+ })
363
+ })
364
+
365
+ describe("i18n custom messages", () => {
366
+ it("should use custom English messages", () => {
367
+ const schema = date({
368
+ i18n: {
369
+ en: { format: "Custom date format error: ${format}" },
370
+ "zh-TW": { format: "自定義日期格式錯誤: ${format}" },
371
+ },
372
+ })
373
+
374
+ setLocale("en")
375
+ try {
376
+ schema.parse("invalid")
377
+ } catch (error: any) {
378
+ expect(error.issues[0].message).toBe("Custom date format error: YYYY-MM-DD")
379
+ }
380
+ })
381
+
382
+ it("should use custom Chinese messages", () => {
383
+ const schema = date({
384
+ i18n: {
385
+ en: { format: "Custom date format error: ${format}" },
386
+ "zh-TW": { format: "自定義日期格式錯誤: ${format}" },
387
+ },
388
+ })
389
+
390
+ setLocale("zh-TW")
391
+ try {
392
+ schema.parse("invalid")
393
+ } catch (error: any) {
394
+ expect(error.issues[0].message).toBe("自定義日期格式錯誤: YYYY-MM-DD")
395
+ }
396
+ })
397
+
398
+ it("should fallback to default messages when custom not provided", () => {
399
+ const schema = date({
400
+ i18n: {
401
+ en: { format: "Custom format error" },
402
+ "zh-TW": { format: "自定義格式錯誤" },
403
+ },
404
+ })
405
+
406
+ setLocale("en")
407
+ try {
408
+ schema.parse("")
409
+ } catch (error: any) {
410
+ expect(error.issues[0].message).toBe("Required") // Default message
411
+ }
412
+ })
413
+
414
+ it("should handle past validation with custom message", () => {
415
+ const schema = date({
416
+ mustBePast: true,
417
+ i18n: {
418
+ en: { past: "Date must be in the past!" },
419
+ "zh-TW": { past: "日期必須在過去!" },
420
+ },
421
+ })
422
+
423
+ const tomorrow = new Date()
424
+ tomorrow.setDate(tomorrow.getDate() + 1)
425
+ const tomorrowStr = tomorrow.toISOString().split("T")[0]
426
+
427
+ setLocale("en")
428
+ try {
429
+ schema.parse(tomorrowStr)
430
+ } catch (error: any) {
431
+ expect(error.issues[0].message).toBe("Date must be in the past!")
432
+ }
433
+ })
434
+ })
435
+
436
+ describe("edge cases", () => {
437
+ it("should handle leap year dates", () => {
438
+ const schema = date()
439
+ expect(schema.parse("2024-02-29")).toBe("2024-02-29")
440
+ })
441
+
442
+ it("should reject invalid leap year date", () => {
443
+ const schema = date()
444
+ expect(() => schema.parse("2023-02-29")).toThrow()
445
+ })
446
+
447
+ it("should handle end of month dates", () => {
448
+ const schema = date()
449
+ expect(schema.parse("2023-01-31")).toBe("2023-01-31")
450
+ expect(schema.parse("2023-04-30")).toBe("2023-04-30")
451
+ })
452
+
453
+ it("should reject invalid end of month dates", () => {
454
+ const schema = date()
455
+ expect(() => schema.parse("2023-04-31")).toThrow()
456
+ })
457
+ })
458
+ })