Simplify fetchWithTimeout + add tests for utils
This commit is contained in:
3 files changed
+45
-21
No files matched your search
@@ -105,9 +105,6 @@ history, code and binary blobs related to React Native functionality filtered ou
|
|||||||
- try [`@expo/ui`](https://docs.expo.dev/versions/latest/sdk/ui/), production-ready as of SDK 56:
|
- try [`@expo/ui`](https://docs.expo.dev/versions/latest/sdk/ui/), production-ready as of SDK 56:
|
||||||
real SwiftUI / Jetpack Compose components that could back the homegrown ones in
|
real SwiftUI / Jetpack Compose components that could back the homegrown ones in
|
||||||
`src/components/` and finally retire `react-native-simple-radio-button`
|
`src/components/` and finally retire `react-native-simple-radio-button`
|
||||||
- simplify `fetchWithTimeout` in `src/utils.ts`: `expo/fetch` is now `globalThis.fetch`, so the
|
|
||||||
wrapper collapses to `fetch(url, { signal: AbortSignal.timeout(ms) })`. Opt back out with
|
|
||||||
`EXPO_PUBLIC_USE_RN_FETCH=1` if it misbehaves
|
|
||||||
- swap `@react-native-async-storage/async-storage` (used by `redux-persist` in
|
- swap `@react-native-async-storage/async-storage` (used by `redux-persist` in
|
||||||
`src/reduxStore.ts`) for
|
`src/reduxStore.ts`) for
|
||||||
[`expo-sqlite/localStorage`](https://docs.expo.dev/versions/latest/sdk/sqlite/#localstorage),
|
[`expo-sqlite/localStorage`](https://docs.expo.dev/versions/latest/sdk/sqlite/#localstorage),
|
||||||
|
|||||||
+43
-1
@@ -1,4 +1,4 @@
|
|||||||
import { withAppSource } from '@/utils';
|
import { fetchWithTimeout, snakeToCamelCase, withAppSource } from '@/utils';
|
||||||
|
|
||||||
describe('withAppSource', () => {
|
describe('withAppSource', () => {
|
||||||
it('tags links to our own sites with UTM params', () => {
|
it('tags links to our own sites with UTM params', () => {
|
||||||
@@ -40,3 +40,45 @@ describe('withAppSource', () => {
|
|||||||
expect(withAppSource('')).toBe('');
|
expect(withAppSource('')).toBe('');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('snakeToCamelCase', () => {
|
||||||
|
it('converts snake_case to camelCase', () => {
|
||||||
|
expect(snakeToCamelCase('date_time_start')).toBe('dateTimeStart');
|
||||||
|
expect(snakeToCamelCase('is_member')).toBe('isMember');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('leaves strings without underscores untouched', () => {
|
||||||
|
expect(snakeToCamelCase('events')).toBe('events');
|
||||||
|
expect(snakeToCamelCase('dateTimeStart')).toBe('dateTimeStart');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('fetchWithTimeout', () => {
|
||||||
|
const realFetch = globalThis.fetch;
|
||||||
|
afterEach(() => {
|
||||||
|
globalThis.fetch = realFetch;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('forwards the url and options and attaches an abort signal', async () => {
|
||||||
|
const fetchMock = jest.fn().mockResolvedValue({ ok: true });
|
||||||
|
globalThis.fetch = fetchMock as unknown as typeof fetch;
|
||||||
|
|
||||||
|
await fetchWithTimeout('https://galtinn.neuf.no/api/me/', { method: 'POST' });
|
||||||
|
|
||||||
|
expect(fetchMock).toHaveBeenCalledWith(
|
||||||
|
'https://galtinn.neuf.no/api/me/',
|
||||||
|
expect.objectContaining({ method: 'POST', signal: expect.any(AbortSignal) }),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects with a TimeoutError that aborts the request when the timeout elapses', async () => {
|
||||||
|
globalThis.fetch = ((_url: string, options?: RequestInit) =>
|
||||||
|
new Promise((_resolve, reject) => {
|
||||||
|
options?.signal?.addEventListener('abort', () => reject(options.signal?.reason));
|
||||||
|
})) as unknown as typeof fetch;
|
||||||
|
|
||||||
|
await expect(fetchWithTimeout('https://galtinn.neuf.no/api/me/', undefined, 10)).rejects.toMatchObject({
|
||||||
|
name: 'TimeoutError',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
+2
-17
@@ -25,22 +25,7 @@ export function snakeToCamelCase(val: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function fetchWithTimeout(url: string, options?: RequestInit, timeout = 10000) {
|
export function fetchWithTimeout(url: string, options?: RequestInit, timeout = 10000) {
|
||||||
/** Wraps fetch to allow it to time out */
|
/** Fetch that rejects with a TimeoutError and aborts the request after `timeout` ms */
|
||||||
// FIXME: show timeout message in UI
|
// FIXME: show timeout message in UI
|
||||||
return new Promise<Response>((resolve, reject) => {
|
return fetch(url, { ...options, signal: AbortSignal.timeout(timeout) });
|
||||||
const timer = setTimeout(() => {
|
|
||||||
reject(new Error('Fetch request timed out'));
|
|
||||||
}, timeout);
|
|
||||||
|
|
||||||
fetch(url, options).then(
|
|
||||||
(response) => {
|
|
||||||
clearTimeout(timer);
|
|
||||||
resolve(response);
|
|
||||||
},
|
|
||||||
(error) => {
|
|
||||||
clearTimeout(timer);
|
|
||||||
reject(error);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user