Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(makeServerFetchye): adding run method to response #92

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
309 changes: 148 additions & 161 deletions README.md

Large diffs are not rendered by default.

23 changes: 23 additions & 0 deletions packages/fetchye/__tests__/computeKey.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,14 @@ describe('computeKey', () => {
}
`);
});
it('should return an object if no options are passed', () => {
expect(computeKey(() => 'abcd')).toMatchInlineSnapshot(`
Object {
"hash": "037ace2918f4083eda9c4be34cccb93de5051b5a",
"key": "abcd",
}
`);
});
it('should return false if key func throws error', () => {
expect(
computeKey(() => {
Expand Down Expand Up @@ -89,6 +97,21 @@ describe('computeKey', () => {
expect(mappedHash).toBe(unmappedHash);
});

it('should call dynamic headers function and return a different hash when dynamic headers change', () => {
const key = 'abcd';
let headerCount = 0;
const options = {
headers: () => {
headerCount += 1;
return { dynamicHeader: headerCount };
},
};
const { hash: mappedHash1 } = computeKey(key, options);
const { hash: mappedHash2 } = computeKey(key, options);
expect(mappedHash1).not.toBe(mappedHash2);
expect(headerCount).toBe(2);
});

it('should pass generated cacheKey to the underlying hash function along with the options, and return the un-mapped key to the caller', () => {
const computedKey = computeKey(() => 'abcd', {
mapKeyToCacheKey: (key, options) => `${key.toUpperCase()}-${options.optionKeyMock}`,
Expand Down
122 changes: 46 additions & 76 deletions packages/fetchye/__tests__/makeServerFetchye.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,6 @@ import { createStore } from 'redux';
import makeServerFetchye from '../src/makeServerFetchye';
import SimpleCache from '../src/SimpleCache';

const cache = SimpleCache();
const store = createStore(cache.reducer, cache.reducer(undefined, { type: '' }));

global.console.error = jest.fn();

const defaultPayload = {
Expand All @@ -34,87 +31,65 @@ const defaultPayload = {
}),
};

const expectedMakeServerFetchyeResponseSnapshot = `
Object {
"data": Object {
"body": Object {
"fakeData": true,
},
"headers": Object {
"content-type": "application/json",
},
"ok": true,
"status": 200,
},
"error": null,
"run": [Function],
}
`;

describe('makeServerFetchye', () => {
let cache;
let store;
let fetchClient;

beforeEach(() => {
cache = SimpleCache();
store = createStore(cache.reducer, cache.reducer(undefined, { type: '' }));
fetchClient = jest.fn(async () => ({
...defaultPayload,
}));
});

afterEach(() => {
jest.resetAllMocks();
});
it('should return data in success state', async () => {
const fetchClient = jest.fn(async () => ({
...defaultPayload,
}));
const fetchyeRes = await makeServerFetchye({
store,
cache,
fetchClient,
})('http://example.com');

expect(fetchyeRes).toMatchInlineSnapshot(`
Object {
"data": Object {
"body": Object {
"fakeData": true,
},
"headers": Object {
"content-type": "application/json",
},
"ok": true,
"status": 200,
},
"error": null,
}
`);
expect(fetchyeRes).toMatchInlineSnapshot(expectedMakeServerFetchyeResponseSnapshot);
});
it('should return data in success state when using default cache', async () => {
const fetchClient = jest.fn(async () => ({
...defaultPayload,
}));
const fetchyeRes = await makeServerFetchye({
store,
fetchClient,
})('http://example.com');

expect(fetchyeRes).toMatchInlineSnapshot(`
Object {
"data": Object {
"body": Object {
"fakeData": true,
},
"headers": Object {
"content-type": "application/json",
},
"ok": true,
"status": 200,
},
"error": null,
}
`);
expect(fetchyeRes).toMatchInlineSnapshot(expectedMakeServerFetchyeResponseSnapshot);
});
it('should return data in success state if no cache and no store provided', async () => {
const fetchClient = jest.fn(async () => ({
...defaultPayload,
}));
const fetchyeRes = await makeServerFetchye({
fetchClient,
})('http://example.com');

expect(fetchyeRes).toMatchInlineSnapshot(`
Object {
"data": Object {
"body": Object {
"fakeData": true,
},
"headers": Object {
"content-type": "application/json",
},
"ok": true,
"status": 200,
},
"error": null,
}
`);
expect(fetchyeRes).toMatchInlineSnapshot(expectedMakeServerFetchyeResponseSnapshot);
});
it('should return null in the error state', async () => {
const fetchClient = jest.fn(async () => {
fetchClient = jest.fn(async () => {
throw new Error('fake error');
});
const fetchyeRes = await makeServerFetchye({
Expand All @@ -134,13 +109,11 @@ describe('makeServerFetchye', () => {
Object {
"data": null,
"error": null,
"run": [Function],
}
`);
});
it('should return previously loaded data', async () => {
const fetchClient = jest.fn(async () => ({
...defaultPayload,
}));
const fetchye = makeServerFetchye({
store,
cache,
Expand All @@ -150,20 +123,17 @@ describe('makeServerFetchye', () => {
const fetchyeResTwo = await fetchye('http://example.com/two');

expect(fetchClient).toHaveBeenCalledTimes(1);
expect(fetchyeResTwo).toMatchInlineSnapshot(`
Object {
"data": Object {
"body": Object {
"fakeData": true,
},
"headers": Object {
"content-type": "application/json",
},
"ok": true,
"status": 200,
},
"error": null,
}
`);
expect(fetchyeResTwo).toMatchInlineSnapshot(expectedMakeServerFetchyeResponseSnapshot);
});
it('should reload the data is the run function returned is called', async () => {
const fetchye = makeServerFetchye({
store,
cache,
fetchClient,
});
const fetchyeResTwo = await fetchye('http://example.com/two');
await fetchyeResTwo.run();
expect(fetchClient).toHaveBeenCalledTimes(2);
expect(fetchyeResTwo).toMatchInlineSnapshot(expectedMakeServerFetchyeResponseSnapshot);
});
});
62 changes: 21 additions & 41 deletions packages/fetchye/__tests__/useFetchye.spec.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ describe('useFetchye', () => {
});
it('should call fetch with the right headers when passed dynamic headers', async () => {
let fetchyeRes;
let dynamicValueCount = 0;
global.fetch = jest.fn(async () => ({
...defaultPayload,
}));
Expand All @@ -123,26 +124,33 @@ describe('useFetchye', () => {
{React.createElement(() => {
fetchyeRes = useFetchye('http://example.com', {
headers: () => ({
dynamicHeader: 'dynamic value',
dynamicHeader: `dynamic value ${dynamicValueCount}`,
}),
mapOptionsToKey: (options) => ({
...options,
dynamicHeader: null,
}),
});
return null;
})}
</AFetchyeProvider>
);
await waitFor(() => fetchyeRes.isLoading === false);
expect(global.fetch.mock.calls).toMatchInlineSnapshot(`
Array [
Array [
"http://example.com",
Object {
"headers": Object {
"dynamicHeader": "dynamic value",
},
},
],
]
`);
dynamicValueCount += 1;
await fetchyeRes.run();
expect(global.fetch).toHaveBeenCalledTimes(2);
expect(global.fetch).toHaveBeenNthCalledWith(1, 'http://example.com', {
headers: {
dynamicHeader: 'dynamic value 0',
},
mapOptionsToKey: expect.any(Function),
});
expect(global.fetch).toHaveBeenNthCalledWith(2, 'http://example.com', {
headers: {
dynamicHeader: 'dynamic value 1',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀

},
mapOptionsToKey: expect.any(Function),
});
});
it('should return data success state when response is empty (204 no content)', async () => {
let fetchyeRes;
Expand Down Expand Up @@ -278,34 +286,6 @@ describe('useFetchye', () => {
]
`);
});
it('should return data when run method is called with dynamic headers', async () => {
let fetchyeRes;
global.fetch = jest.fn(async () => ({
...defaultPayload,
}));
render(
<AFetchyeProvider cache={cache}>
{React.createElement(() => {
fetchyeRes = useFetchye('http://example.com/one', { defer: true, headers: () => ({ dynamicHeader: 'dynamic value' }) });
return null;
})}
</AFetchyeProvider>
);
await fetchyeRes.run();
expect(global.fetch.mock.calls).toMatchInlineSnapshot(`
Array [
Array [
"http://example.com/one",
Object {
"defer": true,
"headers": Object {
"dynamicHeader": "dynamic value",
},
},
],
]
`);
});
it('should use fetcher in hook over provider fetcher', async () => {
const customFetchClient = jest.fn(async () => ({
...defaultPayload,
Expand Down
14 changes: 12 additions & 2 deletions packages/fetchye/src/computeKey.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,19 @@

import computeHash from 'object-hash';
import mapHeaderNamesToLowerCase from './mapHeaderNamesToLowerCase';
import { defaultMapOptionsToKey } from './defaultMapOptionsToKey';
import { handleDynamicHeaders } from './handleDynamicHeaders';

export const computeKey = (key, {
mapOptionsToKey = (options) => options,
...options
} = {}) => {
const {
headers,
mapKeyToCacheKey,
...restOfOptions
} = defaultMapOptionsToKey(mapOptionsToKey(handleDynamicHeaders(options)));

export const computeKey = (key, options) => {
const { headers, mapKeyToCacheKey, ...restOfOptions } = options;
const nextOptions = { ...restOfOptions };
if (headers) {
nextOptions.headers = mapHeaderNamesToLowerCase(headers);
Expand Down
17 changes: 13 additions & 4 deletions packages/fetchye/src/makeServerFetchye.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import { ssrFetcher } from 'fetchye-core';
import SimpleCache from './SimpleCache';
import { runAsync } from './runAsync';
import { computeKey } from './computeKey';
import { defaultMapOptionsToKey } from './defaultMapOptionsToKey';
import { coerceSsrField } from './queryHelpers';

const makeServerFetchye = ({
Expand All @@ -27,18 +26,27 @@ const makeServerFetchye = ({
fetchClient,
}) => async (
key,
{ mapOptionsToKey = (options) => options, ...options } = { },
options = {},
fetcher = ssrFetcher
) => {
const { cacheSelector } = cache;
const computedKey = computeKey(key, defaultMapOptionsToKey(mapOptionsToKey(options)));
const computedKey = computeKey(key, options);
const run = () => runAsync({
dispatch,
computedKey: computeKey(key, options),
fetcher,
fetchClient,
options,
});

if (!getState || !dispatch || !cacheSelector) {
const res = await runAsync({
dispatch: () => {}, computedKey, fetcher, fetchClient, options,
});
return {
data: coerceSsrField(res.data),
error: coerceSsrField(res.error),
run,
};
}
const state = cacheSelector(getState());
Expand All @@ -50,9 +58,10 @@ const makeServerFetchye = ({
return {
data: coerceSsrField(res.data),
error: coerceSsrField(res.error),
run,
};
}
return { data: coerceSsrField(data), error: coerceSsrField(error) };
return { data: coerceSsrField(data), error: coerceSsrField(error), run };
};

export default makeServerFetchye;
3 changes: 2 additions & 1 deletion packages/fetchye/src/runAsync.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
errorAction,

} from 'fetchye-core';
import { handleDynamicHeaders } from './handleDynamicHeaders';

export const runAsync = async ({
dispatch, computedKey, fetcher, fetchClient, options,
Expand All @@ -28,7 +29,7 @@ export const runAsync = async ({
const {
payload: data,
error: requestError,
} = await fetcher(fetchClient, computedKey.key, options);
} = await fetcher(fetchClient, computedKey.key, handleDynamicHeaders(options));
if (!requestError) {
dispatch(setAction({ hash: computedKey.hash, value: data }));
} else {
Expand Down
Loading
Loading