Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optional decoder update #25

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Update tests
Elias Mulhall committed Aug 20, 2018
commit c4bdce71f01b6d1f787bea50cf228a1b3e561ae0
86 changes: 53 additions & 33 deletions test/json-decode.test.ts
Original file line number Diff line number Diff line change
@@ -124,7 +124,7 @@ describe('constant', () => {
interface TrueValue {
x: true;
}
const decoder: Decoder<TrueValue> = object({x: constant(true)});
const decoder: Decoder<TrueValue> = object<TrueValue>({x: constant(true)});

expect(decoder.run({x: true})).toEqual({ok: true, result: {x: true}});
});
@@ -133,7 +133,7 @@ describe('constant', () => {
interface FalseValue {
x: false;
}
const decoder: Decoder<FalseValue> = object({x: constant(false)});
const decoder = object<FalseValue>({x: constant(false)});

expect(decoder.run({x: false})).toEqual({ok: true, result: {x: false}});
});
@@ -142,7 +142,7 @@ describe('constant', () => {
interface NullValue {
x: null;
}
const decoder: Decoder<NullValue> = object({x: constant(null)});
const decoder = object<NullValue>({x: constant(null)});

expect(decoder.run({x: null})).toEqual({ok: true, result: {x: null}});
});
@@ -253,14 +253,53 @@ describe('object', () => {
});
});

it('ignores optional fields that decode to undefined', () => {
const decoder = object({
a: number(),
b: optional(string())
describe('optional and undefined fields', () => {
it('ignores optional fields that decode to undefined', () => {
interface AB {
a: number;
b?: string;
}

const decoder: Decoder<AB> = object<AB>({
a: number(),
b: optional(string())
});

expect(decoder.run({a: 12, b: 'hats'})).toEqual({ok: true, result: {a: 12, b: 'hats'}});
expect(decoder.run({a: 12})).toEqual({ok: true, result: {a: 12}});
});

expect(decoder.run({a: 12, b: 'hats'})).toEqual({ok: true, result: {a: 12, b: 'hats'}});
expect(decoder.run({a: 12})).toEqual({ok: true, result: {a: 12}});
it('includes fields that are mapped to a value when not found', () => {
interface AB {
a: number;
b: string;
}

const decoder: Decoder<AB> = object<AB>({
a: number(),
b: oneOf(string(), constant(undefined)).map(
(b: string | undefined) => (b === undefined ? 'b not found' : b)
)
});

expect(decoder.run({a: 12, b: 'hats'})).toEqual({ok: true, result: {a: 12, b: 'hats'}});
expect(decoder.run({a: 12})).toEqual({ok: true, result: {a: 12, b: 'b not found'}});
});

it('includes fields that are mapped to a undefined when not found', () => {
interface AB {
a: number;
b: string | undefined;
}

const decoder: Decoder<AB> = object<AB>({
a: number(),
b: oneOf(string(), constant(undefined))
});

expect(decoder.run({a: 12, b: 'hats'})).toEqual({ok: true, result: {a: 12, b: 'hats'}});
expect(decoder.run({a: 12})).toEqual({ok: true, result: {a: 12, b: undefined}});
});
});
});

@@ -344,32 +383,13 @@ describe('dict', () => {
});

describe('optional', () => {
describe('decoding a non-object type', () => {
const decoder = optional(number());

it('can decode the given type', () => {
expect(decoder.run(5)).toEqual({ok: true, result: 5});
});

it('can decode undefined', () => {
expect(decoder.run(undefined)).toEqual({ok: true, result: undefined});
});

it('fails when the value is invalid', () => {
expect(decoder.run(false)).toMatchObject({
ok: false,
error: {at: 'input', message: 'expected a number, got a boolean'}
});
});
});

describe('decoding an interface with optional fields', () => {
interface User {
id: number;
isDog?: boolean;
}

const decoder: Decoder<User> = object({
const decoder = object<User>({
id: number(),
isDog: optional(boolean())
});
@@ -459,8 +479,8 @@ describe('union', () => {
type C = A | B;

const decoder: Decoder<C> = union(
object({kind: constant<'a'>('a'), value: number()}),
object({kind: constant<'b'>('b'), value: boolean()})
object<A>({kind: constant<'a'>('a'), value: number()}),
object<B>({kind: constant<'b'>('b'), value: boolean()})
);

it('can decode a value that matches one of the union types', () => {
@@ -537,7 +557,7 @@ describe('valueAt', () => {
});

describe('decode an optional field', () => {
const decoder = valueAt(['a', 'b', 'c'], optional(string()));
const decoder = valueAt(['a', 'b', 'c'], oneOf(string(), constant(undefined)));

it('fails when the path does not exist', () => {
const error = decoder.run({a: {x: 'cats'}});
@@ -638,7 +658,7 @@ describe('lazy', () => {
replies: Comment[];
}

const decoder: Decoder<Comment> = object({
const decoder: Decoder<Comment> = object<Comment>({
msg: string(),
replies: lazy(() => array(decoder))
});
9 changes: 6 additions & 3 deletions test/phone-example.test.ts
Original file line number Diff line number Diff line change
@@ -42,14 +42,14 @@ describe('decode phone number objects', () => {
constant(PhoneUse.Work)
);

const internationalPhoneDecoder: Decoder<InternationalPhone> = object({
const internationalPhoneDecoder = object<InternationalPhone>({
id: number(),
use: optional(phoneUseDecoder),
international: constant(true),
rawNumber: string()
});

const domesticPhoneDecoder: Decoder<DomesticPhone> = object({
const domesticPhoneDecoder = object<DomesticPhone>({
id: number(),
use: optional(phoneUseDecoder),
international: constant(false),
@@ -58,7 +58,10 @@ describe('decode phone number objects', () => {
lineNumber: string()
});

const phoneDecoder: Decoder<Phone> = union(domesticPhoneDecoder, internationalPhoneDecoder);
const phoneDecoder: Decoder<Phone> = union<DomesticPhone, InternationalPhone>(
domesticPhoneDecoder,
internationalPhoneDecoder
);

const phonesDecoder: Decoder<Phone[]> = array(phoneDecoder);

4 changes: 2 additions & 2 deletions test/user-example.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {Decoder, string, number, boolean, object} from '../src/index';
import {string, number, boolean, object} from '../src/index';

describe('decode json as User interface', () => {
interface User {
@@ -22,7 +22,7 @@ describe('decode json as User interface', () => {
active: false
};

const userDecoder: Decoder<User> = object({
const userDecoder = object<User>({
firstname: string(),
lastname: string(),
age: number(),