@data-client/rest 0.15.6 → 0.16.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.
Files changed (54) hide show
  1. package/CHANGELOG.md +118 -0
  2. package/dist/index.js +61 -18
  3. package/dist/index.umd.min.js +1 -1
  4. package/legacy/NetworkError.js +16 -1
  5. package/legacy/RestEndpoint.js +17 -4
  6. package/legacy/RestEndpointTypes.js +1 -1
  7. package/legacy/RestHelpers.js +29 -16
  8. package/legacy/pathTypes.js +1 -1
  9. package/legacy/resource.js +3 -1
  10. package/legacy/resourceTypes.js +1 -1
  11. package/lib/NetworkError.d.ts +12 -0
  12. package/lib/NetworkError.d.ts.map +1 -1
  13. package/lib/NetworkError.js +16 -1
  14. package/lib/RestEndpoint.js +17 -4
  15. package/lib/RestEndpointTypes.d.ts +43 -9
  16. package/lib/RestEndpointTypes.d.ts.map +1 -1
  17. package/lib/RestEndpointTypes.js +1 -1
  18. package/lib/RestHelpers.d.ts +2 -2
  19. package/lib/RestHelpers.d.ts.map +1 -1
  20. package/lib/RestHelpers.js +29 -16
  21. package/lib/pathTypes.d.ts +14 -13
  22. package/lib/pathTypes.d.ts.map +1 -1
  23. package/lib/pathTypes.js +1 -1
  24. package/lib/resource.d.ts.map +1 -1
  25. package/lib/resource.js +3 -1
  26. package/lib/resourceTypes.d.ts +5 -1
  27. package/lib/resourceTypes.d.ts.map +1 -1
  28. package/lib/resourceTypes.js +1 -1
  29. package/package.json +5 -5
  30. package/src/NetworkError.ts +15 -0
  31. package/src/RestEndpoint.js +21 -3
  32. package/src/RestEndpointTypes.ts +33 -9
  33. package/src/RestHelpers.ts +34 -26
  34. package/src/__tests__/Resource.test.ts +131 -0
  35. package/src/__tests__/RestEndpoint.ts +212 -4
  36. package/src/__tests__/__snapshots__/RestEndpoint.ts.snap +2 -0
  37. package/src/__tests__/__snapshots__/resource-construction.test.ts.snap +1 -1
  38. package/src/__tests__/hookifyResource.ts +7 -0
  39. package/src/__tests__/resource-construction.test.ts +543 -0
  40. package/src/pathTypes.ts +35 -23
  41. package/src/resource.ts +2 -0
  42. package/src/resourceTypes.ts +3 -1
  43. package/ts4.0/NetworkError.d.ts +12 -0
  44. package/ts4.0/RestEndpointTypes.d.ts +61 -9
  45. package/ts4.0/RestHelpers.d.ts +2 -2
  46. package/ts4.0/pathTypes.d.ts +1 -1
  47. package/ts4.0/resourceExtendable.d.ts +1 -1
  48. package/ts4.0/resourceTypes.d.ts +7 -1
  49. package/ts4.1/NetworkError.d.ts +12 -0
  50. package/ts4.1/RestEndpointTypes.d.ts +61 -9
  51. package/ts4.1/RestHelpers.d.ts +2 -2
  52. package/ts4.1/pathTypes.d.ts +14 -13
  53. package/ts4.1/resourceExtendable.d.ts +1 -1
  54. package/ts4.1/resourceTypes.d.ts +7 -1
@@ -1,32 +1,39 @@
1
- import { compile, PathFunction, parse } from 'path-to-regexp';
1
+ import { compile, PathFunction, parse, Token, ParamData } from 'path-to-regexp';
2
2
 
3
3
  import { ShortenPath } from './pathTypes.js';
4
4
 
5
- const urlBaseCache: Map<string, PathFunction<object>> = new Map();
6
- export function getUrlBase(path: string): PathFunction {
7
- if (!urlBaseCache.has(path)) {
8
- urlBaseCache.set(
9
- path,
10
- compile(path, {
11
- encode: encodeURIComponent,
12
- validate: false,
13
- }),
14
- );
5
+ const urlBaseCache: Record<string, PathFunction<object>> = Object.create(null);
6
+ export function getUrlBase(path: string): PathFunction<ParamData> {
7
+ if (!(path in urlBaseCache)) {
8
+ urlBaseCache[path] = compile(path);
15
9
  }
16
- return urlBaseCache.get(path) as PathFunction;
10
+ return urlBaseCache[path];
17
11
  }
18
12
 
19
- const urlTokensCache: Map<string, Set<string>> = new Map();
13
+ const urlTokensCache: Record<string, Set<string>> = Object.create(null);
20
14
  export function getUrlTokens(path: string): Set<string> {
21
- if (!urlTokensCache.has(path)) {
22
- urlTokensCache.set(
23
- path,
24
- new Set(
25
- parse(path).map(t => (typeof t === 'string' ? t : `${t['name']}`)),
26
- ),
27
- );
15
+ if (!(path in urlTokensCache)) {
16
+ urlTokensCache[path] = tokenMap(parse(path).tokens);
17
+ }
18
+ return urlTokensCache[path];
19
+ }
20
+
21
+ function tokenMap(tokens: Token[]): Set<string> {
22
+ const tokenNames = new Set<string>();
23
+ for (const token of tokens) {
24
+ switch (token.type) {
25
+ case 'param':
26
+ case 'wildcard':
27
+ tokenNames.add(token.name);
28
+ break;
29
+ case 'group':
30
+ for (const name of tokenMap(token.tokens)) {
31
+ tokenNames.add(name);
32
+ }
33
+ break;
34
+ }
28
35
  }
29
- return urlTokensCache.get(path) as Set<string>;
36
+ return tokenNames;
30
37
  }
31
38
 
32
39
  const proto = Object.prototype;
@@ -40,11 +47,12 @@ export function isPojo(obj: unknown): obj is Record<string, any> {
40
47
  }
41
48
 
42
49
  export function shortenPath<S extends string>(path: S): ShortenPath<S> {
43
- const lastColonIndex = path.lastIndexOf(':');
44
- if (lastColonIndex === -1)
45
- throw new Error('Resource path requires at least one :parameter');
46
- // this is for when not specifying a specific item like create/list
47
- let shortUrlRoot: ShortenPath<S> = path.substring(0, lastColonIndex) as any;
50
+ const lastTokenIndex = Math.max(path.lastIndexOf(':'), path.lastIndexOf('*'));
51
+ if (lastTokenIndex === -1)
52
+ throw new Error(
53
+ 'Resource path requires at least one :parameter or *wildcard',
54
+ );
55
+ let shortUrlRoot: ShortenPath<S> = path.substring(0, lastTokenIndex) as any;
48
56
  if (shortUrlRoot[shortUrlRoot.length - 1] === '/')
49
57
  shortUrlRoot = shortUrlRoot.substring(
50
58
  0,
@@ -5,6 +5,7 @@ import { CacheProvider } from '@data-client/react';
5
5
  import nock from 'nock';
6
6
 
7
7
  import { makeRenderDataClient } from '../../../test';
8
+ import NetworkError from '../NetworkError';
8
9
  import { ResourcePath } from '../pathTypes';
9
10
  import resource from '../resource';
10
11
  import RestEndpoint from '../RestEndpoint';
@@ -133,6 +134,18 @@ describe('resource()', () => {
133
134
  () => UserResource.get.url({ sdf: '5' });
134
135
  });
135
136
 
137
+ it('should omit optional path params when undefined', () => {
138
+ const ep = new RestEndpoint({ path: '/users{/:id}' });
139
+ expect(ep.url({ id: undefined })).toBe('/users');
140
+ expect(ep.url({ id: '5' })).toBe('/users/5');
141
+ expect(ep.url({})).toBe('/users');
142
+
143
+ const ep2 = new RestEndpoint({ path: '/users{/:id}{/:group}' });
144
+ expect(ep2.url({ id: undefined, group: undefined })).toBe('/users');
145
+ expect(ep2.url({ id: '5', group: undefined })).toBe('/users/5');
146
+ expect(ep2.url({ id: undefined })).toBe('/users');
147
+ });
148
+
136
149
  it('should handle multiarg urls', () => {
137
150
  const MyUserResource = resource({
138
151
  path: 'http\\://test.com/groups/:group/users/:id',
@@ -179,6 +192,50 @@ describe('resource()', () => {
179
192
  () => useSuspense(MyUserResource.get, { group: 'yay', id: '5' });
180
193
  });
181
194
 
195
+ it('should shorten wildcard (*) paths for getList', () => {
196
+ const FileResource = resource({
197
+ path: '/repos/:owner/*path',
198
+ schema: User,
199
+ });
200
+
201
+ // get uses the full path with wildcard
202
+ expect(
203
+ FileResource.get.url({ owner: 'john', path: ['src', 'index.ts'] }),
204
+ ).toBe('/repos/john/src/index.ts');
205
+ // getList strips the last *wildcard token
206
+ expect(FileResource.getList.url({ owner: 'john' })).toBe('/repos/john');
207
+ // create (push) also uses shortened path
208
+ expect(FileResource.create.url({ owner: 'john' }, {} as any)).toBe(
209
+ '/repos/john',
210
+ );
211
+
212
+ // type checks: get requires both owner and path (array)
213
+ // @ts-expect-error - missing path
214
+ () => FileResource.get.url({ owner: 'john' });
215
+ // @ts-expect-error - path must be array
216
+ () => FileResource.get.url({ owner: 'john', path: 'src/index.ts' });
217
+ // getList only requires owner (extra props become search params)
218
+ () => FileResource.getList({ owner: 'john' });
219
+ // @ts-expect-error - missing required owner param
220
+ () => FileResource.getList({});
221
+ });
222
+
223
+ it('should shorten path when wildcard comes after multiple params', () => {
224
+ const DeepResource = resource({
225
+ path: '/api/:version/files/*path',
226
+ schema: User,
227
+ });
228
+
229
+ expect(
230
+ DeepResource.get.url({ version: 'v2', path: ['docs', 'readme.md'] }),
231
+ ).toBe('/api/v2/files/docs/readme.md');
232
+ expect(DeepResource.getList.url({ version: 'v2' })).toBe('/api/v2/files');
233
+
234
+ // @ts-expect-error - missing version
235
+ () => DeepResource.getList({});
236
+ () => DeepResource.getList({ version: 'v2' });
237
+ });
238
+
182
239
  it('should automatically name methods', () => {
183
240
  expect(PaginatedArticleResource.get.name).toBe('PaginatedArticle.get');
184
241
  expect(PaginatedArticleResource.create.name).toBe(
@@ -302,6 +359,44 @@ describe('resource()', () => {
302
359
  expect(res).toEqual({ id: 500 });
303
360
  });
304
361
 
362
+ it('getList.move should have similar properties to update', () => {
363
+ expect(UserResource.getList.move.method).toBe('PATCH');
364
+ // move uses the full entity path (same as update), not the shortened list path
365
+ expect(UserResource.getList.move.url({ id: '5' }, {})).toBe(
366
+ 'http://test.com/user/5',
367
+ );
368
+ expect(UserResource.update.url({ id: '5' }, {})).toBe(
369
+ 'http://test.com/user/5',
370
+ );
371
+ // schema is the Collection.move variant (not the raw Entity like update)
372
+ expect(UserResource.getList.move.schema).toBeDefined();
373
+ expect(UserResource.getList.move.schema).not.toBe(
374
+ UserResource.update.schema,
375
+ );
376
+ // inherits getOptimisticResponse from getList (which has optimistic via extraMutateOptions)
377
+ expect(typeof UserResource.getList.move.getOptimisticResponse).toBe(
378
+ typeof UserResource.update.getOptimisticResponse,
379
+ );
380
+ // name distinguishes it from update
381
+ expect(UserResource.getList.move.name).toBe('User.getList.partialUpdate');
382
+ // searchParams are removed (move targets a specific entity, not a filtered list)
383
+ expect(UserResource.getList.move.searchParams).toBeUndefined();
384
+ });
385
+
386
+ it('getList.move should handle multiarg urls', () => {
387
+ const MyUserResource = resource({
388
+ path: 'http\\://test.com/groups/:group/users/:id',
389
+ schema: User,
390
+ });
391
+ expect(MyUserResource.getList.move.url({ group: 'big', id: '5' }, {})).toBe(
392
+ 'http://test.com/groups/big/users/5',
393
+ );
394
+ // same as update path
395
+ expect(MyUserResource.update.url({ group: 'big', id: '5' }, {})).toBe(
396
+ 'http://test.com/groups/big/users/5',
397
+ );
398
+ });
399
+
305
400
  it('should spread `url` member', () => {
306
401
  const entity = UrlArticle.fromJS({ url: 'five' });
307
402
  const spread = { ...entity };
@@ -309,3 +404,39 @@ describe('resource()', () => {
309
404
  expect(Object.hasOwn(entity, 'url')).toBeTruthy();
310
405
  });
311
406
  });
407
+
408
+ describe('NetworkError', () => {
409
+ it('toJSON() should serialize error with status, message, and url', () => {
410
+ const mockResponse = {
411
+ status: 404,
412
+ statusText: 'Not Found',
413
+ url: 'http://test.com/api/missing',
414
+ } as Response;
415
+
416
+ const error = new NetworkError(mockResponse);
417
+ const json = error.toJSON();
418
+
419
+ expect(json).toEqual({
420
+ name: 'NetworkError',
421
+ status: 404,
422
+ message: 'http://test.com/api/missing: Not Found',
423
+ url: 'http://test.com/api/missing',
424
+ });
425
+ });
426
+
427
+ it('toJSON() output should be JSON.stringify-able', () => {
428
+ const mockResponse = {
429
+ status: 500,
430
+ statusText: 'Internal Server Error',
431
+ url: 'http://test.com/api/broken',
432
+ } as Response;
433
+
434
+ const error = new NetworkError(mockResponse);
435
+ const serialized = JSON.stringify(error);
436
+ const parsed = JSON.parse(serialized);
437
+
438
+ expect(parsed.name).toBe('NetworkError');
439
+ expect(parsed.status).toBe(500);
440
+ expect(parsed.url).toBe('http://test.com/api/broken');
441
+ });
442
+ });
@@ -1,11 +1,11 @@
1
1
  import { Entity, schema, Collection } from '@data-client/endpoint';
2
- import { useController } from '@data-client/react';
2
+ import { useController, useCache } from '@data-client/react';
3
3
  import { useSuspense } from '@data-client/react';
4
4
  import { CacheProvider } from '@data-client/react';
5
5
  import { CoolerArticle, CoolerArticleResource } from '__tests__/new';
6
6
  import nock from 'nock';
7
7
 
8
- import { makeRenderDataClient } from '../../../test';
8
+ import { makeRenderDataClient, act } from '../../../test';
9
9
  import RestEndpoint, {
10
10
  Defaults,
11
11
  RestEndpointConstructorOptions,
@@ -132,6 +132,35 @@ const getNextPage3 = getArticleList3.getPage;
132
132
  a.getPage({ cursor: 'hi', blob: 'ho' });
133
133
  // @ts-expect-error
134
134
  a.getPage({ blob: 'ho' });
135
+
136
+ // Compare flat vs nested Collection schema type inference
137
+ const flat = new RestEndpoint({
138
+ urlPrefix: 'http://test.com',
139
+ path: '/articles/:id',
140
+ schema: new Collection([PaginatedArticle]),
141
+ method: 'GET',
142
+ });
143
+ // flat Collection schema should be typed
144
+ // @ts-expect-error - schema should not be any
145
+ flat.move.schema satisfies number;
146
+ // @ts-expect-error - push schema should not be any
147
+ flat.push.schema satisfies number;
148
+
149
+ // nested Collection schema should also be typed
150
+ const nested = new RestEndpoint({
151
+ urlPrefix: 'http://test.com',
152
+ path: '/articles/:id',
153
+ schema: {
154
+ nextPage: '',
155
+ data: { results: new Collection([PaginatedArticle]) },
156
+ },
157
+ method: 'GET',
158
+ searchParams: {} as { group: string },
159
+ });
160
+ // @ts-expect-error - nested move schema should not be any
161
+ nested.move.schema satisfies number;
162
+ // @ts-expect-error - nested push schema should not be any
163
+ nested.push.schema satisfies number;
135
164
  };
136
165
 
137
166
  describe('RestEndpoint', () => {
@@ -208,9 +237,9 @@ describe('RestEndpoint', () => {
208
237
  });
209
238
 
210
239
  it('only optional path means the arg is not required', () => {
211
- const ep = new RestEndpoint({ path: '/users/:id?/:group?' });
240
+ const ep = new RestEndpoint({ path: '/users{/:id}{/:group}' });
212
241
  const epbody = new RestEndpoint({
213
- path: '/users/:id?/:group?',
242
+ path: '/users{/:id}{/:group}',
214
243
  body: { title: '' },
215
244
  method: 'POST',
216
245
  });
@@ -226,6 +255,64 @@ describe('RestEndpoint', () => {
226
255
  () => epbody({ title: 'hi' }, { title: 'hi' });
227
256
  });
228
257
 
258
+ it('should omit optional path params when undefined', () => {
259
+ const ep = new RestEndpoint({ path: '/users{/:id}' });
260
+ expect(ep.url({ id: undefined })).toBe('/users');
261
+ expect(ep.url({ id: '5' })).toBe('/users/5');
262
+ expect(ep.url({})).toBe('/users');
263
+
264
+ const ep2 = new RestEndpoint({ path: '/users{/:id}{/:group}' });
265
+ expect(ep2.url({ id: undefined, group: undefined })).toBe('/users');
266
+ expect(ep2.url({ id: '5', group: undefined })).toBe('/users/5');
267
+ expect(ep2.url({ id: undefined })).toBe('/users');
268
+ });
269
+
270
+ it('should handle wildcard (*) path params as arrays', () => {
271
+ const ep = new RestEndpoint({ path: '/files/*path' });
272
+ expect(ep.url({ path: ['a', 'b', 'c'] })).toBe('/files/a/b/c');
273
+ expect(ep.url({ path: ['single'] })).toBe('/files/single');
274
+
275
+ // @ts-expect-error - path must be an array, not a string
276
+ () => ep.url({ path: 'a/b/c' });
277
+ // @ts-expect-error - missing required wildcard param
278
+ () => ep.url({});
279
+ });
280
+
281
+ it('should handle wildcard with regular params', () => {
282
+ const ep = new RestEndpoint({ path: '/users/:id/*rest' });
283
+ expect(ep.url({ id: '5', rest: ['docs', 'file'] })).toBe(
284
+ '/users/5/docs/file',
285
+ );
286
+ expect(ep.url({ id: '42', rest: ['a'] })).toBe('/users/42/a');
287
+
288
+ // @ts-expect-error - missing id
289
+ () => ep.url({ rest: ['a'] });
290
+ // @ts-expect-error - missing rest
291
+ () => ep.url({ id: '5' });
292
+ });
293
+
294
+ it('should handle optional wildcard params', () => {
295
+ const ep = new RestEndpoint({ path: '/files{/*path}' });
296
+ expect(ep.url({ path: ['a', 'b'] })).toBe('/files/a/b');
297
+ expect(ep.url({})).toBe('/files');
298
+ expect(ep.url({ path: undefined })).toBe('/files');
299
+ () => ep();
300
+ });
301
+
302
+ it('should handle optional wildcard with required param', () => {
303
+ const ep = new RestEndpoint({ path: '/users/:id{/*rest}' });
304
+ expect(ep.url({ id: '5', rest: ['docs', 'file'] })).toBe(
305
+ '/users/5/docs/file',
306
+ );
307
+ expect(ep.url({ id: '5' })).toBe('/users/5');
308
+ expect(ep.url({ id: '5', rest: undefined })).toBe('/users/5');
309
+
310
+ // @ts-expect-error - missing required param id
311
+ () => ep.url({});
312
+ // @ts-expect-error - rest should be array, not string
313
+ () => ep.url({ id: '5', rest: 'docs/file' });
314
+ });
315
+
229
316
  it('should allow sideEffect overrides', () => {
230
317
  const weirdGetUser = new RestEndpoint({
231
318
  path: 'http\\://test.com/user/:id',
@@ -364,6 +451,113 @@ describe('RestEndpoint', () => {
364
451
  expect(getArticleList3.remove.name).toMatchSnapshot();
365
452
  });
366
453
 
454
+ it('move: should extend name of parent endpoint', () => {
455
+ expect(getArticleList3.move.name).toMatchSnapshot();
456
+ });
457
+
458
+ it('move should work with deeply nested Collection', async () => {
459
+ class GroupedArticle extends Entity {
460
+ readonly id: number | undefined = undefined;
461
+ readonly title: string = '';
462
+ readonly group: string = '';
463
+ }
464
+
465
+ const getGroupedArticles = new RestEndpoint({
466
+ urlPrefix: 'http://test.com',
467
+ path: '/grouped-articles',
468
+ movePath: '/grouped-articles/:id',
469
+ schema: {
470
+ nextPage: '',
471
+ data: { results: new Collection([GroupedArticle]) },
472
+ },
473
+ method: 'GET',
474
+ searchParams: {} as { group: string },
475
+ });
476
+
477
+ // Verify schema is correctly extracted from nested structure
478
+ expect(getGroupedArticles.move.schema).toBeTruthy();
479
+ expect(getGroupedArticles.move.method).toBe('PATCH');
480
+
481
+ // Diagnose schema type - should not be any
482
+ // @ts-expect-error - schema should not be any
483
+ getGroupedArticles.move.schema satisfies number;
484
+
485
+ mynock.patch(/grouped-articles/).reply(200, (uri, body: any) => ({
486
+ id: 2,
487
+ title: 'Article 2',
488
+ group: 'beta',
489
+ ...(typeof body === 'string' ? JSON.parse(body) : body),
490
+ }));
491
+
492
+ const { result, controller } = renderDataClient(
493
+ () => {
494
+ return {
495
+ alpha: useCache(getGroupedArticles, { group: 'alpha' }),
496
+ beta: useCache(getGroupedArticles, { group: 'beta' }),
497
+ };
498
+ },
499
+ {
500
+ initialFixtures: [
501
+ {
502
+ endpoint: getGroupedArticles,
503
+ args: [{ group: 'alpha' }],
504
+ response: {
505
+ nextPage: '2',
506
+ data: {
507
+ results: [
508
+ { id: 1, title: 'Article 1', group: 'alpha' },
509
+ { id: 2, title: 'Article 2', group: 'alpha' },
510
+ ],
511
+ },
512
+ },
513
+ },
514
+ {
515
+ endpoint: getGroupedArticles,
516
+ args: [{ group: 'beta' }],
517
+ response: {
518
+ nextPage: '2',
519
+ data: {
520
+ results: [{ id: 3, title: 'Article 3', group: 'beta' }],
521
+ },
522
+ },
523
+ },
524
+ ],
525
+ },
526
+ );
527
+
528
+ expect(result.current.alpha?.data.results).toHaveLength(2);
529
+ expect(result.current.beta?.data.results).toHaveLength(1);
530
+
531
+ // Move article 2 from alpha to beta
532
+ await act(async () => {
533
+ const response = await controller.fetch(
534
+ getGroupedArticles.move,
535
+ { id: 2 },
536
+ {
537
+ id: 2,
538
+ title: 'Article 2',
539
+ group: 'beta',
540
+ },
541
+ );
542
+ // Type test: response should be a single GroupedArticle, not an array
543
+ response.title satisfies string;
544
+ expect(response.title).toBe('Article 2');
545
+ expect(response.group).toBe('beta');
546
+ });
547
+
548
+ // article should be removed from alpha
549
+ expect(result.current.alpha?.data.results).toHaveLength(1);
550
+ const alphaResults = result.current.alpha!.data.results!;
551
+ expect(alphaResults[0]?.title).toBe('Article 1');
552
+
553
+ // article should be added to beta
554
+ const betaResults = result.current.beta!.data.results!;
555
+ expect(betaResults).toHaveLength(2);
556
+ expect(betaResults.map((a: any) => a.title)).toEqual(
557
+ expect.arrayContaining(['Article 3', 'Article 2']),
558
+ );
559
+ });
560
+
367
561
  // TODO: but we need a Values collection
368
562
  // it('assign: should extend name of parent endpoint', () => {
369
563
  // expect(getArticleList3.assign.name).toMatchSnapshot();
@@ -1500,6 +1694,20 @@ describe('RestEndpoint.fetch()', () => {
1500
1694
  expect(noColletionEndpoint.remove.schema).toBeFalsy();
1501
1695
  });
1502
1696
 
1697
+ it('without Collection in schema - endpoint.move schema should be null', () => {
1698
+ const noColletionEndpoint = new RestEndpoint({
1699
+ urlPrefix: 'http://test.com/article-paginated/',
1700
+ path: ':group',
1701
+ name: 'get',
1702
+ schema: {
1703
+ nextPage: '',
1704
+ data: { results: [PaginatedArticle] },
1705
+ },
1706
+ method: 'GET',
1707
+ });
1708
+ expect(noColletionEndpoint.move.schema).toBeFalsy();
1709
+ });
1710
+
1503
1711
  it('without Collection in schema - endpoint.getPage should throw', () => {
1504
1712
  const noColletionEndpoint = new RestEndpoint({
1505
1713
  urlPrefix: 'http://test.com/article-paginated/',
@@ -2,6 +2,8 @@
2
2
 
3
3
  exports[`RestEndpoint getPage: should extend name of parent endpoint 1`] = `"http://test.com/article-paginated.getPage"`;
4
4
 
5
+ exports[`RestEndpoint move: should extend name of parent endpoint 1`] = `"http://test.com/article-paginated.partialUpdate"`;
6
+
5
7
  exports[`RestEndpoint push: should extend name of parent endpoint 1`] = `"http://test.com/article-paginated.create"`;
6
8
 
7
9
  exports[`RestEndpoint remove: should extend name of parent endpoint 1`] = `"http://test.com/article-paginated.remove"`;
@@ -4,7 +4,7 @@ exports[`resource() UserResource.delete should work with 1`] = `"not found"`;
4
4
 
5
5
  exports[`resource() UserResource.delete should work with {"id": 5} 1`] = `"not found"`;
6
6
 
7
- exports[`resource() should not allow paths without at least one argument 1`] = `"Resource path requires at least one :parameter"`;
7
+ exports[`resource() should not allow paths without at least one argument 1`] = `"Resource path requires at least one :parameter or *wildcard"`;
8
8
 
9
9
  exports[`resource() warnings should warn when mis-capitalizing options 1`] = `
10
10
  [
@@ -63,7 +63,14 @@ describe('hookifyResource()', () => {
63
63
  };
64
64
 
65
65
  let renderDataClient: ReturnType<typeof makeRenderDataClient>;
66
+ let errorSpy: jest.SpyInstance;
66
67
 
68
+ beforeAll(() => {
69
+ errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
70
+ });
71
+ afterAll(() => {
72
+ errorSpy.mockRestore();
73
+ });
67
74
  beforeEach(() => {
68
75
  renderDataClient = makeRenderDataClient(CacheProvider);
69
76