Skip to content

Commit eb969e1

Browse files
authored
test: More unit tests! (#38)
test: More unit tests!
2 parents 88bc041 + 95ff6d4 commit eb969e1

15 files changed

Lines changed: 514 additions & 18 deletions

File tree

README.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
[![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/)
2-
[![bundlewatch - bundlewatched](https://img.shields.io/badge/bundle-watched-blue.svg)](https://bundlewatch.io)
1+
[![minified-gzipped-size](https://img.shields.io/bundlephobia/minzip/react-isomorphic-data.svg)](https://bundlephobia.com/result?p=react-isomorphic-data) [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/) [![bundlewatch - bundlewatched](https://img.shields.io/badge/bundle-watched-blue.svg)](https://bundlewatch.io) [![codecov](https://codecov.io/gh/jackyef/react-isomorphic-data/branch/master/graph/badge.svg)](https://codecov.io/gh/jackyef/react-isomorphic-data)
32

43
# react-isomorphic-data monorepo
54
Easily fetch data from a GET request in your React components, with similar APIs to [react-apollo](https://github.com/apollographql/react-apollo/) 🎉

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616
"dev": "lerna run dev --scope=react-isomorphic-data --stream",
1717
"example": "lerna run start --scope=client --stream",
1818
"example:ssr": "lerna run start --scope=ssr --stream",
19-
"deploy": "HUSKY_SKIP_HOOKS=true lerna publish --conventional-commits --create-release github",
20-
"deploy:dryrun": "HUSKY_SKIP_HOOKS=true lerna version --no-commit-hooks --no-git-tag-version --no-push",
19+
"deploy": "yarn test && HUSKY_SKIP_HOOKS=true lerna publish --conventional-commits --create-release github",
20+
"deploy:dryrun": "yarn test && HUSKY_SKIP_HOOKS=true lerna version --no-commit-hooks --no-git-tag-version --no-push",
2121
"docgen:api": "yarn typedoc --out docusaurus/docs/api packages/react-isomorphic-data/src --tsconfig packages/react-isomorphic-data/tsconfig.json --theme docusaurus",
2222
"test": "lerna run test --scope=react-isomorphic-data --stream",
2323
"test:coverage": "lerna run test:coverage --scope=react-isomorphic-data --stream"

packages/react-isomorphic-data/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# react-isomorphic-data [![minified-gzipped-size](https://img.shields.io/bundlephobia/minzip/react-isomorphic-data.svg)](https://bundlephobia.com/result?p=react-isomorphic-data) [![bundlewatch - bundlewatched](https://img.shields.io/badge/bundle-watched-blue.svg)](https://bundlewatch.io)
1+
# react-isomorphic-data [![minified-gzipped-size](https://img.shields.io/bundlephobia/minzip/react-isomorphic-data.svg)](https://bundlephobia.com/result?p=react-isomorphic-data) [![bundlewatch - bundlewatched](https://img.shields.io/badge/bundle-watched-blue.svg)](https://bundlewatch.io) [![codecov](https://codecov.io/gh/jackyef/react-isomorphic-data/branch/master/graph/badge.svg)](https://codecov.io/gh/jackyef/react-isomorphic-data)
22
Easily fetch data in your React components, with similar APIs to [react-apollo](https://github.com/apollographql/react-apollo/) 🎉
33

44
You can use hooks or HOC, both are supported. 🎉
Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,41 @@
1+
const transformers = {
2+
'\\.(js|jsx|ts|tsx)$': '<rootDir>/babelJestTransformer.js',
3+
};
4+
15
const config = {
2-
transform: {
3-
'\\.(js|jsx|ts|tsx)$': '<rootDir>/babelJestTransformer.js',
4-
},
6+
transform: transformers,
57
testMatch: [
68
'<rootDir>/src/**/__tests__/**/*.(ts|js)?(x)',
79
'<rootDir>/src/**/?(*.)(spec|test).(ts|js)?(x)',
810
],
911
moduleFileExtensions: ['ts', 'tsx', 'js', 'json'],
10-
collectCoverageFrom: ['src/**/*.{js,jsx,ts,tsx}'],
11-
// setupFiles: ['<rootDir>/src/setupTests.ts'],
12+
collectCoverageFrom: [
13+
'src/**/*.{js,jsx,ts,tsx}',
14+
'!src/**/__tests__/*.{js,jsx,ts,tsx}'
15+
],
16+
setupFiles: ['<rootDir>/src/setupTests.ts'],
17+
projects: [
18+
{
19+
displayName: 'Core',
20+
testMatch: [
21+
'<rootDir>/src/__tests__/*.test.{ts,tsx}',
22+
'<rootDir>/src/common/**/__tests__/*.test.{ts,tsx}',
23+
'<rootDir>/src/hoc/**/__tests__/*.test.{ts,tsx}',
24+
'<rootDir>/src/hooks/**/__tests__/*.test.{ts,tsx}',
25+
'<rootDir>/src/utils/**/__tests__/*.test.{ts,tsx}',
26+
],
27+
setupFiles: ['<rootDir>/src/setupTests.ts'],
28+
transform: transformers,
29+
},
30+
{
31+
displayName: 'Server-side rendering utilities tests',
32+
testMatch: ['<rootDir>/src/ssr/**/__tests__/*.test.{ts,tsx}'],
33+
testEnvironment: 'node',
34+
transform: transformers,
35+
setupFiles: ['<rootDir>/src/setupTests.ts'],
36+
},
37+
],
1238
};
1339

1440
module.exports = config;
41+

packages/react-isomorphic-data/package.json

Lines changed: 2 additions & 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",
@@ -54,6 +55,7 @@
5455
"eslint-plugin-react-hooks": "^1.7.0",
5556
"hoist-non-react-statics": "^3.3.0",
5657
"jest": "^25.1.0",
58+
"jest-fetch-mock": "^3.0.1",
5759
"lodash.camelcase": "^4.3.0",
5860
"prettier": "^1.18.2",
5961
"react": "^16.8.0",
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
}

packages/react-isomorphic-data/src/hooks/utils/useBaseData.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ const useBaseData = <T, > (
154154

155155
const finalData = dataFromCache !== LoadingSymbol ? dataFromCache : null;
156156
const usedData = (!useTempData ? finalData : state.tempCache[fullUrl]) || null;
157-
const isLoading = dataFromCache === LoadingSymbol;
157+
const isLoading = dataFromCache === LoadingSymbol || (client.ssr && typeof dataFromCache === 'undefined' && !lazy);
158158

159159
return [
160160
memoizedFetchData,
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
require('jest-fetch-mock').enableMocks();

0 commit comments

Comments
 (0)