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
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);
});
});
28 changes: 0 additions & 28 deletions packages/fetchye/__tests__/useFetchye.spec.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -278,34 +278,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,
jjallwood marked this conversation as resolved.
Show resolved Hide resolved
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
17 changes: 5 additions & 12 deletions packages/fetchye/src/useFetchye.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,24 +21,21 @@ import {
isLoading,
} from './queryHelpers';
import { useFetchyeContext } from './useFetchyeContext';
import { defaultMapOptionsToKey } from './defaultMapOptionsToKey';
import { handleDynamicHeaders } from './handleDynamicHeaders';

const passInitialData = (value, initialValue, numOfRenders) => (numOfRenders === 1
? value || initialValue
: value);

const useFetchye = (
key,
{ mapOptionsToKey = (options) => options, ...options } = { },
options = {},
fetcher = undefined
) => {
const {
defaultFetcher, useFetchyeSelector, dispatch, fetchClient,
} = useFetchyeContext();
const dynamicOptions = handleDynamicHeaders(options);
const selectedFetcher = typeof fetcher === 'function' ? fetcher : defaultFetcher;
const computedKey = computeKey(key, defaultMapOptionsToKey(mapOptionsToKey(dynamicOptions)));
const computedKey = computeKey(key, options);
const selectorState = useFetchyeSelector(computedKey.hash);
// create a render version manager using refs
const numOfRenders = useRef(0);
Expand All @@ -55,7 +52,7 @@ const useFetchye = (
const { loading, data, error } = selectorState.current;
if (!loading && !data && !error) {
runAsync({
dispatch, computedKey, fetcher: selectedFetcher, fetchClient, options: dynamicOptions,
dispatch, computedKey, fetcher: selectedFetcher, fetchClient, options,
});
}
});
Expand All @@ -78,16 +75,12 @@ const useFetchye = (
numOfRenders.current
),
run() {
const runOptions = handleDynamicHeaders(options);
const runComputedKey = typeof options.headers === 'function'
? computeKey(key, defaultMapOptionsToKey(mapOptionsToKey(runOptions)))
: computedKey;
return runAsync({
dispatch,
computedKey: runComputedKey,
computedKey,
jjallwood marked this conversation as resolved.
Show resolved Hide resolved
fetcher: selectedFetcher,
fetchClient,
options: runOptions,
options,
});
},
};
Expand Down
Loading