Skip to content

Commit 8acff45

Browse files
authored
Create API for array Slots (#18)
1 parent 421e23f commit 8acff45

31 files changed

+889
-367
lines changed

.eslintrc.js

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
module.exports = {
2+
parser: '@typescript-eslint/parser',
3+
parserOptions: {
4+
sourceType: 'module',
5+
},
6+
plugins: ['react-hooks'],
7+
extends: ['plugin:react/recommended'],
8+
rules: {
9+
'react-hooks/rules-of-hooks': 'error',
10+
'react-hooks/exhaustive-deps': 'warn',
11+
},
12+
settings: {
13+
react: {
14+
version: 'detect',
15+
},
16+
},
17+
};

TODO.md

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
- [x] Rename Slot props to `slotProps`
22
- [x] Test new plug API in react-cosmos with yarn link
33
- [x] Test for context memoization in plugs
4-
- [ ] Create useArraySlot
5-
- [ ] Maybe: createPluginStore
4+
- [x] Create ArraySlot component
65

76
---
87

jest.config.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
module.exports = {
22
preset: 'ts-jest',
3-
testEnvironment: 'node'
3+
testEnvironment: 'node',
4+
collectCoverageFrom: ['**/src/**/*.{ts,tsx}', '!**/testHelpers/**'],
45
};

package.json

+7-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "react-plugin",
3-
"version": "2.0.0-alpha.2",
3+
"version": "2.0.0-alpha.3",
44
"description": "API for composable 3rd party React plugins",
55
"repository": "https://github.com/skidding/react-plugin.git",
66
"author": "Ovidiu Cherecheș <[email protected]>",
@@ -11,7 +11,7 @@
1111
"dist"
1212
],
1313
"scripts": {
14-
"lint": "tslint --project tsconfig.json",
14+
"lint": "eslint 'src/**/*.{ts,tsx}'",
1515
"test": "jest --coverage",
1616
"test:watch": "jest --watch",
1717
"build": "rm -rf dist && yarn lint && yarn tsc -b tsconfig.build.json"
@@ -20,21 +20,23 @@
2020
"@skidding/linked-list": "^1.0.2",
2121
"array-find-index": "^1.0.2",
2222
"lodash": "^4.17.11",
23-
"ui-plugin": "^2.0.0-alpha.2"
23+
"ui-plugin": "^2.0.0-alpha.3"
2424
},
2525
"devDependencies": {
2626
"@skidding/async-retry": "^2.0.0",
2727
"@types/jest": "^24.0.12",
2828
"@types/lodash": "^4.14.123",
2929
"@types/react": "^16.8.17",
3030
"@types/react-test-renderer": "^16.8.1",
31+
"@typescript-eslint/parser": "^1.7.0",
32+
"eslint": "^5.16.0",
33+
"eslint-plugin-react": "^7.13.0",
34+
"eslint-plugin-react-hooks": "^1.6.0",
3135
"jest": "^24.8.0",
3236
"prettier": "^1.17.0",
3337
"react": "^16.8.6",
3438
"react-test-renderer": "^16.8.6",
3539
"ts-jest": "^24.0.2",
36-
"tslint": "^5.16.0",
37-
"tslint-config-prettier": "^1.18.0",
3840
"typescript": "^3.4.5"
3941
}
4042
}

src/ArraySlot/__tests__/index.tsx

+96
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import retry from '@skidding/async-retry';
2+
import * as React from 'react';
3+
import { ReactTestRenderer, act } from 'react-test-renderer';
4+
import { createRenderer } from '../../testHelpers';
5+
import { loadPlugins, createPlugin, resetPlugins, ArraySlot } from '../..';
6+
7+
afterEach(resetPlugins);
8+
9+
function AgeComponent({ age }: { age: number }) {
10+
// TS isn't happy with function components returning strings
11+
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/20544
12+
return <>{`${age}y old`}</>;
13+
}
14+
15+
interface Test {
16+
name: 'test';
17+
state: { age: number };
18+
}
19+
20+
it('renders each plug', async () => {
21+
const { plug, register } = createPlugin<Test>({
22+
name: 'test',
23+
initialState: { age: 28 },
24+
});
25+
plug('root', () => <AgeComponent age={29} />);
26+
plug('root', () => <AgeComponent age={30} />);
27+
plug('root', () => <AgeComponent age={31} />);
28+
register();
29+
30+
loadPlugins();
31+
const renderer = createRenderer(<ArraySlot name="root" />);
32+
expect(renderer.toJSON()).toMatchInlineSnapshot(`
33+
Array [
34+
"29y old",
35+
"30y old",
36+
"31y old",
37+
]
38+
`);
39+
});
40+
41+
it('passes down pluginContext prop', () => {
42+
const { plug, register } = createPlugin<Test>({
43+
name: 'test',
44+
initialState: { age: 29 },
45+
});
46+
plug('root', ({ pluginContext }) => (
47+
<AgeComponent age={pluginContext.getState().age} />
48+
));
49+
register();
50+
51+
loadPlugins();
52+
const renderer = createRenderer(<ArraySlot name="root" />);
53+
expect(renderer.toJSON()).toMatchInlineSnapshot(`"29y old"`);
54+
});
55+
56+
it('passes down slot props', () => {
57+
const { plug, register } = createPlugin<Test>({
58+
name: 'test',
59+
initialState: { age: 28 },
60+
});
61+
plug<{ age: number }>('root', ({ slotProps }) => <AgeComponent age={slotProps.age} />);
62+
register();
63+
64+
loadPlugins();
65+
const renderer = createRenderer(<ArraySlot name="root" slotProps={{ age: 29 }} />);
66+
expect(renderer.toJSON()).toMatchInlineSnapshot(`"29y old"`);
67+
});
68+
69+
it('updates plug on plugin state change', async () => {
70+
const { onLoad, plug, register } = createPlugin<Test>({
71+
name: 'test',
72+
initialState: { age: 29 },
73+
});
74+
plug('root', ({ pluginContext }) => (
75+
<AgeComponent age={pluginContext.getState().age} />
76+
));
77+
onLoad(({ setState }) => {
78+
setTimeout(() => {
79+
act(() => {
80+
setState({ age: 30 });
81+
});
82+
});
83+
});
84+
register();
85+
86+
loadPlugins();
87+
const renderer = createRenderer(<ArraySlot name="root" />);
88+
await retry(() => {
89+
// expect().toMatchInlineSnapshot can't be placed inside async retry()
90+
expect(getAgeProp(renderer)).toEqual(30);
91+
});
92+
});
93+
94+
function getAgeProp(renderer: ReactTestRenderer) {
95+
return renderer.root.findByType(AgeComponent).props.age;
96+
}

src/ArraySlot/index.tsx

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import * as React from 'react';
2+
import { useSlotPlugs } from '../shared/useSlotPlugs';
3+
import { PlugConnect } from '../shared/PlugConnect';
4+
5+
// Possible future features: Sorting and custom plug decoration
6+
type Props = {
7+
name: string;
8+
slotProps?: object;
9+
};
10+
11+
export function ArraySlot({ name, slotProps = {} }: Props) {
12+
return (
13+
// TS isn't happy with function components returning non-JSX.Elements
14+
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/20544
15+
<>
16+
{useSlotPlugs(name).map(plug => (
17+
<PlugConnect
18+
key={plug.id}
19+
pluginName={plug.pluginName}
20+
component={plug.component}
21+
slotProps={slotProps}
22+
/>
23+
))}
24+
</>
25+
);
26+
}

src/PluginsConsumer.tsx

-63
This file was deleted.

src/__tests__/consumer.tsx renamed to src/PluginsConsumer/__tests__/index.tsx

+10-7
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import * as React from 'react';
2-
import { create } from 'react-test-renderer';
2+
import { act } from 'react-test-renderer';
3+
import { createRenderer } from '../../testHelpers';
34
import {
45
enablePlugin,
56
loadPlugins,
67
PluginsConsumer,
78
createPlugin,
89
resetPlugins,
9-
} from '..';
10+
} from '../..';
1011

1112
afterEach(resetPlugins);
1213

@@ -19,7 +20,7 @@ it('calls the consumer render fn with plugin list', () => {
1920
createPlugin({ name: 'Michael Palin' }).register();
2021
loadPlugins();
2122

22-
const renderer = create(<EnabledPluginNames />);
23+
const renderer = createRenderer(<EnabledPluginNames />);
2324
expect(renderer.toJSON()).toMatchInlineSnapshot(
2425
`"Graham Chapman, John Cleese, Terry Gilliam, Eric Idle, Terry Jones, Michael Palin"`,
2526
);
@@ -36,7 +37,7 @@ it('calls the consumer render fn with enabled plugin list', () => {
3637
enablePlugin('Michael Palin', false);
3738
loadPlugins();
3839

39-
const renderer = create(<EnabledPluginNames />);
40+
const renderer = createRenderer(<EnabledPluginNames />);
4041
expect(renderer.toJSON()).toMatchInlineSnapshot(
4142
`"Graham Chapman, John Cleese, Terry Gilliam, Eric Idle"`,
4243
);
@@ -50,11 +51,13 @@ it('calls the consumer render fn with enabled plugin list', () => {
5051
createPlugin({ name: 'Terry Jones' }).register();
5152
createPlugin({ name: 'Michael Palin' }).register();
5253

53-
const renderer = create(<EnabledPluginNames />);
54+
const renderer = createRenderer(<EnabledPluginNames />);
5455
loadPlugins();
5556

56-
enablePlugin('Terry Jones', false);
57-
enablePlugin('Michael Palin', false);
57+
act(() => {
58+
enablePlugin('Terry Jones', false);
59+
enablePlugin('Michael Palin', false);
60+
});
5861

5962
expect(renderer.toJSON()).toMatchInlineSnapshot(
6063
`"Graham Chapman, John Cleese, Terry Gilliam, Eric Idle"`,

src/__tests__/consumerMethods.tsx renamed to src/PluginsConsumer/__tests__/methods.tsx

+11-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as React from 'react';
2-
import { create } from 'react-test-renderer';
3-
import { loadPlugins, PluginsConsumer, createPlugin, resetPlugins } from '..';
2+
import { act } from 'react-test-renderer';
3+
import { createRenderer } from '../../testHelpers';
4+
import { loadPlugins, PluginsConsumer, createPlugin, resetPlugins } from '../..';
45

56
afterEach(resetPlugins);
67

@@ -9,7 +10,7 @@ it('updates plugins from consumer methods', () => {
910
createPlugin({ name: 'Wiz Khalifa' }).register();
1011
loadPlugins();
1112

12-
const renderer = create(<PluginList />);
13+
const renderer = createRenderer(<PluginList />);
1314
expect(renderer.toJSON()).toMatchInlineSnapshot(`
1415
Array [
1516
<p
@@ -27,8 +28,10 @@ Array [
2728

2829
// Disable both
2930
const [snoop, wiz] = renderer.root.findAllByType('p');
30-
snoop.props.onClick();
31-
wiz.props.onClick();
31+
act(() => {
32+
snoop.props.onClick();
33+
wiz.props.onClick();
34+
});
3235

3336
expect(renderer.toJSON()).toMatchInlineSnapshot(`
3437
Array [
@@ -46,7 +49,9 @@ Array [
4649
`);
4750

4851
// Bring back Wiz
49-
wiz.props.onClick();
52+
act(() => {
53+
wiz.props.onClick();
54+
});
5055

5156
expect(renderer.toJSON()).toMatchInlineSnapshot(`
5257
Array [

0 commit comments

Comments
 (0)