| 1 | import { assert, describe, it } from "@effect/vitest" |
| 2 | import { Schema } from "effect" |
| 3 | import { TestSchema } from "effect/testing" |
| 4 | |
| 5 | describe("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 | }) |