Skip to main content
Module

x/value_schema/test/index.test.ts

simple, easy-to-use, and declarative input validator; supports Node.js, TypeScript, Deno, and Bun
Latest
File
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540
import {describe, expect, it} from "@jest/globals";import vs from "value-schema";
{ describe("applySchemaObject", testApplySchemaObject); describe("ifUndefined", testIfUndefined); describe("error", testError);}
/** * test applySchemaObject() */function testApplySchemaObject(): void{ it("should be adjusted", () => { const enum State { ACTIVE = "active", INACTIVE = "inactive", } interface SchemaType { id: number; name: string; birthday: Date; age: number; email: string | null; state: State; classes: number[]; skills: string[]; creditCard: string; remoteAddr: string; remoteAddrIpv6: string; limit: number; offset: number; others?: boolean; }
const input = { id: "1", name: "Pablo Diego José Francisco de Paula Juan Nepomuceno María de los Remedios Ciprin Cipriano de la Santísima Trinidad Ruiz y Picasso", birthday: "2000-12-09T00:00:00.000Z", age: 20.5, email: "picasso@example.com", state: "active", classes: "1,3,abc,4", skills: "c,c++,javascript,python,,swift,kotlin", creditCard: "4111-1111-1111-1111", remoteAddr: "127.0.0.1", remoteAddrIpv6: "::1", limit: "0", }; const expected: SchemaType = { id: 1, name: "Pablo Diego José", birthday: new Date("2000-12-09T00:00:00.000Z"), age: 20, email: "picasso@example.com", state: State.ACTIVE, classes: [1, 3, 4], skills: ["c", "c++", "javascript", "python", "swift", "kotlin"], creditCard: "4111111111111111", remoteAddr: "127.0.0.1", remoteAddrIpv6: "::1", limit: 1, offset: 0, };
// type check const actual = parse(input); expect(actual).toEqual(expected);
/** * parse input value * @param input input value * @returns parsed result */ function parse(input: unknown): SchemaType { const schemaObject = { id: vs.number({ minValue: 1, }), name: vs.string({ maxLength: { length: 16, trims: true, }, }), birthday: vs.date(), age: vs.number({ integer: vs.NUMBER.INTEGER.FLOOR_RZ, minValue: 0, }), email: vs.email({ ifUndefined: null, }), state: vs.enumeration({ only: [State.ACTIVE, State.INACTIVE], }), classes: vs.array({ separatedBy: ",", each: { schema: vs.number(), ignoresErrors: true, }, }), skills: vs.array({ separatedBy: ",", each: { schema: vs.string(), ignoresErrors: true, }, }), creditCard: vs.numericString({ separatedBy: "-", checksum: vs.NUMERIC_STRING.CHECKSUM_ALGORITHM.CREDIT_CARD, }), remoteAddr: vs.string({ pattern: vs.STRING.PATTERN.IPV4, }), remoteAddrIpv6: vs.string({ pattern: vs.STRING.PATTERN.IPV6, }), limit: vs.number({ integer: true, ifUndefined: 10, minValue: { value: 1, adjusts: true, }, maxValue: { value: 100, adjusts: true, }, }), offset: vs.number({ integer: true, ifUndefined: 0, minValue: { value: 0, adjusts: true, }, }), others: vs.boolean({ ifUndefined: undefined, }), }; return vs.applySchemaObject(schemaObject, input); } }); it("property / type-inference test", () => { const schemaObject = { boolean: vs.boolean(), number: vs.number(), string: vs.string(), email: vs.email(), numericString: vs.numericString(),
arrayOfBoolean: vs.array({each: vs.boolean()}), arrayOfNumber: vs.array({each: vs.number()}), arrayOfString: vs.array({each: vs.string()}), arrayOfArrayOfNumber: vs.array({ each: vs.array({ each: vs.number(), }), }), arrayOfObject: vs.array({ each: vs.object({ schemaObject: {foo: vs.number()}, }), }),
object: vs.object({ schemaObject: { boolean: vs.boolean(), number: vs.number(), string: vs.string(), email: vs.email(), numericString: vs.numericString(),
arrayOfBoolean: vs.array({each: vs.boolean()}), arrayOfNumber: vs.array({each: vs.number()}), arrayOfArrayOfString: vs.array({ each: vs.array({ each: vs.string(), }), }), arrayOfString: vs.array({each: vs.string()}), }, }), }; const input = { boolean: false, number: 0, string: "X", email: "user@example.com", numericString: "1234",
arrayOfBoolean: [true, false], arrayOfNumber: [0, 1, 2, 3], arrayOfString: ["a", "b", "c"], arrayOfArrayOfNumber: [[0, 1], [2], [3, 4, 5]], arrayOfObject: [{foo: 1}, {foo: 2}, {foo: 3}],
object: { boolean: false, number: 0, string: "X", email: "user@example.com", numericString: "1234",
arrayOfBoolean: [true, false], arrayOfNumber: [0, 1, 2, 3], arrayOfString: ["a", "b", "c"], arrayOfArrayOfString: [["A", "B"], ["C"], ["X", "Y", "Z"]], }, };
// property / type-inference check const actual = vs.applySchemaObject(schemaObject, input); expect(actual.boolean).toEqual(false); expect(actual.number.toExponential()).toEqual("0e+0"); expect(actual.string.toLowerCase()).toEqual("x"); expect(actual.email.toUpperCase()).toEqual("USER@EXAMPLE.COM"); expect(actual.numericString.padStart(8, "0")).toEqual("00001234");
expect(actual.arrayOfBoolean.slice()).toEqual([true, false]); expect(actual.arrayOfNumber.slice()[0].toFixed()).toEqual("0"); expect(actual.arrayOfString.slice()[0].toUpperCase()).toEqual("A"); expect(actual.arrayOfArrayOfNumber.slice()[0].slice()[0].toFixed()).toEqual("0"); expect(actual.arrayOfObject.slice()[0].foo.toExponential()).toEqual("1e+0");
expect(actual.object.boolean).toEqual(false); expect(actual.object.number.toExponential()).toEqual("0e+0"); expect(actual.object.string.toLowerCase()).toEqual("x"); expect(actual.object.email.toUpperCase()).toEqual("USER@EXAMPLE.COM"); expect(actual.object.numericString.padEnd(8, "0")).toEqual("12340000"); expect(actual.object.arrayOfBoolean.slice()[0]).toEqual(true); expect(actual.object.arrayOfNumber.slice()[0].toFixed()).toEqual("0"); expect(actual.object.arrayOfString.slice()[0].toUpperCase()).toEqual("A"); expect(actual.object.arrayOfArrayOfString.slice()[0].slice()[1].toLowerCase()).toEqual("b"); });}
/** * test "ifUndefined" */function testIfUndefined(): void{ it("should be OK", () => { const schemaObject = { value: vs.number({ ifUndefined: undefined, }), }; const input = {}; const expected = {};
const actual = vs.applySchemaObject(schemaObject, input); expect(actual).toEqual(expected); }); it("should cause error(s)", () => { // "{isUndefined: undefined}" differs from "{}"! const schemaObject = { value: vs.number({ // ifUndefined: undefined, }), }; const input = {};
expect(() => { vs.applySchemaObject(schemaObject, input); }).toThrow(); });}
/** * error handling */function testError(): void{ it("should be adjusted", () => { const schemaObject = { id: vs.number({ minValue: 1, }), name: vs.string({ maxLength: { length: 16, trims: true, }, }), email: vs.email(), }; const input = { id: 0, // error! (>= 1) name: "", // error! (empty string is not allowed) email: "john@example.com", // OK }; const expected = { id: 100, name: "John", email: "john@example.com", };
const actual = vs.applySchemaObject(schemaObject, input, (err) => { switch(err.keyStack.shift()) { case "id": return 100;
default: return "John"; } }); expect(actual).toEqual(expected); }); it("should cause error(s)", () => { expect(() => { // input must be an object vs.applySchemaObject({}, null, (err) => { expect(err.rule).toEqual(vs.RULE.TYPE); return null; }); }).toThrow(vs.RULE.TYPE);
expect( // input must be an object vs.applySchemaObject({}, null, (err) => { expect(err.rule).toEqual(vs.RULE.TYPE); return {}; }) ).toEqual({});
expect(() => { const schemaObject = {}; const input = 0;
vs.applySchemaObject(schemaObject, input); }).toThrow(vs.RULE.TYPE); // input must be an object
expect(() => { const schemaObject = {}; const input = null;
vs.applySchemaObject(schemaObject, input); }).toThrow(vs.RULE.TYPE); // input must be an object; typeof null === "object"
expect(() => { const schemaObject = {}; const input: number[] = [];
vs.applySchemaObject(schemaObject, input); }).toThrow(vs.RULE.TYPE); // input must be an object; typeof [] === "object"
expect(() => { const schemaObject = { id: vs.number({ minValue: 1, }), name: vs.string({ maxLength: { length: 16, trims: true, }, }), email: vs.email(), }; const input = { id: 0, // error! (>= 1) name: "", // error! (empty string is not allowed) email: "john@example.com", // OK };
const keys: (string | number)[] = []; vs.applySchemaObject(schemaObject, input, (err) => { if(err.keyStack.length === 0) { return; } // append key name keys.push(err.keyStack[0]); }, () => { // finish with error; join key name as message throw new Error(keys.join(",")); }); }).toThrow("id,name");
expect(() => { const schemaObject = { id: vs.number({ minValue: 1, }), name: vs.string({ maxLength: { length: 16, trims: true, }, }), email: vs.email(), }; const input = { id: 0, // error! (>= 1) name: "", // error! (empty string is not allowed) email: "john@example.com", // OK };
vs.applySchemaObject(schemaObject, input); }).toThrow(); // throw a first error if error handler is omitted
try { const schemaObject = { id: vs.number({ minValue: 1, }), name: vs.string({ maxLength: { length: 4, trims: true, }, }), }; const input = { id: "0", name: "John Doe", dummy: true, }; vs.object({ schemaObject: schemaObject, }).applyTo(input); expect(true).toEqual(false); } catch(err) { expect(vs.ValueSchemaError.is(err)).toBeTruthy(); if(!vs.ValueSchemaError.is(err)) { return; } expect(err.rule).toEqual(vs.RULE.MIN_VALUE); expect(err.keyStack).toEqual(["id"]); }
try { const schemaObject = { ids: vs.array({ each: vs.number({ minValue: 1, }), }), }; const input = { ids: [true, "2", "+3", "four", 5], }; vs.object({ schemaObject: schemaObject, }).applyTo(input); expect(true).toEqual(false); } catch(err) { expect(vs.ValueSchemaError.is(err)).toBeTruthy(); if(!vs.ValueSchemaError.is(err)) { return; } expect(err.rule).toEqual(vs.RULE.TYPE); expect(err.keyStack).toEqual(["ids", 3]); }
try { // complex schema const schemaObject = { infoList: vs.array({ each: vs.object({ schemaObject: { id: vs.number(), name: vs.string({ maxLength: 8, }), }, }), }), }; const input = { infoList: [ { id: "1", name: "John Doe", }, { id: "two", // ERROR! name: "John Doe", }, { id: 3, name: "John Doe 2", // ERROR! }, ], }; vs.object({ schemaObject: schemaObject, }).applyTo(input); expect(true).toEqual(false); } catch(err) { expect(vs.ValueSchemaError.is(err)).toBeTruthy(); if(!vs.ValueSchemaError.is(err)) { return; } expect(err.rule).toEqual(vs.RULE.TYPE); expect(err.keyStack).toEqual(["infoList", 1, "id"]); } });}