Skip to content
2 changes: 2 additions & 0 deletions src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ describe('index', () => {
"createModuleLogger",
"createNumber",
"createProjectLogger",
"exactOptional",
"getChecksumAddress",
"getJsonRpcIdValidator",
"getJsonSize",
Expand Down Expand Up @@ -115,6 +116,7 @@ describe('index', () => {
"jsonrpc2",
"numberToBytes",
"numberToHex",
"object",
"parseCaipAccountId",
"parseCaipChainId",
"remove0x",
Expand Down
23 changes: 23 additions & 0 deletions src/json.test-d.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
/* eslint-disable @typescript-eslint/consistent-type-definitions */

import type { Infer } from 'superstruct';
import { boolean, number, optional, string } from 'superstruct';
import { expectAssignable, expectNotAssignable } from 'tsd';

import type { Json } from '.';
import { exactOptional, object } from '.';

// Valid Json:

Expand Down Expand Up @@ -134,3 +137,23 @@ class Foo {
}
const foo = new Foo();
expectNotAssignable<Json>(foo);

// Object using `exactOptional`:

const exactOptionalObject = object({
a: number(),
b: optional(string()),
c: exactOptional(boolean()),
});

type ExactOptionalObject = Infer<typeof exactOptionalObject>;

expectAssignable<ExactOptionalObject>({ a: 0 });
expectAssignable<ExactOptionalObject>({ a: 0, b: 'test' });
expectAssignable<ExactOptionalObject>({ a: 0, b: 'test', c: true });
expectNotAssignable<ExactOptionalObject>({ a: 0, b: 'test', c: 0 });
expectNotAssignable<ExactOptionalObject>({
a: 0,
b: 'test',
c: undefined,
});
150 changes: 149 additions & 1 deletion src/json.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
import { validate, assert as superstructAssert } from 'superstruct';
import {
validate,
assert as superstructAssert,
is,
string,
union,
literal,
max,
number,
} from 'superstruct';

import {
assert,
Expand All @@ -20,6 +29,8 @@ import {
isJsonRpcSuccess,
isPendingJsonRpcResponse,
isValidJson,
object,
exactOptional,
JsonStruct,
} from '.';
import {
Expand All @@ -39,6 +50,143 @@ jest.mock('superstruct', () => ({
assert: jest.fn(),
}));

describe('object', () => {
it('validates an object', () => {
expect(
is(
{
foo: 'bar',
},
object({
foo: string(),
}),
),
).toBe(true);

expect(
is(
{
foo: 123,
},
object({
foo: string(),
}),
),
).toBe(false);
});

it('validates an object with exact optional values', () => {
expect(
is(
{
foo: 'bar',
},
object({
foo: exactOptional(string()),
}),
),
).toBe(true);

expect(
is(
{},
object({
foo: exactOptional(string()),
}),
),
).toBe(true);

expect(
is(
{
foo: 123,
},
object({
foo: string(),
}),
),
).toBe(false);

expect(
is(
{
foo: undefined,
},
object({
foo: exactOptional(string()),
}),
),
).toBe(false);
});
});

describe('exactOptional', () => {
const simpleStruct = object({
foo: exactOptional(string()),
});

it.each([
{ struct: simpleStruct, obj: {}, expected: true },
{ struct: simpleStruct, obj: { foo: undefined }, expected: false },
{ struct: simpleStruct, obj: { foo: 'hi' }, expected: true },
{ struct: simpleStruct, obj: { bar: 'hi' }, expected: false },
{ struct: simpleStruct, obj: { foo: 1 }, expected: false },
])(
'returns $expected for is($obj, <struct>)',
({ struct, obj, expected }) => {
expect(is(obj, struct)).toBe(expected);
},
);

const nestedStruct = object({
foo: object({
bar: exactOptional(string()),
}),
});

it.each([
{ struct: nestedStruct, obj: { foo: {} }, expected: true },
{ struct: nestedStruct, obj: { foo: { bar: 'hi' } }, expected: true },
{
struct: nestedStruct,
obj: { foo: { bar: undefined } },
expected: false,
},
])(
'returns $expected for is($obj, <struct>)',
({ struct, obj, expected }) => {
expect(is(obj, struct)).toBe(expected);
},
);

const structWithUndefined = object({
foo: exactOptional(union([string(), literal(undefined)])),
});

it.each([
{ struct: structWithUndefined, obj: {}, expected: true },
{ struct: structWithUndefined, obj: { foo: undefined }, expected: true },
{ struct: structWithUndefined, obj: { foo: 'hi' }, expected: true },
{ struct: structWithUndefined, obj: { bar: 'hi' }, expected: false },
{ struct: structWithUndefined, obj: { foo: 1 }, expected: false },
])(
'returns $expected for is($obj, <struct>)',
({ struct, obj, expected }) => {
expect(is(obj, struct)).toBe(expected);
},
);

it('supports refinements', () => {
const struct = object({
foo: exactOptional(max(number(), 0)),
});

expect(is({ foo: 0 }, struct)).toBe(true);
expect(is({ foo: -1 }, struct)).toBe(true);
expect(is({ foo: 1 }, struct)).toBe(false);
});
});

describe('json', () => {
beforeEach(() => {
const actual = jest.requireActual('superstruct');
Expand Down
Loading