Skip to content

Commit f6f9d94

Browse files
authored
feat(makeServerFetchye): adding run method to response (#92)
* refactor(makeServerFetchye): added run to existing method * test(fetchye): coverage for makeServerFetchye changes * fix(useFetchye): need to recalculate key on run, maybe dynamic * fix(README): lint
1 parent 94bfd7f commit f6f9d94

File tree

8 files changed

+270
-297
lines changed

8 files changed

+270
-297
lines changed

README.md

Lines changed: 148 additions & 161 deletions
Large diffs are not rendered by default.

packages/fetchye/__tests__/computeKey.spec.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,14 @@ describe('computeKey', () => {
4242
}
4343
`);
4444
});
45+
it('should return an object if no options are passed', () => {
46+
expect(computeKey(() => 'abcd')).toMatchInlineSnapshot(`
47+
Object {
48+
"hash": "037ace2918f4083eda9c4be34cccb93de5051b5a",
49+
"key": "abcd",
50+
}
51+
`);
52+
});
4553
it('should return false if key func throws error', () => {
4654
expect(
4755
computeKey(() => {
@@ -89,6 +97,21 @@ describe('computeKey', () => {
8997
expect(mappedHash).toBe(unmappedHash);
9098
});
9199

100+
it('should call dynamic headers function and return a different hash when dynamic headers change', () => {
101+
const key = 'abcd';
102+
let headerCount = 0;
103+
const options = {
104+
headers: () => {
105+
headerCount += 1;
106+
return { dynamicHeader: headerCount };
107+
},
108+
};
109+
const { hash: mappedHash1 } = computeKey(key, options);
110+
const { hash: mappedHash2 } = computeKey(key, options);
111+
expect(mappedHash1).not.toBe(mappedHash2);
112+
expect(headerCount).toBe(2);
113+
});
114+
92115
it('should pass generated cacheKey to the underlying hash function along with the options, and return the un-mapped key to the caller', () => {
93116
const computedKey = computeKey(() => 'abcd', {
94117
mapKeyToCacheKey: (key, options) => `${key.toUpperCase()}-${options.optionKeyMock}`,

packages/fetchye/__tests__/makeServerFetchye.spec.js

Lines changed: 46 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,6 @@ import { createStore } from 'redux';
1818
import makeServerFetchye from '../src/makeServerFetchye';
1919
import SimpleCache from '../src/SimpleCache';
2020

21-
const cache = SimpleCache();
22-
const store = createStore(cache.reducer, cache.reducer(undefined, { type: '' }));
23-
2421
global.console.error = jest.fn();
2522

2623
const defaultPayload = {
@@ -34,87 +31,65 @@ const defaultPayload = {
3431
}),
3532
};
3633

34+
const expectedMakeServerFetchyeResponseSnapshot = `
35+
Object {
36+
"data": Object {
37+
"body": Object {
38+
"fakeData": true,
39+
},
40+
"headers": Object {
41+
"content-type": "application/json",
42+
},
43+
"ok": true,
44+
"status": 200,
45+
},
46+
"error": null,
47+
"run": [Function],
48+
}
49+
`;
50+
3751
describe('makeServerFetchye', () => {
52+
let cache;
53+
let store;
54+
let fetchClient;
55+
3856
beforeEach(() => {
57+
cache = SimpleCache();
58+
store = createStore(cache.reducer, cache.reducer(undefined, { type: '' }));
59+
fetchClient = jest.fn(async () => ({
60+
...defaultPayload,
61+
}));
62+
});
63+
64+
afterEach(() => {
3965
jest.resetAllMocks();
4066
});
4167
it('should return data in success state', async () => {
42-
const fetchClient = jest.fn(async () => ({
43-
...defaultPayload,
44-
}));
4568
const fetchyeRes = await makeServerFetchye({
4669
store,
4770
cache,
4871
fetchClient,
4972
})('http://example.com');
5073

51-
expect(fetchyeRes).toMatchInlineSnapshot(`
52-
Object {
53-
"data": Object {
54-
"body": Object {
55-
"fakeData": true,
56-
},
57-
"headers": Object {
58-
"content-type": "application/json",
59-
},
60-
"ok": true,
61-
"status": 200,
62-
},
63-
"error": null,
64-
}
65-
`);
74+
expect(fetchyeRes).toMatchInlineSnapshot(expectedMakeServerFetchyeResponseSnapshot);
6675
});
6776
it('should return data in success state when using default cache', async () => {
68-
const fetchClient = jest.fn(async () => ({
69-
...defaultPayload,
70-
}));
7177
const fetchyeRes = await makeServerFetchye({
7278
store,
7379
fetchClient,
7480
})('http://example.com');
7581

76-
expect(fetchyeRes).toMatchInlineSnapshot(`
77-
Object {
78-
"data": Object {
79-
"body": Object {
80-
"fakeData": true,
81-
},
82-
"headers": Object {
83-
"content-type": "application/json",
84-
},
85-
"ok": true,
86-
"status": 200,
87-
},
88-
"error": null,
89-
}
90-
`);
82+
expect(fetchyeRes).toMatchInlineSnapshot(expectedMakeServerFetchyeResponseSnapshot);
9183
});
9284
it('should return data in success state if no cache and no store provided', async () => {
93-
const fetchClient = jest.fn(async () => ({
94-
...defaultPayload,
95-
}));
9685
const fetchyeRes = await makeServerFetchye({
9786
fetchClient,
9887
})('http://example.com');
9988

100-
expect(fetchyeRes).toMatchInlineSnapshot(`
101-
Object {
102-
"data": Object {
103-
"body": Object {
104-
"fakeData": true,
105-
},
106-
"headers": Object {
107-
"content-type": "application/json",
108-
},
109-
"ok": true,
110-
"status": 200,
111-
},
112-
"error": null,
113-
}
114-
`);
89+
expect(fetchyeRes).toMatchInlineSnapshot(expectedMakeServerFetchyeResponseSnapshot);
11590
});
11691
it('should return null in the error state', async () => {
117-
const fetchClient = jest.fn(async () => {
92+
fetchClient = jest.fn(async () => {
11893
throw new Error('fake error');
11994
});
12095
const fetchyeRes = await makeServerFetchye({
@@ -134,13 +109,11 @@ describe('makeServerFetchye', () => {
134109
Object {
135110
"data": null,
136111
"error": null,
112+
"run": [Function],
137113
}
138114
`);
139115
});
140116
it('should return previously loaded data', async () => {
141-
const fetchClient = jest.fn(async () => ({
142-
...defaultPayload,
143-
}));
144117
const fetchye = makeServerFetchye({
145118
store,
146119
cache,
@@ -150,20 +123,17 @@ describe('makeServerFetchye', () => {
150123
const fetchyeResTwo = await fetchye('http://example.com/two');
151124

152125
expect(fetchClient).toHaveBeenCalledTimes(1);
153-
expect(fetchyeResTwo).toMatchInlineSnapshot(`
154-
Object {
155-
"data": Object {
156-
"body": Object {
157-
"fakeData": true,
158-
},
159-
"headers": Object {
160-
"content-type": "application/json",
161-
},
162-
"ok": true,
163-
"status": 200,
164-
},
165-
"error": null,
166-
}
167-
`);
126+
expect(fetchyeResTwo).toMatchInlineSnapshot(expectedMakeServerFetchyeResponseSnapshot);
127+
});
128+
it('should reload the data is the run function returned is called', async () => {
129+
const fetchye = makeServerFetchye({
130+
store,
131+
cache,
132+
fetchClient,
133+
});
134+
const fetchyeResTwo = await fetchye('http://example.com/two');
135+
await fetchyeResTwo.run();
136+
expect(fetchClient).toHaveBeenCalledTimes(2);
137+
expect(fetchyeResTwo).toMatchInlineSnapshot(expectedMakeServerFetchyeResponseSnapshot);
168138
});
169139
});

packages/fetchye/__tests__/useFetchye.spec.jsx

Lines changed: 21 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ describe('useFetchye', () => {
115115
});
116116
it('should call fetch with the right headers when passed dynamic headers', async () => {
117117
let fetchyeRes;
118+
let dynamicValueCount = 0;
118119
global.fetch = jest.fn(async () => ({
119120
...defaultPayload,
120121
}));
@@ -123,26 +124,33 @@ describe('useFetchye', () => {
123124
{React.createElement(() => {
124125
fetchyeRes = useFetchye('http://example.com', {
125126
headers: () => ({
126-
dynamicHeader: 'dynamic value',
127+
dynamicHeader: `dynamic value ${dynamicValueCount}`,
128+
}),
129+
mapOptionsToKey: (options) => ({
130+
...options,
131+
dynamicHeader: null,
127132
}),
128133
});
129134
return null;
130135
})}
131136
</AFetchyeProvider>
132137
);
133138
await waitFor(() => fetchyeRes.isLoading === false);
134-
expect(global.fetch.mock.calls).toMatchInlineSnapshot(`
135-
Array [
136-
Array [
137-
"http://example.com",
138-
Object {
139-
"headers": Object {
140-
"dynamicHeader": "dynamic value",
141-
},
142-
},
143-
],
144-
]
145-
`);
139+
dynamicValueCount += 1;
140+
await fetchyeRes.run();
141+
expect(global.fetch).toHaveBeenCalledTimes(2);
142+
expect(global.fetch).toHaveBeenNthCalledWith(1, 'http://example.com', {
143+
headers: {
144+
dynamicHeader: 'dynamic value 0',
145+
},
146+
mapOptionsToKey: expect.any(Function),
147+
});
148+
expect(global.fetch).toHaveBeenNthCalledWith(2, 'http://example.com', {
149+
headers: {
150+
dynamicHeader: 'dynamic value 1',
151+
},
152+
mapOptionsToKey: expect.any(Function),
153+
});
146154
});
147155
it('should return data success state when response is empty (204 no content)', async () => {
148156
let fetchyeRes;
@@ -278,34 +286,6 @@ describe('useFetchye', () => {
278286
]
279287
`);
280288
});
281-
it('should return data when run method is called with dynamic headers', async () => {
282-
let fetchyeRes;
283-
global.fetch = jest.fn(async () => ({
284-
...defaultPayload,
285-
}));
286-
render(
287-
<AFetchyeProvider cache={cache}>
288-
{React.createElement(() => {
289-
fetchyeRes = useFetchye('http://example.com/one', { defer: true, headers: () => ({ dynamicHeader: 'dynamic value' }) });
290-
return null;
291-
})}
292-
</AFetchyeProvider>
293-
);
294-
await fetchyeRes.run();
295-
expect(global.fetch.mock.calls).toMatchInlineSnapshot(`
296-
Array [
297-
Array [
298-
"http://example.com/one",
299-
Object {
300-
"defer": true,
301-
"headers": Object {
302-
"dynamicHeader": "dynamic value",
303-
},
304-
},
305-
],
306-
]
307-
`);
308-
});
309289
it('should use fetcher in hook over provider fetcher', async () => {
310290
const customFetchClient = jest.fn(async () => ({
311291
...defaultPayload,

packages/fetchye/src/computeKey.js

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,19 @@
1616

1717
import computeHash from 'object-hash';
1818
import mapHeaderNamesToLowerCase from './mapHeaderNamesToLowerCase';
19+
import { defaultMapOptionsToKey } from './defaultMapOptionsToKey';
20+
import { handleDynamicHeaders } from './handleDynamicHeaders';
21+
22+
export const computeKey = (key, {
23+
mapOptionsToKey = (options) => options,
24+
...options
25+
} = {}) => {
26+
const {
27+
headers,
28+
mapKeyToCacheKey,
29+
...restOfOptions
30+
} = defaultMapOptionsToKey(mapOptionsToKey(handleDynamicHeaders(options)));
1931

20-
export const computeKey = (key, options) => {
21-
const { headers, mapKeyToCacheKey, ...restOfOptions } = options;
2232
const nextOptions = { ...restOfOptions };
2333
if (headers) {
2434
nextOptions.headers = mapHeaderNamesToLowerCase(headers);

packages/fetchye/src/makeServerFetchye.js

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import { ssrFetcher } from 'fetchye-core';
1818
import SimpleCache from './SimpleCache';
1919
import { runAsync } from './runAsync';
2020
import { computeKey } from './computeKey';
21-
import { defaultMapOptionsToKey } from './defaultMapOptionsToKey';
2221
import { coerceSsrField } from './queryHelpers';
2322

2423
const makeServerFetchye = ({
@@ -27,18 +26,27 @@ const makeServerFetchye = ({
2726
fetchClient,
2827
}) => async (
2928
key,
30-
{ mapOptionsToKey = (options) => options, ...options } = { },
29+
options = {},
3130
fetcher = ssrFetcher
3231
) => {
3332
const { cacheSelector } = cache;
34-
const computedKey = computeKey(key, defaultMapOptionsToKey(mapOptionsToKey(options)));
33+
const computedKey = computeKey(key, options);
34+
const run = () => runAsync({
35+
dispatch,
36+
computedKey: computeKey(key, options),
37+
fetcher,
38+
fetchClient,
39+
options,
40+
});
41+
3542
if (!getState || !dispatch || !cacheSelector) {
3643
const res = await runAsync({
3744
dispatch: () => {}, computedKey, fetcher, fetchClient, options,
3845
});
3946
return {
4047
data: coerceSsrField(res.data),
4148
error: coerceSsrField(res.error),
49+
run,
4250
};
4351
}
4452
const state = cacheSelector(getState());
@@ -50,9 +58,10 @@ const makeServerFetchye = ({
5058
return {
5159
data: coerceSsrField(res.data),
5260
error: coerceSsrField(res.error),
61+
run,
5362
};
5463
}
55-
return { data: coerceSsrField(data), error: coerceSsrField(error) };
64+
return { data: coerceSsrField(data), error: coerceSsrField(error), run };
5665
};
5766

5867
export default makeServerFetchye;

packages/fetchye/src/runAsync.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
errorAction,
2121

2222
} from 'fetchye-core';
23+
import { handleDynamicHeaders } from './handleDynamicHeaders';
2324

2425
export const runAsync = async ({
2526
dispatch, computedKey, fetcher, fetchClient, options,
@@ -28,7 +29,7 @@ export const runAsync = async ({
2829
const {
2930
payload: data,
3031
error: requestError,
31-
} = await fetcher(fetchClient, computedKey.key, options);
32+
} = await fetcher(fetchClient, computedKey.key, handleDynamicHeaders(options));
3233
if (!requestError) {
3334
dispatch(setAction({ hash: computedKey.hash, value: data }));
3435
} else {

0 commit comments

Comments
 (0)