-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathguards.test.ts
130 lines (106 loc) · 3.26 KB
/
guards.test.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
import { Guard, GuardEach } from "./index";
import { and } from "./operators/operators";
import { isString } from "./guards/guards";
describe("Guard", () => {
it("takes an object of keys and validators, validates input with the same keys, and passes if every validator passes (short-ciruit on fail)", () => {
interface Person {
name: string;
age: number;
}
const isName = (x: string): x is string => x.length > 1;
const isAge = (x: number): x is number => 0 <= x && x < 120;
const isPerson = Guard<Person>({
name: isName,
age: isAge
});
expect(isPerson({ name: "Kirk", age: 65 })).toBe(true);
expect(
isPerson({
name: "Spock",
age: 161
})
).toBe(false);
// @ts-ignore
expect(isPerson({ name: "tribble" })).toBe(false);
});
it("fails gracefully, if passed null or undefined", () => {
const holmes: User = {
name: "Sherlock Holmes",
address: {
street: "221B Baker St",
city: "London",
geo: { lat: "51.520664584", long: "-0.15499938" }
}
};
//@ts-ignore
const dent: User = {
name: "Arthur Dent",
address: null
};
const lte = (max: number) => (x: string | number) => +x <= max;
const gte = (min: number) => (x: string | number) => +x >= min;
const isNumberAsString = (x: string) =>
typeof +x === "number" && !isNaN(+x);
const isValidLat = and(isNumberAsString, gte(-90), lte(90));
const isValidLong = and(isNumberAsString, gte(-180), lte(180));
const isValidAddress = Guard<Address>({
street: isString,
city: isString,
geo: Guard({ lat: isValidLat, long: isValidLong })
});
const isValidUser = Guard<User>({
name: isString,
address: isValidAddress
});
interface User {
name: string;
address: Address;
}
interface Address {
street: string;
city: string;
geo: Geo;
}
interface Geo {
lat: string;
long: string;
}
// @ts-ignore
expect(isValidUser(null)).toBe(false);
expect(isValidUser(holmes)).toBe(true);
expect(isValidUser(dent)).toBe(false);
});
});
describe("GuardEach", () => {
it("takes a TypeGuard and an Iterable<T> and passes if each T passes", () => {
interface CrewMember {
name: string;
role: string;
posting: string;
}
const isCrewMember = (x: CrewMember): x is CrewMember =>
x.posting === "Enterprise";
const isCrew = GuardEach(isCrewMember);
const crew1 = [
{ name: "Kirk", role: "captain", posting: "Enterprise" },
{ name: "Spock", role: "commander", posting: "Enterprise" }
];
const crew2 = [
{ name: "Picard", role: "captain", posting: "Enterprise" },
{ name: "Q", role: "foil", posting: "Q-Continuum" }
];
expect(isCrew(crew1)).toBe(true);
expect(isCrew(crew2)).toBe(false);
});
it("returns false if given a value which is not an array", () => {
const allRight = GuardEach((x: boolean): x is true => x === true);
// @ts-ignore
expect(allRight({})).toBe(false);
// @ts-ignore
expect(allRight(false)).toBe(false);
// @ts-ignore
expect(allRight([false])).toBe(false);
expect(allRight([])).toBe(true);
expect(allRight([true])).toBe(true);
});
});