test(web): api/index.jsx

This commit is contained in:
Paul Armstrong 2021-02-11 07:13:21 -08:00 committed by Blake Blackshear
parent 53288d361c
commit 6d133ef724
5 changed files with 136 additions and 7 deletions

View File

@ -12,3 +12,11 @@ Object.defineProperty(window, 'matchMedia', {
dispatchEvent: jest.fn(), dispatchEvent: jest.fn(),
}), }),
}); });
window.fetch = () => Promise.resolve();
beforeEach(() => {
jest.spyOn(window, 'fetch').mockImplementation(async (url, opts = {}) => {
throw new Error(`Unexpected fetch to ${url}, ${JSON.stringify(opts)}`);
});
});

View File

@ -0,0 +1 @@
export const baseUrl = 'http://base-url.local:5000';

View File

@ -0,0 +1,116 @@
import { h } from 'preact';
import { ApiProvider, useFetch, useApiHost } from '..';
import { render, screen } from '@testing-library/preact';
jest.mock('../baseUrl');
describe('useApiHost', () => {
test('is set from the baseUrl', async () => {
function Test() {
const apiHost = useApiHost();
return <div>{apiHost}</div>;
}
render(
<ApiProvider>
<Test />
</ApiProvider>
);
expect(screen.queryByText('http://base-url.local:5000')).toBeInTheDocument();
});
});
describe('useFetch', () => {
function Test() {
const { data, status } = useFetch('/api/tacos');
return (
<div>
<span>{data ? data.returnData : ''}</span>
<span>{status}</span>
</div>
);
}
test('loads data', async () => {
const fetchSpy = jest.spyOn(window, 'fetch').mockImplementation(
(url) =>
new Promise((resolve) => {
setTimeout(() => {
resolve({ ok: true, json: () => Promise.resolve({ returnData: 'yep' }) });
}, 1);
})
);
render(
<ApiProvider>
<Test />
</ApiProvider>
);
expect(screen.queryByText('loading')).toBeInTheDocument();
expect(screen.queryByText('yep')).not.toBeInTheDocument();
jest.runAllTimers();
await screen.findByText('loaded');
expect(fetchSpy).toHaveBeenCalledWith('http://base-url.local:5000/api/tacos');
expect(screen.queryByText('loaded')).toBeInTheDocument();
expect(screen.queryByText('yep')).toBeInTheDocument();
});
test('sets error if response is not okay', async () => {
jest.spyOn(window, 'fetch').mockImplementation(
(url) =>
new Promise((resolve) => {
setTimeout(() => {
resolve({ ok: false });
}, 1);
})
);
render(
<ApiProvider>
<Test />
</ApiProvider>
);
expect(screen.queryByText('loading')).toBeInTheDocument();
jest.runAllTimers();
await screen.findByText('error');
});
test('does not re-fetch if the query has already been made', async () => {
const fetchSpy = jest.spyOn(window, 'fetch').mockImplementation(
(url) =>
new Promise((resolve) => {
setTimeout(() => {
resolve({ ok: true, json: () => Promise.resolve({ returnData: 'yep' }) });
}, 1);
})
);
const { rerender } = render(
<ApiProvider>
<Test key={0} />
</ApiProvider>
);
expect(screen.queryByText('loading')).toBeInTheDocument();
expect(screen.queryByText('yep')).not.toBeInTheDocument();
jest.runAllTimers();
await screen.findByText('loaded');
expect(fetchSpy).toHaveBeenCalledWith('http://base-url.local:5000/api/tacos');
rerender(
<ApiProvider>
<Test key={1} />
</ApiProvider>
);
expect(screen.queryByText('loaded')).toBeInTheDocument();
expect(screen.queryByText('yep')).toBeInTheDocument();
jest.runAllTimers();
expect(fetchSpy).toHaveBeenCalledTimes(1);
});
});

1
web/src/api/baseUrl.js Normal file
View File

@ -0,0 +1 @@
export const baseUrl = import.meta.env.SNOWPACK_PUBLIC_API_HOST || window.baseUrl || '';

View File

@ -1,9 +1,8 @@
import { baseUrl } from './baseUrl';
import { h, createContext } from 'preact'; import { h, createContext } from 'preact';
import produce from 'immer'; import produce from 'immer';
import { useContext, useEffect, useReducer } from 'preact/hooks'; import { useContext, useEffect, useReducer } from 'preact/hooks';
export const ApiHost = createContext(import.meta.env.SNOWPACK_PUBLIC_API_HOST || window.baseUrl || '');
export const FetchStatus = { export const FetchStatus = {
NONE: 'none', NONE: 'none',
LOADING: 'loading', LOADING: 'loading',
@ -12,11 +11,11 @@ export const FetchStatus = {
}; };
const initialState = Object.freeze({ const initialState = Object.freeze({
host: import.meta.env.SNOWPACK_PUBLIC_API_HOST || window.baseUrl || '', host: baseUrl,
queries: {}, queries: {},
}); });
export const Api = createContext(initialState);
export default Api; const Api = createContext(initialState);
function reducer(state, { type, payload, meta }) { function reducer(state, { type, payload, meta }) {
switch (type) { switch (type) {
@ -65,8 +64,12 @@ export function useFetch(url, fetchId) {
async function fetchData() { async function fetchData() {
await dispatch({ type: 'REQUEST', payload: { url, fetchId } }); await dispatch({ type: 'REQUEST', payload: { url, fetchId } });
const response = await fetch(`${state.host}${url}`); const response = await fetch(`${state.host}${url}`);
const data = await response.json(); try {
await dispatch({ type: 'RESPONSE', payload: { url, ok: response.ok, data, fetchId } }); const data = await response.json();
await dispatch({ type: 'RESPONSE', payload: { url, ok: response.ok, data, fetchId } });
} catch (e) {
await dispatch({ type: 'RESPONSE', payload: { url, ok: false, data: null, fetchId } });
}
} }
fetchData(); fetchData();