@data-client/rest 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +17 -16
- package/dist/index.umd.min.js +1 -1
- package/dist/next.js +7 -2
- package/legacy/OptionsToFunction.js +1 -1
- package/legacy/RestEndpoint.js +5 -5
- package/legacy/RestHelpers.js +4 -2
- package/legacy/createResource.js +14 -13
- package/legacy/index.js +2 -1
- package/legacy/next/index.js +4 -4
- package/legacy/pathTypes.js +1 -1
- package/legacy/resourceTypes.js +2 -0
- package/lib/OptionsToFunction.d.ts.map +1 -1
- package/lib/OptionsToFunction.js +1 -1
- package/lib/RestEndpoint.d.ts +30 -36
- package/lib/RestEndpoint.js +5 -5
- package/lib/RestHelpers.js +4 -2
- package/lib/createResource.d.ts +65 -32
- package/lib/createResource.d.ts.map +1 -1
- package/lib/createResource.js +11 -12
- package/lib/index.d.ts +3 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +2 -1
- package/lib/next/index.d.ts +5 -5
- package/lib/next/index.d.ts.map +1 -1
- package/lib/next/index.js +4 -4
- package/lib/pathTypes.d.ts +1 -0
- package/lib/pathTypes.d.ts.map +1 -1
- package/lib/pathTypes.js +1 -1
- package/lib/{next/resourceTypes.d.ts → resourceTypes.d.ts} +12 -2
- package/lib/resourceTypes.d.ts.map +1 -0
- package/lib/resourceTypes.js +2 -0
- package/package.json +3 -3
- package/src/OptionsToFunction.ts +0 -1
- package/src/RestEndpoint.d.ts +30 -36
- package/src/RestEndpoint.js +4 -4
- package/src/RestHelpers.ts +4 -4
- package/src/__tests__/Resource.test.ts +2 -2
- package/src/__tests__/RestEndpoint.ts +38 -9
- package/src/__tests__/__snapshots__/createResource.test.ts.snap +2 -0
- package/src/__tests__/createResource.test.ts +25 -4
- package/src/__tests__/hookifyResource.ts +7 -3
- package/src/__tests__/types.test.ts +353 -212
- package/src/createResource.ts +111 -62
- package/src/index.ts +2 -2
- package/src/next/index.ts +5 -5
- package/src/pathTypes.ts +2 -0
- package/src/{next/resourceTypes.ts → resourceTypes.ts} +12 -2
- package/ts4.0/RestEndpoint.d.ts +19 -33
- package/ts4.0/createResource.d.ts +77 -32
- package/ts4.0/index.d.ts +3 -1
- package/ts4.0/next/index.d.ts +5 -5
- package/ts4.0/pathTypes.d.ts +2 -0
- package/ts4.0/{next/resourceTypes.d.ts → resourceTypes.d.ts} +12 -2
- package/legacy/next/OptionsToFunction.js +0 -2
- package/legacy/next/RestEndpoint.js +0 -225
- package/legacy/next/createResource.js +0 -86
- package/legacy/next/resourceTypes.js +0 -2
- package/lib/next/OptionsToFunction.d.ts +0 -7
- package/lib/next/OptionsToFunction.d.ts.map +0 -1
- package/lib/next/OptionsToFunction.js +0 -2
- package/lib/next/RestEndpoint.d.ts +0 -542
- package/lib/next/RestEndpoint.js +0 -229
- package/lib/next/createResource.d.ts +0 -86
- package/lib/next/createResource.d.ts.map +0 -1
- package/lib/next/createResource.js +0 -100
- package/lib/next/resourceTypes.d.ts.map +0 -1
- package/lib/next/resourceTypes.js +0 -2
- package/src/next/OptionsToFunction.ts +0 -41
- package/src/next/RestEndpoint.d.ts +0 -542
- package/src/next/RestEndpoint.js +0 -262
- package/src/next/__tests__/RestEndpoint.ts +0 -1333
- package/src/next/__tests__/__snapshots__/RestEndpoint.ts.snap +0 -18
- package/src/next/__tests__/types.test.ts +0 -429
- package/src/next/createResource.ts +0 -194
- package/ts4.0/next/OptionsToFunction.d.ts +0 -16
- package/ts4.0/next/RestEndpoint.d.ts +0 -226
- package/ts4.0/next/createResource.d.ts +0 -94
|
@@ -1,1333 +0,0 @@
|
|
|
1
|
-
import { Entity } from '@data-client/endpoint';
|
|
2
|
-
import { useController } from '@data-client/react';
|
|
3
|
-
import { useSuspense } from '@data-client/react';
|
|
4
|
-
import { CacheProvider } from '@data-client/react';
|
|
5
|
-
import { Article, CoolerArticle, CoolerArticleResource } from '__tests__/new';
|
|
6
|
-
import nock from 'nock';
|
|
7
|
-
|
|
8
|
-
import { makeRenderRestHook } from '../../../../test';
|
|
9
|
-
import {
|
|
10
|
-
payload,
|
|
11
|
-
createPayload,
|
|
12
|
-
users,
|
|
13
|
-
nested,
|
|
14
|
-
moreNested,
|
|
15
|
-
paginatedFirstPage,
|
|
16
|
-
paginatedSecondPage,
|
|
17
|
-
} from '../../test-fixtures';
|
|
18
|
-
import RestEndpoint, {
|
|
19
|
-
Defaults,
|
|
20
|
-
RestEndpointConstructorOptions,
|
|
21
|
-
RestGenerics,
|
|
22
|
-
} from '../RestEndpoint';
|
|
23
|
-
|
|
24
|
-
export class User extends Entity {
|
|
25
|
-
readonly id: number | undefined = undefined;
|
|
26
|
-
readonly username: string = '';
|
|
27
|
-
readonly email: string = '';
|
|
28
|
-
readonly isAdmin: boolean = false;
|
|
29
|
-
|
|
30
|
-
pk() {
|
|
31
|
-
return this.id?.toString();
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
const getUser = new RestEndpoint({
|
|
35
|
-
path: 'http\\://test.com/user/:id',
|
|
36
|
-
name: 'User.get',
|
|
37
|
-
schema: User,
|
|
38
|
-
method: 'GET',
|
|
39
|
-
searchParams: {} as { extra?: string },
|
|
40
|
-
});
|
|
41
|
-
export class PaginatedArticle extends Entity {
|
|
42
|
-
readonly id: number | undefined = undefined;
|
|
43
|
-
readonly title: string = '';
|
|
44
|
-
readonly content: string = '';
|
|
45
|
-
readonly author: number | null = null;
|
|
46
|
-
readonly tags: string[] = [];
|
|
47
|
-
|
|
48
|
-
pk() {
|
|
49
|
-
return this.id?.toString();
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
static schema = {
|
|
53
|
-
author: User,
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
const getArticleList = new RestEndpoint({
|
|
57
|
-
urlPrefix: 'http://test.com',
|
|
58
|
-
path: '/article-paginated',
|
|
59
|
-
schema: {
|
|
60
|
-
nextPage: '',
|
|
61
|
-
data: { results: [PaginatedArticle] },
|
|
62
|
-
},
|
|
63
|
-
method: 'GET',
|
|
64
|
-
});
|
|
65
|
-
const getNextPage = getArticleList.paginated(
|
|
66
|
-
(v: { cursor: string | number }) => [],
|
|
67
|
-
);
|
|
68
|
-
|
|
69
|
-
const getArticleList2 = new RestEndpoint({
|
|
70
|
-
urlPrefix: 'http://test.com/article-paginated/',
|
|
71
|
-
path: ':group',
|
|
72
|
-
name: 'get',
|
|
73
|
-
schema: {
|
|
74
|
-
nextPage: '',
|
|
75
|
-
data: { results: [PaginatedArticle] },
|
|
76
|
-
},
|
|
77
|
-
method: 'GET',
|
|
78
|
-
});
|
|
79
|
-
const getNextPage2 = getArticleList2.paginated(
|
|
80
|
-
({
|
|
81
|
-
cursor,
|
|
82
|
-
...rest
|
|
83
|
-
}: {
|
|
84
|
-
cursor: string | number;
|
|
85
|
-
group: string | number;
|
|
86
|
-
}) => [rest],
|
|
87
|
-
);
|
|
88
|
-
|
|
89
|
-
describe('RestEndpoint', () => {
|
|
90
|
-
const renderRestHook: ReturnType<typeof makeRenderRestHook> =
|
|
91
|
-
makeRenderRestHook(CacheProvider);
|
|
92
|
-
let mynock: nock.Scope;
|
|
93
|
-
|
|
94
|
-
beforeEach(() => {
|
|
95
|
-
nock(/.*/)
|
|
96
|
-
.persist()
|
|
97
|
-
.defaultReplyHeaders({
|
|
98
|
-
'Access-Control-Allow-Origin': '*',
|
|
99
|
-
'Content-Type': 'application/json',
|
|
100
|
-
})
|
|
101
|
-
.options(/.*/)
|
|
102
|
-
.reply(200)
|
|
103
|
-
.get(`/article-cooler/${payload.id}`)
|
|
104
|
-
.reply(200, payload)
|
|
105
|
-
.delete(`/article-cooler/${payload.id}`)
|
|
106
|
-
.reply(204, '')
|
|
107
|
-
.delete(`/article/${payload.id}`)
|
|
108
|
-
.reply(200, {})
|
|
109
|
-
.get(`/article-cooler/0`)
|
|
110
|
-
.reply(403, {})
|
|
111
|
-
.get(`/article-cooler/666`)
|
|
112
|
-
.reply(200, '')
|
|
113
|
-
.get(`/article-cooler`)
|
|
114
|
-
.reply(200, nested)
|
|
115
|
-
.post(`/article-cooler`)
|
|
116
|
-
.reply(200, createPayload)
|
|
117
|
-
.get(`/user`)
|
|
118
|
-
.reply(200, users);
|
|
119
|
-
mynock = nock(/.*/).defaultReplyHeaders({
|
|
120
|
-
'Access-Control-Allow-Origin': '*',
|
|
121
|
-
'Content-Type': 'application/json',
|
|
122
|
-
});
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
afterEach(() => {
|
|
126
|
-
nock.cleanAll();
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
it('testKey should match keys', () => {
|
|
130
|
-
expect(getArticleList.testKey(getArticleList.key())).toBeTruthy();
|
|
131
|
-
expect(
|
|
132
|
-
getUser.testKey(getUser.key({ id: '100', extra: '345' })),
|
|
133
|
-
).toBeTruthy();
|
|
134
|
-
expect(getUser.testKey(getUser.key({ id: 'xxx?*' }))).toBeTruthy();
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
it('should assign members', () => {
|
|
138
|
-
expect(getUser.path).toBe('http\\://test.com/user/:id');
|
|
139
|
-
expect(getUser.sideEffect).toBe(undefined);
|
|
140
|
-
expect(getUser.method).toBe('GET');
|
|
141
|
-
|
|
142
|
-
// @ts-expect-error
|
|
143
|
-
() => getUser.notassigned;
|
|
144
|
-
// @ts-expect-error
|
|
145
|
-
const a: true = getUser.sideEffect;
|
|
146
|
-
// @ts-expect-error
|
|
147
|
-
const b: 'POST' = getUser.method;
|
|
148
|
-
// @ts-expect-error
|
|
149
|
-
((m: 'POST') => {})(getUser.method);
|
|
150
|
-
|
|
151
|
-
const updateUser = new RestEndpoint({
|
|
152
|
-
path: 'http\\://test.com/user/:id',
|
|
153
|
-
name: 'update',
|
|
154
|
-
schema: User,
|
|
155
|
-
method: 'POST',
|
|
156
|
-
});
|
|
157
|
-
expect(updateUser.sideEffect).toBe(true);
|
|
158
|
-
// @ts-expect-error
|
|
159
|
-
const y: undefined = updateUser.sideEffect;
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
it('only optional path means the arg is not required', () => {
|
|
163
|
-
const ep = new RestEndpoint({ path: '/users/:id?/:group?' });
|
|
164
|
-
const epbody = new RestEndpoint({
|
|
165
|
-
path: '/users/:id?/:group?',
|
|
166
|
-
body: { title: '' },
|
|
167
|
-
});
|
|
168
|
-
() => ep();
|
|
169
|
-
() => ep({ id: 5 });
|
|
170
|
-
() => ep({ group: 5 });
|
|
171
|
-
() => ep({ id: 5, group: 5 });
|
|
172
|
-
() => epbody({ title: 'hi' });
|
|
173
|
-
() => epbody({ id: 5 }, { title: 'hi' });
|
|
174
|
-
() => epbody({ group: 5 }, { title: 'hi' });
|
|
175
|
-
() => epbody({ id: 5, group: 5 }, { title: 'hi' });
|
|
176
|
-
// @ts-expect-error
|
|
177
|
-
() => epbody({ title: 'hi' }, { title: 'hi' });
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
/* TODO: it('should allow sideEffect overrides', () => {
|
|
181
|
-
const weirdGetUser = new RestEndpoint({
|
|
182
|
-
path: 'http\\://test.com/user/:id',
|
|
183
|
-
name: 'getter',
|
|
184
|
-
schema: User,
|
|
185
|
-
method: 'POST',
|
|
186
|
-
sideEffect: undefined,
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
expect(weirdGetUser.sideEffect).toBe(undefined);
|
|
190
|
-
//s @ts-expect-error
|
|
191
|
-
//const y: true = weirdGetUser.sideEffect;
|
|
192
|
-
});*/
|
|
193
|
-
|
|
194
|
-
it('should handle simple urls', () => {
|
|
195
|
-
expect(getUser.url({ id: '5' })).toBe('http://test.com/user/5');
|
|
196
|
-
expect(getUser.url({ id: '100' })).toBe('http://test.com/user/100');
|
|
197
|
-
|
|
198
|
-
// @ts-expect-error
|
|
199
|
-
() => getUser.url({ sdf: '5' });
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
it('should handle multiarg urls', () => {
|
|
203
|
-
const getMyUser = new RestEndpoint({
|
|
204
|
-
path: 'http\\://test.com/groups/:group/users/:id',
|
|
205
|
-
schema: User,
|
|
206
|
-
method: 'GET',
|
|
207
|
-
extra: 5,
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
expect(getMyUser.url({ group: 'big', id: '5' })).toBe(
|
|
211
|
-
'http://test.com/groups/big/users/5',
|
|
212
|
-
);
|
|
213
|
-
expect(getMyUser.url({ group: 'big', id: '100' })).toBe(
|
|
214
|
-
'http://test.com/groups/big/users/100',
|
|
215
|
-
);
|
|
216
|
-
|
|
217
|
-
// missing required
|
|
218
|
-
expect(() =>
|
|
219
|
-
// @ts-expect-error
|
|
220
|
-
getMyUser.url({ id: '5' }),
|
|
221
|
-
).toThrow();
|
|
222
|
-
// extra fields
|
|
223
|
-
() =>
|
|
224
|
-
getMyUser.url({
|
|
225
|
-
group: 'mygroup',
|
|
226
|
-
id: '5',
|
|
227
|
-
// @ts-expect-error
|
|
228
|
-
notexisting: 'hi',
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
// @ts-expect-error
|
|
232
|
-
() => useSuspense(getMyUser, { id: '5' });
|
|
233
|
-
// @ts-expect-error
|
|
234
|
-
() => useSuspense(getMyUser);
|
|
235
|
-
() => useSuspense(getMyUser, { group: 'yay', id: '5' });
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
it('should automatically name methods', () => {
|
|
239
|
-
expect(getUser.name).toBe('User.get');
|
|
240
|
-
expect(getArticleList.name).toMatchInlineSnapshot(
|
|
241
|
-
`"http://test.com/article-paginated"`,
|
|
242
|
-
);
|
|
243
|
-
expect(
|
|
244
|
-
getArticleList.extend({ path: '/:something' }).name,
|
|
245
|
-
).toMatchInlineSnapshot(`"http://test.com/:something"`);
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
it('should update on get for a paginated resource', async () => {
|
|
249
|
-
mynock.get(`/article-paginated`).reply(200, paginatedFirstPage);
|
|
250
|
-
mynock.get(`/article-paginated?cursor=2`).reply(200, paginatedSecondPage);
|
|
251
|
-
|
|
252
|
-
const { result, waitForNextUpdate, controller } = renderRestHook(() => {
|
|
253
|
-
const { fetch } = useController();
|
|
254
|
-
const {
|
|
255
|
-
data: { results: articles },
|
|
256
|
-
nextPage,
|
|
257
|
-
} = useSuspense(getArticleList);
|
|
258
|
-
return { articles, nextPage, fetch };
|
|
259
|
-
});
|
|
260
|
-
await waitForNextUpdate();
|
|
261
|
-
// @ts-expect-error
|
|
262
|
-
() => controller.fetch(getNextPage);
|
|
263
|
-
// @ts-expect-error
|
|
264
|
-
() => controller.fetch(getNextPage, { fake: 5 });
|
|
265
|
-
expect(result.current.nextPage).toEqual(paginatedFirstPage.nextPage);
|
|
266
|
-
await controller.fetch(getNextPage, {
|
|
267
|
-
cursor: result.current.nextPage,
|
|
268
|
-
});
|
|
269
|
-
expect(result.current.articles.map(({ id }) => id)).toEqual([5, 3, 7, 8]);
|
|
270
|
-
expect(result.current.nextPage).toBeUndefined();
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
it('should update on get for a paginated resource with parameter in path', async () => {
|
|
274
|
-
mynock.get(`/article-paginated/happy`).reply(200, paginatedFirstPage);
|
|
275
|
-
mynock
|
|
276
|
-
.get(`/article-paginated/happy?cursor=2`)
|
|
277
|
-
.reply(200, paginatedSecondPage);
|
|
278
|
-
|
|
279
|
-
const { result, waitForNextUpdate } = renderRestHook(() => {
|
|
280
|
-
const { fetch } = useController();
|
|
281
|
-
const {
|
|
282
|
-
data: { results: articles },
|
|
283
|
-
nextPage,
|
|
284
|
-
} = useSuspense(getArticleList2, {
|
|
285
|
-
group: 'happy',
|
|
286
|
-
});
|
|
287
|
-
return { articles, nextPage, fetch };
|
|
288
|
-
});
|
|
289
|
-
await waitForNextUpdate();
|
|
290
|
-
// @ts-expect-error
|
|
291
|
-
() => result.current.fetch(getNextPage2);
|
|
292
|
-
// @ts-expect-error
|
|
293
|
-
() => result.current.fetch(getNextPage2, { fake: 5 });
|
|
294
|
-
// @ts-expect-error
|
|
295
|
-
() => result.current.fetch(getNextPage2, { group: 'happy' });
|
|
296
|
-
// @ts-expect-error
|
|
297
|
-
() => result.current.fetch(getNextPage2, { cursor: 2 });
|
|
298
|
-
await result.current.fetch(getNextPage2, {
|
|
299
|
-
group: 'happy',
|
|
300
|
-
cursor: 2,
|
|
301
|
-
});
|
|
302
|
-
expect(result.current.articles.map(({ id }) => id)).toEqual([5, 3, 7, 8]);
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
it('should deduplicate results', async () => {
|
|
306
|
-
mynock.get(`/article-paginated`).reply(200, paginatedFirstPage);
|
|
307
|
-
mynock.get(`/article-paginated?cursor=2`).reply(200, {
|
|
308
|
-
...paginatedSecondPage,
|
|
309
|
-
results: [nested[nested.length - 1], ...moreNested],
|
|
310
|
-
});
|
|
311
|
-
|
|
312
|
-
const { result, waitForNextUpdate } = renderRestHook(() => {
|
|
313
|
-
const { fetch } = useController();
|
|
314
|
-
const {
|
|
315
|
-
data: { results: articles },
|
|
316
|
-
nextPage,
|
|
317
|
-
} = useSuspense(getArticleList);
|
|
318
|
-
return { articles, nextPage, fetch };
|
|
319
|
-
});
|
|
320
|
-
await waitForNextUpdate();
|
|
321
|
-
await result.current.fetch(getNextPage, {
|
|
322
|
-
cursor: 2,
|
|
323
|
-
});
|
|
324
|
-
//TODO: Why is this broken? expect(result.current.articles.map(({ id }) => id)).toEqual([5, 3, 7, 8]);
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
it('should not deep-merge deeply defined entities', async () => {
|
|
328
|
-
interface Complex {
|
|
329
|
-
firstvalue: number;
|
|
330
|
-
secondthing: {
|
|
331
|
-
arg?: number;
|
|
332
|
-
other?: string;
|
|
333
|
-
};
|
|
334
|
-
}
|
|
335
|
-
class ComplexEntity extends Entity {
|
|
336
|
-
readonly id: string = '';
|
|
337
|
-
readonly complexThing?: Complex = undefined;
|
|
338
|
-
readonly extra: string = '';
|
|
339
|
-
|
|
340
|
-
pk() {
|
|
341
|
-
return this.id;
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
const getComplex = new RestEndpoint({
|
|
345
|
-
path: '/complex-thing/:id',
|
|
346
|
-
schema: ComplexEntity,
|
|
347
|
-
method: 'GET',
|
|
348
|
-
});
|
|
349
|
-
|
|
350
|
-
const firstResponse = {
|
|
351
|
-
id: '5',
|
|
352
|
-
complexThing: {
|
|
353
|
-
firstvalue: 233,
|
|
354
|
-
secondthing: { arg: 88 },
|
|
355
|
-
},
|
|
356
|
-
extra: 'hi',
|
|
357
|
-
};
|
|
358
|
-
mynock.get(`/complex-thing/5`).reply(200, firstResponse);
|
|
359
|
-
|
|
360
|
-
const { result, waitForNextUpdate } = renderRestHook(() => {
|
|
361
|
-
const { fetch } = useController();
|
|
362
|
-
const article = useSuspense(getComplex, { id: '5' });
|
|
363
|
-
return { article, fetch };
|
|
364
|
-
});
|
|
365
|
-
await waitForNextUpdate();
|
|
366
|
-
expect(result.current.article).toEqual(firstResponse);
|
|
367
|
-
|
|
368
|
-
const secondResponse = {
|
|
369
|
-
id: '5',
|
|
370
|
-
complexThing: {
|
|
371
|
-
firstvalue: 5,
|
|
372
|
-
secondthing: { other: 'hi' },
|
|
373
|
-
},
|
|
374
|
-
};
|
|
375
|
-
|
|
376
|
-
mynock.get(`/complex-thing/5`).reply(200, secondResponse);
|
|
377
|
-
await result.current.fetch(getComplex, {
|
|
378
|
-
id: '5',
|
|
379
|
-
});
|
|
380
|
-
expect(result.current.article).toEqual({ ...secondResponse, extra: 'hi' });
|
|
381
|
-
});
|
|
382
|
-
|
|
383
|
-
it('overriding methods should work', async () => {
|
|
384
|
-
mynock.put(`/user/5`).reply(200, (uri, body: any) => ({
|
|
385
|
-
id: 5,
|
|
386
|
-
username: 'bob',
|
|
387
|
-
...body,
|
|
388
|
-
}));
|
|
389
|
-
const updateUser = new RestEndpoint({
|
|
390
|
-
method: 'PUT',
|
|
391
|
-
path: 'http\\://test.com/user/:id',
|
|
392
|
-
name: 'get',
|
|
393
|
-
schema: User,
|
|
394
|
-
getRequestInit(body) {
|
|
395
|
-
if (body && isPojo(body)) {
|
|
396
|
-
return RestEndpoint.prototype.getRequestInit.call(this, {
|
|
397
|
-
...body,
|
|
398
|
-
email: 'always@always.com',
|
|
399
|
-
});
|
|
400
|
-
}
|
|
401
|
-
return RestEndpoint.prototype.getRequestInit.call(this, body);
|
|
402
|
-
},
|
|
403
|
-
});
|
|
404
|
-
const response = await updateUser(
|
|
405
|
-
{ id: 5 },
|
|
406
|
-
{ username: 'micky', email: 'micky@gmail.com' },
|
|
407
|
-
);
|
|
408
|
-
expect(response).toMatchInlineSnapshot(`
|
|
409
|
-
{
|
|
410
|
-
"email": "always@always.com",
|
|
411
|
-
"id": 5,
|
|
412
|
-
"username": "micky",
|
|
413
|
-
}
|
|
414
|
-
`);
|
|
415
|
-
});
|
|
416
|
-
|
|
417
|
-
describe('class extensions', () => {
|
|
418
|
-
class DefaultUser extends User {
|
|
419
|
-
defaultUserExtra = 'yay';
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
class MyEndpoint<O extends RestGenerics = any> extends RestEndpoint<
|
|
423
|
-
Defaults<O, { schema: DefaultUser }>
|
|
424
|
-
> {
|
|
425
|
-
constructor(options: Readonly<RestEndpointConstructorOptions<O> & O>) {
|
|
426
|
-
super({ schema: DefaultUser, ...options } as any);
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
parseResponse(response: Response): Promise<any> {
|
|
430
|
-
return super.parseResponse(response);
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
getRequestInit(body: any) {
|
|
434
|
-
if (isPojo(body)) {
|
|
435
|
-
return super.getRequestInit({ ...body, email: 'always@always.com' });
|
|
436
|
-
}
|
|
437
|
-
return super.getRequestInit(body);
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
additional = 5;
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
it('should work with constructor', async () => {
|
|
444
|
-
mynock.put('/user/5').reply(200, (uri, body: any) => ({
|
|
445
|
-
id: 5,
|
|
446
|
-
username: 'bob',
|
|
447
|
-
...body,
|
|
448
|
-
}));
|
|
449
|
-
|
|
450
|
-
const updateUser = new MyEndpoint({
|
|
451
|
-
method: 'PUT',
|
|
452
|
-
path: 'http\\://test.com/user/:id',
|
|
453
|
-
name: 'update',
|
|
454
|
-
schema: User,
|
|
455
|
-
});
|
|
456
|
-
const response = await updateUser(
|
|
457
|
-
{ id: 5 },
|
|
458
|
-
{ username: 'micky', email: 'micky@gmail.com' },
|
|
459
|
-
);
|
|
460
|
-
expect(response).toMatchInlineSnapshot(`
|
|
461
|
-
{
|
|
462
|
-
"email": "always@always.com",
|
|
463
|
-
"id": 5,
|
|
464
|
-
"username": "micky",
|
|
465
|
-
}
|
|
466
|
-
`);
|
|
467
|
-
expect(updateUser.additional).toBe(5);
|
|
468
|
-
});
|
|
469
|
-
|
|
470
|
-
it('setting body in extend should work', async () => {
|
|
471
|
-
mynock.put('/charmer/5').reply(200, (uri, body: any) => ({
|
|
472
|
-
id: 5,
|
|
473
|
-
username: 'bob',
|
|
474
|
-
...body,
|
|
475
|
-
}));
|
|
476
|
-
|
|
477
|
-
const updateUser = new MyEndpoint({
|
|
478
|
-
method: 'PUT',
|
|
479
|
-
path: 'http\\://test.com/user/:id',
|
|
480
|
-
name: 'update',
|
|
481
|
-
schema: User,
|
|
482
|
-
}).extend({
|
|
483
|
-
body: 5,
|
|
484
|
-
path: 'http\\://test.com/charmer/:charm',
|
|
485
|
-
});
|
|
486
|
-
const response = await updateUser(
|
|
487
|
-
{ charm: 5 },
|
|
488
|
-
// @ts-expect-error
|
|
489
|
-
{ username: 'micky', email: 'micky@gmail.com' },
|
|
490
|
-
);
|
|
491
|
-
() => updateUser({ charm: 5 }, 5);
|
|
492
|
-
expect(response).toMatchInlineSnapshot(`
|
|
493
|
-
{
|
|
494
|
-
"email": "always@always.com",
|
|
495
|
-
"id": 5,
|
|
496
|
-
"username": "micky",
|
|
497
|
-
}
|
|
498
|
-
`);
|
|
499
|
-
expect(updateUser.additional).toBe(5);
|
|
500
|
-
const nobody = updateUser.extend({
|
|
501
|
-
path: 'http\\://test.com/user/:charm',
|
|
502
|
-
body: undefined,
|
|
503
|
-
});
|
|
504
|
-
() => nobody({ charm: 5 });
|
|
505
|
-
// @ts-expect-error
|
|
506
|
-
() => nobody({ id: 5 });
|
|
507
|
-
});
|
|
508
|
-
|
|
509
|
-
it('setting body in extend should work without path', async () => {
|
|
510
|
-
mynock.put('/user/5').reply(200, (uri, body: any) => ({
|
|
511
|
-
id: 5,
|
|
512
|
-
username: 'bob',
|
|
513
|
-
...body,
|
|
514
|
-
}));
|
|
515
|
-
|
|
516
|
-
const updateUser = new MyEndpoint({
|
|
517
|
-
method: 'PUT',
|
|
518
|
-
path: 'http\\://test.com/user/:id',
|
|
519
|
-
name: 'update',
|
|
520
|
-
schema: User,
|
|
521
|
-
}).extend({
|
|
522
|
-
body: 5,
|
|
523
|
-
});
|
|
524
|
-
const response = await updateUser(
|
|
525
|
-
{ id: 5 },
|
|
526
|
-
// @ts-expect-error
|
|
527
|
-
{ username: 'micky', email: 'micky@gmail.com' },
|
|
528
|
-
);
|
|
529
|
-
() => updateUser({ id: 5 }, 5);
|
|
530
|
-
expect(response).toMatchInlineSnapshot(`
|
|
531
|
-
{
|
|
532
|
-
"email": "always@always.com",
|
|
533
|
-
"id": 5,
|
|
534
|
-
"username": "micky",
|
|
535
|
-
}
|
|
536
|
-
`);
|
|
537
|
-
expect(updateUser.additional).toBe(5);
|
|
538
|
-
const nobody = updateUser.extend({
|
|
539
|
-
path: 'http\\://test.com/user/:charm',
|
|
540
|
-
body: undefined,
|
|
541
|
-
});
|
|
542
|
-
() => nobody({ charm: 5 });
|
|
543
|
-
// @ts-expect-error
|
|
544
|
-
() => nobody({ id: 5 });
|
|
545
|
-
|
|
546
|
-
const updateUser2 = new MyEndpoint({
|
|
547
|
-
method: 'PUT',
|
|
548
|
-
path: 'http\\://test.com/user/:id',
|
|
549
|
-
name: 'update',
|
|
550
|
-
schema: User,
|
|
551
|
-
}).extend({
|
|
552
|
-
searchParams: {} as { isAdmin: boolean },
|
|
553
|
-
});
|
|
554
|
-
() =>
|
|
555
|
-
updateUser2(
|
|
556
|
-
{ id: 5, isAdmin: true },
|
|
557
|
-
{ username: 'micky', email: 'micky@gmail.com' },
|
|
558
|
-
);
|
|
559
|
-
() =>
|
|
560
|
-
// @ts-expect-error
|
|
561
|
-
updateUser2({ id: 5 }, { username: 'micky', email: 'micky@gmail.com' });
|
|
562
|
-
() =>
|
|
563
|
-
updateUser2(
|
|
564
|
-
// @ts-expect-error
|
|
565
|
-
{ isAdmin: true },
|
|
566
|
-
{ username: 'micky', email: 'micky@gmail.com' },
|
|
567
|
-
);
|
|
568
|
-
|
|
569
|
-
const updateBasic = new MyEndpoint({
|
|
570
|
-
method: 'PUT',
|
|
571
|
-
path: 'http\\://test.com/user/:id',
|
|
572
|
-
name: 'update',
|
|
573
|
-
schema: User,
|
|
574
|
-
});
|
|
575
|
-
() =>
|
|
576
|
-
updateBasic({ id: 5 }, { username: 'micky', email: 'micky@gmail.com' });
|
|
577
|
-
() =>
|
|
578
|
-
updateBasic(
|
|
579
|
-
// @ts-expect-error
|
|
580
|
-
{ id: 5, isAdmin: true },
|
|
581
|
-
{ username: 'micky', email: 'micky@gmail.com' },
|
|
582
|
-
);
|
|
583
|
-
});
|
|
584
|
-
|
|
585
|
-
it('should work with default schema in class definition', async () => {
|
|
586
|
-
mynock.get('/user/5').reply(200, (uri, body: any) => ({
|
|
587
|
-
id: 5,
|
|
588
|
-
username: 'bob',
|
|
589
|
-
email: 'bob@gmail.com',
|
|
590
|
-
}));
|
|
591
|
-
|
|
592
|
-
const getUser = new MyEndpoint({
|
|
593
|
-
path: 'http\\://test.com/user/:id',
|
|
594
|
-
name: 'update',
|
|
595
|
-
});
|
|
596
|
-
const { result, waitForNextUpdate } = renderRestHook(() => {
|
|
597
|
-
return useSuspense(getUser, { id: 5 });
|
|
598
|
-
});
|
|
599
|
-
await waitForNextUpdate();
|
|
600
|
-
|
|
601
|
-
expect(result.current.username).toBe('bob');
|
|
602
|
-
// @ts-expect-error
|
|
603
|
-
expect(result.current.sdfsd).toBeUndefined();
|
|
604
|
-
expect(result.current.defaultUserExtra).toBe('yay');
|
|
605
|
-
expect(result.current.email).toBe('bob@gmail.com');
|
|
606
|
-
});
|
|
607
|
-
|
|
608
|
-
it('should work with default path in class definition', async () => {
|
|
609
|
-
mynock.get('/user/5').reply(200, (uri, body: any) => ({
|
|
610
|
-
id: 5,
|
|
611
|
-
username: 'bob',
|
|
612
|
-
email: 'bob@gmail.com',
|
|
613
|
-
}));
|
|
614
|
-
// this seems like a less common use case; so we're fine with it being annoying
|
|
615
|
-
class UserEndpoint<
|
|
616
|
-
O extends Partial<RestGenerics> = {
|
|
617
|
-
schema: DefaultUser;
|
|
618
|
-
path: 'http\\://test.com/user/:id';
|
|
619
|
-
},
|
|
620
|
-
> extends MyEndpoint<
|
|
621
|
-
Defaults<O, { schema: DefaultUser; path: 'http\\://test.com/user/:id' }>
|
|
622
|
-
> {
|
|
623
|
-
constructor({
|
|
624
|
-
path = 'http\\://test.com/user/:id',
|
|
625
|
-
...options
|
|
626
|
-
}: Readonly<O & { name: string }>) {
|
|
627
|
-
super({ path, ...options } as any);
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
const getUser = new UserEndpoint({
|
|
632
|
-
method: 'GET',
|
|
633
|
-
name: 'update',
|
|
634
|
-
});
|
|
635
|
-
const { result, waitForNextUpdate } = renderRestHook(() => {
|
|
636
|
-
return useSuspense(getUser, { id: 5 });
|
|
637
|
-
});
|
|
638
|
-
await waitForNextUpdate();
|
|
639
|
-
|
|
640
|
-
expect(result.current.username).toBe('bob');
|
|
641
|
-
// @ts-expect-error
|
|
642
|
-
expect(result.current.sdfsd).toBeUndefined();
|
|
643
|
-
expect(result.current.defaultUserExtra).toBe('yay');
|
|
644
|
-
expect(result.current.email).toBe('bob@gmail.com');
|
|
645
|
-
|
|
646
|
-
// @ts-expect-error
|
|
647
|
-
() => getUser({ group: 5 });
|
|
648
|
-
// @ts-expect-error
|
|
649
|
-
() => useSuspense(getUser, { group: 5 });
|
|
650
|
-
// @ts-expect-error
|
|
651
|
-
() => useSuspense(getUser);
|
|
652
|
-
});
|
|
653
|
-
|
|
654
|
-
it('should work with extends', async () => {
|
|
655
|
-
mynock.put('/6/user/5').reply(200, (uri, body: any) => ({
|
|
656
|
-
id: 5,
|
|
657
|
-
username: 'charles',
|
|
658
|
-
...body,
|
|
659
|
-
}));
|
|
660
|
-
|
|
661
|
-
const updateUser = new MyEndpoint({
|
|
662
|
-
method: 'PUT',
|
|
663
|
-
path: 'http\\://test.com/user/:id',
|
|
664
|
-
name: 'update',
|
|
665
|
-
schema: User,
|
|
666
|
-
}).extend({
|
|
667
|
-
path: 'http\\://test.com/:group/user/:id',
|
|
668
|
-
body: 0 as Partial<User>,
|
|
669
|
-
});
|
|
670
|
-
expect(updateUser.additional).toBe(5);
|
|
671
|
-
expect(updateUser.method).toBe('PUT');
|
|
672
|
-
const response = await updateUser(
|
|
673
|
-
{ group: '6', id: 5 },
|
|
674
|
-
{ email: 'micky@gmail.com' },
|
|
675
|
-
);
|
|
676
|
-
expect(response).toMatchInlineSnapshot(`
|
|
677
|
-
{
|
|
678
|
-
"email": "always@always.com",
|
|
679
|
-
"id": 5,
|
|
680
|
-
"username": "charles",
|
|
681
|
-
}
|
|
682
|
-
`);
|
|
683
|
-
});
|
|
684
|
-
|
|
685
|
-
it('should work with extends', async () => {
|
|
686
|
-
mynock.get('/6/user/5').reply(200, (uri, body: any) => ({
|
|
687
|
-
id: 5,
|
|
688
|
-
username2: 'charles',
|
|
689
|
-
...body,
|
|
690
|
-
}));
|
|
691
|
-
class User2 extends Entity {
|
|
692
|
-
readonly id: number | undefined = undefined;
|
|
693
|
-
readonly username2: string = '';
|
|
694
|
-
readonly email: string = '';
|
|
695
|
-
readonly isAdmin: boolean = false;
|
|
696
|
-
|
|
697
|
-
pk() {
|
|
698
|
-
return this.id?.toString();
|
|
699
|
-
}
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
const getUserBase = new MyEndpoint({
|
|
703
|
-
method: 'GET',
|
|
704
|
-
path: 'http\\://test.com/user/:id',
|
|
705
|
-
name: 'getuser',
|
|
706
|
-
schema: User,
|
|
707
|
-
});
|
|
708
|
-
const getUser = getUserBase.extend({
|
|
709
|
-
path: 'http\\://test.com/:group/user/:id',
|
|
710
|
-
schema: User2,
|
|
711
|
-
});
|
|
712
|
-
getUserBase.body;
|
|
713
|
-
expect(getUserBase.name).toBe('getuser');
|
|
714
|
-
expect(getUserBase.extend({ method: 'GET' }).name).toBe('getuser');
|
|
715
|
-
expect(getUser.name).toBe('getuser');
|
|
716
|
-
expect(getUser.additional).toBe(5);
|
|
717
|
-
expect(getUser.method).toBe('GET');
|
|
718
|
-
const user = await getUser({ group: '6', id: 5 });
|
|
719
|
-
expect(user.username2).toBe('charles');
|
|
720
|
-
() => {
|
|
721
|
-
const a = useSuspense(getUser, { group: '6', id: 5 });
|
|
722
|
-
// @ts-expect-error
|
|
723
|
-
a.username;
|
|
724
|
-
};
|
|
725
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
726
|
-
// @ts-ignore
|
|
727
|
-
expect(user.username).toBeUndefined();
|
|
728
|
-
expect(user).toMatchInlineSnapshot(`
|
|
729
|
-
{
|
|
730
|
-
"id": 5,
|
|
731
|
-
"username2": "charles",
|
|
732
|
-
}
|
|
733
|
-
`);
|
|
734
|
-
|
|
735
|
-
const newBody = getUser
|
|
736
|
-
.extend({
|
|
737
|
-
body: {} as { title: string },
|
|
738
|
-
dataExpiryLength: 0,
|
|
739
|
-
})
|
|
740
|
-
.extend({ dataExpiryLength: 5 });
|
|
741
|
-
() => newBody({ group: 'hi', id: 'what' }, { title: 'cool' });
|
|
742
|
-
// @ts-expect-error
|
|
743
|
-
() => newBody({ id: 'what' }, { title: 'cool' });
|
|
744
|
-
// @ts-expect-error
|
|
745
|
-
() => newBody({ title: 'cool' });
|
|
746
|
-
// @ts-expect-error
|
|
747
|
-
() => newBody({ group: 'hi', id: 'what' });
|
|
748
|
-
// @ts-expect-error
|
|
749
|
-
() => newBody({ group: 'hi', id: 'what' }, { sdfsd: 'cool' });
|
|
750
|
-
|
|
751
|
-
const bodyNoPath = newBody.extend({
|
|
752
|
-
path: '/',
|
|
753
|
-
});
|
|
754
|
-
const bodyNoParams = bodyNoPath.extend({
|
|
755
|
-
body: {} as { happy: string },
|
|
756
|
-
});
|
|
757
|
-
() => bodyNoParams({ happy: 'cool' });
|
|
758
|
-
// @ts-expect-error
|
|
759
|
-
() => bodyNoParams({ group: 'hi', id: 'what' }, { happy: 'cool' });
|
|
760
|
-
// @ts-expect-error
|
|
761
|
-
() => bodyNoParams({ group: 'hi', id: 'what' }, { title: 'cool' });
|
|
762
|
-
// @ts-expect-error
|
|
763
|
-
() => bodyNoParams({ sdfd: 'cool' });
|
|
764
|
-
|
|
765
|
-
const searchParams = getUser.extend({
|
|
766
|
-
path: 'http\\://test.com/:group/user/:id',
|
|
767
|
-
searchParams: {} as { isAdmin?: boolean; sort: 'asc' | 'desc' },
|
|
768
|
-
});
|
|
769
|
-
() => searchParams({ group: 'hi', id: 'what', sort: 'asc' });
|
|
770
|
-
() =>
|
|
771
|
-
searchParams({ group: 'hi', id: 'what', sort: 'asc', isAdmin: true });
|
|
772
|
-
// @ts-expect-error
|
|
773
|
-
() => searchParams({ group: 'hi', id: 'what', sort: 'abc' });
|
|
774
|
-
// @ts-expect-error
|
|
775
|
-
() => searchParams({ group: 'hi', id: 'what' });
|
|
776
|
-
// @ts-expect-error
|
|
777
|
-
() => searchParams.url({ group: 'hi', id: 'what' });
|
|
778
|
-
expect(
|
|
779
|
-
searchParams.url({
|
|
780
|
-
group: 'hi',
|
|
781
|
-
id: 'what',
|
|
782
|
-
sort: 'desc',
|
|
783
|
-
isAdmin: true,
|
|
784
|
-
}),
|
|
785
|
-
).toMatchInlineSnapshot(
|
|
786
|
-
`"http://test.com/hi/user/what?isAdmin=true&sort=desc"`,
|
|
787
|
-
);
|
|
788
|
-
|
|
789
|
-
const searchParams2 = searchParams.extend({
|
|
790
|
-
searchParams: {} as { bigger: boolean },
|
|
791
|
-
});
|
|
792
|
-
() => searchParams2({ group: 'hi', id: 'what', bigger: true });
|
|
793
|
-
() => searchParams2({ group: 'hi', id: 'what', bigger: false });
|
|
794
|
-
// @ts-expect-error
|
|
795
|
-
() => searchParams2({ group: 'hi', id: 'what', bigger: 5 });
|
|
796
|
-
// @ts-expect-error
|
|
797
|
-
() => searchParams2({ group: 'hi', id: 'what', sort: 'asc' });
|
|
798
|
-
// @ts-expect-error
|
|
799
|
-
() => searchParams2({ group: 'hi', id: 'what' });
|
|
800
|
-
// @ts-expect-error
|
|
801
|
-
() => searchParams2.url({ group: 'hi', id: 'what' });
|
|
802
|
-
expect(
|
|
803
|
-
searchParams2.url({
|
|
804
|
-
group: 'hi',
|
|
805
|
-
id: 'what',
|
|
806
|
-
bigger: true,
|
|
807
|
-
}),
|
|
808
|
-
).toMatchInlineSnapshot(`"http://test.com/hi/user/what?bigger=true"`);
|
|
809
|
-
|
|
810
|
-
const searchParams3 = getUserBase.extend({
|
|
811
|
-
searchParams: {} as { bigger: boolean },
|
|
812
|
-
});
|
|
813
|
-
() => searchParams3({ id: 'what', bigger: true });
|
|
814
|
-
() => searchParams3({ id: 'what', bigger: false });
|
|
815
|
-
// @ts-expect-error
|
|
816
|
-
() => searchParams3({ id: 'what', bigger: 5 });
|
|
817
|
-
// @ts-expect-error
|
|
818
|
-
() => searchParams3({ id: 'what', sort: 'asc' });
|
|
819
|
-
// @ts-expect-error
|
|
820
|
-
() => searchParams3({ id: 'what' });
|
|
821
|
-
// @ts-expect-error
|
|
822
|
-
() => searchParams3.url({ id: 'what' });
|
|
823
|
-
expect(
|
|
824
|
-
searchParams3.url({
|
|
825
|
-
id: 'what',
|
|
826
|
-
bigger: true,
|
|
827
|
-
}),
|
|
828
|
-
).toMatchInlineSnapshot(`"http://test.com/user/what?bigger=true"`);
|
|
829
|
-
|
|
830
|
-
const searchParams4 = getUserBase
|
|
831
|
-
.extend({
|
|
832
|
-
path: '/users',
|
|
833
|
-
})
|
|
834
|
-
.extend({ searchParams: {} as { bigger?: boolean } | undefined });
|
|
835
|
-
() => searchParams4({ bigger: true });
|
|
836
|
-
() => searchParams4();
|
|
837
|
-
() => searchParams4({});
|
|
838
|
-
// @ts-expect-error
|
|
839
|
-
() => searchParams4({ id: 'what', bigger: false });
|
|
840
|
-
// @ts-expect-error
|
|
841
|
-
() => searchParams4({ bigger: 5 });
|
|
842
|
-
// @ts-expect-error
|
|
843
|
-
() => searchParams4({ id: 'what' });
|
|
844
|
-
// @ts-expect-error
|
|
845
|
-
() => searchParams4.url({ id: 'what' });
|
|
846
|
-
expect(
|
|
847
|
-
searchParams4.url({
|
|
848
|
-
bigger: true,
|
|
849
|
-
}),
|
|
850
|
-
).toMatchInlineSnapshot(`"/users?bigger=true"`);
|
|
851
|
-
expect(searchParams4.url()).toMatchInlineSnapshot(`"/users"`);
|
|
852
|
-
});
|
|
853
|
-
});
|
|
854
|
-
it('extending with name should work', () => {
|
|
855
|
-
const endpoint = CoolerArticleResource.get.extend({ name: 'mything' });
|
|
856
|
-
const endpoint2 = CoolerArticleResource.get.extend({ path: '/:bob' });
|
|
857
|
-
expect(CoolerArticleResource.get.name).toMatchInlineSnapshot(
|
|
858
|
-
`"CoolerArticle.get"`,
|
|
859
|
-
);
|
|
860
|
-
expect(endpoint.name).toBe('mything');
|
|
861
|
-
expect(endpoint2.name).toMatchInlineSnapshot(`"CoolerArticle.get"`);
|
|
862
|
-
});
|
|
863
|
-
it('should infer default method when sideEffect is set', async () => {
|
|
864
|
-
const endpoint = new RestEndpoint({
|
|
865
|
-
sideEffect: true,
|
|
866
|
-
path: 'http\\://test.com/article-cooler',
|
|
867
|
-
body: 0 as any,
|
|
868
|
-
schema: CoolerArticle,
|
|
869
|
-
}).extend({ name: 'createarticle' });
|
|
870
|
-
expect(endpoint.method).toBe('POST');
|
|
871
|
-
const article = await endpoint(payload);
|
|
872
|
-
expect(article).toMatchInlineSnapshot(`
|
|
873
|
-
{
|
|
874
|
-
"content": "whatever",
|
|
875
|
-
"id": 1,
|
|
876
|
-
"tags": [
|
|
877
|
-
"a",
|
|
878
|
-
"best",
|
|
879
|
-
"react",
|
|
880
|
-
],
|
|
881
|
-
"title": "hi ho",
|
|
882
|
-
}
|
|
883
|
-
`);
|
|
884
|
-
});
|
|
885
|
-
|
|
886
|
-
describe('body type setting', () => {
|
|
887
|
-
it('should work in constructors', () => {
|
|
888
|
-
interface TodoInterface {
|
|
889
|
-
title: string;
|
|
890
|
-
completed: boolean;
|
|
891
|
-
}
|
|
892
|
-
const update = new RestEndpoint({
|
|
893
|
-
path: '/:id',
|
|
894
|
-
method: 'POST',
|
|
895
|
-
body: {} as TodoInterface,
|
|
896
|
-
});
|
|
897
|
-
() => update({ id: 5 }, { title: 'updated', completed: true });
|
|
898
|
-
// @ts-expect-error
|
|
899
|
-
() => update({ id: 5 });
|
|
900
|
-
// @ts-expect-error
|
|
901
|
-
() => update({ id: 5 }, { title: 5, completed: true });
|
|
902
|
-
// @ts-expect-error
|
|
903
|
-
() => update({ id: 5 }, { completed: true });
|
|
904
|
-
});
|
|
905
|
-
});
|
|
906
|
-
|
|
907
|
-
describe('process() return type setting', () => {
|
|
908
|
-
const getArticle = new RestEndpoint({
|
|
909
|
-
path: 'http\\://test.com/article-cooler/:id',
|
|
910
|
-
process(value): CoolerArticle {
|
|
911
|
-
return value;
|
|
912
|
-
},
|
|
913
|
-
});
|
|
914
|
-
|
|
915
|
-
it('should work with constructors', async () => {
|
|
916
|
-
const article = await getArticle({ id: '5' });
|
|
917
|
-
article.author;
|
|
918
|
-
// @ts-expect-error
|
|
919
|
-
article.asdf;
|
|
920
|
-
() => useSuspense(getArticle, { id: '5' }).content;
|
|
921
|
-
// @ts-expect-error
|
|
922
|
-
() => useSuspense(getArticle, { id: '5' }).asdf;
|
|
923
|
-
});
|
|
924
|
-
|
|
925
|
-
it('should set on .extend()', async () => {
|
|
926
|
-
const getExtends = new RestEndpoint({
|
|
927
|
-
path: 'http\\://test.com/article-cooler/:id',
|
|
928
|
-
}).extend({
|
|
929
|
-
process(value: any): CoolerArticle {
|
|
930
|
-
return value;
|
|
931
|
-
},
|
|
932
|
-
});
|
|
933
|
-
const ex = await getExtends({ id: '5' });
|
|
934
|
-
ex.author;
|
|
935
|
-
// @ts-expect-error
|
|
936
|
-
ex.asdf;
|
|
937
|
-
() => useSuspense(getExtends, { id: '5' }).content;
|
|
938
|
-
// @ts-expect-error
|
|
939
|
-
() => useSuspense(getExtends, { id: '5' }).asdf;
|
|
940
|
-
});
|
|
941
|
-
|
|
942
|
-
it('should override existing type on .extend()', async () => {
|
|
943
|
-
const getOverride = getArticle.extend({
|
|
944
|
-
process(value: any, param: any): { asdf: string } {
|
|
945
|
-
return value;
|
|
946
|
-
},
|
|
947
|
-
});
|
|
948
|
-
const ov = await getOverride({ id: '5' });
|
|
949
|
-
ov.asdf;
|
|
950
|
-
// @ts-expect-error
|
|
951
|
-
ov.author;
|
|
952
|
-
() => useSuspense(getOverride, { id: '5' }).asdf;
|
|
953
|
-
// @ts-expect-error
|
|
954
|
-
() => useSuspense(getOverride, { id: '5' }).content;
|
|
955
|
-
});
|
|
956
|
-
|
|
957
|
-
it('should maintain existing type on .extend() when not specified', async () => {
|
|
958
|
-
const getOverride = getArticle.extend({
|
|
959
|
-
dataExpiryLength: 7,
|
|
960
|
-
});
|
|
961
|
-
const ov = await getOverride({ id: '5' });
|
|
962
|
-
ov.author;
|
|
963
|
-
// @ts-expect-error
|
|
964
|
-
ov.asdf;
|
|
965
|
-
});
|
|
966
|
-
|
|
967
|
-
it('should maintain existing type on .extend() when process is not supplied but path is', async () => {
|
|
968
|
-
const getOverride = getArticle.extend({
|
|
969
|
-
path: '/:a/:b',
|
|
970
|
-
});
|
|
971
|
-
async () => {
|
|
972
|
-
const ov = await getOverride({ a: '5', b: '7' });
|
|
973
|
-
ov.author;
|
|
974
|
-
// @ts-expect-error
|
|
975
|
-
ov.asdf;
|
|
976
|
-
};
|
|
977
|
-
});
|
|
978
|
-
|
|
979
|
-
it('should override existing type on .extend() when path is also supplied', async () => {
|
|
980
|
-
const getOverride = getArticle.extend({
|
|
981
|
-
path: '/:a/:b',
|
|
982
|
-
process(value: any, param: any): { asdf: string } {
|
|
983
|
-
return value;
|
|
984
|
-
},
|
|
985
|
-
});
|
|
986
|
-
async () => {
|
|
987
|
-
const ov = await getOverride({ a: '5', b: '7' });
|
|
988
|
-
ov.asdf;
|
|
989
|
-
// @ts-expect-error
|
|
990
|
-
ov.author;
|
|
991
|
-
};
|
|
992
|
-
() => useSuspense(getOverride, { a: '5', b: '7' }).asdf;
|
|
993
|
-
// @ts-expect-error
|
|
994
|
-
() => useSuspense(getOverride, { a: '5', b: '7' }).content;
|
|
995
|
-
});
|
|
996
|
-
});
|
|
997
|
-
|
|
998
|
-
it('extend options should match function of path set', () => {
|
|
999
|
-
const endpoint = new RestEndpoint({
|
|
1000
|
-
sideEffect: true,
|
|
1001
|
-
path: 'http\\://test.com/article-cooler/:id',
|
|
1002
|
-
body: 0 as any,
|
|
1003
|
-
schema: CoolerArticle,
|
|
1004
|
-
getOptimisticResponse(snap, params, body) {
|
|
1005
|
-
params.id;
|
|
1006
|
-
// @ts-expect-error
|
|
1007
|
-
params.two;
|
|
1008
|
-
|
|
1009
|
-
body.hi;
|
|
1010
|
-
},
|
|
1011
|
-
}).extend({
|
|
1012
|
-
path: '/:group/next/:two',
|
|
1013
|
-
body: undefined,
|
|
1014
|
-
getOptimisticResponse(snap, params) {
|
|
1015
|
-
params.two;
|
|
1016
|
-
params.group;
|
|
1017
|
-
// @ts-expect-error
|
|
1018
|
-
params.id;
|
|
1019
|
-
},
|
|
1020
|
-
});
|
|
1021
|
-
|
|
1022
|
-
const endpoint2 = new RestEndpoint({
|
|
1023
|
-
sideEffect: true,
|
|
1024
|
-
path: 'http\\://test.com/article-cooler/:id',
|
|
1025
|
-
body: 0 as any,
|
|
1026
|
-
schema: CoolerArticle,
|
|
1027
|
-
getOptimisticResponse(snap, params, body) {
|
|
1028
|
-
params.id;
|
|
1029
|
-
// @ts-expect-error
|
|
1030
|
-
params.two;
|
|
1031
|
-
|
|
1032
|
-
body.hi;
|
|
1033
|
-
},
|
|
1034
|
-
}).extend({
|
|
1035
|
-
getOptimisticResponse(snap, params, body) {
|
|
1036
|
-
params.id;
|
|
1037
|
-
// @ts-expect-error
|
|
1038
|
-
params.two;
|
|
1039
|
-
|
|
1040
|
-
body.hi;
|
|
1041
|
-
},
|
|
1042
|
-
});
|
|
1043
|
-
});
|
|
1044
|
-
});
|
|
1045
|
-
|
|
1046
|
-
describe('RestEndpoint.fetch()', () => {
|
|
1047
|
-
const id = 5;
|
|
1048
|
-
const idHtml = 6;
|
|
1049
|
-
const idNoContent = 7;
|
|
1050
|
-
const payload = {
|
|
1051
|
-
id,
|
|
1052
|
-
title: 'happy',
|
|
1053
|
-
author: User.fromJS({ id: 5 }),
|
|
1054
|
-
};
|
|
1055
|
-
const putResponseBody = {
|
|
1056
|
-
id,
|
|
1057
|
-
title: 'happy',
|
|
1058
|
-
completed: true,
|
|
1059
|
-
};
|
|
1060
|
-
const patchPayload = {
|
|
1061
|
-
title: 'happy',
|
|
1062
|
-
};
|
|
1063
|
-
const patchResponseBody = {
|
|
1064
|
-
id,
|
|
1065
|
-
title: 'happy',
|
|
1066
|
-
completed: false,
|
|
1067
|
-
};
|
|
1068
|
-
|
|
1069
|
-
beforeEach(() => {
|
|
1070
|
-
nock(/.*/)
|
|
1071
|
-
.defaultReplyHeaders({
|
|
1072
|
-
'Access-Control-Allow-Origin': '*',
|
|
1073
|
-
'Content-Type': 'application/json',
|
|
1074
|
-
})
|
|
1075
|
-
.options(/.*/)
|
|
1076
|
-
.reply(200)
|
|
1077
|
-
.get(`/article-cooler/${payload.id}`)
|
|
1078
|
-
.reply(200, payload)
|
|
1079
|
-
.get(`/article-cooler/${idHtml}`)
|
|
1080
|
-
.reply(200, '<body>this is html</body>')
|
|
1081
|
-
.get(`/article-cooler/${idNoContent}`)
|
|
1082
|
-
.reply(204, '')
|
|
1083
|
-
.post('/article-cooler')
|
|
1084
|
-
.reply((uri, requestBody) => [
|
|
1085
|
-
201,
|
|
1086
|
-
requestBody,
|
|
1087
|
-
{ 'content-type': 'application/json' },
|
|
1088
|
-
])
|
|
1089
|
-
.put('/article-cooler/5')
|
|
1090
|
-
.reply((uri, requestBody) => {
|
|
1091
|
-
let body = requestBody as any;
|
|
1092
|
-
if (typeof requestBody === 'string') {
|
|
1093
|
-
body = JSON.parse(requestBody);
|
|
1094
|
-
}
|
|
1095
|
-
for (const key of Object.keys(CoolerArticle.fromJS({}))) {
|
|
1096
|
-
if (key !== 'id' && !(key in body)) {
|
|
1097
|
-
return [400, {}, { 'content-type': 'application/json' }];
|
|
1098
|
-
}
|
|
1099
|
-
}
|
|
1100
|
-
return [200, putResponseBody, { 'content-type': 'application/json' }];
|
|
1101
|
-
})
|
|
1102
|
-
.patch('/article-cooler/5')
|
|
1103
|
-
.reply(() => [
|
|
1104
|
-
200,
|
|
1105
|
-
patchResponseBody,
|
|
1106
|
-
{ 'content-type': 'application/json' },
|
|
1107
|
-
])
|
|
1108
|
-
.intercept('/article-cooler/5', 'DELETE')
|
|
1109
|
-
.reply(200, {});
|
|
1110
|
-
});
|
|
1111
|
-
|
|
1112
|
-
afterEach(() => {
|
|
1113
|
-
nock.cleanAll();
|
|
1114
|
-
});
|
|
1115
|
-
|
|
1116
|
-
it('should GET', async () => {
|
|
1117
|
-
const article = await CoolerArticleResource.get({
|
|
1118
|
-
id: payload.id,
|
|
1119
|
-
});
|
|
1120
|
-
expect(article).toBeDefined();
|
|
1121
|
-
if (!article) {
|
|
1122
|
-
throw new Error('ahh');
|
|
1123
|
-
}
|
|
1124
|
-
expect(article.title).toBe(payload.title);
|
|
1125
|
-
});
|
|
1126
|
-
|
|
1127
|
-
it('should POST', async () => {
|
|
1128
|
-
const payload2 = { id: 20, content: 'better task' };
|
|
1129
|
-
const article = await CoolerArticleResource.create(payload2);
|
|
1130
|
-
expect(article).toMatchObject(payload2);
|
|
1131
|
-
});
|
|
1132
|
-
|
|
1133
|
-
it('should PUT with multipart form data', async () => {
|
|
1134
|
-
const payload2 = { id: 500, content: 'another' };
|
|
1135
|
-
let lastRequest: any;
|
|
1136
|
-
nock(/.*/)
|
|
1137
|
-
.defaultReplyHeaders({
|
|
1138
|
-
'Access-Control-Allow-Origin': '*',
|
|
1139
|
-
})
|
|
1140
|
-
.put('/article-cooler/500')
|
|
1141
|
-
.reply(function (uri, requestBody) {
|
|
1142
|
-
lastRequest = this.req;
|
|
1143
|
-
return [201, payload2, { 'Content-Type': 'application/json' }];
|
|
1144
|
-
});
|
|
1145
|
-
const newPhoto = new Blob();
|
|
1146
|
-
const body = new FormData();
|
|
1147
|
-
body.append('photo', newPhoto);
|
|
1148
|
-
|
|
1149
|
-
const article = await CoolerArticleResource.update.extend({
|
|
1150
|
-
path: CoolerArticleResource.update.path,
|
|
1151
|
-
body: new FormData(),
|
|
1152
|
-
})({ id: '500' }, body);
|
|
1153
|
-
expect(lastRequest.headers['content-type']).toContain(
|
|
1154
|
-
'multipart/form-data',
|
|
1155
|
-
);
|
|
1156
|
-
expect(article).toMatchObject(payload2);
|
|
1157
|
-
});
|
|
1158
|
-
|
|
1159
|
-
it('should DELETE', async () => {
|
|
1160
|
-
const res = await CoolerArticleResource.delete({
|
|
1161
|
-
id: payload.id,
|
|
1162
|
-
});
|
|
1163
|
-
expect(res).toEqual({ id });
|
|
1164
|
-
});
|
|
1165
|
-
|
|
1166
|
-
it('should PUT', async () => {
|
|
1167
|
-
const response = await CoolerArticleResource.update(
|
|
1168
|
-
{ id: payload.id },
|
|
1169
|
-
{ ...CoolerArticle.fromJS(payload) },
|
|
1170
|
-
);
|
|
1171
|
-
expect(response).toEqual(putResponseBody);
|
|
1172
|
-
});
|
|
1173
|
-
|
|
1174
|
-
it('should PATCH', async () => {
|
|
1175
|
-
const response = await CoolerArticleResource.partialUpdate(
|
|
1176
|
-
{ id },
|
|
1177
|
-
patchPayload,
|
|
1178
|
-
);
|
|
1179
|
-
expect(response).toEqual(patchResponseBody);
|
|
1180
|
-
});
|
|
1181
|
-
|
|
1182
|
-
it('should throw if response is not json', async () => {
|
|
1183
|
-
let error: any;
|
|
1184
|
-
try {
|
|
1185
|
-
await CoolerArticleResource.get({ id: idHtml });
|
|
1186
|
-
} catch (e) {
|
|
1187
|
-
error = e;
|
|
1188
|
-
}
|
|
1189
|
-
expect(error).toBeDefined();
|
|
1190
|
-
// This is very weird, but we're forced to use node-fetch for react native
|
|
1191
|
-
// node-fetch doesn't handle errors consistently with normal fetch implementations, so this won't work
|
|
1192
|
-
// react-native itself should match this correctly however.
|
|
1193
|
-
if (typeof window !== 'undefined') expect(error.status).toBe(400);
|
|
1194
|
-
});
|
|
1195
|
-
|
|
1196
|
-
it('should throw if network is down', async () => {
|
|
1197
|
-
const oldError = console.error;
|
|
1198
|
-
console.error = () => {};
|
|
1199
|
-
|
|
1200
|
-
const id = 10;
|
|
1201
|
-
nock(/.*/)
|
|
1202
|
-
.defaultReplyHeaders({
|
|
1203
|
-
'Access-Control-Allow-Origin': '*',
|
|
1204
|
-
'Content-Type': 'application/json',
|
|
1205
|
-
})
|
|
1206
|
-
.get(`/article-cooler/${id}`)
|
|
1207
|
-
.replyWithError(new TypeError('Network Down'));
|
|
1208
|
-
|
|
1209
|
-
let error: any;
|
|
1210
|
-
try {
|
|
1211
|
-
await CoolerArticleResource.get({ id });
|
|
1212
|
-
} catch (e) {
|
|
1213
|
-
error = e;
|
|
1214
|
-
}
|
|
1215
|
-
expect(error).toBeDefined();
|
|
1216
|
-
expect(error.status).toBe(500);
|
|
1217
|
-
|
|
1218
|
-
// eslint-disable-next-line require-atomic-updates
|
|
1219
|
-
console.error = oldError;
|
|
1220
|
-
});
|
|
1221
|
-
|
|
1222
|
-
it('should return raw response if status is 204 No Content', async () => {
|
|
1223
|
-
const res = await CoolerArticleResource.get({ id: idNoContent });
|
|
1224
|
-
expect(res).toBe(null);
|
|
1225
|
-
});
|
|
1226
|
-
|
|
1227
|
-
it('should reject if content-type is not json with schema', async () => {
|
|
1228
|
-
const id = 8;
|
|
1229
|
-
const text = '<body>this is html</body>';
|
|
1230
|
-
nock(/.*/)
|
|
1231
|
-
.defaultReplyHeaders({
|
|
1232
|
-
'Access-Control-Allow-Origin': '*',
|
|
1233
|
-
'Content-Type': 'application/json',
|
|
1234
|
-
})
|
|
1235
|
-
.get(`/article-cooler/${id}`)
|
|
1236
|
-
.reply(200, text, { 'content-type': 'html/text' });
|
|
1237
|
-
|
|
1238
|
-
await expect(
|
|
1239
|
-
async () => await CoolerArticleResource.get({ id }),
|
|
1240
|
-
).rejects.toMatchSnapshot();
|
|
1241
|
-
});
|
|
1242
|
-
|
|
1243
|
-
it('should reject if content-type does not exist with schema', async () => {
|
|
1244
|
-
const id = 10;
|
|
1245
|
-
const text = '<body>this is html</body>';
|
|
1246
|
-
nock(/.*/)
|
|
1247
|
-
.defaultReplyHeaders({
|
|
1248
|
-
'Access-Control-Allow-Origin': '*',
|
|
1249
|
-
})
|
|
1250
|
-
.get(`/article-cooler/${id}`)
|
|
1251
|
-
.reply(200, text, {});
|
|
1252
|
-
|
|
1253
|
-
await expect(
|
|
1254
|
-
async () => await CoolerArticleResource.get({ id }),
|
|
1255
|
-
).rejects.toMatchSnapshot();
|
|
1256
|
-
});
|
|
1257
|
-
|
|
1258
|
-
it('should return text if content-type is not json with no schema', async () => {
|
|
1259
|
-
const id = 8;
|
|
1260
|
-
const text = '<body>this is html</body>';
|
|
1261
|
-
nock(/.*/)
|
|
1262
|
-
.defaultReplyHeaders({
|
|
1263
|
-
'Access-Control-Allow-Origin': '*',
|
|
1264
|
-
'Content-Type': 'application/json',
|
|
1265
|
-
})
|
|
1266
|
-
.get(`/article-cooler/${id}`)
|
|
1267
|
-
.reply(200, text, { 'content-type': 'html/text' });
|
|
1268
|
-
|
|
1269
|
-
const res = await CoolerArticleResource.get.extend({ schema: undefined })({
|
|
1270
|
-
id,
|
|
1271
|
-
});
|
|
1272
|
-
expect(res).toBe('<body>this is html</body>');
|
|
1273
|
-
});
|
|
1274
|
-
|
|
1275
|
-
it('should return text if content-type does not exist with no schema', async () => {
|
|
1276
|
-
const id = 10;
|
|
1277
|
-
const text = '<body>this is html</body>';
|
|
1278
|
-
nock(/.*/)
|
|
1279
|
-
.defaultReplyHeaders({
|
|
1280
|
-
'Access-Control-Allow-Origin': '*',
|
|
1281
|
-
})
|
|
1282
|
-
.get(`/article-cooler/${id}`)
|
|
1283
|
-
.reply(200, text, {});
|
|
1284
|
-
|
|
1285
|
-
const res = await CoolerArticleResource.get.extend({ schema: undefined })({
|
|
1286
|
-
id,
|
|
1287
|
-
});
|
|
1288
|
-
expect(res).toBe(text);
|
|
1289
|
-
});
|
|
1290
|
-
|
|
1291
|
-
it('should reject with custom message if content type is set but json parsable', async () => {
|
|
1292
|
-
const id = 8;
|
|
1293
|
-
nock(/.*/)
|
|
1294
|
-
.defaultReplyHeaders({
|
|
1295
|
-
'Access-Control-Allow-Origin': '*',
|
|
1296
|
-
'Content-Type': 'text',
|
|
1297
|
-
})
|
|
1298
|
-
.get(`/article-cooler/${id}`)
|
|
1299
|
-
.reply(200, { id, title: 'hi' }, {});
|
|
1300
|
-
|
|
1301
|
-
await expect(
|
|
1302
|
-
async () => await CoolerArticleResource.get({ id }),
|
|
1303
|
-
).rejects.toMatchSnapshot();
|
|
1304
|
-
});
|
|
1305
|
-
|
|
1306
|
-
it('should still work with empty content-type', async () => {
|
|
1307
|
-
const id = 8;
|
|
1308
|
-
nock(/.*/)
|
|
1309
|
-
.defaultReplyHeaders({
|
|
1310
|
-
'Access-Control-Allow-Origin': '*',
|
|
1311
|
-
})
|
|
1312
|
-
.get(`/article-cooler/${id}`)
|
|
1313
|
-
.reply(200, { id, title: 'hi' });
|
|
1314
|
-
|
|
1315
|
-
const res = await CoolerArticleResource.get({
|
|
1316
|
-
id,
|
|
1317
|
-
});
|
|
1318
|
-
expect(res).toEqual({ id, title: 'hi' });
|
|
1319
|
-
});
|
|
1320
|
-
|
|
1321
|
-
it('without Collection in schema - endpoint.push schema should be null', () => {
|
|
1322
|
-
expect(getArticleList2.push.schema).toBeFalsy();
|
|
1323
|
-
});
|
|
1324
|
-
});
|
|
1325
|
-
const proto = Object.prototype;
|
|
1326
|
-
const gpo = Object.getPrototypeOf;
|
|
1327
|
-
|
|
1328
|
-
function isPojo(obj: unknown): obj is Record<string, any> {
|
|
1329
|
-
if (obj === null || typeof obj !== 'object') {
|
|
1330
|
-
return false;
|
|
1331
|
-
}
|
|
1332
|
-
return gpo(obj) === proto;
|
|
1333
|
-
}
|