|
import crypto from 'node:crypto'; |
|
import * as v from 'valibot'; |
|
import { vAfter, vId, vTrimOptional, vTrimString } from './valibotUtils'; |
|
import { describe, it, expect, vi } from 'vitest'; |
|
import { encodeAfterParam } from './afterParam'; |
|
|
|
describe('vTrimString', () => { |
|
it('fails undefined', () => { |
|
const res = v.safeParse(vTrimString(), undefined); |
|
|
|
expect(res.success).toEqual(false); |
|
}); |
|
|
|
it('passes a string', () => { |
|
const res = v.parse(vTrimString(), 'asdf'); |
|
|
|
expect(res).toEqual('asdf'); |
|
}); |
|
|
|
it('trims a string', () => { |
|
const res = v.parse(vTrimString(), ' asdf '); |
|
|
|
expect(res).toEqual('asdf'); |
|
}); |
|
|
|
it('fails an empty string', () => { |
|
const res = v.safeParse(vTrimString(), ''); |
|
|
|
expect(res.success).toEqual(false); |
|
}); |
|
|
|
it('fails a whitespace string', () => { |
|
const res = v.safeParse(vTrimString(), ' '); |
|
|
|
expect(res.success).toEqual(false); |
|
}); |
|
|
|
it('fails an invalid url string', () => { |
|
const res = v.safeParse(v.pipe(vTrimString(), v.url()), 'asdf'); |
|
|
|
expect(res.success).toEqual(false); |
|
}); |
|
|
|
it('passes a valid untrimmed url string', () => { |
|
const res = v.parse( |
|
v.pipe(vTrimString(), v.url()), |
|
' https://example.com ' |
|
); |
|
|
|
expect(res).toEqual('https://example.com'); |
|
}); |
|
|
|
it('returns the error message when validation fails due to an incorrect type', () => { |
|
const res = v.safeParse(vTrimString('Test error message'), 0); |
|
|
|
expect(res.success).toEqual(false); |
|
expect(res.issues![0].message).toEqual('Test error message'); |
|
}); |
|
|
|
it('returns the error message when validation fails due to being an empty string', () => { |
|
const res = v.safeParse(vTrimString('Test error message'), ''); |
|
|
|
expect(res.success).toEqual(false); |
|
expect(res.issues![0].message).toEqual('Test error message'); |
|
}); |
|
}); |
|
|
|
describe('vTrimOptional', () => { |
|
it('allows a missing property on an object', () => { |
|
const schema = v.object({ val: vTrimOptional() }); |
|
const res = v.parse(schema, {}); |
|
|
|
expect(res).toEqual({ val: undefined }); |
|
}); |
|
|
|
it('allows undefined', () => { |
|
const schema = vTrimOptional(); |
|
const res = v.parse(schema, undefined); |
|
|
|
expect(res).toEqual(undefined); |
|
}); |
|
|
|
it('allows undefined, even when a schema is provided', () => { |
|
const schema = vTrimOptional(v.pipe(v.string(), v.uuid())); |
|
const res = v.parse(schema, undefined); |
|
|
|
expect(res).toEqual(undefined); |
|
}); |
|
|
|
it('does not execute inner schema when passed an empty string', () => { |
|
const fn = vi.fn((data: string): string => data); |
|
const res = v.parse(vTrimOptional(v.pipe(v.string(), v.transform(fn))), ''); |
|
|
|
expect(res).toEqual(undefined); |
|
expect(fn).not.toHaveBeenCalled(); |
|
}); |
|
|
|
it('infers transformed types correctly', () => { |
|
const schema = vTrimOptional(v.pipe(v.string(), v.transform(Number))); |
|
const res = v.parse(schema, '1'); |
|
|
|
// Left as `=== 1` instead of `.toEqual(1)` because we're also checking that Typescript infers the type correctly |
|
expect(res === 1).toEqual(true); |
|
}); |
|
|
|
it('uses the default value when passed undefined', () => { |
|
const schema = vTrimOptional(undefined, '1'); |
|
const res = v.parse(schema, undefined); |
|
|
|
expect(res).toEqual('1'); |
|
}); |
|
|
|
it('uses the default value when passed a whitespace string', () => { |
|
const schema = vTrimOptional(undefined, '1'); |
|
const res = v.parse(schema, ' '); |
|
|
|
expect(res).toEqual('1'); |
|
}); |
|
|
|
it("converts '' to undefined", () => { |
|
const schema = vTrimOptional(); |
|
const res = v.parse(schema, ''); |
|
|
|
expect(res).toEqual(undefined); |
|
}); |
|
|
|
it('converts whitespace to undefined', () => { |
|
const schema = vTrimOptional(); |
|
const res = v.parse(schema, ' '); |
|
|
|
expect(res).toEqual(undefined); |
|
}); |
|
|
|
it('trims whitespace', () => { |
|
const schema = vTrimOptional(); |
|
const res = v.parse(schema, ' a '); |
|
|
|
expect(res).toEqual('a'); |
|
}); |
|
|
|
it('runs schema against passed value after whitespace is trimmed', () => { |
|
const uuid = crypto.randomUUID(); |
|
const schema = vTrimOptional(v.pipe(v.string(), v.uuid())); |
|
const res = v.parse(schema, ` ${uuid} `); |
|
|
|
expect(res).toEqual(uuid); |
|
}); |
|
|
|
it('trims newlines', () => { |
|
const schema = vTrimOptional(); |
|
const res = v.parse(schema, '\na\r\n'); |
|
|
|
expect(res).toEqual('a'); |
|
}); |
|
}); |
|
|
|
describe('vAfter', () => { |
|
it('should return undefined when no value is passed', () => { |
|
const schema = vAfter(v.object({ id: v.number() })); |
|
const result = v.safeParse(schema, undefined); |
|
|
|
expect(result.success).toBeTruthy(); |
|
expect(result.output).toBeUndefined(); |
|
}); |
|
|
|
it('should parse a valid encoded JSON string', () => { |
|
const schema = vAfter(v.object({ id: v.number() })); |
|
const encoded = encodeAfterParam({ id: 1 }); |
|
const result = v.safeParse(schema, encoded); |
|
|
|
expect(result.success).toBeTruthy(); |
|
expect(result.output).toEqual({ id: 1 }); |
|
}); |
|
|
|
it('should infer types', () => { |
|
const schema = vAfter(v.object({ id: v.number() })); |
|
const encoded = encodeAfterParam({ id: 1 }); |
|
const result = v.parse(schema, encoded); |
|
|
|
expect(result!.id).toEqual(1); |
|
}); |
|
|
|
it('should return an error for non-JSON strings', () => { |
|
const schema = vAfter(v.object({ id: v.number() })); |
|
const result = v.safeParse(schema, encodeAfterParam('not-json')); |
|
|
|
expect(result.success).toBeFalsy(); |
|
expect(result.issues![0].message).toBe( |
|
'Invalid format. Must be base64 encoded JSON.' |
|
); |
|
}); |
|
|
|
it('should return an error for invalid JSON', () => { |
|
const schema = vAfter( |
|
v.object({ id: v.pipe(v.number(), v.transform(Number)) }) |
|
); |
|
const encoded = encodeAfterParam({ id: 'not-a-number' }); |
|
const result = v.safeParse(schema, encoded); |
|
|
|
expect(result.success).toBeFalsy(); |
|
expect(result.issues![0].message).toContain( |
|
'Invalid type: Expected number but received "not-a-number"' |
|
); |
|
}); |
|
}); |