-
-
Notifications
You must be signed in to change notification settings - Fork 98
Expand file tree
/
Copy pathreset.tsx
More file actions
222 lines (196 loc) · 6.62 KB
/
reset.tsx
File metadata and controls
222 lines (196 loc) · 6.62 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
import { CacheProvider } from '@data-client/react';
import { makeRenderDataClient, renderHook } from '@data-client/test';
import { act } from '@data-client/test';
import { FixtureEndpoint } from '@data-client/test';
import { waitFor } from '@testing-library/react';
import { CoolerArticleDetail, FutureArticleResource } from '__tests__/new';
import nock from 'nock';
import { useEffect } from 'react';
import { useCache, useSuspense, useFetch, useController } from '../..';
export const payload = {
id: 5,
title: 'hi ho',
content: 'whatever',
tags: ['a', 'best', 'react'],
};
export const createPayload = {
id: 1,
title: 'hi ho',
content: 'whatever',
tags: ['a', 'best', 'react'],
};
export const detail: FixtureEndpoint = {
endpoint: FutureArticleResource.get,
args: [5],
response: payload,
};
export const nested: FixtureEndpoint = {
endpoint: FutureArticleResource.getList,
args: [],
response: [
{
id: 5,
title: 'hi ho',
content: 'whatever',
tags: ['a', 'best', 'react'],
author: {
id: 23,
username: 'bob',
},
},
{
id: 3,
title: 'the next time',
content: 'whatever',
author: {
id: 23,
username: 'charles',
email: 'bob@bob.com',
},
},
],
};
let renderDataClient: ReturnType<typeof makeRenderDataClient>;
let mynock: nock.Scope;
beforeEach(() => {
renderDataClient = makeRenderDataClient(CacheProvider);
mynock = nock(/.*/).defaultReplyHeaders({
'Access-Control-Allow-Origin': '*',
'Content-Type': 'application/json',
});
});
afterEach(() => {
nock.cleanAll();
});
describe('resetEntireStore', () => {
it('should result in useCache having no entry', async () => {
const { result, controller } = renderDataClient(
() => {
return {
data: useCache(FutureArticleResource.get, 5),
controller: useController(),
};
},
{ initialFixtures: [detail] },
);
expect(result.current.data).toBeDefined();
act(() => {
controller.resetEntireStore();
});
expect(result.current.data).toBeUndefined();
});
it('should return the same === function each time', () => {
const track = jest.fn();
const { rerender } = renderHook(() => {
const reset = useController().resetEntireStore;
useEffect(track, [reset]);
});
expect(track.mock.calls.length).toBe(1);
for (let i = 0; i < 4; ++i) {
rerender();
}
expect(track.mock.calls.length).toBe(1);
});
describe('integration', () => {
beforeEach(() => {
renderDataClient = makeRenderDataClient(CacheProvider);
});
afterEach(() => {
jest.useRealTimers();
});
/**
* useSuspense(), useFetch() will re-issue needed fetches upon reset so they never end up loading infinitely
* this only triggers after commit of reset action so users have a chance to unmount those components if they are no longer relevant (like doing a url redirect from an unauthorized page)
*/
it('should refetch useSuspense() after reset', async () => {
const consoleSpy = jest.spyOn(console, 'error');
mynock
.get(`/article-cooler/${9999}`)
.delay(2)
.reply(200, () => {
return { ...payload, id: 9999 };
})
.persist();
const { result, rerender, controller } = renderDataClient(() => {
return useSuspense(CoolerArticleDetail, { id: 9999 });
});
expect(result.current).toBeUndefined();
act(() => rerender());
// should not be resolved - it is still in flight as it takes 2s
expect(result.current).toBeUndefined();
act(() => {
controller.resetEntireStore();
});
act(() => rerender());
await waitFor(() => expect(result.current).toBeDefined());
expect(result.current.title).toEqual(payload.title);
// ensure it doesn't try to setstate during render (dispatching during fetch - which is called from memo)
expect(consoleSpy.mock.calls.length).toBeLessThan(1);
});
/**
* upon reset, all inflight requests will not dispatch sets
* promises still reject so external listeners know (abort signals do this as well)
*/
it('should not set fetches that started before RESET', async () => {
const detail: FixtureEndpoint = {
endpoint: CoolerArticleDetail,
args: [{ id: 9999 }],
response: { ...payload, id: 9999 },
};
mynock
.get(`/article-cooler/${9999}`)
.delay(2)
.reply(200, {
...payload,
id: 9999,
title: 'latest and greatest title',
})
.persist();
const { result, rerender, controller } = renderDataClient(
() => {
return useCache(CoolerArticleDetail, { id: 9999 });
},
{ initialFixtures: [detail] },
);
expect(result.current).toBeDefined();
expect(result.current?.title).not.toEqual('latest and greatest title');
controller.fetch(CoolerArticleDetail, { id: 9999 }).catch((e: any) => {
console.log('...', e);
});
act(() => rerender());
// should not be resolved
expect(result.current?.title).not.toEqual('latest and greatest title');
act(() => {
controller.resetEntireStore();
});
act(() => rerender());
// TODO: Figure out a way to wait until fetch chain resolution instead of waiting on time
await act(() => new Promise(resolve => setTimeout(resolve, 20)));
// should still not be resolved as we aren't useSuspense() and didn't manually fetch
expect(result.current?.title).not.toEqual('latest and greatest title');
});
/**
* unmounting CacheProvider with inflight requests will not attempt to dispatch after unmount causing react error
* promises are neither resolved nor rejected
*/
it('should not dispatch resolutions after CacheProvider unmounts', async () => {
mynock
.get(`/article-cooler/${9999}`)
.reply(200, () => {
return { ...payload, id: 9999 };
})
.persist();
jest.useRealTimers();
const { unmount, result } = renderDataClient(() => {
return useFetch(CoolerArticleDetail, { id: 9999 });
});
//expect(result.current.resolved).toBe(undefined);
const consoleSpy = jest.spyOn(console, 'error');
act(() => unmount());
// TODO: Figure out a way to wait until fetch chain resolution instead of waiting on time
await act(() => new Promise(resolve => setTimeout(resolve, 20)));
// when trying to dispatch on unmounted this will trigger console errors
expect(consoleSpy.mock.calls.length).toBeLessThan(1);
});
});
});