@hy_ong/zod-kit 0.0.1

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.
@@ -0,0 +1,65 @@
1
+ {
2
+ "common": {
3
+ "boolean": {
4
+ "required": "請輸入 ${label}",
5
+ "shouldBe": {
6
+ "true": "${label} 必須為是",
7
+ "false": "${label} 必須為否"
8
+ }
9
+ },
10
+ "email": {
11
+ "required": "請輸入 ${label}",
12
+ "min": "${label} 長度至少 ${min} 字元",
13
+ "max": "${label} 長度最多 ${max} 字元",
14
+ "includes": "${label} 必須包含「${includes}」",
15
+ "invalid": "${label} 格式錯誤",
16
+ "domain": "${label} 必須為 @${domain} 網域"
17
+ },
18
+ "url": {
19
+ "required": "請輸入 ${label}",
20
+ "min": "${label} 長度至少 ${min} 字元",
21
+ "max": "${label} 長度最多 ${max} 字元",
22
+ "includes": "${label} 必須包含「${includes}」",
23
+ "invalid": "${label} 格式錯誤"
24
+ },
25
+ "password": {
26
+ "required": "請輸入 ${label}",
27
+ "min": "${label} 長度至少 ${min} 字元",
28
+ "max": "${label} 長度最多 ${max} 字元",
29
+ "uppercase": "${label} 必須包含至少一個大寫字母",
30
+ "lowercase": "${label} 必須包含至少一個小寫字母",
31
+ "digits": "${label} 必須包含至少一個數字",
32
+ "special": "${label} 必須包含至少一個特殊符號"
33
+ },
34
+ "number": {
35
+ "required": "請輸入 ${label}",
36
+ "min": "${label} 最小值 ${min}",
37
+ "max": "${label} 最大值 ${max}"
38
+ },
39
+ "id": {
40
+ "required": "請選擇 ${label}",
41
+ "invalid": "${label} 無效"
42
+ },
43
+ "integer": {
44
+ "required": "請輸入 ${label}",
45
+ "min": "${label} 最小值 ${min}",
46
+ "max": "${label} 最大值 ${max}",
47
+ "integer": "${label} 必須為整數"
48
+ },
49
+ "float": {
50
+ "required": "請輸入 ${label}",
51
+ "float": "${label} 必須為浮點數",
52
+ "min": "${label} 最小值 ${min}",
53
+ "max": "${label} 最大值 ${max}"
54
+ },
55
+ "text": {
56
+ "required": "請輸入 ${label}",
57
+ "min": "${label} 長度至少 ${min} 字元",
58
+ "max": "${label} 長度最多 ${max} 字元",
59
+ "startsWith": "${label} 必須以「${startsWith}」開頭",
60
+ "endsWith": "${label} 必須以「${endsWith}」結尾",
61
+ "includes": "${label} 必須包含「${includes}」",
62
+ "invalid": "${label} 格式錯誤"
63
+ }
64
+ }
65
+ }
package/src/index.ts ADDED
@@ -0,0 +1,8 @@
1
+ export * from "./common/boolean"
2
+ export * from "./common/email"
3
+ export * from "./common/url"
4
+ export * from "./common/text"
5
+ export * from "./common/number"
6
+ export * from "./common/password"
7
+ export * from "./common/integer"
8
+ export * from "./config"
@@ -0,0 +1,118 @@
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: "TEST is required",
9
+ shouldBeTrue: "TEST must be True",
10
+ shouldBeFalse: "TEST must be False",
11
+ },
12
+ },
13
+ {
14
+ locale: "zh-TW",
15
+ messages: {
16
+ required: "請輸入 TEST",
17
+ shouldBeTrue: "TEST 必須為是",
18
+ shouldBeFalse: "TEST 必須為否",
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({ label: "TEST", required: true })
28
+ expect(schema.parse(true)).toBe(true)
29
+ expect(schema.parse("true")).toBe(true)
30
+ expect(schema.parse(1)).toBe(true)
31
+ })
32
+
33
+ it("should parse false values correctly", () => {
34
+ const schema = boolean({ label: "TEST", required: true })
35
+ expect(schema.parse(false)).toBe(false)
36
+ expect(schema.parse("false")).toBe(false)
37
+ expect(schema.parse(0)).toBe(false)
38
+ })
39
+
40
+ it("should throw when input is empty and required", () => {
41
+ const schema = boolean({ label: "TEST", 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)
45
+ })
46
+
47
+ it("should return null when not required and empty input", () => {
48
+ const schema = boolean({ label: "TEST", required: false })
49
+ expect(schema.parse(undefined)).toBe(null)
50
+ expect(schema.parse(null)).toBe(null)
51
+ expect(schema.parse("")).toBe(null)
52
+ })
53
+
54
+ it("should apply defaultValue when required", () => {
55
+ const schema = boolean({ label: "TEST", required: true, defaultValue: true })
56
+ expect(schema.parse("")).toBe(true)
57
+ expect(schema.parse(null)).toBe(true)
58
+ expect(schema.parse(undefined)).toBe(true)
59
+
60
+ const schema2 = boolean({ label: "TEST", required: true, defaultValue: false })
61
+ expect(schema2.parse("")).toBe(false)
62
+ expect(schema2.parse(null)).toBe(false)
63
+ expect(schema2.parse(undefined)).toBe(false)
64
+ })
65
+
66
+ it("should apply defaultValue when not required", () => {
67
+ const schema = boolean({ label: "TEST", required: false, defaultValue: true })
68
+ expect(schema.parse("")).toBe(true)
69
+ expect(schema.parse(null)).toBe(true)
70
+ expect(schema.parse(undefined)).toBe(true)
71
+
72
+ const schema2 = boolean({ label: "TEST", required: false, defaultValue: false })
73
+ expect(schema2.parse("")).toBe(false)
74
+ expect(schema2.parse(null)).toBe(false)
75
+ expect(schema2.parse(undefined)).toBe(false)
76
+ })
77
+
78
+ it("should throw error for invalid string", () => {
79
+ const schema = boolean({ label: "TEST" })
80
+ expect(() => schema.parse("yes")).toThrow()
81
+ expect(() => schema.parse("no")).toThrow()
82
+ })
83
+
84
+ it("should throw error for non-boolean, non-convertible values", () => {
85
+ const schema = boolean({ label: "TEST" })
86
+ expect(() => schema.parse({})).toThrow()
87
+ expect(() => schema.parse([])).toThrow()
88
+ })
89
+
90
+ it("should validate shouldBe: true", () => {
91
+ const schema = boolean({ label: "TEST", 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)
97
+ })
98
+
99
+ it("should validate shouldBe: false", () => {
100
+ const schema = boolean({ label: "TEST", 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)
107
+ })
108
+
109
+ it("should show correct error message when shouldBe is true but value is false", () => {
110
+ const schema = boolean({ label: "TEST", shouldBe: true })
111
+ expect(() => schema.parse(false)).toThrow(messages.shouldBeTrue)
112
+ })
113
+
114
+ it("should show correct error message when shouldBe is false but value is true", () => {
115
+ const schema = boolean({ label: "TEST", shouldBe: false })
116
+ expect(() => schema.parse(true)).toThrow(messages.shouldBeFalse)
117
+ })
118
+ })
@@ -0,0 +1,77 @@
1
+ import { describe, it, expect, beforeEach } from "vitest"
2
+ import { setLocale, email, 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 20 characters",
10
+ max: "TEST must be at most 10 characters",
11
+ includes: "TEST must include company",
12
+ invalid: "TEST is invalid format",
13
+ domain: "TEST must be under the domain @example.com",
14
+ },
15
+ },
16
+ {
17
+ locale: "zh-TW",
18
+ messages: {
19
+ required: "請輸入 TEST",
20
+ min: "TEST 長度至少 20 字元",
21
+ max: "TEST 長度最多 10 字元",
22
+ includes: "TEST 必須包含「company」",
23
+ invalid: "TEST 格式錯誤",
24
+ domain: "TEST 必須為 @example.com 網域",
25
+ },
26
+ },
27
+ ] as const
28
+
29
+ describe.each(locales)("email() locale: $locale", ({ locale, messages }) => {
30
+ beforeEach(() => setLocale(locale as Locale))
31
+
32
+ it("should pass for valid email", () => {
33
+ const schema = email({ label: "TEST" })
34
+ expect(schema.parse("test@example.com")).toBe("test@example.com")
35
+ })
36
+
37
+ it("should fail for invalid email format", () => {
38
+ const schema = email({ label: "TEST" })
39
+ expect(() => schema.parse("invalid-email")).toThrow(messages.invalid)
40
+ })
41
+
42
+ it("should enforce required when set", () => {
43
+ const schema = email({ label: "TEST", required: true })
44
+ expect(() => schema.parse("")).toThrow(messages.required)
45
+ expect(() => schema.parse(null)).toThrow(messages.required)
46
+ expect(() => schema.parse(undefined)).toThrow(messages.required)
47
+ })
48
+
49
+ it("should allow null if not required", () => {
50
+ const schema = email({ label: "TEST", required: false })
51
+ expect(schema.parse("")).toBe(null)
52
+ expect(schema.parse(null)).toBe(null)
53
+ expect(schema.parse(undefined)).toBe(null)
54
+ })
55
+
56
+ it("should enforce max length", () => {
57
+ const schema = email({ label: "TEST", max: 10 })
58
+ expect(() => schema.parse("verylongemail@example.com")).toThrow(messages.max)
59
+ })
60
+
61
+ it("should enforce min length", () => {
62
+ const schema = email({ label: "TEST", min: 20 })
63
+ expect(() => schema.parse("short@example.com")).toThrow(messages.min)
64
+ })
65
+
66
+ it("should enforce includes", () => {
67
+ const schema = email({ label: "TEST", includes: "company" })
68
+ expect(schema.parse("john@company.com")).toBe("john@company.com")
69
+ expect(() => schema.parse("short@other.com")).toThrow(messages.includes)
70
+ })
71
+
72
+ it("should validate against custom domain", () => {
73
+ const schema = email({ label: "TEST", domain: "example.com" })
74
+ expect(schema.parse("test@example.com")).toBe("test@example.com")
75
+ expect(() => schema.parse("short@notexample.com")).toThrow(messages.domain)
76
+ })
77
+ })
@@ -0,0 +1,90 @@
1
+ import { describe, it, expect, beforeEach } from "vitest"
2
+ import { setLocale, integer, 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 3",
10
+ max: "TEST must be at most 10",
11
+ integer: "TEST must be an integer",
12
+ },
13
+ },
14
+ {
15
+ locale: "zh-TW",
16
+ messages: {
17
+ required: "請輸入 TEST",
18
+ min: "TEST 最小值 3",
19
+ max: "TEST 最大值 10",
20
+ integer: "TEST 必須為整數",
21
+ },
22
+ },
23
+ ] as const
24
+
25
+ describe.each(locales)("integer() locale: $locale", ({ locale, messages }) => {
26
+ beforeEach(() => setLocale(locale as Locale))
27
+
28
+ it("should pass with valid integer", () => {
29
+ const schema = integer({ label: "TEST" })
30
+ expect(schema.parse(42)).toBe(42)
31
+ })
32
+
33
+ it("should parse string number as integer", () => {
34
+ const schema = integer({ label: "TEST" })
35
+ expect(schema.parse("100")).toBe(100)
36
+ })
37
+
38
+ it("should fail for decimal value", () => {
39
+ const schema = integer({ label: "TEST" })
40
+ expect(() => schema.parse(3.14)).toThrow(messages.integer)
41
+ })
42
+
43
+ it("should fail for non-numeric string", () => {
44
+ const schema = integer({ label: "TEST" })
45
+ expect(() => schema.parse("abc")).toThrow(messages.integer)
46
+ expect(() => schema.parse("10a")).toThrow(messages.integer)
47
+ })
48
+
49
+ it("should allow null if not required", () => {
50
+ const schema = integer({ label: "TEST", required: false })
51
+ expect(schema.parse("")).toBe(null)
52
+ expect(schema.parse(null)).toBe(null)
53
+ expect(schema.parse(undefined)).toBe(null)
54
+ })
55
+
56
+ it("should fail when required and input is empty", () => {
57
+ const schema = integer({ label: "TEST", required: true })
58
+ expect(() => schema.parse("")).toThrow(messages.required)
59
+ expect(() => schema.parse(null)).toThrow(messages.required)
60
+ expect(() => schema.parse(undefined)).toThrow(messages.required)
61
+ })
62
+
63
+ it("should apply default value even when required", () => {
64
+ const schema = integer({ label: "TEST", required: true, defaultValue: 5 })
65
+ expect(schema.parse("")).toBe(5)
66
+ expect(schema.parse(null)).toBe(5)
67
+ expect(schema.parse(undefined)).toBe(5)
68
+ })
69
+
70
+ it("should apply default value even when required", () => {
71
+ const schema = integer({ label: "TEST", required: false, defaultValue: 5 })
72
+ expect(schema.parse("")).toBe(5)
73
+ expect(schema.parse(null)).toBe(5)
74
+ expect(schema.parse(undefined)).toBe(5)
75
+ })
76
+
77
+ it("should enforce min", () => {
78
+ const schema = integer({ label: "TEST", min: 3 })
79
+ expect(schema.parse(3)).toBe(3)
80
+ expect(() => schema.parse(2)).toThrow(messages.min)
81
+ expect(() => schema.parse("2")).toThrow(messages.min)
82
+ })
83
+
84
+ it("should enforce max", () => {
85
+ const schema = integer({ label: "TEST", max: 10 })
86
+ expect(schema.parse(10)).toBe(10)
87
+ expect(() => schema.parse(11)).toThrow(messages.max)
88
+ expect(() => schema.parse("11")).toThrow(messages.max)
89
+ })
90
+ })
@@ -0,0 +1,77 @@
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)
29
+ })
30
+
31
+ it("should parse string number", () => {
32
+ const schema = number({ label: "TEST" })
33
+ expect(schema.parse("123")).toBe(123)
34
+ })
35
+
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)
41
+ })
42
+
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)
48
+ })
49
+
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)
55
+ })
56
+
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)
62
+ })
63
+
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)
69
+ })
70
+
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)
76
+ })
77
+ })
@@ -0,0 +1,89 @@
1
+ import { describe, expect, it, beforeEach } from "vitest"
2
+ import { Locale, setLocale, password } 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 characters",
10
+ max: "TEST must be at most 5 characters",
11
+ uppercase: "TEST must include at least one uppercase letter",
12
+ lowercase: "TEST must include at least one lowercase letter",
13
+ digits: "TEST must include at least one digit",
14
+ special: "TEST must include at least one special character",
15
+ },
16
+ },
17
+ {
18
+ locale: "zh-TW",
19
+ messages: {
20
+ required: "請輸入 TEST",
21
+ min: "TEST 長度至少 5 字元",
22
+ max: "TEST 長度最多 5 字元",
23
+ uppercase: "TEST 必須包含至少一個大寫字母",
24
+ lowercase: "TEST 必須包含至少一個小寫字母",
25
+ digits: "TEST 必須包含至少一個數字",
26
+ special: "TEST 必須包含至少一個特殊符號",
27
+ },
28
+ },
29
+ ] as const
30
+
31
+ describe.each(locales)("password() locale: $locale", ({ locale, messages }) => {
32
+ beforeEach(() => setLocale(locale as Locale))
33
+
34
+ it("should pass with valid string", () => {
35
+ const schema = password({ label: "TEST" })
36
+ expect(schema.parse("hello")).toBe("hello")
37
+ })
38
+
39
+ it("should fail with empty string when required", () => {
40
+ const schema = password({ label: "TEST" })
41
+ expect(() => schema.parse("")).toThrow(messages.required)
42
+ expect(() => schema.parse(null)).toThrow(messages.required)
43
+ expect(() => schema.parse(undefined)).toThrow(messages.required)
44
+ })
45
+
46
+ it("should pass with null when not required", () => {
47
+ const schema = password({ label: "TEST", required: false })
48
+ expect(schema.parse("")).toBe(null)
49
+ expect(schema.parse(null)).toBe(null)
50
+ expect(schema.parse(undefined)).toBe(null)
51
+ })
52
+
53
+ it("should fail with string shorter than min", () => {
54
+ const schema = password({ label: "TEST", min: 5 })
55
+ expect(() => schema.parse("hi")).toThrow(messages.min)
56
+ expect(() => schema.parse("")).toThrow(messages.min)
57
+ expect(() => schema.parse(null)).toThrow(messages.min)
58
+ expect(() => schema.parse(undefined)).toThrow(messages.min)
59
+ })
60
+
61
+ it("should fail with string longer than max", () => {
62
+ const schema = password({ label: "TEST", max: 5 })
63
+ expect(() => schema.parse("hello world")).toThrow(messages.max)
64
+ })
65
+
66
+ it("should enforce uppercase requirement", () => {
67
+ const schema = password({ label: "TEST", uppercase: true })
68
+ expect(() => schema.parse("lowercase1!")).toThrow(messages.uppercase)
69
+ expect(schema.parse("Hello1!")).toBe("Hello1!")
70
+ })
71
+
72
+ it("should enforce lowercase requirement", () => {
73
+ const schema = password({ label: "TEST", lowercase: true })
74
+ expect(() => schema.parse("UPPERCASE1!")).toThrow(messages.lowercase)
75
+ expect(schema.parse("Test1!")).toBe("Test1!")
76
+ })
77
+
78
+ it("should enforce digit requirement", () => {
79
+ const schema = password({ label: "TEST", digits: true })
80
+ expect(() => schema.parse("NoDigits!")).toThrow(messages.digits)
81
+ expect(schema.parse("With1!")).toBe("With1!")
82
+ })
83
+
84
+ it("should enforce special character requirement", () => {
85
+ const schema = password({ label: "TEST", special: true })
86
+ expect(() => schema.parse("NoSpecial1")).toThrow(messages.special)
87
+ expect(schema.parse("Valid1!")).toBe("Valid1!")
88
+ })
89
+ })
@@ -0,0 +1,89 @@
1
+ import { describe, expect, it, beforeEach } from "vitest"
2
+ import { Locale, setLocale, text } 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 characters",
10
+ max: "TEST must be at most 5 characters",
11
+ startsWith: "TEST must start with pre",
12
+ endsWith: "TEST must end with xyz",
13
+ includes: "TEST must include foo",
14
+ invalid: "TEST is invalid format",
15
+ },
16
+ },
17
+ {
18
+ locale: "zh-TW",
19
+ messages: {
20
+ required: "請輸入 TEST",
21
+ min: "TEST 長度至少 5 字元",
22
+ max: "TEST 長度最多 5 字元",
23
+ startsWith: "TEST 必須以「pre」開頭",
24
+ endsWith: "TEST 必須以「xyz」結尾",
25
+ includes: "TEST 必須包含「foo」",
26
+ invalid: "TEST 格式錯誤",
27
+ },
28
+ },
29
+ ] as const
30
+
31
+ describe.each(locales)("text() locale: $locale", ({ locale, messages }) => {
32
+ beforeEach(() => setLocale(locale as Locale))
33
+
34
+ it("should pass with valid string", () => {
35
+ const schema = text({ label: "TEST" })
36
+ expect(schema.parse("hello")).toBe("hello")
37
+ })
38
+
39
+ it("should fail with empty string when required", () => {
40
+ const schema = text({ label: "TEST" })
41
+ expect(() => schema.parse("")).toThrow(messages.required)
42
+ expect(() => schema.parse(null)).toThrow(messages.required)
43
+ expect(() => schema.parse(undefined)).toThrow(messages.required)
44
+ })
45
+
46
+ it("should pass with null when not required", () => {
47
+ const schema = text({ label: "TEST", required: false })
48
+ expect(schema.parse("")).toBe(null)
49
+ expect(schema.parse(null)).toBe(null)
50
+ expect(schema.parse(undefined)).toBe(null)
51
+ })
52
+
53
+ it("should fail with string shorter than min", () => {
54
+ const schema = text({ label: "TEST", min: 5 })
55
+ expect(() => schema.parse("hi")).toThrow(messages.min)
56
+ expect(() => schema.parse("")).toThrow(messages.min)
57
+ expect(() => schema.parse(null)).toThrow(messages.min)
58
+ expect(() => schema.parse(undefined)).toThrow(messages.min)
59
+ })
60
+
61
+ it("should fail with string longer than max", () => {
62
+ const schema = text({ label: "TEST", max: 5 })
63
+ expect(() => schema.parse("hello world")).toThrow(messages.max)
64
+ })
65
+
66
+ it("should fail if does not start with specified string", () => {
67
+ const schema = text({ label: "TEST", startsWith: "pre" })
68
+ expect(schema.parse("prepaid")).toBe("prepaid")
69
+ expect(() => schema.parse("hello world")).toThrow(messages.startsWith)
70
+ })
71
+
72
+ it("should fail if does not end with specified string", () => {
73
+ const schema = text({ label: "TEST", endsWith: "xyz" })
74
+ expect(schema.parse("hello xyz")).toBe("hello xyz")
75
+ expect(() => schema.parse("hello world")).toThrow(messages.endsWith)
76
+ })
77
+
78
+ it("should fail if does not include specified substring", () => {
79
+ const schema = text({ label: "TEST", includes: "foo" })
80
+ expect(schema.parse("hello foo")).toBe("hello foo")
81
+ expect(() => schema.parse("hello world")).toThrow(messages.includes)
82
+ })
83
+
84
+ it("should fail if does not match regex", () => {
85
+ const schema = text({ label: "TEST", regex: /^[A-Z]+$/ })
86
+ expect(schema.parse("HELLO")).toBe("HELLO")
87
+ expect(() => schema.parse("hello")).toThrow(messages.invalid)
88
+ })
89
+ })
@@ -0,0 +1,69 @@
1
+ import { describe, it, expect, beforeEach } from "vitest"
2
+ import { setLocale, url, 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 20 characters",
10
+ max: "TEST must be at most 10 characters",
11
+ includes: "TEST must include company",
12
+ invalid: "TEST is invalid format",
13
+ },
14
+ },
15
+ {
16
+ locale: "zh-TW",
17
+ messages: {
18
+ required: "請輸入 TEST",
19
+ min: "TEST 長度至少 20 字元",
20
+ max: "TEST 長度最多 10 字元",
21
+ includes: "TEST 必須包含「company」",
22
+ invalid: "TEST 格式錯誤",
23
+ },
24
+ },
25
+ ] as const
26
+
27
+ describe.each(locales)("url() locale: $locale", ({ locale, messages }) => {
28
+ beforeEach(() => setLocale(locale as Locale))
29
+
30
+ it("should pass for valid url", () => {
31
+ const schema = url({ label: "TEST" })
32
+ expect(schema.parse("https://example.com")).toBe("https://example.com")
33
+ })
34
+
35
+ it("should fail for invalid url format", () => {
36
+ const schema = url({ label: "TEST" })
37
+ expect(() => schema.parse("invalid-url")).toThrow(messages.invalid)
38
+ })
39
+
40
+ it("should enforce required when set", () => {
41
+ const schema = url({ label: "TEST", required: true })
42
+ expect(() => schema.parse("")).toThrow(messages.required)
43
+ expect(() => schema.parse(null)).toThrow(messages.required)
44
+ expect(() => schema.parse(undefined)).toThrow(messages.required)
45
+ })
46
+
47
+ it("should allow null if not required", () => {
48
+ const schema = url({ label: "TEST", required: false })
49
+ expect(schema.parse("")).toBe(null)
50
+ expect(schema.parse(null)).toBe(null)
51
+ expect(schema.parse(undefined)).toBe(null)
52
+ })
53
+
54
+ it("should enforce max length", () => {
55
+ const schema = url({ label: "TEST", max: 10 })
56
+ expect(() => schema.parse("https://verylongurl.com")).toThrow(messages.max)
57
+ })
58
+
59
+ it("should enforce min length", () => {
60
+ const schema = url({ label: "TEST", min: 20 })
61
+ expect(() => schema.parse("https://short.co")).toThrow(messages.min)
62
+ })
63
+
64
+ it("should enforce includes", () => {
65
+ const schema = url({ label: "TEST", includes: "company" })
66
+ expect(schema.parse("https://company.com")).toBe("https://company.com")
67
+ expect(() => schema.parse("https://other.com")).toThrow(messages.includes)
68
+ })
69
+ })