@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
@@ -1,118 +1,366 @@
1
1
  import { describe, it, expect, beforeEach } from "vitest"
2
- import { setLocale, boolean, Locale } from "../../src"
3
-
4
- const locales = [
5
- {
6
- locale: "en",
7
- messages: {
8
- required: "Required",
9
- shouldBeTrue: "Must be True",
10
- shouldBeFalse: "Must be False",
11
- },
12
- },
13
- {
14
- locale: "zh-TW",
15
- messages: {
16
- required: "必填",
17
- shouldBeTrue: "必須為是",
18
- shouldBeFalse: "必須為否",
19
- },
20
- },
21
- ] as const
22
-
23
- describe.each(locales)("boolean() locale: $locale", ({ locale, messages }) => {
24
- beforeEach(() => setLocale(locale as Locale))
25
-
26
- it("should parse true values correctly", () => {
27
- const schema = boolean({ required: true })
28
- expect(schema.parse(true)).toBe(true)
29
- expect(schema.parse("true")).toBe(true)
30
- expect(schema.parse(1)).toBe(true)
31
- })
2
+ import { boolean, setLocale } from "../../src"
3
+
4
+ describe("boolean", () => {
5
+ describe("required (default)", () => {
6
+ it("should validate true values", () => {
7
+ const schema = boolean()
8
+ expect(schema.parse(true)).toBe(true)
9
+ expect(schema.parse("true")).toBe(true)
10
+ expect(schema.parse(1)).toBe(true)
11
+ expect(schema.parse("1")).toBe(true)
12
+ expect(schema.parse("yes")).toBe(true)
13
+ expect(schema.parse("on")).toBe(true)
14
+ })
15
+
16
+ it("should validate false values", () => {
17
+ const schema = boolean()
18
+ expect(schema.parse(false)).toBe(false)
19
+ expect(schema.parse("false")).toBe(false)
20
+ expect(schema.parse(0)).toBe(false)
21
+ expect(schema.parse("0")).toBe(false)
22
+ expect(schema.parse("no")).toBe(false)
23
+ expect(schema.parse("off")).toBe(false)
24
+ })
25
+
26
+ it("should reject empty string", () => {
27
+ const schema = boolean()
28
+ expect(() => schema.parse("")).toThrow()
29
+ })
30
+
31
+ it("should reject null", () => {
32
+ const schema = boolean()
33
+ expect(() => schema.parse(null)).toThrow()
34
+ })
32
35
 
33
- it("should parse false values correctly", () => {
34
- const schema = boolean({ required: true })
35
- expect(schema.parse(false)).toBe(false)
36
- expect(schema.parse("false")).toBe(false)
37
- expect(schema.parse(0)).toBe(false)
36
+ it("should reject undefined", () => {
37
+ const schema = boolean()
38
+ expect(() => schema.parse(undefined)).toThrow()
39
+ })
40
+
41
+ it("should reject invalid values", () => {
42
+ const schema = boolean()
43
+ expect(() => schema.parse("invalid")).toThrow()
44
+ expect(() => schema.parse({})).toThrow()
45
+ expect(() => schema.parse([])).toThrow()
46
+ expect(() => schema.parse(2)).toThrow()
47
+ })
38
48
  })
39
49
 
40
- it("should throw when input is empty and required", () => {
41
- const schema = boolean({ required: true })
42
- expect(() => schema.parse(undefined)).toThrow(messages.required)
43
- expect(() => schema.parse(null)).toThrow(messages.required)
44
- expect(() => schema.parse("")).toThrow(messages.required)
50
+ describe("optional", () => {
51
+ it("should allow null when not required", () => {
52
+ const schema = boolean({ required: false })
53
+ expect(schema.parse(null)).toBe(null)
54
+ })
55
+
56
+ it("should allow empty string when not required", () => {
57
+ const schema = boolean({ required: false })
58
+ expect(schema.parse("")).toBe(null)
59
+ })
60
+
61
+ it("should allow undefined when not required", () => {
62
+ const schema = boolean({ required: false })
63
+ expect(schema.parse(undefined)).toBe(null)
64
+ })
65
+
66
+ it("should validate true values when not required", () => {
67
+ const schema = boolean({ required: false })
68
+ expect(schema.parse(true)).toBe(true)
69
+ expect(schema.parse("true")).toBe(true)
70
+ expect(schema.parse(1)).toBe(true)
71
+ })
72
+
73
+ it("should validate false values when not required", () => {
74
+ const schema = boolean({ required: false })
75
+ expect(schema.parse(false)).toBe(false)
76
+ expect(schema.parse("false")).toBe(false)
77
+ expect(schema.parse(0)).toBe(false)
78
+ })
45
79
  })
46
80
 
47
- it("should return null when not required and empty input", () => {
48
- const schema = boolean({ required: false })
49
- expect(schema.parse(undefined)).toBe(null)
50
- expect(schema.parse(null)).toBe(null)
51
- expect(schema.parse("")).toBe(null)
81
+ describe("shouldBe validation", () => {
82
+ it("should enforce true when shouldBe is true", () => {
83
+ const schema = boolean({ shouldBe: true })
84
+ expect(schema.parse(true)).toBe(true)
85
+ expect(schema.parse("true")).toBe(true)
86
+ expect(schema.parse(1)).toBe(true)
87
+ })
88
+
89
+ it("should reject false when shouldBe is true", () => {
90
+ const schema = boolean({ shouldBe: true })
91
+ expect(() => schema.parse(false)).toThrow()
92
+ expect(() => schema.parse("false")).toThrow()
93
+ expect(() => schema.parse(0)).toThrow()
94
+ })
95
+
96
+ it("should enforce false when shouldBe is false", () => {
97
+ const schema = boolean({ shouldBe: false })
98
+ expect(schema.parse(false)).toBe(false)
99
+ expect(schema.parse("false")).toBe(false)
100
+ expect(schema.parse(0)).toBe(false)
101
+ })
102
+
103
+ it("should reject true when shouldBe is false", () => {
104
+ const schema = boolean({ shouldBe: false })
105
+ expect(() => schema.parse(true)).toThrow()
106
+ expect(() => schema.parse("true")).toThrow()
107
+ expect(() => schema.parse(1)).toThrow()
108
+ })
52
109
  })
53
110
 
54
- it("should apply defaultValue when required", () => {
55
- const schema = boolean({ required: true, defaultValue: true })
56
- expect(schema.parse("")).toBe(true)
57
- expect(schema.parse(null)).toBe(true)
58
- expect(schema.parse(undefined)).toBe(true)
111
+ describe("default value", () => {
112
+ it("should use default value true when input is empty", () => {
113
+ const schema = boolean({ defaultValue: true })
114
+ expect(schema.parse("")).toBe(true)
115
+ expect(schema.parse(null)).toBe(true)
116
+ expect(schema.parse(undefined)).toBe(true)
117
+ })
118
+
119
+ it("should use default value false when input is empty", () => {
120
+ const schema = boolean({ defaultValue: false })
121
+ expect(schema.parse("")).toBe(false)
122
+ expect(schema.parse(null)).toBe(false)
123
+ expect(schema.parse(undefined)).toBe(false)
124
+ })
125
+
126
+ it("should use default value when optional and input is empty", () => {
127
+ const schema = boolean({ required: false, defaultValue: true })
128
+ expect(schema.parse("")).toBe(true)
129
+ expect(schema.parse(null)).toBe(true)
130
+ expect(schema.parse(undefined)).toBe(true)
131
+ })
59
132
 
60
- const schema2 = boolean({ required: true, defaultValue: false })
61
- expect(schema2.parse("")).toBe(false)
62
- expect(schema2.parse(null)).toBe(false)
63
- expect(schema2.parse(undefined)).toBe(false)
133
+ it("should override default value with provided input", () => {
134
+ const schema = boolean({ defaultValue: true })
135
+ expect(schema.parse(false)).toBe(false)
136
+ expect(schema.parse("false")).toBe(false)
137
+ expect(schema.parse(0)).toBe(false)
138
+ })
64
139
  })
65
140
 
66
- it("should apply defaultValue when not required", () => {
67
- const schema = boolean({ required: false, defaultValue: true })
68
- expect(schema.parse("")).toBe(true)
69
- expect(schema.parse(null)).toBe(true)
70
- expect(schema.parse(undefined)).toBe(true)
141
+ describe("combined options", () => {
142
+ it("should work with shouldBe and defaultValue", () => {
143
+ const schema = boolean({ shouldBe: true, defaultValue: true })
144
+ expect(schema.parse("")).toBe(true)
145
+ expect(schema.parse(null)).toBe(true)
146
+ expect(schema.parse(true)).toBe(true)
147
+ expect(() => schema.parse(false)).toThrow()
148
+ })
71
149
 
72
- const schema2 = boolean({ required: false, defaultValue: false })
73
- expect(schema2.parse("")).toBe(false)
74
- expect(schema2.parse(null)).toBe(false)
75
- expect(schema2.parse(undefined)).toBe(false)
150
+ it("should work with optional, shouldBe and defaultValue", () => {
151
+ const schema = boolean({ required: false, shouldBe: false, defaultValue: false })
152
+ expect(schema.parse("")).toBe(false)
153
+ expect(schema.parse(null)).toBe(false)
154
+ expect(schema.parse(false)).toBe(false)
155
+ expect(() => schema.parse(true)).toThrow()
156
+ })
157
+
158
+ it("should fail validation when defaultValue conflicts with shouldBe", () => {
159
+ const schema = boolean({ shouldBe: true, defaultValue: false })
160
+ expect(() => schema.parse("")).toThrow()
161
+ expect(() => schema.parse(null)).toThrow()
162
+ expect(() => schema.parse(undefined)).toThrow()
163
+ })
76
164
  })
77
165
 
78
- it("should throw error for invalid string", () => {
79
- const schema = boolean()
80
- expect(() => schema.parse("yes")).toThrow()
81
- expect(() => schema.parse("no")).toThrow()
166
+ describe("edge cases", () => {
167
+ it("should handle string numbers correctly", () => {
168
+ const schema = boolean()
169
+ expect(schema.parse("1")).toBe(true)
170
+ expect(schema.parse("0")).toBe(false)
171
+ })
172
+
173
+ it("should handle exact boolean strings", () => {
174
+ const schema = boolean()
175
+ expect(schema.parse("true")).toBe(true)
176
+ expect(schema.parse("false")).toBe(false)
177
+ })
178
+
179
+ it("should reject partial boolean strings", () => {
180
+ const schema = boolean()
181
+ expect(() => schema.parse("t")).toThrow()
182
+ expect(() => schema.parse("f")).toThrow()
183
+ expect(() => schema.parse("TRUE")).toThrow()
184
+ expect(() => schema.parse("FALSE")).toThrow()
185
+ })
186
+
187
+ it("should reject numeric values other than 0 and 1", () => {
188
+ const schema = boolean()
189
+ expect(() => schema.parse(2)).toThrow()
190
+ expect(() => schema.parse(-1)).toThrow()
191
+ expect(() => schema.parse(0.5)).toThrow()
192
+ expect(() => schema.parse(NaN)).toThrow()
193
+ expect(() => schema.parse(Infinity)).toThrow()
194
+ })
195
+
196
+ it("should reject non-boolean objects", () => {
197
+ const schema = boolean()
198
+ expect(() => schema.parse({})).toThrow()
199
+ expect(() => schema.parse([])).toThrow()
200
+ expect(() => schema.parse(new Date())).toThrow()
201
+ expect(() => schema.parse(/test/)).toThrow()
202
+ expect(() => schema.parse(Symbol("test"))).toThrow()
203
+ expect(() => schema.parse(() => true)).toThrow()
204
+ })
205
+
206
+ it("should handle complex scenarios", () => {
207
+ const schema = boolean({ required: false, shouldBe: true, defaultValue: true })
208
+
209
+ expect(schema.parse("")).toBe(true)
210
+ expect(schema.parse(null)).toBe(true)
211
+ expect(schema.parse(undefined)).toBe(true)
212
+ expect(schema.parse(true)).toBe(true)
213
+ expect(() => schema.parse(false)).toThrow()
214
+ expect(() => schema.parse("invalid")).toThrow()
215
+ })
82
216
  })
83
217
 
84
- it("should throw error for non-boolean, non-convertible values", () => {
85
- const schema = boolean()
86
- expect(() => schema.parse({})).toThrow()
87
- expect(() => schema.parse([])).toThrow()
218
+ describe("custom truthy/falsy values", () => {
219
+ it("should accept custom truthy values", () => {
220
+ const schema = boolean({
221
+ truthyValues: ["Y", "YES", 1],
222
+ falsyValues: ["N", "NO", 0],
223
+ })
224
+
225
+ expect(schema.parse("Y")).toBe(true)
226
+ expect(schema.parse("YES")).toBe(true)
227
+ expect(schema.parse(1)).toBe(true)
228
+ expect(schema.parse("N")).toBe(false)
229
+ expect(schema.parse("NO")).toBe(false)
230
+ expect(schema.parse(0)).toBe(false)
231
+
232
+ // Original values should now be invalid
233
+ expect(() => schema.parse("yes")).toThrow()
234
+ expect(() => schema.parse("true")).toThrow()
235
+ })
88
236
  })
89
237
 
90
- it("should validate shouldBe: true", () => {
91
- const schema = boolean({ shouldBe: true })
92
- expect(schema.parse(true)).toBe(true)
93
- expect(schema.parse("true")).toBe(true)
94
- expect(() => schema.parse(false)).toThrow(messages.shouldBeTrue)
95
- expect(() => schema.parse("false")).toThrow(messages.shouldBeTrue)
96
- expect(() => schema.parse(0)).toThrow(messages.shouldBeTrue)
238
+ describe("strict mode", () => {
239
+ it("should only accept actual booleans in strict mode", () => {
240
+ const schema = boolean({ strict: true })
241
+
242
+ expect(schema.parse(true)).toBe(true)
243
+ expect(schema.parse(false)).toBe(false)
244
+
245
+ expect(() => schema.parse("true")).toThrow()
246
+ expect(() => schema.parse("false")).toThrow()
247
+ expect(() => schema.parse(1)).toThrow()
248
+ expect(() => schema.parse(0)).toThrow()
249
+ expect(() => schema.parse("yes")).toThrow()
250
+ })
251
+
252
+ it("should allow null when not required in strict mode", () => {
253
+ const schema = boolean({ strict: true, required: false })
254
+
255
+ expect(schema.parse(null)).toBe(null)
256
+ expect(schema.parse(undefined)).toBe(null)
257
+ expect(schema.parse("")).toBe(null)
258
+ })
97
259
  })
98
260
 
99
- it("should validate shouldBe: false", () => {
100
- const schema = boolean({ shouldBe: false })
101
- expect(schema.parse(false)).toBe(false)
102
- expect(schema.parse("false")).toBe(false)
103
- expect(schema.parse(0)).toBe(false)
104
- expect(() => schema.parse(true)).toThrow(messages.shouldBeFalse)
105
- expect(() => schema.parse("true")).toThrow(messages.shouldBeFalse)
106
- expect(() => schema.parse(1)).toThrow(messages.shouldBeFalse)
261
+ describe("transform functionality", () => {
262
+ it("should apply transform to boolean values", () => {
263
+ const schema = boolean({
264
+ transform: (val) => !val, // Invert the boolean
265
+ })
266
+
267
+ expect(schema.parse(true)).toBe(false)
268
+ expect(schema.parse(false)).toBe(true)
269
+ expect(schema.parse("yes")).toBe(false)
270
+ expect(schema.parse("no")).toBe(true)
271
+ })
107
272
  })
108
273
 
109
- it("should show correct error message when shouldBe is true but value is false", () => {
110
- const schema = boolean({ shouldBe: true })
111
- expect(() => schema.parse(false)).toThrow(messages.shouldBeTrue)
274
+ describe("custom i18n messages", () => {
275
+ beforeEach(() => setLocale("en"))
276
+
277
+ it("should use custom messages when provided", () => {
278
+ const schema = boolean({
279
+ shouldBe: true,
280
+ i18n: {
281
+ en: {
282
+ required: "Custom required message",
283
+ shouldBeTrue: "Custom should be true message",
284
+ },
285
+ "zh-TW": {
286
+ required: "客製化必填訊息",
287
+ shouldBeTrue: "客製化必須為真訊息",
288
+ },
289
+ },
290
+ })
291
+
292
+ expect(() => schema.parse("")).toThrow("Custom required message")
293
+ expect(() => schema.parse(false)).toThrow("Custom should be true message")
294
+ })
295
+
296
+ it("should fallback to default messages when custom not provided", () => {
297
+ const schema = boolean({
298
+ shouldBe: false,
299
+ i18n: {
300
+ en: {
301
+ required: "Custom required message",
302
+ },
303
+ "zh-TW": {
304
+ required: "客製化必填訊息",
305
+ },
306
+ },
307
+ })
308
+
309
+ expect(() => schema.parse("")).toThrow("Custom required message")
310
+ expect(() => schema.parse(true)).toThrow("Must be False")
311
+ })
312
+
313
+ it("should use correct locale for custom messages", () => {
314
+ setLocale("en")
315
+ const schemaEn = boolean({
316
+ i18n: {
317
+ en: {
318
+ required: "English required",
319
+ },
320
+ "zh-TW": {
321
+ required: "繁體中文必填",
322
+ },
323
+ },
324
+ })
325
+ expect(() => schemaEn.parse("")).toThrow("English required")
326
+
327
+ setLocale("zh-TW")
328
+ const schemaZh = boolean({
329
+ i18n: {
330
+ en: {
331
+ required: "English required",
332
+ },
333
+ "zh-TW": {
334
+ required: "繁體中文必填",
335
+ },
336
+ },
337
+ })
338
+ expect(() => schemaZh.parse("")).toThrow("繁體中文必填")
339
+ })
340
+
341
+ it("should work with strict mode (note: uses Zod's built-in error messages)", () => {
342
+ const schema = boolean({ strict: true })
343
+
344
+ expect(() => schema.parse("true")).toThrow() // Will throw Zod's union error
345
+ expect(() => schema.parse(1)).toThrow() // Will throw Zod's union error
346
+ })
112
347
  })
113
348
 
114
- it("should show correct error message when shouldBe is false but value is true", () => {
115
- const schema = boolean({ shouldBe: false })
116
- expect(() => schema.parse(true)).toThrow(messages.shouldBeFalse)
349
+ describe("complex scenarios", () => {
350
+ it("should work with multiple options", () => {
351
+ const schema = boolean({
352
+ truthyValues: ["YES", "Y"],
353
+ falsyValues: ["NO", "N"],
354
+ shouldBe: true,
355
+ transform: (val) => val, // Identity transform
356
+ defaultValue: true,
357
+ })
358
+
359
+ expect(schema.parse("")).toBe(true)
360
+ expect(schema.parse("YES")).toBe(true)
361
+ expect(schema.parse("Y")).toBe(true)
362
+ expect(() => schema.parse("NO")).toThrow("Must be True")
363
+ expect(() => schema.parse("N")).toThrow("Must be True")
364
+ })
117
365
  })
118
366
  })