Skip to content
This repository was archived by the owner on Feb 14, 2023. It is now read-only.

Commit 3feb41f

Browse files
committed
resolves #150
1 parent 83255c4 commit 3feb41f

File tree

7 files changed

+2307
-1984
lines changed

7 files changed

+2307
-1984
lines changed

jest.config.js

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,14 @@
11
module.exports = {
22
cacheDirectory: './jest/cache',
33
collectCoverage: false,
4-
collectCoverageFrom: [
5-
'src/**/*',
6-
],
4+
collectCoverageFrom: ['src/**/*'],
75
coverageDirectory: './jest/coverage',
86
preset: 'ts-jest',
97
resetMocks: true,
108
resetModules: true,
119
restoreMocks: true,
12-
roots: [ "<rootDir>/tests" ],
13-
setupFilesAfterEnv: [
14-
'@testing-library/jest-dom/extend-expect',
15-
'@testing-library/react/cleanup-after-each',
16-
],
10+
roots: ['<rootDir>/tests'],
11+
setupFilesAfterEnv: ['@testing-library/jest-dom/extend-expect'],
1712
testRegex: '/tests/.+\\.test\\.tsx?$',
1813
verbose: false,
1914
};

package.json

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "reactn",
3-
"version": "2.2.5",
3+
"version": "2.2.6",
44
"author": "Charles Stover <reactn@charlesstover.com>",
55
"description": "React, but with built-in global state management.",
66
"homepage": "https://github.com/CharlesStover/reactn#readme",
@@ -32,25 +32,25 @@
3232
"use-force-update": "^1.0.5"
3333
},
3434
"devDependencies": {
35-
"@babel/core": "^7.4.0",
36-
"@babel/preset-env": "^7.4.2",
37-
"@babel/preset-typescript": "^7.3.3",
38-
"@testing-library/jest-dom": "^4.0.0",
39-
"@testing-library/react": "8.0.5",
40-
"@types/jest": "^24.0.11",
41-
"@types/node": "^12.6.2",
42-
"@types/react": "^16.7.13",
43-
"@typescript-eslint/eslint-plugin": "^1.4.2",
44-
"@typescript-eslint/parser": "^1.4.2",
45-
"babel-jest": "^24.6.0",
46-
"eslint": "^5.15.2",
47-
"eslint-plugin-react": "^7.12.4",
48-
"jest": "^24.6.0",
49-
"react": "~16.8",
50-
"react-dom": "~16.8",
51-
"ts-jest": "^24.0.1",
52-
"ts-node": "^8.0.2",
53-
"typescript": "^3.3.1"
35+
"@babel/core": "^7.8.4",
36+
"@babel/preset-env": "^7.8.4",
37+
"@babel/preset-typescript": "^7.8.3",
38+
"@testing-library/jest-dom": "^5.1.1",
39+
"@testing-library/react": "^9.4.0",
40+
"@types/jest": "^25.1.2",
41+
"@types/node": "^13.7.1",
42+
"@types/react": "^16.9.20",
43+
"@typescript-eslint/eslint-plugin": "^2.20.0",
44+
"@typescript-eslint/parser": "^2.20.0",
45+
"babel-jest": "^25.1.0",
46+
"eslint": "^6.8.0",
47+
"eslint-plugin-react": "^7.18.3",
48+
"jest": "^25.1.0",
49+
"react": "^16.12.0",
50+
"react-dom": "^16.12.0",
51+
"ts-jest": "^25.2.0",
52+
"ts-node": "^8.6.2",
53+
"typescript": "^3.7.5"
5454
},
5555
"peerDependencies": {
5656
"@types/react": "^16.8.0",

src/use-global.ts

Lines changed: 23 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,8 @@ import GlobalStateManager from './global-state-manager';
1010
import setGlobal from './set-global';
1111
import REACT_HOOKS_ERROR from './utils/react-hooks-error';
1212

13-
14-
1513
type VoidFunction = () => void;
1614

17-
18-
1915
// useGlobal()
2016
export default function _useGlobal<G extends {} = State>(
2117
overrideGlobalStateManager: GlobalStateManager<G> | null,
@@ -24,7 +20,7 @@ export default function _useGlobal<G extends {} = State>(
2420
// useGlobal('property')
2521
export default function _useGlobal<
2622
G extends {} = State,
27-
Property extends keyof G = keyof G,
23+
Property extends keyof G = keyof G
2824
>(
2925
overrideGlobalStateManager: GlobalStateManager<G> | null,
3026
property: Property,
@@ -33,12 +29,11 @@ export default function _useGlobal<
3329
// Implementation
3430
export default function _useGlobal<
3531
G extends {} = State,
36-
Property extends keyof G = keyof G,
32+
Property extends keyof G = keyof G
3733
>(
3834
overrideGlobalStateManager: GlobalStateManager<G> | null,
3935
property?: Property,
4036
): UseGlobal<G, Property> {
41-
4237
// Require hooks.
4338
if (!useContext) {
4439
throw REACT_HOOKS_ERROR;
@@ -56,47 +51,39 @@ export default function _useGlobal<
5651

5752
// Return the entire global state.
5853
if (typeof property === 'undefined') {
59-
60-
6154
// If this component ever updates or unmounts, remove the force update
6255
// listener.
63-
useEffect((): VoidFunction => removeForceUpdateListener);
56+
useEffect((): VoidFunction => removeForceUpdateListener, []);
6457

6558
const globalStateSetter = useCallback(
6659
(
6760
newGlobalState: NewGlobalState<G>,
6861
callback: Callback<G> | null = null,
69-
): Promise<G> =>
70-
setGlobal(globalStateManager, newGlobalState, callback),
62+
): Promise<G> => setGlobal(globalStateManager, newGlobalState, callback),
7163
[],
7264
);
7365

74-
return [
75-
globalStateManager.spyState(forceUpdate),
76-
globalStateSetter,
77-
];
66+
return [globalStateManager.spyState(forceUpdate), globalStateSetter];
7867
}
7968

80-
useEffect((): VoidFunction => {
81-
82-
// We add the listener as an effect, so that there are not race conditions
83-
// between subscribing and unsubscribing.
84-
// Subscribing outside of useEffect via `spyState()[property]` will
85-
// cause the re-render subscription to occur before the unmount
86-
// unsubscription occurs. As a result, the unmount unsubscription
87-
// removes the re-rendered subscription.
88-
globalStateManager.addPropertyListener(property, forceUpdate);
89-
90-
// If this component ever updates or unmounts, remove the force update
91-
// listener.
92-
return removeForceUpdateListener;
93-
});
69+
useEffect(
70+
(): VoidFunction => {
71+
// We add the listener as an effect, so that there are not race conditions
72+
// between subscribing and unsubscribing.
73+
// Subscribing outside of useEffect via `spyState()[property]` will
74+
// cause the re-render subscription to occur before the unmount
75+
// unsubscription occurs. As a result, the unmount unsubscription
76+
// removes the re-rendered subscription.
77+
globalStateManager.addPropertyListener(property, forceUpdate);
78+
79+
// If this component ever updates or unmounts, remove the force update
80+
// listener.
81+
return removeForceUpdateListener;
82+
},
83+
);
9484

9585
const globalPropertySetter = useCallback(
96-
(
97-
value: G[Property],
98-
callback: Callback<G> | null = null,
99-
): Promise<G> => {
86+
(value: G[Property], callback: Callback<G> | null = null): Promise<G> => {
10087
const newGlobalState: Partial<G> = Object.create(null);
10188
newGlobalState[property] = value;
10289
return setGlobal(globalStateManager, newGlobalState, callback);
@@ -105,8 +92,5 @@ export default function _useGlobal<
10592
);
10693

10794
// Return both getter and setter.
108-
return [
109-
globalStateManager.state[property],
110-
globalPropertySetter,
111-
];
112-
};
95+
return [globalStateManager.state[property], globalPropertySetter];
96+
}

tests/index.js

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,14 @@ const supportedVersions = [
1616
'~0.12',
1717
'~0.13',
1818
*/
19-
'~0.14', // ReactDOM
20-
'~15.0',
21-
'~15.3', // PureComponent
22-
'~16.0',
23-
'~16.3', // Context
24-
'~16.8', // Hooks
19+
// '~0.14', // ReactDOM
20+
// '~15.0',
21+
// '~15.3', // PureComponent
22+
// '~16.0',
23+
// '~16.3', // Context
24+
// '~16.8', // Hooks
2525
// '~16.9', // async act
26-
// 'latest',
26+
'latest',
2727
];
2828

2929
const header = str => {
@@ -32,18 +32,19 @@ const header = str => {
3232
console.log();
3333
const bar = '-'.repeat(str.length);
3434
console.log(`/-${bar}-\\`);
35-
console.log(`| ${str} |`)
35+
console.log(`| ${str} |`);
3636
console.log(`\\-${bar}-/`);
3737
};
3838

3939
for (const supportedVersion of supportedVersions) {
40-
4140
header(`Installing react@${supportedVersion}.`);
4241
execSync(
4342
`yarn add react@${supportedVersion} react-dom@${supportedVersion} ` +
44-
'--dev --no-lockfile', {
43+
'--dev --no-lockfile',
44+
{
4545
stdio: 'inherit',
46-
});
46+
},
47+
);
4748

4849
const { version } = require('react/package.json');
4950
delete require.cache[require.resolve('react/package.json')];
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { act, getByTestId, render, RenderResult } from '@testing-library/react';
2+
import * as React from 'react';
3+
import GlobalStateManager from '../../src/global-state-manager';
4+
import setGlobal from '../../src/set-global';
5+
import useGlobal from '../../src/use-global';
6+
import { G, INITIAL_REDUCERS, INITIAL_STATE, R } from '../utils/initial';
7+
import { hasAsyncAct, hasHooks } from '../utils/react-version';
8+
9+
const TEST_ID = 'test-id';
10+
11+
const createTestComponent = (
12+
globalStateManager: GlobalStateManager<G, R>,
13+
): React.ComponentType<React.PropsWithChildren<{}>> => {
14+
return function TestComponent(): JSX.Element {
15+
const [global] = useGlobal(globalStateManager);
16+
return <span data-testid={TEST_ID} id={global.z} />;
17+
};
18+
};
19+
20+
describe('useGlobal() component', (): void => {
21+
if (!hasAsyncAct || !hasHooks) {
22+
return;
23+
}
24+
25+
let globalStateManager: GlobalStateManager<G, R>;
26+
let TestComponent: React.ComponentType<React.PropsWithChildren<{}>>;
27+
beforeEach((): void => {
28+
globalStateManager = new GlobalStateManager(
29+
INITIAL_STATE,
30+
INITIAL_REDUCERS,
31+
);
32+
TestComponent = createTestComponent(globalStateManager);
33+
});
34+
35+
it('should re-render when the global state changes', async (): Promise<
36+
void
37+
> => {
38+
const NEW_STATE_1 = 'new-string-1';
39+
const NEW_STATE_2 = 'new-string-2';
40+
const { container }: RenderResult = render(<TestComponent />);
41+
const getNode = (): any =>
42+
getByTestId(container, TEST_ID) as HTMLSpanElement;
43+
expect(getNode().getAttribute('id')).toBe(INITIAL_STATE.z);
44+
expect(getNode().getAttribute('id')).not.toBe(NEW_STATE_1);
45+
46+
await act(
47+
async (): Promise<void> => {
48+
await setGlobal(globalStateManager, { z: NEW_STATE_1 });
49+
},
50+
);
51+
52+
expect(getNode().getAttribute('id')).toBe(NEW_STATE_1);
53+
expect(getNode().getAttribute('id')).not.toBe(NEW_STATE_2);
54+
55+
await act(
56+
async (): Promise<void> => {
57+
await setGlobal(globalStateManager, { z: NEW_STATE_2 });
58+
},
59+
);
60+
61+
expect(getNode().getAttribute('id')).toBe(NEW_STATE_2);
62+
});
63+
});

tests/utils/react-version.ts

Lines changed: 9 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,15 @@
11
import { version } from 'react/package.json';
22

3-
type SemVer = [ number, number, number ];
3+
type SemVer = [number, number, number];
44

5-
const semVer: SemVer = version.split('.').map(
6-
(i: string): number => parseInt(i, 10),
7-
) as SemVer;
5+
const semVer: SemVer = version
6+
.split('.')
7+
.map((i: string): number => parseInt(i, 10)) as SemVer;
88

9-
const [ major, minor ] = semVer;
9+
const [major, minor] = semVer;
1010

11-
export const hasContext = (
12-
major > 16 ||
13-
(
14-
major === 16 &&
15-
minor >= 3
16-
)
17-
);
11+
export const hasAsyncAct = major > 16 || (major === 16 && minor >= 9);
1812

19-
export const hasHooks = (
20-
major > 16 ||
21-
(
22-
major === 16 &&
23-
minor >= 8
24-
)
25-
);
13+
export const hasContext = major > 16 || (major === 16 && minor >= 3);
14+
15+
export const hasHooks = major > 16 || (major === 16 && minor >= 8);

0 commit comments

Comments
 (0)