@hy_ong/zod-kit 0.0.5 → 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.
- package/.claude/settings.local.json +6 -1
- package/README.md +465 -97
- package/dist/index.cjs +1628 -121
- package/dist/index.d.cts +2699 -2
- package/dist/index.d.ts +2699 -2
- package/dist/index.js +1610 -120
- package/package.json +1 -1
- package/src/i18n/locales/en.json +62 -0
- package/src/i18n/locales/zh-TW.json +62 -0
- package/src/index.ts +4 -0
- package/src/validators/common/boolean.ts +94 -0
- package/src/validators/common/date.ts +128 -0
- package/src/validators/common/datetime.ts +673 -0
- package/src/validators/common/email.ts +113 -0
- package/src/validators/common/file.ts +384 -0
- package/src/validators/common/id.ts +224 -12
- package/src/validators/common/number.ts +125 -0
- package/src/validators/common/password.ts +174 -2
- package/src/validators/common/text.ts +120 -0
- package/src/validators/common/time.ts +600 -0
- package/src/validators/common/url.ts +140 -0
- package/src/validators/taiwan/business-id.ts +124 -2
- package/src/validators/taiwan/fax.ts +147 -2
- package/src/validators/taiwan/mobile.ts +134 -2
- package/src/validators/taiwan/national-id.ts +227 -10
- package/src/validators/taiwan/postal-code.ts +1049 -0
- package/src/validators/taiwan/tel.ts +150 -2
- package/tests/common/datetime.test.ts +693 -0
- package/tests/common/file.test.ts +479 -0
- package/tests/common/time.test.ts +528 -0
- package/tests/taiwan/postal-code.test.ts +705 -0
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from "vitest"
|
|
2
|
+
import { time, setLocale, validateTimeFormat, parseTimeToMinutes, normalizeTime } from "../../src"
|
|
3
|
+
|
|
4
|
+
describe("Taiwan time() validator", () => {
|
|
5
|
+
beforeEach(() => setLocale("en"))
|
|
6
|
+
|
|
7
|
+
describe("basic functionality", () => {
|
|
8
|
+
it("should validate correct time formats", () => {
|
|
9
|
+
const schema24 = time({ format: "HH:mm" })
|
|
10
|
+
const schema12 = time({ format: "hh:mm A" })
|
|
11
|
+
const schemaWithSeconds = time({ format: "HH:mm:ss" })
|
|
12
|
+
|
|
13
|
+
// 24-hour format
|
|
14
|
+
expect(schema24.parse("09:30")).toBe("09:30")
|
|
15
|
+
expect(schema24.parse("14:45")).toBe("14:45")
|
|
16
|
+
expect(schema24.parse("00:00")).toBe("00:00")
|
|
17
|
+
expect(schema24.parse("23:59")).toBe("23:59")
|
|
18
|
+
|
|
19
|
+
// 12-hour format
|
|
20
|
+
expect(schema12.parse("09:30 AM")).toBe("09:30 AM")
|
|
21
|
+
expect(schema12.parse("02:45 PM")).toBe("02:45 PM")
|
|
22
|
+
expect(schema12.parse("12:00 AM")).toBe("12:00 AM")
|
|
23
|
+
expect(schema12.parse("12:00 PM")).toBe("12:00 PM")
|
|
24
|
+
|
|
25
|
+
// With seconds
|
|
26
|
+
expect(schemaWithSeconds.parse("14:30:45")).toBe("14:30:45")
|
|
27
|
+
expect(schemaWithSeconds.parse("00:00:00")).toBe("00:00:00")
|
|
28
|
+
expect(schemaWithSeconds.parse("23:59:59")).toBe("23:59:59")
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it("should validate single digit hour format", () => {
|
|
32
|
+
const schemaH = time({ format: "H:mm" })
|
|
33
|
+
const schemah = time({ format: "h:mm A" })
|
|
34
|
+
|
|
35
|
+
// Single digit 24-hour
|
|
36
|
+
expect(schemaH.parse("9:30")).toBe("9:30")
|
|
37
|
+
expect(schemaH.parse("14:45")).toBe("14:45")
|
|
38
|
+
|
|
39
|
+
// Single digit 12-hour
|
|
40
|
+
expect(schemah.parse("9:30 AM")).toBe("9:30 AM")
|
|
41
|
+
expect(schemah.parse("2:45 PM")).toBe("2:45 PM")
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it("should reject invalid time formats", () => {
|
|
45
|
+
const schema = time({ format: "HH:mm" })
|
|
46
|
+
|
|
47
|
+
// Invalid formats
|
|
48
|
+
expect(() => schema.parse("25:30")).toThrow("Must be in HH:mm format")
|
|
49
|
+
expect(() => schema.parse("14:70")).toThrow("Must be in HH:mm format")
|
|
50
|
+
expect(() => schema.parse("abc")).toThrow("Must be in HH:mm format")
|
|
51
|
+
expect(() => schema.parse("14")).toThrow("Must be in HH:mm format")
|
|
52
|
+
expect(() => schema.parse("14:30:45")).toThrow("Must be in HH:mm format") // seconds not allowed
|
|
53
|
+
expect(() => schema.parse("2:30 PM")).toThrow("Must be in HH:mm format") // 12-hour in 24-hour format
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it("should handle whitespace trimming", () => {
|
|
57
|
+
const schema = time({ format: "HH:mm" })
|
|
58
|
+
|
|
59
|
+
expect(schema.parse(" 14:30 ")).toBe("14:30")
|
|
60
|
+
expect(schema.parse("\t09:15\n")).toBe("09:15")
|
|
61
|
+
})
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
describe("whitelist functionality", () => {
|
|
65
|
+
it("should accept any string in whitelist regardless of format", () => {
|
|
66
|
+
const schema = time({
|
|
67
|
+
format: "HH:mm",
|
|
68
|
+
whitelist: ["anytime", "flexible", "TBD"]
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
expect(schema.parse("anytime")).toBe("anytime")
|
|
72
|
+
expect(schema.parse("flexible")).toBe("flexible")
|
|
73
|
+
expect(schema.parse("TBD")).toBe("TBD")
|
|
74
|
+
expect(schema.parse("14:30")).toBe("14:30") // Valid time still works
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it("should reject values not in whitelist when whitelistOnly is true", () => {
|
|
78
|
+
const schema = time({
|
|
79
|
+
format: "HH:mm",
|
|
80
|
+
whitelist: ["morning", "14:30"],
|
|
81
|
+
whitelistOnly: true
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
expect(schema.parse("morning")).toBe("morning")
|
|
85
|
+
expect(schema.parse("14:30")).toBe("14:30")
|
|
86
|
+
|
|
87
|
+
// Invalid times not in whitelist should be rejected
|
|
88
|
+
expect(() => schema.parse("15:30")).toThrow("Time is not in the allowed list")
|
|
89
|
+
expect(() => schema.parse("evening")).toThrow("Time is not in the allowed list")
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it("should work with empty whitelist", () => {
|
|
93
|
+
const schema = time({
|
|
94
|
+
format: "HH:mm",
|
|
95
|
+
whitelist: []
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
// With empty whitelist, should still validate time format
|
|
99
|
+
expect(schema.parse("14:30")).toBe("14:30")
|
|
100
|
+
expect(() => schema.parse("invalid")).toThrow("Must be in HH:mm format")
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
it("should prioritize whitelist over format validation", () => {
|
|
104
|
+
const schema = time({
|
|
105
|
+
required: false,
|
|
106
|
+
format: "HH:mm",
|
|
107
|
+
whitelist: ["not-a-time", "123", ""]
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
expect(schema.parse("not-a-time")).toBe("not-a-time")
|
|
111
|
+
expect(schema.parse("123")).toBe("123")
|
|
112
|
+
expect(schema.parse("")).toBe("")
|
|
113
|
+
})
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
describe("required/optional behavior", () => {
|
|
117
|
+
it("should handle required=true (default)", () => {
|
|
118
|
+
const schema = time({ format: "HH:mm" })
|
|
119
|
+
|
|
120
|
+
expect(() => schema.parse("")).toThrow("Required")
|
|
121
|
+
expect(() => schema.parse(null)).toThrow("Required")
|
|
122
|
+
expect(() => schema.parse(undefined)).toThrow("Required")
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
it("should handle required=false", () => {
|
|
126
|
+
const schema = time({ format: "HH:mm", required: false })
|
|
127
|
+
|
|
128
|
+
expect(schema.parse("")).toBe(null)
|
|
129
|
+
expect(schema.parse(null)).toBe(null)
|
|
130
|
+
expect(schema.parse(undefined)).toBe(null)
|
|
131
|
+
expect(schema.parse("14:30")).toBe("14:30")
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
it("should use default values", () => {
|
|
135
|
+
const requiredSchema = time({ format: "HH:mm", defaultValue: "09:00" })
|
|
136
|
+
const optionalSchema = time({ format: "HH:mm", required: false, defaultValue: "12:00" })
|
|
137
|
+
|
|
138
|
+
expect(requiredSchema.parse("")).toBe("09:00")
|
|
139
|
+
expect(requiredSchema.parse(null)).toBe("09:00")
|
|
140
|
+
expect(optionalSchema.parse("")).toBe("12:00")
|
|
141
|
+
expect(optionalSchema.parse(null)).toBe("12:00")
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
it("should handle whitelist with optional fields", () => {
|
|
145
|
+
const schema = time({
|
|
146
|
+
format: "HH:mm",
|
|
147
|
+
required: false,
|
|
148
|
+
whitelist: ["flexible", "14:30"],
|
|
149
|
+
whitelistOnly: true
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
expect(schema.parse("")).toBe(null)
|
|
153
|
+
expect(schema.parse("flexible")).toBe("flexible")
|
|
154
|
+
expect(schema.parse("14:30")).toBe("14:30")
|
|
155
|
+
expect(() => schema.parse("15:30")).toThrow("Time is not in the allowed list")
|
|
156
|
+
})
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
describe("time range validation", () => {
|
|
160
|
+
it("should validate minimum and maximum times", () => {
|
|
161
|
+
const schema = time({
|
|
162
|
+
format: "HH:mm",
|
|
163
|
+
min: "09:00",
|
|
164
|
+
max: "17:00"
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
// Valid times within range
|
|
168
|
+
expect(schema.parse("09:00")).toBe("09:00")
|
|
169
|
+
expect(schema.parse("12:30")).toBe("12:30")
|
|
170
|
+
expect(schema.parse("17:00")).toBe("17:00")
|
|
171
|
+
|
|
172
|
+
// Invalid times outside range
|
|
173
|
+
expect(() => schema.parse("08:59")).toThrow("Time must be after")
|
|
174
|
+
expect(() => schema.parse("17:01")).toThrow("Time must be before")
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
it("should validate hour ranges", () => {
|
|
178
|
+
const schema = time({
|
|
179
|
+
format: "HH:mm",
|
|
180
|
+
minHour: 9,
|
|
181
|
+
maxHour: 17
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
// Valid hours
|
|
185
|
+
expect(schema.parse("09:30")).toBe("09:30")
|
|
186
|
+
expect(schema.parse("17:45")).toBe("17:45")
|
|
187
|
+
|
|
188
|
+
// Invalid hours
|
|
189
|
+
expect(() => schema.parse("08:30")).toThrow("Hour must be between")
|
|
190
|
+
expect(() => schema.parse("18:30")).toThrow("Hour must be between")
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
it("should validate allowed hours", () => {
|
|
194
|
+
const schema = time({
|
|
195
|
+
format: "HH:mm",
|
|
196
|
+
allowedHours: [9, 12, 15, 18]
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
// Valid hours
|
|
200
|
+
expect(schema.parse("09:30")).toBe("09:30")
|
|
201
|
+
expect(schema.parse("12:00")).toBe("12:00")
|
|
202
|
+
expect(schema.parse("15:45")).toBe("15:45")
|
|
203
|
+
expect(schema.parse("18:00")).toBe("18:00")
|
|
204
|
+
|
|
205
|
+
// Invalid hours
|
|
206
|
+
expect(() => schema.parse("10:30")).toThrow("Hour must be between")
|
|
207
|
+
expect(() => schema.parse("16:30")).toThrow("Hour must be between")
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
it("should validate minute steps", () => {
|
|
211
|
+
const schema = time({
|
|
212
|
+
format: "HH:mm",
|
|
213
|
+
minuteStep: 15
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
// Valid minute steps (0, 15, 30, 45)
|
|
217
|
+
expect(schema.parse("14:00")).toBe("14:00")
|
|
218
|
+
expect(schema.parse("14:15")).toBe("14:15")
|
|
219
|
+
expect(schema.parse("14:30")).toBe("14:30")
|
|
220
|
+
expect(schema.parse("14:45")).toBe("14:45")
|
|
221
|
+
|
|
222
|
+
// Invalid minute steps
|
|
223
|
+
expect(() => schema.parse("14:05")).toThrow("minute")
|
|
224
|
+
expect(() => schema.parse("14:37")).toThrow("minute")
|
|
225
|
+
})
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
describe("transform function", () => {
|
|
229
|
+
it("should apply custom transform", () => {
|
|
230
|
+
const schema = time({
|
|
231
|
+
format: "HH:mm",
|
|
232
|
+
transform: (val) => val.toUpperCase()
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
expect(schema.parse("14:30")).toBe("14:30")
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
it("should apply transform before validation", () => {
|
|
239
|
+
const schema = time({
|
|
240
|
+
format: "HH:mm",
|
|
241
|
+
transform: (val) => val.replace(/\s+/g, "")
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
expect(schema.parse("1 4 : 3 0")).toBe("14:30")
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
it("should work with whitelist after transform", () => {
|
|
248
|
+
const schema = time({
|
|
249
|
+
format: "HH:mm",
|
|
250
|
+
transform: (val) => val.toLowerCase(),
|
|
251
|
+
whitelist: ["morning", "14:30"]
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
expect(schema.parse("MORNING")).toBe("morning")
|
|
255
|
+
expect(schema.parse("14:30")).toBe("14:30")
|
|
256
|
+
})
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
describe("input preprocessing", () => {
|
|
260
|
+
it("should handle string conversion", () => {
|
|
261
|
+
const schema = time({ format: "HH:mm" })
|
|
262
|
+
|
|
263
|
+
// Test string conversion of numbers
|
|
264
|
+
expect(() => schema.parse(1430)).toThrow("Must be in HH:mm format") // Invalid because not in HH:mm format
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
it("should trim whitespace", () => {
|
|
268
|
+
const schema = time({ format: "HH:mm" })
|
|
269
|
+
|
|
270
|
+
expect(schema.parse(" 14:30 ")).toBe("14:30")
|
|
271
|
+
expect(schema.parse("\t09:15\n")).toBe("09:15")
|
|
272
|
+
})
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
describe("utility function", () => {
|
|
276
|
+
describe("validateTimeFormat", () => {
|
|
277
|
+
it("should correctly validate time formats", () => {
|
|
278
|
+
// 24-hour format
|
|
279
|
+
expect(validateTimeFormat("14:30", "HH:mm")).toBe(true)
|
|
280
|
+
expect(validateTimeFormat("09:00", "HH:mm")).toBe(true)
|
|
281
|
+
expect(validateTimeFormat("25:30", "HH:mm")).toBe(false)
|
|
282
|
+
expect(validateTimeFormat("14:70", "HH:mm")).toBe(false)
|
|
283
|
+
|
|
284
|
+
// 12-hour format
|
|
285
|
+
expect(validateTimeFormat("02:30 PM", "hh:mm A")).toBe(true)
|
|
286
|
+
expect(validateTimeFormat("12:00 AM", "hh:mm A")).toBe(true)
|
|
287
|
+
expect(validateTimeFormat("13:30 PM", "hh:mm A")).toBe(false)
|
|
288
|
+
expect(validateTimeFormat("02:30", "hh:mm A")).toBe(false)
|
|
289
|
+
|
|
290
|
+
// With seconds
|
|
291
|
+
expect(validateTimeFormat("14:30:45", "HH:mm:ss")).toBe(true)
|
|
292
|
+
expect(validateTimeFormat("14:30:70", "HH:mm:ss")).toBe(false)
|
|
293
|
+
|
|
294
|
+
// Single digit hours
|
|
295
|
+
expect(validateTimeFormat("9:30", "H:mm")).toBe(true)
|
|
296
|
+
expect(validateTimeFormat("9:30 AM", "h:mm A")).toBe(true)
|
|
297
|
+
})
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
describe("parseTimeToMinutes", () => {
|
|
301
|
+
it("should parse 24-hour format correctly", () => {
|
|
302
|
+
expect(parseTimeToMinutes("00:00", "HH:mm")).toBe(0)
|
|
303
|
+
expect(parseTimeToMinutes("12:30", "HH:mm")).toBe(750) // 12*60 + 30
|
|
304
|
+
expect(parseTimeToMinutes("23:59", "HH:mm")).toBe(1439) // 23*60 + 59
|
|
305
|
+
})
|
|
306
|
+
|
|
307
|
+
it("should parse 12-hour format correctly", () => {
|
|
308
|
+
expect(parseTimeToMinutes("12:00 AM", "hh:mm A")).toBe(0)
|
|
309
|
+
expect(parseTimeToMinutes("12:30 PM", "hh:mm A")).toBe(750)
|
|
310
|
+
expect(parseTimeToMinutes("11:59 PM", "hh:mm A")).toBe(1439)
|
|
311
|
+
expect(parseTimeToMinutes("01:30 PM", "hh:mm A")).toBe(810) // 13*60 + 30
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
it("should handle invalid times", () => {
|
|
315
|
+
expect(parseTimeToMinutes("25:30", "HH:mm")).toBe(null)
|
|
316
|
+
expect(parseTimeToMinutes("14:70", "HH:mm")).toBe(null)
|
|
317
|
+
expect(parseTimeToMinutes("invalid", "HH:mm")).toBe(null)
|
|
318
|
+
})
|
|
319
|
+
})
|
|
320
|
+
|
|
321
|
+
describe("normalizeTime", () => {
|
|
322
|
+
it("should normalize 12-hour to 24-hour format", () => {
|
|
323
|
+
expect(normalizeTime("12:00 AM", "hh:mm A")).toBe("00:00")
|
|
324
|
+
expect(normalizeTime("12:30 PM", "hh:mm A")).toBe("12:30")
|
|
325
|
+
expect(normalizeTime("01:30 PM", "hh:mm A")).toBe("13:30")
|
|
326
|
+
expect(normalizeTime("11:59 PM", "hh:mm A")).toBe("23:59")
|
|
327
|
+
})
|
|
328
|
+
|
|
329
|
+
it("should normalize single digit hours", () => {
|
|
330
|
+
expect(normalizeTime("9:30", "H:mm")).toBe("09:30")
|
|
331
|
+
expect(normalizeTime("14:30", "H:mm")).toBe("14:30")
|
|
332
|
+
})
|
|
333
|
+
|
|
334
|
+
it("should handle seconds format", () => {
|
|
335
|
+
expect(normalizeTime("01:30:45 PM", "hh:mm:ss A")).toBe("13:30:45")
|
|
336
|
+
})
|
|
337
|
+
})
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
describe("i18n support", () => {
|
|
341
|
+
it("should use English messages by default", () => {
|
|
342
|
+
setLocale("en")
|
|
343
|
+
const schema = time({ format: "HH:mm" })
|
|
344
|
+
|
|
345
|
+
expect(() => schema.parse("")).toThrow("Required")
|
|
346
|
+
expect(() => schema.parse("invalid")).toThrow("Must be in HH:mm format")
|
|
347
|
+
})
|
|
348
|
+
|
|
349
|
+
it("should use Chinese messages when locale is zh-TW", () => {
|
|
350
|
+
setLocale("zh-TW")
|
|
351
|
+
const schema = time({ format: "HH:mm" })
|
|
352
|
+
|
|
353
|
+
expect(() => schema.parse("")).toThrow("必填")
|
|
354
|
+
expect(() => schema.parse("invalid")).toThrow("必須為 HH:mm 格式")
|
|
355
|
+
})
|
|
356
|
+
|
|
357
|
+
it("should support whitelist error messages", () => {
|
|
358
|
+
setLocale("en")
|
|
359
|
+
const schema = time({
|
|
360
|
+
format: "HH:mm",
|
|
361
|
+
whitelist: ["morning"],
|
|
362
|
+
whitelistOnly: true
|
|
363
|
+
})
|
|
364
|
+
|
|
365
|
+
expect(() => schema.parse("14:30")).toThrow("Time is not in the allowed list")
|
|
366
|
+
})
|
|
367
|
+
|
|
368
|
+
it("should support custom i18n messages", () => {
|
|
369
|
+
const schema = time({
|
|
370
|
+
format: "HH:mm",
|
|
371
|
+
i18n: {
|
|
372
|
+
en: {
|
|
373
|
+
required: "Time is required",
|
|
374
|
+
invalid: "Please enter a valid time"
|
|
375
|
+
},
|
|
376
|
+
"zh-TW": {
|
|
377
|
+
required: "請輸入時間",
|
|
378
|
+
invalid: "請輸入有效的時間格式"
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
})
|
|
382
|
+
|
|
383
|
+
setLocale("en")
|
|
384
|
+
expect(() => schema.parse("")).toThrow("Time is required")
|
|
385
|
+
|
|
386
|
+
setLocale("zh-TW")
|
|
387
|
+
expect(() => schema.parse("")).toThrow("請輸入時間")
|
|
388
|
+
})
|
|
389
|
+
|
|
390
|
+
it("should support custom whitelist messages", () => {
|
|
391
|
+
const schema = time({
|
|
392
|
+
format: "HH:mm",
|
|
393
|
+
whitelist: ["morning"],
|
|
394
|
+
whitelistOnly: true,
|
|
395
|
+
i18n: {
|
|
396
|
+
en: {
|
|
397
|
+
notInWhitelist: "This time is not allowed"
|
|
398
|
+
},
|
|
399
|
+
"zh-TW": {
|
|
400
|
+
notInWhitelist: "此時間不被允許"
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
})
|
|
404
|
+
|
|
405
|
+
setLocale("en")
|
|
406
|
+
expect(() => schema.parse("14:30")).toThrow("This time is not allowed")
|
|
407
|
+
|
|
408
|
+
setLocale("zh-TW")
|
|
409
|
+
expect(() => schema.parse("14:30")).toThrow("此時間不被允許")
|
|
410
|
+
})
|
|
411
|
+
})
|
|
412
|
+
|
|
413
|
+
describe("real world time scenarios", () => {
|
|
414
|
+
it("should validate business hours", () => {
|
|
415
|
+
const businessHours = time({
|
|
416
|
+
format: "HH:mm",
|
|
417
|
+
min: "09:00",
|
|
418
|
+
max: "17:00",
|
|
419
|
+
minuteStep: 30
|
|
420
|
+
})
|
|
421
|
+
|
|
422
|
+
expect(businessHours.parse("09:00")).toBe("09:00")
|
|
423
|
+
expect(businessHours.parse("12:30")).toBe("12:30")
|
|
424
|
+
expect(businessHours.parse("17:00")).toBe("17:00")
|
|
425
|
+
|
|
426
|
+
expect(() => businessHours.parse("08:30")).toThrow("Time must be after")
|
|
427
|
+
expect(() => businessHours.parse("17:30")).toThrow("Time must be before")
|
|
428
|
+
expect(() => businessHours.parse("12:15")).toThrow("minute")
|
|
429
|
+
})
|
|
430
|
+
|
|
431
|
+
it("should validate appointment slots", () => {
|
|
432
|
+
const appointmentSlots = time({
|
|
433
|
+
format: "hh:mm A",
|
|
434
|
+
allowedHours: [9, 10, 11, 14, 15, 16],
|
|
435
|
+
minuteStep: 15
|
|
436
|
+
})
|
|
437
|
+
|
|
438
|
+
expect(appointmentSlots.parse("09:00 AM")).toBe("09:00 AM")
|
|
439
|
+
expect(appointmentSlots.parse("02:15 PM")).toBe("02:15 PM")
|
|
440
|
+
expect(appointmentSlots.parse("04:45 PM")).toBe("04:45 PM")
|
|
441
|
+
|
|
442
|
+
expect(() => appointmentSlots.parse("12:00 PM")).toThrow("Hour must be between")
|
|
443
|
+
expect(() => appointmentSlots.parse("09:05 AM")).toThrow("minute")
|
|
444
|
+
})
|
|
445
|
+
|
|
446
|
+
it("should handle flexible time input", () => {
|
|
447
|
+
const flexibleTime = time({
|
|
448
|
+
format: "HH:mm",
|
|
449
|
+
whitelist: ["morning", "afternoon", "evening", "anytime"],
|
|
450
|
+
required: false
|
|
451
|
+
})
|
|
452
|
+
|
|
453
|
+
expect(flexibleTime.parse("morning")).toBe("morning")
|
|
454
|
+
expect(flexibleTime.parse("14:30")).toBe("14:30")
|
|
455
|
+
expect(flexibleTime.parse("")).toBe(null)
|
|
456
|
+
expect(() => flexibleTime.parse("invalid")).toThrow("Must be in HH:mm format")
|
|
457
|
+
})
|
|
458
|
+
})
|
|
459
|
+
|
|
460
|
+
describe("edge cases", () => {
|
|
461
|
+
it("should handle various input types", () => {
|
|
462
|
+
const schema = time({ format: "HH:mm" })
|
|
463
|
+
|
|
464
|
+
// Test different input types that should be converted to string
|
|
465
|
+
expect(schema.parse("14:30")).toBe("14:30")
|
|
466
|
+
})
|
|
467
|
+
|
|
468
|
+
it("should handle empty and whitespace inputs", () => {
|
|
469
|
+
const requiredSchema = time({ format: "HH:mm", required: true })
|
|
470
|
+
const optionalSchema = time({ format: "HH:mm", required: false })
|
|
471
|
+
|
|
472
|
+
expect(() => requiredSchema.parse("")).toThrow("Required")
|
|
473
|
+
expect(() => requiredSchema.parse(" ")).toThrow("Required")
|
|
474
|
+
|
|
475
|
+
expect(optionalSchema.parse("")).toBe(null)
|
|
476
|
+
expect(optionalSchema.parse(" ")).toBe(null)
|
|
477
|
+
})
|
|
478
|
+
|
|
479
|
+
it("should preserve valid format after transformation", () => {
|
|
480
|
+
const schema = time({
|
|
481
|
+
format: "HH:mm",
|
|
482
|
+
transform: (val) => val.replace(/[^0-9:]/g, "").replace(/^(\d{2})(\d{2})$/, "$1:$2")
|
|
483
|
+
})
|
|
484
|
+
|
|
485
|
+
expect(schema.parse("14abc:30def")).toBe("14:30")
|
|
486
|
+
expect(schema.parse("0915")).toBe("09:15")
|
|
487
|
+
})
|
|
488
|
+
|
|
489
|
+
it("should work with complex whitelist scenarios", () => {
|
|
490
|
+
const schema = time({
|
|
491
|
+
format: "HH:mm",
|
|
492
|
+
whitelist: ["14:30", "TBD", "flexible", ""],
|
|
493
|
+
whitelistOnly: true,
|
|
494
|
+
required: false
|
|
495
|
+
})
|
|
496
|
+
|
|
497
|
+
// Whitelist scenarios
|
|
498
|
+
expect(schema.parse("14:30")).toBe("14:30")
|
|
499
|
+
expect(schema.parse("TBD")).toBe("TBD")
|
|
500
|
+
expect(schema.parse("flexible")).toBe("flexible")
|
|
501
|
+
expect(schema.parse("")).toBe("")
|
|
502
|
+
|
|
503
|
+
// Not in the whitelist
|
|
504
|
+
expect(() => schema.parse("15:30")).toThrow("Time is not in the allowed list")
|
|
505
|
+
expect(() => schema.parse("other-value")).toThrow("Time is not in the allowed list")
|
|
506
|
+
})
|
|
507
|
+
|
|
508
|
+
it("should handle boundary cases for different formats", () => {
|
|
509
|
+
const schema24 = time({ format: "HH:mm" })
|
|
510
|
+
const schema12 = time({ format: "hh:mm A" })
|
|
511
|
+
|
|
512
|
+
// 24-hour boundary cases
|
|
513
|
+
expect(schema24.parse("00:00")).toBe("00:00")
|
|
514
|
+
expect(schema24.parse("23:59")).toBe("23:59")
|
|
515
|
+
|
|
516
|
+
// 12-hour boundary cases
|
|
517
|
+
expect(schema12.parse("12:00 AM")).toBe("12:00 AM") // Midnight
|
|
518
|
+
expect(schema12.parse("12:00 PM")).toBe("12:00 PM") // Noon
|
|
519
|
+
expect(schema12.parse("01:00 AM")).toBe("01:00 AM")
|
|
520
|
+
expect(schema12.parse("11:59 PM")).toBe("11:59 PM")
|
|
521
|
+
|
|
522
|
+
// Invalid boundary cases
|
|
523
|
+
expect(() => schema24.parse("24:00")).toThrow("Must be in")
|
|
524
|
+
expect(() => schema12.parse("00:00 AM")).toThrow("Must be in")
|
|
525
|
+
expect(() => schema12.parse("13:00 PM")).toThrow("Must be in")
|
|
526
|
+
})
|
|
527
|
+
})
|
|
528
|
+
})
|