@hy_ong/zod-kit 0.0.4 → 0.0.5

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 (51) hide show
  1. package/.claude/settings.local.json +23 -0
  2. package/LICENSE +21 -0
  3. package/README.md +5 -5
  4. package/debug.js +21 -0
  5. package/debug.ts +16 -0
  6. package/dist/index.cjs +1619 -145
  7. package/dist/index.d.cts +324 -25
  8. package/dist/index.d.ts +324 -25
  9. package/dist/index.js +1590 -143
  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 +99 -25
  14. package/src/i18n/locales/zh-TW.json +103 -26
  15. package/src/index.ts +13 -7
  16. package/src/validators/common/boolean.ts +97 -0
  17. package/src/validators/common/date.ts +171 -0
  18. package/src/validators/common/email.ts +200 -0
  19. package/src/validators/common/id.ts +259 -0
  20. package/src/validators/common/number.ts +194 -0
  21. package/src/validators/common/password.ts +214 -0
  22. package/src/validators/common/text.ts +151 -0
  23. package/src/validators/common/url.ts +207 -0
  24. package/src/validators/taiwan/business-id.ts +140 -0
  25. package/src/validators/taiwan/fax.ts +182 -0
  26. package/src/validators/taiwan/mobile.ts +110 -0
  27. package/src/validators/taiwan/national-id.ts +208 -0
  28. package/src/validators/taiwan/tel.ts +182 -0
  29. package/tests/common/boolean.test.ts +340 -92
  30. package/tests/common/date.test.ts +458 -0
  31. package/tests/common/email.test.ts +232 -60
  32. package/tests/common/id.test.ts +535 -0
  33. package/tests/common/number.test.ts +230 -60
  34. package/tests/common/password.test.ts +271 -44
  35. package/tests/common/text.test.ts +210 -13
  36. package/tests/common/url.test.ts +492 -67
  37. package/tests/taiwan/business-id.test.ts +240 -0
  38. package/tests/taiwan/fax.test.ts +463 -0
  39. package/tests/taiwan/mobile.test.ts +373 -0
  40. package/tests/taiwan/national-id.test.ts +435 -0
  41. package/tests/taiwan/tel.test.ts +467 -0
  42. package/eslint.config.mjs +0 -10
  43. package/src/common/boolean.ts +0 -36
  44. package/src/common/date.ts +0 -43
  45. package/src/common/email.ts +0 -44
  46. package/src/common/integer.ts +0 -46
  47. package/src/common/number.ts +0 -37
  48. package/src/common/password.ts +0 -33
  49. package/src/common/text.ts +0 -34
  50. package/src/common/url.ts +0 -37
  51. package/tests/common/integer.test.ts +0 -90
@@ -0,0 +1,373 @@
1
+ import { describe, it, expect, beforeEach } from "vitest"
2
+ import { mobile, setLocale, validateTaiwanMobile } from "../../src"
3
+
4
+ describe("Taiwan mobile() validator", () => {
5
+ beforeEach(() => setLocale("en"))
6
+
7
+ describe("basic functionality", () => {
8
+ it("should validate correct Taiwan mobile phone numbers", () => {
9
+ const schema = mobile()
10
+
11
+ // Valid Taiwan mobile phone numbers
12
+ expect(schema.parse("0901234567")).toBe("0901234567")
13
+ expect(schema.parse("0911234567")).toBe("0911234567")
14
+ expect(schema.parse("0921234567")).toBe("0921234567")
15
+ expect(schema.parse("0931234567")).toBe("0931234567")
16
+ expect(schema.parse("0941234567")).toBe("0941234567")
17
+ expect(schema.parse("0951234567")).toBe("0951234567")
18
+ expect(schema.parse("0961234567")).toBe("0961234567")
19
+ expect(schema.parse("0971234567")).toBe("0971234567")
20
+ expect(schema.parse("0981234567")).toBe("0981234567")
21
+ expect(schema.parse("0991234567")).toBe("0991234567")
22
+ })
23
+
24
+ it("should reject invalid Taiwan mobile phone numbers", () => {
25
+ const schema = mobile()
26
+
27
+ // Invalid formats
28
+ expect(() => schema.parse("123456789")).toThrow("Invalid Taiwan mobile phone format") // Missing leading 09
29
+ expect(() => schema.parse("01234567890")).toThrow("Invalid Taiwan mobile phone format") // Too long
30
+ expect(() => schema.parse("090123456")).toThrow("Invalid Taiwan mobile phone format") // Too short
31
+ expect(() => schema.parse("0801234567")).toThrow("Invalid Taiwan mobile phone format") // Wrong prefix (08)
32
+ expect(() => schema.parse("0701234567")).toThrow("Invalid Taiwan mobile phone format") // Wrong prefix (07)
33
+ expect(() => schema.parse("1901234567")).toThrow("Invalid Taiwan mobile phone format") // Wrong prefix (19)
34
+ expect(() => schema.parse("")).toThrow("Required")
35
+ expect(() => schema.parse("abcdefghij")).toThrow("Invalid Taiwan mobile phone format")
36
+ })
37
+
38
+ it("should handle whitespace trimming", () => {
39
+ const schema = mobile()
40
+
41
+ expect(schema.parse(" 0901234567 ")).toBe("0901234567")
42
+ expect(schema.parse("\t0911234567\n")).toBe("0911234567")
43
+ })
44
+ })
45
+
46
+ describe("whitelist functionality", () => {
47
+ it("should accept any string in whitelist regardless of format", () => {
48
+ const schema = mobile({
49
+ whitelist: ["custom-phone", "emergency-contact", "0801234567"],
50
+ })
51
+
52
+ // Allowlist entries should be accepted even if they don't match Taiwan mobile formats
53
+ expect(schema.parse("custom-phone")).toBe("custom-phone")
54
+ expect(schema.parse("emergency-contact")).toBe("emergency-contact")
55
+ expect(schema.parse("0801234567")).toBe("0801234567") // Invalid mobile but in allowlist
56
+
57
+ // Valid mobile phones not in the allowlist should be rejected
58
+ expect(() => schema.parse("0901234567")).toThrow("Not in allowed mobile phone list")
59
+ expect(() => schema.parse("0801111111")).toThrow("Not in allowed mobile phone list")
60
+ })
61
+
62
+ it("should reject values not in whitelist when whitelist is provided", () => {
63
+ const schema = mobile({
64
+ whitelist: ["allowed-value", "0901234567"],
65
+ })
66
+
67
+ expect(() => schema.parse("0911234567")).toThrow("Not in allowed mobile phone list")
68
+ expect(() => schema.parse("invalid-value")).toThrow("Not in allowed mobile phone list")
69
+ })
70
+
71
+ it("should work with empty whitelist", () => {
72
+ const schema = mobile({
73
+ whitelist: [],
74
+ })
75
+
76
+ // With empty allowlist, should still validate a mobile phone format
77
+ expect(schema.parse("0901234567")).toBe("0901234567")
78
+ expect(() => schema.parse("0801234567")).toThrow("Invalid Taiwan mobile phone format")
79
+ })
80
+
81
+ it("should prioritize whitelist over format validation", () => {
82
+ const schema = mobile({
83
+ required: false,
84
+ whitelist: ["not-a-phone", "123", ""],
85
+ })
86
+
87
+ // These should be accepted despite being invalid mobile phone formats
88
+ expect(schema.parse("not-a-phone")).toBe("not-a-phone")
89
+ expect(schema.parse("123")).toBe("123")
90
+ expect(schema.parse("")).toBe("")
91
+ })
92
+ })
93
+
94
+ describe("required/optional behavior", () => {
95
+ it("should handle required=true (default)", () => {
96
+ const schema = mobile()
97
+
98
+ expect(() => schema.parse("")).toThrow("Required")
99
+ expect(() => schema.parse(null)).toThrow()
100
+ expect(() => schema.parse(undefined)).toThrow()
101
+ })
102
+
103
+ it("should handle required=false", () => {
104
+ const schema = mobile({ required: false })
105
+
106
+ expect(schema.parse("")).toBe(null)
107
+ expect(schema.parse(null)).toBe(null)
108
+ expect(schema.parse(undefined)).toBe(null)
109
+ expect(schema.parse("0901234567")).toBe("0901234567")
110
+ })
111
+
112
+ it("should use default values", () => {
113
+ const requiredSchema = mobile({ defaultValue: "0901234567" })
114
+ const optionalSchema = mobile({ required: false, defaultValue: "0901234567" })
115
+
116
+ expect(requiredSchema.parse("")).toBe("0901234567")
117
+ expect(optionalSchema.parse("")).toBe("0901234567")
118
+ })
119
+
120
+ it("should handle whitelist with optional fields", () => {
121
+ const schema = mobile({
122
+ required: false,
123
+ whitelist: ["custom-value", "0901234567"],
124
+ })
125
+
126
+ expect(schema.parse("")).toBe(null)
127
+ expect(schema.parse("custom-value")).toBe("custom-value")
128
+ expect(schema.parse("0901234567")).toBe("0901234567")
129
+ expect(() => schema.parse("0911234567")).toThrow("Not in allowed mobile phone list")
130
+ })
131
+ })
132
+
133
+ describe("transform function", () => {
134
+ it("should apply custom transform", () => {
135
+ const schema = mobile({
136
+ transform: (val) => val.replace(/[-\s]/g, ""),
137
+ })
138
+
139
+ expect(schema.parse("090-123-4567")).toBe("0901234567")
140
+ expect(schema.parse("091 123 4567")).toBe("0911234567")
141
+ })
142
+
143
+ it("should apply transform before validation", () => {
144
+ const schema = mobile({
145
+ transform: (val) => val.replace(/\s+/g, ""),
146
+ })
147
+
148
+ expect(schema.parse(" 090 123 4567 ")).toBe("0901234567")
149
+ expect(() => schema.parse(" 080 123 4567 ")).toThrow("Invalid Taiwan mobile phone format")
150
+ })
151
+
152
+ it("should work with whitelist after transform", () => {
153
+ // First test basic transform without allowlist
154
+ const basicSchema = mobile({
155
+ transform: (val) => val.replace(/[-\s]/g, ""),
156
+ })
157
+ expect(basicSchema.parse("090-123-4567")).toBe("0901234567")
158
+
159
+ // Then test with whitelist - use simple value that transforms won't affect
160
+ const whitelistSchema = mobile({
161
+ transform: (val) => val.replace(/[-\s]/g, ""),
162
+ whitelist: ["0901234567", "customvalue"],
163
+ })
164
+
165
+ expect(whitelistSchema.parse("customvalue")).toBe("customvalue")
166
+ expect(() => whitelistSchema.parse("091-123-4567")).toThrow("Not in allowed mobile phone list")
167
+ })
168
+ })
169
+
170
+ describe("input preprocessing", () => {
171
+ it("should handle string conversion", () => {
172
+ const schema = mobile()
173
+
174
+ // Test string conversion of numbers
175
+ expect(() => schema.parse(901234567)).toThrow("Invalid Taiwan mobile phone format") // Invalid because missing leading 0
176
+ })
177
+
178
+ it("should trim whitespace", () => {
179
+ const schema = mobile()
180
+
181
+ expect(schema.parse(" 0901234567 ")).toBe("0901234567")
182
+ expect(schema.parse("\t0911234567\n")).toBe("0911234567")
183
+ })
184
+ })
185
+
186
+ describe("utility function", () => {
187
+ describe("validateTaiwanMobile", () => {
188
+ it("should correctly validate Taiwan mobile phone numbers", () => {
189
+ // Valid numbers
190
+ expect(validateTaiwanMobile("0901234567")).toBe(true)
191
+ expect(validateTaiwanMobile("0911234567")).toBe(true)
192
+ expect(validateTaiwanMobile("0921234567")).toBe(true)
193
+ expect(validateTaiwanMobile("0931234567")).toBe(true)
194
+ expect(validateTaiwanMobile("0941234567")).toBe(true)
195
+ expect(validateTaiwanMobile("0951234567")).toBe(true)
196
+ expect(validateTaiwanMobile("0961234567")).toBe(true)
197
+ expect(validateTaiwanMobile("0971234567")).toBe(true)
198
+ expect(validateTaiwanMobile("0981234567")).toBe(true)
199
+ expect(validateTaiwanMobile("0991234567")).toBe(true)
200
+
201
+ // Invalid numbers
202
+ expect(validateTaiwanMobile("0801234567")).toBe(false) // Wrong prefix
203
+ expect(validateTaiwanMobile("1901234567")).toBe(false) // Wrong prefix
204
+ expect(validateTaiwanMobile("090123456")).toBe(false) // Too short
205
+ expect(validateTaiwanMobile("09012345678")).toBe(false) // Too long
206
+ expect(validateTaiwanMobile("901234567")).toBe(false) // Missing leading 0
207
+ expect(validateTaiwanMobile("")).toBe(false) // Empty
208
+ expect(validateTaiwanMobile("abcdefghij")).toBe(false) // Non-numeric
209
+ })
210
+ })
211
+ })
212
+
213
+ describe("i18n support", () => {
214
+ it("should use English messages by default", () => {
215
+ setLocale("en")
216
+ const schema = mobile()
217
+
218
+ expect(() => schema.parse("")).toThrow("Required")
219
+ expect(() => schema.parse("0801234567")).toThrow("Invalid Taiwan mobile phone format")
220
+ })
221
+
222
+ it("should use Chinese messages when locale is zh-TW", () => {
223
+ setLocale("zh-TW")
224
+ const schema = mobile()
225
+
226
+ expect(() => schema.parse("")).toThrow("必填")
227
+ expect(() => schema.parse("0801234567")).toThrow("無效的手機號碼格式")
228
+ })
229
+
230
+ it("should support whitelist error messages", () => {
231
+ setLocale("en")
232
+ const schema = mobile({
233
+ whitelist: ["0901234567"],
234
+ })
235
+
236
+ expect(() => schema.parse("0911234567")).toThrow("Not in allowed mobile phone list")
237
+
238
+ setLocale("zh-TW")
239
+ expect(() => schema.parse("0911234567")).toThrow("不在允許的手機號碼清單中")
240
+ })
241
+
242
+ it("should support custom i18n messages", () => {
243
+ const schema = mobile({
244
+ i18n: {
245
+ en: {
246
+ required: "Mobile phone is required",
247
+ invalid: "Mobile phone format is invalid",
248
+ notInWhitelist: "Mobile phone not allowed",
249
+ },
250
+ "zh-TW": {
251
+ required: "請輸入手機號碼",
252
+ invalid: "手機號碼格式錯誤",
253
+ notInWhitelist: "手機號碼不被允許",
254
+ },
255
+ },
256
+ })
257
+
258
+ setLocale("en")
259
+ expect(() => schema.parse("")).toThrow("Mobile phone is required")
260
+ expect(() => schema.parse("0801234567")).toThrow("Mobile phone format is invalid")
261
+
262
+ setLocale("zh-TW")
263
+ expect(() => schema.parse("")).toThrow("請輸入手機號碼")
264
+ expect(() => schema.parse("0801234567")).toThrow("手機號碼格式錯誤")
265
+ })
266
+
267
+ it("should support custom whitelist messages", () => {
268
+ const schema = mobile({
269
+ whitelist: ["0901234567"],
270
+ i18n: {
271
+ en: {
272
+ notInWhitelist: "This mobile phone number is not allowed",
273
+ },
274
+ "zh-TW": {
275
+ notInWhitelist: "此手機號碼不被允許",
276
+ },
277
+ },
278
+ })
279
+
280
+ setLocale("en")
281
+ expect(() => schema.parse("0911234567")).toThrow("This mobile phone number is not allowed")
282
+
283
+ setLocale("zh-TW")
284
+ expect(() => schema.parse("0911234567")).toThrow("此手機號碼不被允許")
285
+ })
286
+ })
287
+
288
+ describe("real world Taiwan mobile phone numbers", () => {
289
+ it("should validate all carrier prefixes", () => {
290
+ const schema = mobile()
291
+
292
+ // Test all valid Taiwan mobile prefixes (090-099)
293
+ const validPrefixes = ["090", "091", "092", "093", "094", "095", "096", "097", "098", "099"]
294
+
295
+ validPrefixes.forEach((prefix) => {
296
+ const phoneNumber = prefix + "1234567"
297
+ expect(schema.parse(phoneNumber)).toBe(phoneNumber)
298
+ })
299
+ })
300
+
301
+ it("should reject non-mobile phone prefixes", () => {
302
+ const schema = mobile()
303
+
304
+ // Test invalid prefixes
305
+ const invalidPrefixes = ["080", "081", "082", "083", "084", "085", "086", "087", "088", "089"]
306
+
307
+ invalidPrefixes.forEach((prefix) => {
308
+ const phoneNumber = prefix + "1234567"
309
+ expect(() => schema.parse(phoneNumber)).toThrow("Invalid Taiwan mobile phone format")
310
+ })
311
+ })
312
+
313
+ it("should validate realistic phone number patterns", () => {
314
+ const schema = mobile()
315
+
316
+ const realPhoneNumbers = ["0901234567", "0912345678", "0923456789", "0934567890", "0945678901", "0956789012", "0967890123", "0978901234", "0989012345", "0990123456"]
317
+
318
+ realPhoneNumbers.forEach((phone) => {
319
+ expect(schema.parse(phone)).toBe(phone)
320
+ })
321
+ })
322
+ })
323
+
324
+ describe("edge cases", () => {
325
+ it("should handle various input types", () => {
326
+ const schema = mobile()
327
+
328
+ // Test different input types that should be converted to string
329
+ expect(schema.parse("0901234567")).toBe("0901234567")
330
+ })
331
+
332
+ it("should handle empty and whitespace inputs", () => {
333
+ const schema = mobile()
334
+ const optionalSchema = mobile({ required: false })
335
+
336
+ expect(() => schema.parse("")).toThrow("Required")
337
+ expect(() => schema.parse(" ")).toThrow("Required")
338
+ expect(() => schema.parse("\t\n")).toThrow("Required")
339
+
340
+ expect(optionalSchema.parse("")).toBe(null)
341
+ expect(optionalSchema.parse(" ")).toBe(null)
342
+ expect(optionalSchema.parse("\t\n")).toBe(null)
343
+ })
344
+
345
+ it("should preserve valid format after transformation", () => {
346
+ const schema = mobile({
347
+ transform: (val) => val.replace(/[^0-9]/g, ""),
348
+ })
349
+
350
+ expect(schema.parse("090-123-4567")).toBe("0901234567")
351
+ expect(schema.parse("090 123 4567")).toBe("0901234567")
352
+ // Test with letters that should be filtered out, leaving a valid number
353
+ expect(schema.parse("090abc123def4567")).toBe("0901234567")
354
+ })
355
+
356
+ it("should work with complex whitelist scenarios", () => {
357
+ const schema = mobile({
358
+ whitelist: ["0901234567", "emergency", "custom-contact-123", ""],
359
+ required: false,
360
+ })
361
+
362
+ // Allowlist scenarios
363
+ expect(schema.parse("0901234567")).toBe("0901234567")
364
+ expect(schema.parse("emergency")).toBe("emergency")
365
+ expect(schema.parse("custom-contact-123")).toBe("custom-contact-123")
366
+ expect(schema.parse("")).toBe("")
367
+
368
+ // Not in the allowlist
369
+ expect(() => schema.parse("0911234567")).toThrow("Not in allowed mobile phone list")
370
+ expect(() => schema.parse("other-value")).toThrow("Not in allowed mobile phone list")
371
+ })
372
+ })
373
+ })