Skip to content

Commit b5ca992

Browse files
committed
test: added tests related to preloading data and fetch-as-you-render pattern
1 parent 971861d commit b5ca992

5 files changed

Lines changed: 233 additions & 4 deletions

File tree

packages/react-isomorphic-data/jest.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const config = {
1818
{
1919
displayName: 'Core',
2020
testMatch: [
21+
'<rootDir>/src/__tests__/*.test.{ts,tsx}',
2122
'<rootDir>/src/common/**/__tests__/*.test.{ts,tsx}',
2223
'<rootDir>/src/hoc/**/__tests__/*.test.{ts,tsx}',
2324
'<rootDir>/src/hooks/**/__tests__/*.test.{ts,tsx}',

packages/react-isomorphic-data/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
"homepage": "https://github.com/jackyef/react-isomorphic-data#readme",
3838
"devDependencies": {
3939
"@rollup/plugin-replace": "^2.2.0",
40+
"@testing-library/react": "^9.4.0",
4041
"@types/jest": "^25.1.0",
4142
"@types/lodash.camelcase": "^4.3.6",
4243
"@types/node": "^12.7.9",
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import * as React from 'react';
2+
import { FetchMock } from 'jest-fetch-mock';
3+
import { render, wait } from '@testing-library/react';
4+
5+
import { DataProvider, preloadData, createDataClient } from '../common';
6+
import { useDataClient } from '../hooks';
7+
8+
const fetchMock = fetch as FetchMock;
9+
10+
describe('render-as-you-fetch tests', () => {
11+
const onError = (e: Event) => {
12+
e.preventDefault();
13+
};
14+
15+
beforeEach(() => {
16+
// to suppress error logs from error boundaries
17+
// https://github.com/facebook/react/issues/11098#issuecomment-412682721
18+
window.addEventListener('error', onError);
19+
fetchMock.resetMocks();
20+
jest.useFakeTimers();
21+
});
22+
afterEach(() => {
23+
window.removeEventListener('error', onError);
24+
jest.useRealTimers();
25+
});
26+
27+
it('Should fallback to Suspense boundary properly', async () => {
28+
fetchMock.mockResponse(JSON.stringify({ message: 'Hello world!' }));
29+
const client = createDataClient();
30+
const url = 'http://localhost:3000/fetch-as-you-render';
31+
32+
const Component = (props: any) => {
33+
const data = props.resource.read();
34+
35+
return <div>{data.message}</div>;
36+
};
37+
const Wrapper = () => {
38+
const client = useDataClient();
39+
const resource = preloadData(client, url);
40+
41+
return (
42+
<React.Suspense fallback={<div>Resource is not ready yet...</div>}>
43+
<Component resource={resource} />
44+
</React.Suspense>
45+
);
46+
};
47+
48+
const App = (
49+
<DataProvider client={client}>
50+
<Wrapper />
51+
</DataProvider>
52+
);
53+
54+
const { findByText } = render(App);
55+
56+
const suspenseFallback = await findByText('Resource is not ready yet...');
57+
58+
expect(suspenseFallback).toBeDefined();
59+
60+
await wait();
61+
62+
const actualComponent = await findByText('Hello world!');
63+
64+
expect(actualComponent).toBeDefined();
65+
});
66+
67+
it('Should fallback to Suspense and Error boundaries properly', async () => {
68+
const fabricatedError = new Error('Fabricated error');
69+
fetchMock.mockReject(fabricatedError);
70+
const client = createDataClient();
71+
const url = 'http://localhost:3000/fetch-as-you-render/2';
72+
73+
class ErrorBoundary extends React.Component {
74+
state = { error: false, errorObject: null };
75+
76+
static getDerivedStateFromError(error: Error) {
77+
return { error: true, errorObject: error };
78+
}
79+
80+
render() {
81+
return !this.state.error ? (
82+
this.props.children
83+
) : (
84+
<div>Error happened!</div>
85+
);
86+
}
87+
}
88+
89+
const Component = (props: any) => {
90+
const data = props.resource.read();
91+
92+
return <div>{data.message}</div>;
93+
};
94+
const Wrapper = () => {
95+
const client = useDataClient();
96+
const resource = preloadData(client, url);
97+
98+
return (
99+
<React.Suspense fallback={<div>Resource is not ready yet...</div>}>
100+
<ErrorBoundary>
101+
<Component resource={resource} />
102+
</ErrorBoundary>
103+
</React.Suspense>
104+
);
105+
};
106+
107+
const App = (
108+
<DataProvider client={client}>
109+
<Wrapper />
110+
</DataProvider>
111+
);
112+
113+
const { findByText } = render(App);
114+
115+
const suspenseFallback = await findByText('Resource is not ready yet...');
116+
117+
expect(suspenseFallback).toBeDefined();
118+
119+
await wait();
120+
121+
const errorFallback = await findByText('Error happened!');
122+
123+
expect(errorFallback).toBeDefined();
124+
});
125+
});
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import { FetchMock } from 'jest-fetch-mock';
2+
import preloadData from '../preloadData';
3+
import { createDataClient } from '../Client';
4+
5+
const fetchMock = fetch as FetchMock;
6+
7+
describe('preloadData tests', () => {
8+
beforeEach(() => {
9+
fetchMock.resetMocks();
10+
jest.useFakeTimers();
11+
});
12+
afterEach(() => {
13+
jest.useRealTimers();
14+
});
15+
16+
it('Should throw a promise when data is not ready yet', async () => {
17+
fetchMock.mockResponse(JSON.stringify({ message: 'Hello world!' }));
18+
const client = createDataClient({ ssr: false });
19+
const url = 'http://localhost:3000/some-rest-api';
20+
21+
const resource = preloadData(client, url);
22+
23+
expect(resource.read).toThrow();
24+
25+
try {
26+
resource.read();
27+
} catch (promise) {
28+
await promise;
29+
30+
expect(resource.read()).toStrictEqual({ message: 'Hello world!' });
31+
}
32+
});
33+
34+
it('Should throw an error the fetch is rejected', async () => {
35+
const fabricatedError = new Error('Fabricated error');
36+
const client = createDataClient({ ssr: false });
37+
const url = 'http://localhost:3000/some-rest-api/2';
38+
39+
const resource = preloadData(client, url);
40+
41+
expect(resource.read).toThrow();
42+
43+
fetchMock.mockReject(fabricatedError);
44+
45+
try {
46+
resource.read();
47+
} catch (promise) {
48+
await promise;
49+
50+
expect(resource.read).toThrowError();
51+
}
52+
});
53+
54+
it('Should use data from cache if available', async () => {
55+
fetchMock.mockResponse(JSON.stringify({ message: 'Hello world!' }));
56+
const client = createDataClient({ ssr: false });
57+
const url = 'http://localhost:3000/some-rest-api/3';
58+
59+
const resource = preloadData(client, url);
60+
61+
expect(resource.read).toThrow();
62+
63+
try {
64+
resource.read();
65+
} catch (promise) {
66+
await promise;
67+
68+
expect(resource.read()).toStrictEqual({ message: 'Hello world!' });
69+
70+
const resource2 = preloadData(client, url);
71+
72+
expect(resource2.read()).toStrictEqual(resource.read());
73+
}
74+
});
75+
76+
it('Should not store data to cache when fetchPolicy is network-only', async () => {
77+
fetchMock.mockResponse(JSON.stringify({ message: 'Hello world!' }));
78+
const fabricatedError = new Error('Fabricated error');
79+
const client = createDataClient({ ssr: false });
80+
const url = 'http://localhost:3000/some-rest-api/4';
81+
82+
const resource = preloadData(client, url, {}, {}, {
83+
fetchPolicy: 'network-only',
84+
});
85+
86+
expect(resource.read).toThrow();
87+
88+
fetchMock.mockReject(fabricatedError);
89+
90+
try {
91+
resource.read();
92+
} catch (promise) {
93+
await promise;
94+
95+
expect(resource.read()).toStrictEqual({ message: 'Hello world!' });
96+
97+
const resource2 = preloadData(client, url);
98+
99+
expect(resource2.read).toThrowError();
100+
}
101+
});
102+
});

packages/react-isomorphic-data/src/common/preloadData.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,12 @@ const preloadData = (
4646
});
4747

4848
const read = () => {
49-
if (!fulfilled && promise instanceof Promise) {
50-
// let a SuspenseBoundary catch this
51-
throw promise;
52-
} else if (error !== null) {
49+
if (error !== null) {
5350
// let an ErrorBoundary catch this
5451
throw error;
52+
} else if (!fulfilled && promise instanceof Promise) {
53+
// let a SuspenseBoundary catch this
54+
throw promise;
5555
} else {
5656
return data;
5757
}

0 commit comments

Comments
 (0)