@hy_ong/zod-kit 0.0.2 → 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 +7 -7
  4. package/debug.js +21 -0
  5. package/debug.ts +16 -0
  6. package/dist/index.cjs +1663 -189
  7. package/dist/index.d.cts +324 -32
  8. package/dist/index.d.ts +324 -32
  9. package/dist/index.js +1634 -187
  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 +123 -49
  14. package/src/i18n/locales/zh-TW.json +123 -46
  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 +281 -54
  35. package/tests/common/text.test.ts +227 -30
  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 -37
  44. package/src/common/date.ts +0 -44
  45. package/src/common/email.ts +0 -45
  46. package/src/common/integer.ts +0 -47
  47. package/src/common/number.ts +0 -38
  48. package/src/common/password.ts +0 -34
  49. package/src/common/text.ts +0 -35
  50. package/src/common/url.ts +0 -38
  51. package/tests/common/integer.test.ts +0 -90
@@ -1,77 +1,247 @@
1
1
  import { describe, it, expect, beforeEach } from "vitest"
2
- import { setLocale, number, Locale } from "../../src"
3
-
4
- const locales = [
5
- {
6
- locale: "en",
7
- messages: {
8
- required: "TEST is required",
9
- min: "TEST must be at least 5",
10
- max: "TEST must be at most 10",
11
- },
12
- },
13
- {
14
- locale: "zh-TW",
15
- messages: {
16
- required: "請輸入 TEST",
17
- min: "TEST 最小值 5",
18
- max: "TEST 最大值 10",
19
- },
20
- },
21
- ] as const
22
-
23
- describe.each(locales)("number() locale: $locale", ({ locale, messages }) => {
24
- beforeEach(() => setLocale(locale as Locale))
25
-
26
- it("should pass with valid number", () => {
27
- const schema = number({ label: "TEST" })
28
- expect(schema.parse(42)).toBe(42)
2
+ import { number, setLocale } from "../../src"
3
+
4
+ describe("number() features", () => {
5
+ beforeEach(() => setLocale("en"))
6
+
7
+ describe("type validation", () => {
8
+ it("should accept integers when type='integer'", () => {
9
+ const schema = number({ type: "integer" })
10
+ expect(schema.parse(42)).toBe(42)
11
+ expect(schema.parse("123")).toBe(123)
12
+ })
13
+
14
+ it("should reject floats when type='integer'", () => {
15
+ const schema = number({ type: "integer" })
16
+ expect(() => schema.parse(3.14)).toThrow("Must be an integer")
17
+ expect(() => schema.parse("3.14")).toThrow("Must be an integer")
18
+ })
19
+
20
+ it("should accept floats when type='float'", () => {
21
+ const schema = number({ type: "float" })
22
+ expect(schema.parse(3.14)).toBe(3.14)
23
+ expect(schema.parse("3.14")).toBe(3.14)
24
+ })
25
+
26
+ it("should reject integers when type='float'", () => {
27
+ const schema = number({ type: "float" })
28
+ expect(() => schema.parse(42)).toThrow("Must be a decimal number")
29
+ expect(() => schema.parse("42")).toThrow("Must be a decimal number")
30
+ })
31
+
32
+ it("should accept both when type='both' (default)", () => {
33
+ const schema = number({ type: "both" })
34
+ expect(schema.parse(42)).toBe(42)
35
+ expect(schema.parse(3.14)).toBe(3.14)
36
+ })
29
37
  })
30
38
 
31
- it("should parse string number", () => {
32
- const schema = number({ label: "TEST" })
33
- expect(schema.parse("123")).toBe(123)
39
+ describe("sign validation", () => {
40
+ it("should enforce positive numbers", () => {
41
+ const schema = number({ positive: true })
42
+ expect(schema.parse(5)).toBe(5)
43
+ expect(() => schema.parse(0)).toThrow("Must be positive")
44
+ expect(() => schema.parse(-5)).toThrow("Must be positive")
45
+ })
46
+
47
+ it("should enforce negative numbers", () => {
48
+ const schema = number({ negative: true })
49
+ expect(schema.parse(-5)).toBe(-5)
50
+ expect(() => schema.parse(0)).toThrow("Must be negative")
51
+ expect(() => schema.parse(5)).toThrow("Must be negative")
52
+ })
53
+
54
+ it("should enforce non-negative numbers", () => {
55
+ const schema = number({ nonNegative: true })
56
+ expect(schema.parse(0)).toBe(0)
57
+ expect(schema.parse(5)).toBe(5)
58
+ expect(() => schema.parse(-5)).toThrow("Must be non-negative")
59
+ })
60
+
61
+ it("should enforce non-positive numbers", () => {
62
+ const schema = number({ nonPositive: true })
63
+ expect(schema.parse(0)).toBe(0)
64
+ expect(schema.parse(-5)).toBe(-5)
65
+ expect(() => schema.parse(5)).toThrow("Must be non-positive")
66
+ })
34
67
  })
35
68
 
36
- it("should fail for empty string when required", () => {
37
- const schema = number({ label: "TEST", required: true })
38
- expect(() => schema.parse("")).toThrow(messages.required)
39
- expect(() => schema.parse(null)).toThrow(messages.required)
40
- expect(() => schema.parse(undefined)).toThrow(messages.required)
69
+ describe("multipleOf validation", () => {
70
+ it("should enforce multiple of constraint", () => {
71
+ const schema = number({ multipleOf: 5 })
72
+ expect(schema.parse(0)).toBe(0)
73
+ expect(schema.parse(5)).toBe(5)
74
+ expect(schema.parse(10)).toBe(10)
75
+ expect(schema.parse(-5)).toBe(-5)
76
+ expect(() => schema.parse(3)).toThrow("Must be a multiple of 5")
77
+ expect(() => schema.parse(7)).toThrow("Must be a multiple of 5")
78
+ })
41
79
  })
42
80
 
43
- it("should allow null if not required", () => {
44
- const schema = number({ label: "TEST", required: false })
45
- expect(schema.parse("")).toBe(null)
46
- expect(schema.parse(null)).toBe(null)
47
- expect(schema.parse(undefined)).toBe(null)
81
+ describe("precision validation", () => {
82
+ it("should enforce decimal precision", () => {
83
+ const schema = number({ precision: 2 })
84
+ expect(schema.parse(3.14)).toBe(3.14)
85
+ expect(schema.parse(3.1)).toBe(3.1)
86
+ expect(schema.parse(3)).toBe(3)
87
+ expect(() => schema.parse(3.141)).toThrow("Must have at most 2 decimal places")
88
+ expect(() => schema.parse(3.1234)).toThrow("Must have at most 2 decimal places")
89
+ })
90
+ })
91
+
92
+ describe("finite validation", () => {
93
+ it("should reject infinite values by default", () => {
94
+ const schema = number()
95
+ expect(() => schema.parse(Infinity)).toThrow("Must be a finite number")
96
+ expect(() => schema.parse(-Infinity)).toThrow("Must be a finite number")
97
+ })
98
+
99
+ it("should allow infinite values when finite=false", () => {
100
+ const schema = number({ finite: false })
101
+ expect(schema.parse(Infinity)).toBe(Infinity)
102
+ expect(schema.parse(-Infinity)).toBe(-Infinity)
103
+ })
48
104
  })
49
105
 
50
- it("should apply default value with not required", () => {
51
- const schema = number({ label: "TEST", required: false, defaultValue: 42 })
52
- expect(schema.parse("")).toBe(42)
53
- expect(schema.parse(null)).toBe(42)
54
- expect(schema.parse(undefined)).toBe(42)
106
+ describe("parseCommas functionality", () => {
107
+ it("should parse comma-separated numbers", () => {
108
+ const schema = number({ parseCommas: true })
109
+ expect(schema.parse("1,234")).toBe(1234)
110
+ expect(schema.parse("1,234.56")).toBe(1234.56)
111
+ expect(schema.parse("12,345,678")).toBe(12345678)
112
+ })
113
+
114
+ it("should not parse commas by default", () => {
115
+ const schema = number()
116
+ expect(() => schema.parse("1,234")).toThrow("Must be a valid number")
117
+ })
55
118
  })
56
119
 
57
- it("should apply default value with required", () => {
58
- const schema = number({ label: "TEST", required: true, defaultValue: 42 })
59
- expect(schema.parse("")).toBe(42)
60
- expect(schema.parse(null)).toBe(42)
61
- expect(schema.parse(undefined)).toBe(42)
120
+ describe("transform functionality", () => {
121
+ it("should apply transform function", () => {
122
+ const schema = number({
123
+ transform: (val) => Math.round(val * 100) / 100, // Round to 2 decimals
124
+ })
125
+ expect(schema.parse(3.14159)).toBe(3.14)
126
+ expect(schema.parse("2.71828")).toBe(2.72)
127
+ })
62
128
  })
63
129
 
64
- it("should enforce min", () => {
65
- const schema = number({ label: "TEST", min: 5 })
66
- expect(schema.parse(5)).toBe(5)
67
- expect(() => schema.parse(2)).toThrow(messages.min)
68
- expect(() => schema.parse("2")).toThrow(messages.min)
130
+ describe("custom i18n messages", () => {
131
+ it("should use custom messages when provided", () => {
132
+ const schema = number({
133
+ type: "integer",
134
+ min: 0,
135
+ i18n: {
136
+ en: {
137
+ required: "Custom required message",
138
+ integer: "Custom integer message",
139
+ min: "Custom min: at least ${min}",
140
+ },
141
+ "zh-TW": {
142
+ required: "客製化必填訊息",
143
+ integer: "客製化整數訊息",
144
+ min: "客製化最小值: 至少 ${min}",
145
+ },
146
+ },
147
+ })
148
+
149
+ expect(() => schema.parse("")).toThrow("Custom required message")
150
+ expect(() => schema.parse(3.14)).toThrow("Custom integer message")
151
+ expect(() => schema.parse(-1)).toThrow("Custom min: at least 0")
152
+ })
153
+
154
+ it("should fallback to default messages when custom not provided", () => {
155
+ const schema = number({
156
+ type: "integer",
157
+ max: 10,
158
+ i18n: {
159
+ en: {
160
+ required: "Custom required message",
161
+ },
162
+ "zh-TW": {
163
+ required: "客製化必填訊息",
164
+ },
165
+ },
166
+ })
167
+
168
+ expect(() => schema.parse("")).toThrow("Custom required message")
169
+ expect(() => schema.parse(3.14)).toThrow("Must be an integer")
170
+ expect(() => schema.parse(15)).toThrow("Must be at most 10")
171
+ })
172
+
173
+ it("should use correct locale for custom messages", () => {
174
+ setLocale("en")
175
+ const schemaEn = number({
176
+ type: "integer",
177
+ i18n: {
178
+ en: {
179
+ integer: "English integer message",
180
+ },
181
+ "zh-TW": {
182
+ integer: "繁體中文整數訊息",
183
+ },
184
+ },
185
+ })
186
+ expect(() => schemaEn.parse(3.14)).toThrow("English integer message")
187
+
188
+ setLocale("zh-TW")
189
+ const schemaZh = number({
190
+ type: "integer",
191
+ i18n: {
192
+ en: {
193
+ integer: "English integer message",
194
+ },
195
+ "zh-TW": {
196
+ integer: "繁體中文整數訊息",
197
+ },
198
+ },
199
+ })
200
+ expect(() => schemaZh.parse(3.14)).toThrow("繁體中文整數訊息")
201
+ })
69
202
  })
70
203
 
71
- it("should enforce max", () => {
72
- const schema = number({ label: "TEST", max: 10 })
73
- expect(schema.parse(10)).toBe(10)
74
- expect(() => schema.parse(11)).toThrow(messages.max)
75
- expect(() => schema.parse("11")).toThrow(messages.max)
204
+ describe("complex scenarios", () => {
205
+ it("should work with multiple validations", () => {
206
+ const schema = number({
207
+ type: "integer",
208
+ min: 1,
209
+ max: 100,
210
+ multipleOf: 5,
211
+ positive: true,
212
+ })
213
+
214
+ expect(schema.parse(5)).toBe(5)
215
+ expect(schema.parse(10)).toBe(10)
216
+ expect(schema.parse(95)).toBe(95)
217
+
218
+ expect(() => schema.parse(0)).toThrow("Must be positive")
219
+ expect(() => schema.parse(3)).toThrow("Must be a multiple of 5")
220
+ expect(() => schema.parse(105)).toThrow("Must be at most 100")
221
+ expect(() => schema.parse(5.5)).toThrow("Must be an integer")
222
+ })
223
+
224
+ it("should handle null values correctly when not required", () => {
225
+ const schema = number({
226
+ required: false,
227
+ type: "integer",
228
+ min: 0,
229
+ })
230
+
231
+ expect(schema.parse(null)).toBe(null)
232
+ expect(schema.parse("")).toBe(null)
233
+ expect(schema.parse(undefined)).toBe(null)
234
+ expect(schema.parse(5)).toBe(5)
235
+ })
236
+
237
+ it("should work with parseCommas and transform together", () => {
238
+ const schema = number({
239
+ parseCommas: true,
240
+ transform: (val) => Math.floor(val / 100) * 100, // Round down to nearest 100
241
+ })
242
+
243
+ expect(schema.parse("1,234")).toBe(1200)
244
+ expect(schema.parse("5,678.90")).toBe(5600)
245
+ })
76
246
  })
77
247
  })