@data-client/rest 0.1.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/LICENSE +201 -0
- package/README.md +110 -0
- package/dist/index.js +561 -0
- package/dist/index.umd.min.js +1 -0
- package/dist/next.js +520 -0
- package/dist/package.json +1 -0
- package/legacy/NetworkError.js +13 -0
- package/legacy/OptionsToFunction.js +2 -0
- package/legacy/RestEndpoint.js +225 -0
- package/legacy/RestEndpointTypeHelp.js +2 -0
- package/legacy/RestHelpers.js +33 -0
- package/legacy/createResource.js +88 -0
- package/legacy/extractCollection.js +14 -0
- package/legacy/getArrayPath.js +19 -0
- package/legacy/hookifyResource.js +24 -0
- package/legacy/index.js +10 -0
- package/legacy/mapCollection.js +14 -0
- package/legacy/next/OptionsToFunction.js +2 -0
- package/legacy/next/RestEndpoint.js +225 -0
- package/legacy/next/createResource.js +86 -0
- package/legacy/next/index.js +4 -0
- package/legacy/next/resourceTypes.js +2 -0
- package/legacy/paginatedCollections.js +29 -0
- package/legacy/paginationUpdate.js +36 -0
- package/legacy/paramsToString.js +6 -0
- package/legacy/paramsToString.native.js +9 -0
- package/legacy/pathTypes.js +2 -0
- package/legacy/test-fixtures.js +111 -0
- package/legacy/utiltypes.js +2 -0
- package/lib/NetworkError.d.ts +11 -0
- package/lib/NetworkError.d.ts.map +1 -0
- package/lib/NetworkError.js +13 -0
- package/lib/OptionsToFunction.d.ts +7 -0
- package/lib/OptionsToFunction.d.ts.map +1 -0
- package/lib/OptionsToFunction.js +2 -0
- package/lib/RestEndpoint.d.ts +548 -0
- package/lib/RestEndpoint.js +229 -0
- package/lib/RestEndpointTypeHelp.d.ts +10 -0
- package/lib/RestEndpointTypeHelp.d.ts.map +1 -0
- package/lib/RestEndpointTypeHelp.js +2 -0
- package/lib/RestHelpers.d.ts +7 -0
- package/lib/RestHelpers.d.ts.map +1 -0
- package/lib/RestHelpers.js +33 -0
- package/lib/createResource.d.ts +66 -0
- package/lib/createResource.d.ts.map +1 -0
- package/lib/createResource.js +104 -0
- package/lib/extractCollection.d.ts +11 -0
- package/lib/extractCollection.d.ts.map +1 -0
- package/lib/extractCollection.js +16 -0
- package/lib/getArrayPath.d.ts +3 -0
- package/lib/getArrayPath.d.ts.map +1 -0
- package/lib/getArrayPath.js +19 -0
- package/lib/hookifyResource.d.ts +14 -0
- package/lib/hookifyResource.d.ts.map +1 -0
- package/lib/hookifyResource.js +24 -0
- package/lib/index.d.ts +12 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +10 -0
- package/lib/mapCollection.d.ts +16 -0
- package/lib/mapCollection.d.ts.map +1 -0
- package/lib/mapCollection.js +16 -0
- package/lib/next/OptionsToFunction.d.ts +7 -0
- package/lib/next/OptionsToFunction.d.ts.map +1 -0
- package/lib/next/OptionsToFunction.js +2 -0
- package/lib/next/RestEndpoint.d.ts +542 -0
- package/lib/next/RestEndpoint.js +229 -0
- package/lib/next/createResource.d.ts +86 -0
- package/lib/next/createResource.d.ts.map +1 -0
- package/lib/next/createResource.js +100 -0
- package/lib/next/index.d.ts +6 -0
- package/lib/next/index.d.ts.map +1 -0
- package/lib/next/index.js +4 -0
- package/lib/next/resourceTypes.d.ts +31 -0
- package/lib/next/resourceTypes.d.ts.map +1 -0
- package/lib/next/resourceTypes.js +2 -0
- package/lib/paginatedCollections.d.ts +3 -0
- package/lib/paginatedCollections.d.ts.map +1 -0
- package/lib/paginatedCollections.js +29 -0
- package/lib/paginationUpdate.d.ts +8 -0
- package/lib/paginationUpdate.d.ts.map +1 -0
- package/lib/paginationUpdate.js +40 -0
- package/lib/paramsToString.d.ts +2 -0
- package/lib/paramsToString.d.ts.map +1 -0
- package/lib/paramsToString.js +6 -0
- package/lib/paramsToString.native.d.ts +2 -0
- package/lib/paramsToString.native.d.ts.map +1 -0
- package/lib/paramsToString.native.js +9 -0
- package/lib/pathTypes.d.ts +20 -0
- package/lib/pathTypes.d.ts.map +1 -0
- package/lib/pathTypes.js +2 -0
- package/lib/test-fixtures.d.ts +141 -0
- package/lib/test-fixtures.d.ts.map +1 -0
- package/lib/test-fixtures.js +111 -0
- package/lib/utiltypes.d.ts +21 -0
- package/lib/utiltypes.d.ts.map +1 -0
- package/lib/utiltypes.js +2 -0
- package/node.mjs +1 -0
- package/package.json +121 -0
- package/src/NetworkError.ts +17 -0
- package/src/OptionsToFunction.ts +42 -0
- package/src/RestEndpoint.d.ts +548 -0
- package/src/RestEndpoint.js +262 -0
- package/src/RestEndpointTypeHelp.ts +20 -0
- package/src/RestHelpers.ts +48 -0
- package/src/__tests__/Resource.test.ts +317 -0
- package/src/__tests__/RestEndpoint.ts +1304 -0
- package/src/__tests__/__snapshots__/RestEndpoint.ts.snap +18 -0
- package/src/__tests__/__snapshots__/createResource.test.ts.snap +5 -0
- package/src/__tests__/createResource.test.ts +583 -0
- package/src/__tests__/getArrayPath.test.ts +30 -0
- package/src/__tests__/hookifyResource.ts +197 -0
- package/src/__tests__/types.test.ts +309 -0
- package/src/createResource.ts +172 -0
- package/src/extractCollection.ts +34 -0
- package/src/getArrayPath.ts +20 -0
- package/src/hookifyResource.ts +36 -0
- package/src/index.ts +42 -0
- package/src/mapCollection.ts +54 -0
- package/src/next/OptionsToFunction.ts +41 -0
- package/src/next/RestEndpoint.d.ts +542 -0
- package/src/next/RestEndpoint.js +262 -0
- package/src/next/__tests__/RestEndpoint.ts +1333 -0
- package/src/next/__tests__/__snapshots__/RestEndpoint.ts.snap +18 -0
- package/src/next/__tests__/types.test.ts +429 -0
- package/src/next/createResource.ts +194 -0
- package/src/next/index.ts +5 -0
- package/src/next/resourceTypes.ts +32 -0
- package/src/package.json +1 -0
- package/src/paginatedCollections.ts +32 -0
- package/src/paginationUpdate.ts +49 -0
- package/src/paramsToString.native.ts +10 -0
- package/src/paramsToString.ts +7 -0
- package/src/pathTypes.ts +58 -0
- package/src/test-fixtures.ts +128 -0
- package/src/utiltypes.ts +25 -0
- package/ts4.0/NetworkError.d.ts +11 -0
- package/ts4.0/OptionsToFunction.d.ts +16 -0
- package/ts4.0/RestEndpoint.d.ts +240 -0
- package/ts4.0/RestEndpointTypeHelp.d.ts +10 -0
- package/ts4.0/RestHelpers.d.ts +7 -0
- package/ts4.0/createResource.d.ts +66 -0
- package/ts4.0/extractCollection.d.ts +11 -0
- package/ts4.0/getArrayPath.d.ts +3 -0
- package/ts4.0/hookifyResource.d.ts +19 -0
- package/ts4.0/index.d.ts +12 -0
- package/ts4.0/mapCollection.d.ts +16 -0
- package/ts4.0/next/OptionsToFunction.d.ts +16 -0
- package/ts4.0/next/RestEndpoint.d.ts +226 -0
- package/ts4.0/next/createResource.d.ts +94 -0
- package/ts4.0/next/index.d.ts +6 -0
- package/ts4.0/next/resourceTypes.d.ts +31 -0
- package/ts4.0/paginatedCollections.d.ts +3 -0
- package/ts4.0/paginationUpdate.d.ts +10 -0
- package/ts4.0/paramsToString.d.ts +2 -0
- package/ts4.0/paramsToString.native.d.ts +2 -0
- package/ts4.0/pathTypes.d.ts +20 -0
- package/ts4.0/test-fixtures.d.ts +141 -0
- package/ts4.0/utiltypes.d.ts +5 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
+
|
|
3
|
+
exports[`RestEndpoint.fetch() should reject if content-type does not exist with schema 1`] = `[NetworkError: Unexpected text response for schema CoolerArticle]`;
|
|
4
|
+
|
|
5
|
+
exports[`RestEndpoint.fetch() should reject if content-type is not json with schema 1`] = `
|
|
6
|
+
[NetworkError: Unexpected html response for schema CoolerArticle
|
|
7
|
+
This likely means no API endpoint was configured for this request, resulting in an HTML fallback.
|
|
8
|
+
|
|
9
|
+
Response (first 300 characters): <body>this is html</body>]
|
|
10
|
+
`;
|
|
11
|
+
|
|
12
|
+
exports[`RestEndpoint.fetch() should reject with custom message if content type is set but json parsable 1`] = `
|
|
13
|
+
[NetworkError: "content-type" header does not include "json", but JSON response found.
|
|
14
|
+
See https://www.rfc-editor.org/rfc/rfc4627 for information on JSON responses
|
|
15
|
+
|
|
16
|
+
Using parsed JSON.
|
|
17
|
+
If text content was expected see https://resthooks.io/rest/api/RestEndpoint#parseResponse]
|
|
18
|
+
`;
|
|
@@ -0,0 +1,583 @@
|
|
|
1
|
+
import { Entity, schema } from '@data-client/endpoint';
|
|
2
|
+
import {
|
|
3
|
+
CacheProvider,
|
|
4
|
+
useCache,
|
|
5
|
+
useController,
|
|
6
|
+
Controller,
|
|
7
|
+
useSuspense,
|
|
8
|
+
} from '@data-client/react';
|
|
9
|
+
import { makeRenderRestHook } from '@data-client/test';
|
|
10
|
+
import { act } from '@testing-library/react-hooks';
|
|
11
|
+
import nock, { ReplyHeaders } from 'nock';
|
|
12
|
+
|
|
13
|
+
import createResource from '../createResource';
|
|
14
|
+
import RestEndpoint, { RestGenerics } from '../RestEndpoint';
|
|
15
|
+
|
|
16
|
+
describe('createResource()', () => {
|
|
17
|
+
const renderRestHook: ReturnType<typeof makeRenderRestHook> =
|
|
18
|
+
makeRenderRestHook(CacheProvider);
|
|
19
|
+
let mynock: nock.Scope;
|
|
20
|
+
|
|
21
|
+
class User extends Entity {
|
|
22
|
+
readonly id: number | undefined = undefined;
|
|
23
|
+
readonly username: string = '';
|
|
24
|
+
readonly email: string = '';
|
|
25
|
+
readonly isAdmin: boolean = false;
|
|
26
|
+
|
|
27
|
+
pk() {
|
|
28
|
+
return this.id?.toString();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
class MyEndpoint<O extends RestGenerics = any> extends RestEndpoint<O> {
|
|
32
|
+
parseResponse(response: Response): Promise<any> {
|
|
33
|
+
return super.parseResponse(response);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
getRequestInit(body: any) {
|
|
37
|
+
if (typeof body === 'object') {
|
|
38
|
+
return super.getRequestInit({ ...body, email: 'always@always.com' });
|
|
39
|
+
}
|
|
40
|
+
return super.getRequestInit(body);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
additional = 5;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const UserResource = createResource({
|
|
47
|
+
path: 'http\\://test.com/groups/:group/users/:id',
|
|
48
|
+
schema: User,
|
|
49
|
+
Endpoint: MyEndpoint,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const userPayload = {
|
|
53
|
+
id: 5,
|
|
54
|
+
username: 'ntucker',
|
|
55
|
+
email: 'bob@vila.com',
|
|
56
|
+
isAdmin: true,
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
beforeEach(() => {
|
|
60
|
+
nock(/.*/)
|
|
61
|
+
.persist()
|
|
62
|
+
.defaultReplyHeaders({
|
|
63
|
+
'Access-Control-Allow-Origin': '*',
|
|
64
|
+
'Content-Type': 'application/json',
|
|
65
|
+
})
|
|
66
|
+
.get(`/groups/five/users/${userPayload.id}`)
|
|
67
|
+
.reply(200, userPayload)
|
|
68
|
+
.get(`/groups/five/users`)
|
|
69
|
+
.reply(200, [userPayload])
|
|
70
|
+
.options(/.*/)
|
|
71
|
+
.reply(200);
|
|
72
|
+
mynock = nock(/.*/).defaultReplyHeaders({
|
|
73
|
+
'Access-Control-Allow-Origin': '*',
|
|
74
|
+
'Content-Type': 'application/json',
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
afterEach(() => {
|
|
79
|
+
nock.cleanAll();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('UserResource.get should work', async () => {
|
|
83
|
+
const { result, waitForNextUpdate } = renderRestHook(() => {
|
|
84
|
+
return useSuspense(UserResource.get, { group: 'five', id: '5' });
|
|
85
|
+
});
|
|
86
|
+
await waitForNextUpdate();
|
|
87
|
+
expect(result.current).toEqual(User.fromJS(userPayload));
|
|
88
|
+
result.current.isAdmin;
|
|
89
|
+
//@ts-expect-error
|
|
90
|
+
expect(result.current.notaMember).toBeUndefined();
|
|
91
|
+
|
|
92
|
+
// @ts-expect-error
|
|
93
|
+
() => useSuspense(UserResource.get, { id: '5' });
|
|
94
|
+
// @ts-expect-error
|
|
95
|
+
() => useSuspense(UserResource.get, { group: 'five' });
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('UserResource.getList should work', async () => {
|
|
99
|
+
const { result, waitForNextUpdate } = renderRestHook(() => {
|
|
100
|
+
return useSuspense(UserResource.getList, { group: 'five' });
|
|
101
|
+
});
|
|
102
|
+
await waitForNextUpdate();
|
|
103
|
+
expect(result.current[0]).toEqual(User.fromJS(userPayload));
|
|
104
|
+
result.current[0].isAdmin;
|
|
105
|
+
//@ts-expect-error
|
|
106
|
+
expect(result.current[0].notaMember).toBeUndefined();
|
|
107
|
+
type A = Parameters<typeof UserResource.getList>;
|
|
108
|
+
|
|
109
|
+
// @ts-expect-error
|
|
110
|
+
() => useSuspense(UserResource.getList, { id: '5' });
|
|
111
|
+
// @ts-expect-error
|
|
112
|
+
() => useSuspense(UserResource.getList, {});
|
|
113
|
+
// @ts-expect-error
|
|
114
|
+
() => useSuspense(UserResource.getList);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('UserResource.update should work', async () => {
|
|
118
|
+
mynock
|
|
119
|
+
.put(`/groups/five/users/${userPayload.id}`)
|
|
120
|
+
.reply(200, (uri, body: any) => ({
|
|
121
|
+
...userPayload,
|
|
122
|
+
...body,
|
|
123
|
+
}));
|
|
124
|
+
|
|
125
|
+
const { result, waitForNextUpdate } = renderRestHook(
|
|
126
|
+
() => {
|
|
127
|
+
return [
|
|
128
|
+
useSuspense(UserResource.get, { group: 'five', id: '5' }),
|
|
129
|
+
useController(),
|
|
130
|
+
] as const;
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
initialFixtures: [
|
|
134
|
+
{
|
|
135
|
+
endpoint: UserResource.get,
|
|
136
|
+
args: [{ group: 'five', id: '5' }],
|
|
137
|
+
response: userPayload,
|
|
138
|
+
},
|
|
139
|
+
],
|
|
140
|
+
},
|
|
141
|
+
);
|
|
142
|
+
// eslint-disable-next-line prefer-const
|
|
143
|
+
let [user, controller] = result.current;
|
|
144
|
+
expect(user.username).toBe(userPayload.username);
|
|
145
|
+
await act(() => {
|
|
146
|
+
controller.fetch(
|
|
147
|
+
UserResource.update,
|
|
148
|
+
{ group: 'five', id: '5' },
|
|
149
|
+
{ username: 'never' },
|
|
150
|
+
);
|
|
151
|
+
});
|
|
152
|
+
await waitForNextUpdate();
|
|
153
|
+
[user] = result.current;
|
|
154
|
+
expect(user.username).toBe('never');
|
|
155
|
+
|
|
156
|
+
() =>
|
|
157
|
+
controller.fetch(
|
|
158
|
+
UserResource.update,
|
|
159
|
+
{ group: 'five', id: '5' },
|
|
160
|
+
{ username: 'never' },
|
|
161
|
+
// @ts-expect-error
|
|
162
|
+
{ username: 'never' },
|
|
163
|
+
);
|
|
164
|
+
// @ts-expect-error
|
|
165
|
+
() => controller.fetch(UserResource.update, { username: 'never' });
|
|
166
|
+
// @ts-expect-error
|
|
167
|
+
() => controller.fetch(UserResource.update, 1, 'hi');
|
|
168
|
+
() =>
|
|
169
|
+
controller.fetch(
|
|
170
|
+
UserResource.update,
|
|
171
|
+
{ group: 'five', id: '5' },
|
|
172
|
+
// @ts-expect-error
|
|
173
|
+
{ sdf: 'never' },
|
|
174
|
+
);
|
|
175
|
+
() =>
|
|
176
|
+
controller.fetch(
|
|
177
|
+
UserResource.update,
|
|
178
|
+
// @ts-expect-error
|
|
179
|
+
{ sdf: 'five', id: '5' },
|
|
180
|
+
{ username: 'never' },
|
|
181
|
+
);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('UserResource.create should work', async () => {
|
|
185
|
+
mynock.post(`/groups/five/users`).reply(200, (uri, body: any) => ({
|
|
186
|
+
id: 5,
|
|
187
|
+
...body,
|
|
188
|
+
}));
|
|
189
|
+
|
|
190
|
+
const { result, waitForNextUpdate } = renderRestHook(() => {
|
|
191
|
+
return [
|
|
192
|
+
useCache(UserResource.get, { group: 'five', id: '5' }),
|
|
193
|
+
useController(),
|
|
194
|
+
] as const;
|
|
195
|
+
});
|
|
196
|
+
// eslint-disable-next-line prefer-const
|
|
197
|
+
let [_, controller] = result.current;
|
|
198
|
+
await act(() => {
|
|
199
|
+
controller.fetch(
|
|
200
|
+
UserResource.create,
|
|
201
|
+
{ group: 'five' },
|
|
202
|
+
{
|
|
203
|
+
username: 'createduser',
|
|
204
|
+
email: 'haha@gmail.com',
|
|
205
|
+
},
|
|
206
|
+
);
|
|
207
|
+
});
|
|
208
|
+
await waitForNextUpdate();
|
|
209
|
+
const user = result.current[0];
|
|
210
|
+
expect(user).toBeDefined();
|
|
211
|
+
expect(user?.username).toBe('createduser');
|
|
212
|
+
// our custom endpoint ensures this
|
|
213
|
+
expect(user?.email).toBe('always@always.com');
|
|
214
|
+
|
|
215
|
+
() =>
|
|
216
|
+
controller.fetch(
|
|
217
|
+
UserResource.create,
|
|
218
|
+
// @ts-expect-error
|
|
219
|
+
{ id: 'five' },
|
|
220
|
+
{ username: 'never' },
|
|
221
|
+
);
|
|
222
|
+
// @ts-expect-error
|
|
223
|
+
() => controller.fetch(UserResource.create, { username: 'never' });
|
|
224
|
+
// @ts-expect-error
|
|
225
|
+
() => controller.fetch(UserResource.create, 1, 'hi');
|
|
226
|
+
() =>
|
|
227
|
+
controller.fetch(
|
|
228
|
+
UserResource.create,
|
|
229
|
+
{ group: 'five' },
|
|
230
|
+
// @ts-expect-error
|
|
231
|
+
{ sdf: 'never' },
|
|
232
|
+
);
|
|
233
|
+
() =>
|
|
234
|
+
controller.fetch(
|
|
235
|
+
UserResource.create,
|
|
236
|
+
// @ts-expect-error
|
|
237
|
+
{ sdf: 'five' },
|
|
238
|
+
{ username: 'never' },
|
|
239
|
+
);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it.each([
|
|
243
|
+
{
|
|
244
|
+
response: {
|
|
245
|
+
id: userPayload.id,
|
|
246
|
+
},
|
|
247
|
+
headers: {
|
|
248
|
+
'Access-Control-Allow-Origin': '*',
|
|
249
|
+
'Content-Type': 'application/json',
|
|
250
|
+
} as ReplyHeaders,
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
response: '',
|
|
254
|
+
headers: {
|
|
255
|
+
'Access-Control-Allow-Origin': '*',
|
|
256
|
+
'Content-Type': 'text',
|
|
257
|
+
} as ReplyHeaders,
|
|
258
|
+
},
|
|
259
|
+
])(
|
|
260
|
+
'UserResource.delete should work with $response',
|
|
261
|
+
async ({ response, headers }) => {
|
|
262
|
+
mynock
|
|
263
|
+
.delete(`/groups/five/users/${userPayload.id}`)
|
|
264
|
+
.reply(200, (uri, body: any) => response, headers);
|
|
265
|
+
const { result, waitForNextUpdate } = renderRestHook(
|
|
266
|
+
() => {
|
|
267
|
+
return [
|
|
268
|
+
useSuspense(UserResource.get, { group: 'five', id: '5' }),
|
|
269
|
+
useController(),
|
|
270
|
+
] as const;
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
initialFixtures: [
|
|
274
|
+
{
|
|
275
|
+
endpoint: UserResource.get,
|
|
276
|
+
args: [{ group: 'five', id: '5' }],
|
|
277
|
+
response: userPayload,
|
|
278
|
+
},
|
|
279
|
+
],
|
|
280
|
+
resolverFixtures: [
|
|
281
|
+
{
|
|
282
|
+
endpoint: UserResource.get,
|
|
283
|
+
args: [{ group: 'five', id: '5' }],
|
|
284
|
+
response: 'not found',
|
|
285
|
+
error: true,
|
|
286
|
+
},
|
|
287
|
+
],
|
|
288
|
+
},
|
|
289
|
+
);
|
|
290
|
+
// eslint-disable-next-line prefer-const
|
|
291
|
+
let [user, controller] = result.current;
|
|
292
|
+
expect(user.username).toBe(userPayload.username);
|
|
293
|
+
await act(() => {
|
|
294
|
+
controller.fetch(UserResource.delete, { group: 'five', id: '5' });
|
|
295
|
+
});
|
|
296
|
+
await waitForNextUpdate();
|
|
297
|
+
// this means we suspended; so it hit the resolver fixture
|
|
298
|
+
expect(result.error).toMatchSnapshot();
|
|
299
|
+
|
|
300
|
+
() =>
|
|
301
|
+
controller.fetch(
|
|
302
|
+
UserResource.delete,
|
|
303
|
+
{ group: 'five', id: '5' },
|
|
304
|
+
// @ts-expect-error
|
|
305
|
+
{ username: 'never' },
|
|
306
|
+
);
|
|
307
|
+
// @ts-expect-error
|
|
308
|
+
() => controller.fetch(UserResource.delete);
|
|
309
|
+
// @ts-expect-error
|
|
310
|
+
() => controller.fetch(UserResource.delete, 1);
|
|
311
|
+
() =>
|
|
312
|
+
controller.fetch(
|
|
313
|
+
UserResource.delete,
|
|
314
|
+
// @ts-expect-error
|
|
315
|
+
{ sdf: 'never' },
|
|
316
|
+
);
|
|
317
|
+
},
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
it('should allow complete overrides', async () => {
|
|
321
|
+
mynock
|
|
322
|
+
.get(`/groups/vi/users/5`)
|
|
323
|
+
.reply(200, { id: 5, title: 'hi', username2: 'bob' });
|
|
324
|
+
|
|
325
|
+
class User2 extends Entity {
|
|
326
|
+
readonly id: number | undefined = undefined;
|
|
327
|
+
readonly username2: string = '';
|
|
328
|
+
readonly email: string = '';
|
|
329
|
+
readonly isAdmin: boolean = false;
|
|
330
|
+
|
|
331
|
+
pk() {
|
|
332
|
+
return this.id?.toString();
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
const UserResourceExtend = {
|
|
336
|
+
...UserResource,
|
|
337
|
+
get: UserResource.get.extend({
|
|
338
|
+
path: 'http\\://test.com/groups/:magic/users/:id',
|
|
339
|
+
schema: User2,
|
|
340
|
+
}),
|
|
341
|
+
};
|
|
342
|
+
const { result, waitForNextUpdate } = renderRestHook(() => {
|
|
343
|
+
return useSuspense(UserResourceExtend.get, { magic: 'vi', id: 5 });
|
|
344
|
+
});
|
|
345
|
+
await waitForNextUpdate();
|
|
346
|
+
|
|
347
|
+
expect(result.current.username2).toBe('bob');
|
|
348
|
+
// @ts-expect-error
|
|
349
|
+
expect(result.current.username).toBeUndefined();
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
describe('unions', () => {
|
|
353
|
+
const feedPayload = {
|
|
354
|
+
id: '5',
|
|
355
|
+
title: 'my first feed',
|
|
356
|
+
type: 'link' as const,
|
|
357
|
+
url: 'https://true.io',
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
class Feed extends Entity {
|
|
361
|
+
readonly id: string = '';
|
|
362
|
+
readonly title: string = '';
|
|
363
|
+
readonly type: 'link' | 'post' = 'post';
|
|
364
|
+
pk() {
|
|
365
|
+
return this.id;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
class FeedLink extends Feed {
|
|
369
|
+
readonly url: string = '';
|
|
370
|
+
readonly type = 'link' as const;
|
|
371
|
+
}
|
|
372
|
+
class FeedPost extends Feed {
|
|
373
|
+
readonly content: string = '';
|
|
374
|
+
readonly type = 'post' as const;
|
|
375
|
+
}
|
|
376
|
+
const FeedUnion = new schema.Union(
|
|
377
|
+
{ post: FeedPost, link: FeedLink },
|
|
378
|
+
'type' as const,
|
|
379
|
+
);
|
|
380
|
+
const FeedResource = createResource({
|
|
381
|
+
path: 'http\\://test.com/feed/:id',
|
|
382
|
+
schema: FeedUnion,
|
|
383
|
+
Endpoint: MyEndpoint,
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
it('should work with detail', async () => {
|
|
387
|
+
mynock.get(`/feed/${feedPayload.id}`).reply(200, feedPayload);
|
|
388
|
+
|
|
389
|
+
const { result, waitForNextUpdate } = renderRestHook(() => {
|
|
390
|
+
return useSuspense(FeedResource.get, { id: '5' });
|
|
391
|
+
});
|
|
392
|
+
await waitForNextUpdate();
|
|
393
|
+
const feed = result.current;
|
|
394
|
+
if (feed.type === 'link') {
|
|
395
|
+
expect(feed.url).toBe(feedPayload.url);
|
|
396
|
+
// @ts-expect-error
|
|
397
|
+
expect(feed.content).toBeUndefined();
|
|
398
|
+
} else {
|
|
399
|
+
// this branch doesn't run - just a type test
|
|
400
|
+
feed.content;
|
|
401
|
+
// @ts-expect-error
|
|
402
|
+
expect(feed.url).toBeUndefined();
|
|
403
|
+
}
|
|
404
|
+
// another type test
|
|
405
|
+
// @ts-expect-error
|
|
406
|
+
() => useSuspense(FeedResource.get, { sdf: '5' });
|
|
407
|
+
|
|
408
|
+
// @ts-expect-error
|
|
409
|
+
() => FeedResource.get({ id: '5', sdf: '5' });
|
|
410
|
+
// @ts-expect-error
|
|
411
|
+
() => FeedResource.get({ id: '5' }, 5);
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
it('should work with list [no args]', async () => {
|
|
415
|
+
mynock.get(`/feed`).reply(200, [feedPayload]);
|
|
416
|
+
|
|
417
|
+
const { result, waitForNextUpdate } = renderRestHook(() => {
|
|
418
|
+
return useSuspense(FeedResource.getList);
|
|
419
|
+
});
|
|
420
|
+
await waitForNextUpdate();
|
|
421
|
+
const feed = result.current[0];
|
|
422
|
+
if (feed.type === 'link') {
|
|
423
|
+
expect(feed.url).toBe(feedPayload.url);
|
|
424
|
+
// @ts-expect-error
|
|
425
|
+
expect(feed.content).toBeUndefined();
|
|
426
|
+
} else {
|
|
427
|
+
// this branch doesn't run - just a type test
|
|
428
|
+
feed.content;
|
|
429
|
+
// @ts-expect-error
|
|
430
|
+
expect(feed.url).toBeUndefined();
|
|
431
|
+
}
|
|
432
|
+
// another type test
|
|
433
|
+
// @ts-expect-error
|
|
434
|
+
() => useSuspense(FeedResource.getList, 5);
|
|
435
|
+
|
|
436
|
+
// @ts-expect-error
|
|
437
|
+
() => FeedResource.getList({ id: '5' }, 5);
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
it('should work with create [no args]', async () => {
|
|
441
|
+
mynock.post(`/feed`).reply(200, (uri, body: any) => ({
|
|
442
|
+
id: 5,
|
|
443
|
+
...body,
|
|
444
|
+
}));
|
|
445
|
+
|
|
446
|
+
const { result, waitForNextUpdate } = renderRestHook(() => {
|
|
447
|
+
return [
|
|
448
|
+
useCache(FeedResource.get, { id: '5', type: 'link' }),
|
|
449
|
+
useController(),
|
|
450
|
+
] as const;
|
|
451
|
+
});
|
|
452
|
+
// eslint-disable-next-line prefer-const
|
|
453
|
+
let [_, controller] = result.current;
|
|
454
|
+
await act(() => {
|
|
455
|
+
controller.fetch(FeedResource.create, feedPayload);
|
|
456
|
+
});
|
|
457
|
+
await waitForNextUpdate();
|
|
458
|
+
const feed = result.current[0];
|
|
459
|
+
expect(feed).toBeDefined();
|
|
460
|
+
if (!feed) throw new Error('never');
|
|
461
|
+
if (feed.type === 'link') {
|
|
462
|
+
expect(feed.url).toBe(feedPayload.url);
|
|
463
|
+
// @ts-expect-error
|
|
464
|
+
expect(feed.content).toBeUndefined();
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
() =>
|
|
468
|
+
controller.fetch(
|
|
469
|
+
UserResource.create,
|
|
470
|
+
// @ts-expect-error
|
|
471
|
+
{ id: 'five' },
|
|
472
|
+
{ username: 'never' },
|
|
473
|
+
);
|
|
474
|
+
// @ts-expect-error
|
|
475
|
+
() => controller.fetch(UserResource.create, { username: 'never' });
|
|
476
|
+
// @ts-expect-error
|
|
477
|
+
() => controller.fetch(UserResource.create, 1, 'hi');
|
|
478
|
+
() =>
|
|
479
|
+
controller.fetch(
|
|
480
|
+
UserResource.create,
|
|
481
|
+
{ group: 'five' },
|
|
482
|
+
// @ts-expect-error
|
|
483
|
+
{ sdf: 'never' },
|
|
484
|
+
);
|
|
485
|
+
() =>
|
|
486
|
+
controller.fetch(
|
|
487
|
+
UserResource.create,
|
|
488
|
+
// @ts-expect-error
|
|
489
|
+
{ sdf: 'five' },
|
|
490
|
+
{ username: 'never' },
|
|
491
|
+
);
|
|
492
|
+
});
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
it('UserResource.create.extends() should work', async () => {
|
|
496
|
+
interface CreateDeviceBody {
|
|
497
|
+
username: string;
|
|
498
|
+
}
|
|
499
|
+
interface UserInterface {
|
|
500
|
+
readonly id: number | undefined;
|
|
501
|
+
readonly username: string;
|
|
502
|
+
readonly email: string;
|
|
503
|
+
readonly isAdmin: boolean;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
const createUser = UserResource.create.extend({
|
|
507
|
+
update: (newId, params) => {
|
|
508
|
+
return {
|
|
509
|
+
[UserResource.getList.key({ group: params.group })]: (
|
|
510
|
+
prevResponse = { items: [] },
|
|
511
|
+
) => ({
|
|
512
|
+
items: [...prevResponse.items, newId],
|
|
513
|
+
}),
|
|
514
|
+
};
|
|
515
|
+
},
|
|
516
|
+
//searchParams: undefined as any,
|
|
517
|
+
body: {} as CreateDeviceBody,
|
|
518
|
+
schema: User,
|
|
519
|
+
sideEffect: true,
|
|
520
|
+
process(...args: any) {
|
|
521
|
+
return UserResource.create.process.apply(this, args) as UserInterface;
|
|
522
|
+
},
|
|
523
|
+
});
|
|
524
|
+
const ctrl = new Controller();
|
|
525
|
+
() => ctrl.fetch(createUser, { group: 'hi' }, { username: 'bob' });
|
|
526
|
+
() => createUser({ group: 'hi' }, { username: 'bob' });
|
|
527
|
+
// @ts-expect-error
|
|
528
|
+
() => createUser({ group: 'hi', id: 'what' }, { username: 'bob' });
|
|
529
|
+
// @ts-expect-error
|
|
530
|
+
() => createUser({ group: 'hi' });
|
|
531
|
+
// @ts-expect-error
|
|
532
|
+
() => createUser.url({ group: 'hi', id: 'what' }, { username: 'bob' });
|
|
533
|
+
expect(createUser.url({ group: 'hi' }, {} as any)).toMatchInlineSnapshot(
|
|
534
|
+
`"http://test.com/groups/hi/users"`,
|
|
535
|
+
);
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
it('UserResource.create.extends() should work with zero urlParams', async () => {
|
|
539
|
+
const UserResource = createResource({
|
|
540
|
+
path: 'http\\://test.com/users/:id',
|
|
541
|
+
schema: User,
|
|
542
|
+
Endpoint: MyEndpoint,
|
|
543
|
+
});
|
|
544
|
+
interface CreateDeviceBody {
|
|
545
|
+
username: string;
|
|
546
|
+
}
|
|
547
|
+
interface UserInterface {
|
|
548
|
+
readonly id: number | undefined;
|
|
549
|
+
readonly username: string;
|
|
550
|
+
readonly email: string;
|
|
551
|
+
readonly isAdmin: boolean;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
const createUser = UserResource.create.extend({
|
|
555
|
+
update: newId => {
|
|
556
|
+
return {
|
|
557
|
+
[UserResource.getList.key()]: (prevResponse = { items: [] }) => ({
|
|
558
|
+
items: [...prevResponse.items, newId],
|
|
559
|
+
}),
|
|
560
|
+
};
|
|
561
|
+
},
|
|
562
|
+
//searchParams: undefined as any,
|
|
563
|
+
body: {} as CreateDeviceBody,
|
|
564
|
+
schema: User,
|
|
565
|
+
sideEffect: true,
|
|
566
|
+
process(...args: any) {
|
|
567
|
+
return UserResource.create.process.apply(this, args) as UserInterface;
|
|
568
|
+
},
|
|
569
|
+
});
|
|
570
|
+
const ctrl = new Controller();
|
|
571
|
+
() => ctrl.fetch(createUser, { username: 'bob' });
|
|
572
|
+
() => createUser({ username: 'bob' });
|
|
573
|
+
// @ts-expect-error
|
|
574
|
+
() => createUser({ id: 'what' }, { username: 'bob' });
|
|
575
|
+
// @ts-expect-error
|
|
576
|
+
() => createUser({ id: 'what' });
|
|
577
|
+
// @ts-expect-error
|
|
578
|
+
() => createUser.url({ id: 'what' }, { username: 'bob' });
|
|
579
|
+
expect(createUser.url({} as any)).toMatchInlineSnapshot(
|
|
580
|
+
`"http://test.com/users"`,
|
|
581
|
+
);
|
|
582
|
+
});
|
|
583
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { CoolerArticle } from '__tests__/new';
|
|
2
|
+
|
|
3
|
+
import { schema } from '..';
|
|
4
|
+
import getArrayPath from '../getArrayPath';
|
|
5
|
+
|
|
6
|
+
describe('getArrayPath()', () => {
|
|
7
|
+
it('should be false when there is no array', () => {
|
|
8
|
+
expect(getArrayPath({ data: 5 })).toBe(false);
|
|
9
|
+
expect(getArrayPath({ data: 5, other: undefined, stringer: 'hi' })).toBe(
|
|
10
|
+
false,
|
|
11
|
+
);
|
|
12
|
+
});
|
|
13
|
+
it('should find entity inside pojo', () => {
|
|
14
|
+
expect(
|
|
15
|
+
getArrayPath({ data: 5, right: [CoolerArticle], stringer: 'hi' }),
|
|
16
|
+
).toStrictEqual(['right']);
|
|
17
|
+
});
|
|
18
|
+
it('should find entity inside schema.Object', () => {
|
|
19
|
+
expect(
|
|
20
|
+
getArrayPath(
|
|
21
|
+
new schema.Object({ data: 5, right: [CoolerArticle], stringer: 'hi' }),
|
|
22
|
+
),
|
|
23
|
+
).toStrictEqual(['right']);
|
|
24
|
+
});
|
|
25
|
+
it('should be false when has entity but no array', () => {
|
|
26
|
+
expect(
|
|
27
|
+
getArrayPath({ data: 5, right: CoolerArticle, stringer: 'hi' }),
|
|
28
|
+
).toStrictEqual(false);
|
|
29
|
+
});
|
|
30
|
+
});
|