1import { assert, describe, it } from "@effect/vitest"
2import { Schema } from "effect"
3import { TestSchema } from "effect/testing"
4
5describe("code point checks", () => {
6 const cases = [
7 ["", 0],
8 ["a", 1],
9 ["abc", 3],
10 ["😀", 1],
11 ["a😀b", 3],
12 ["é", 1],
13 ["e\u0301", 2],
14 ["👨‍👩‍👧‍👦", 7],
15 ["\uD800", 1],
16 ["\uDC00", 1],
17 ["\uD800\uD800\uDC00", 2],
18 ["\r\n", 2]
19 ] as const
20
21 it("counts code points at inclusive minimum and maximum boundaries", () => {
22 for (let bound = 0; bound <= 8; bound++) {
23 const min = Schema.is(Schema.String.check(Schema.isMinCodePoints(bound)))
24 const max = Schema.is(Schema.String.check(Schema.isMaxCodePoints(bound)))
25 const exact = Schema.is(Schema.String.check(Schema.isBetweenCodePoints(bound, bound)))
26 for (const [value, count] of cases) {
27 assert.strictEqual(min(value), count >= bound)
28 assert.strictEqual(max(value), count <= bound)
29 assert.strictEqual(exact(value), count === bound)
30 }
31 }
32 })
33
34 it("checks inclusive ranges and rejects inverted ranges", () => {
35 const between = Schema.is(Schema.String.check(Schema.isBetweenCodePoints(1, 3)))
36 const inverted = Schema.is(Schema.String.check(Schema.isBetweenCodePoints(3, 1)))
37 for (const [value, count] of cases) {
38 assert.strictEqual(between(value), count >= 1 && count <= 3)
39 assert.isFalse(inverted(value))
40 }
41 })
42
43 it("rounds down bounds and clamps negative bounds to zero", () => {
44 assert.isTrue(Schema.is(Schema.String.check(Schema.isMinCodePoints(1.9)))("😀"))
45 assert.isFalse(Schema.is(Schema.String.check(Schema.isMaxCodePoints(1.9)))("😀a"))
46 assert.isTrue(Schema.is(Schema.String.check(Schema.isBetweenCodePoints(1.9, 2.9)))("😀a"))
47 assert.isTrue(Schema.is(Schema.String.check(Schema.isMinCodePoints(-1)))(""))
48 assert.isTrue(Schema.is(Schema.String.check(Schema.isMaxCodePoints(-1)))(""))
49 assert.isFalse(Schema.is(Schema.String.check(Schema.isMaxCodePoints(-1)))("a"))
50 assert.isTrue(Schema.is(Schema.String.check(Schema.isBetweenCodePoints(-2, -1)))(""))
51 })
52
53 it("preserves length checks based on UTF-16 code units", () => {
54 assert.isTrue(Schema.is(Schema.String.check(Schema.isMinLength(2)))("😀"))
55 assert.isFalse(Schema.is(Schema.String.check(Schema.isMaxLength(1)))("😀"))
56 })
57
58 it("validates decoding and encoding with code point error messages", async () => {
59 for (
60 const [check, invalid, message] of [
61 [Schema.isMinCodePoints(1), "", "Expected a string with at least 1 code points"],
62 [Schema.isMaxCodePoints(1), "😀a", "Expected a string with at most 1 code points"],
63 [Schema.isBetweenCodePoints(1, 2), "😀ab", "Expected a string with between 1 and 2 code points"],
64 [Schema.isBetweenCodePoints(1, 1), "😀a", "Expected a string with 1 code points"]
65 ] as const
66 ) {
67 const asserts = new TestSchema.Asserts(Schema.String.check(check))
68 await asserts.decoding().succeed("😀")
69 await asserts.encoding().succeed("😀")
70 await asserts.decoding().fail(invalid, message)
71 await asserts.encoding().fail(invalid, message)
72 }
73 })
74
75 it("allows custom annotations", async () => {
76 const asserts = new TestSchema.Asserts(
77 Schema.String.check(Schema.isMaxCodePoints(1, { expected: "a single code point" }))
78 )
79 await asserts.decoding().fail("ab", "Expected a single code point")
80 })
81})