@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.
- package/.claude/settings.local.json +23 -0
- package/LICENSE +21 -0
- package/README.md +5 -5
- package/debug.js +21 -0
- package/debug.ts +16 -0
- package/dist/index.cjs +1619 -145
- package/dist/index.d.cts +324 -25
- package/dist/index.d.ts +324 -25
- package/dist/index.js +1590 -143
- package/eslint.config.mts +8 -0
- package/package.json +10 -9
- package/src/config.ts +1 -1
- package/src/i18n/locales/en.json +99 -25
- package/src/i18n/locales/zh-TW.json +103 -26
- package/src/index.ts +13 -7
- package/src/validators/common/boolean.ts +97 -0
- package/src/validators/common/date.ts +171 -0
- package/src/validators/common/email.ts +200 -0
- package/src/validators/common/id.ts +259 -0
- package/src/validators/common/number.ts +194 -0
- package/src/validators/common/password.ts +214 -0
- package/src/validators/common/text.ts +151 -0
- package/src/validators/common/url.ts +207 -0
- package/src/validators/taiwan/business-id.ts +140 -0
- package/src/validators/taiwan/fax.ts +182 -0
- package/src/validators/taiwan/mobile.ts +110 -0
- package/src/validators/taiwan/national-id.ts +208 -0
- package/src/validators/taiwan/tel.ts +182 -0
- package/tests/common/boolean.test.ts +340 -92
- package/tests/common/date.test.ts +458 -0
- package/tests/common/email.test.ts +232 -60
- package/tests/common/id.test.ts +535 -0
- package/tests/common/number.test.ts +230 -60
- package/tests/common/password.test.ts +271 -44
- package/tests/common/text.test.ts +210 -13
- package/tests/common/url.test.ts +492 -67
- package/tests/taiwan/business-id.test.ts +240 -0
- package/tests/taiwan/fax.test.ts +463 -0
- package/tests/taiwan/mobile.test.ts +373 -0
- package/tests/taiwan/national-id.test.ts +435 -0
- package/tests/taiwan/tel.test.ts +467 -0
- package/eslint.config.mjs +0 -10
- package/src/common/boolean.ts +0 -36
- package/src/common/date.ts +0 -43
- package/src/common/email.ts +0 -44
- package/src/common/integer.ts +0 -46
- package/src/common/number.ts +0 -37
- package/src/common/password.ts +0 -33
- package/src/common/text.ts +0 -34
- package/src/common/url.ts +0 -37
- 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
|
+
})
|