Skip to content

Commit 654c8d8

Browse files
committed
feat: add sorting
1 parent df89165 commit 654c8d8

File tree

6 files changed

+293
-30
lines changed

6 files changed

+293
-30
lines changed

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,20 @@ component), and they will be passed to the created element.
153153
}
154154
```
155155

156+
## GatewayDest props
157+
158+
| Prop Name | Description |
159+
| -- | -- |
160+
| `name` | name of this container. Renders all `Gateway` that have the matching `Gateway` `into` prop value. |
161+
| `component` | React component that renders. Defaults to `div`. |
162+
| `unmountOnEmpty` | if true, the DOM container removes itself if there are no child `Gateway` to be rendered. |
163+
164+
## Gateway props
165+
| Prop Name | Description |
166+
| -- | -- |
167+
| `into` | name of the `GatewayDest` to render itself into. |
168+
| `sort` | Number. Defines its sorting order among its sibling `Gateway` |
169+
156170
## How it works
157171

158172
React Gateway works very differently than most React "portals" in order to work

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"format": "jsfmt -w *.js src/ test/",
1010
"lint": "eslint src/ test/",
1111
"prepublish": "npm run build",
12-
"test": "jest"
12+
"test": "jest --verbose"
1313
},
1414
"keywords": [
1515
"react",
@@ -47,7 +47,8 @@
4747
"minimist": "^1.2.0",
4848
"react": "^16.12.0",
4949
"react-dom": "^16.12.0",
50-
"react-test-renderer": "^16.12.0"
50+
"react-test-renderer": "^16.12.0",
51+
"@testing-library/react": "12.1.5"
5152
},
5253
"peerDependencies": {
5354
"react": "^0.14.0 || ^15.0.0 || ^16.0.0"

src/Gateway.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import React, {
77
import PropTypes from 'prop-types';
88
import GatewayContext from './GatewayContext';
99

10-
function Gateway({ into, children }) {
10+
function Gateway({ into, children, sort }) {
1111
const [gatewayId, setGatewayId] = useState(null);
1212
const { addGateway, removeGateway, updateGateway } = useContext(GatewayContext);
1313

@@ -17,7 +17,7 @@ function Gateway({ into, children }) {
1717
gatewayId = gatewayIdParam;
1818
setGatewayId(gatewayIdParam);
1919
};
20-
addGateway(into, children, onSetGatewayId);
20+
addGateway(into, children, onSetGatewayId, sort);
2121
return () => {
2222
removeGateway(gatewayId);
2323
};

src/GatewayProvider.js

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,33 @@ import React, { useState, useEffect } from 'react';
22
import PropTypes from 'prop-types';
33
import GatewayContext from './GatewayContext';
44

5+
56
function GatewayProvider({ children }) {
67
const [_, setCurrentId] = useState(0);
78
const [gateways, setGateways] = useState({});
9+
const [gatewaySorts, setGatewaysSorts] = useState({});
810
const [containers, setContainer] = useState({});
911

10-
const addGateway = (destName, child, setGatewayId) => {
12+
const addGateway = (destName, child, setGatewayId, sort) => {
1113
verifyDestNameValid(destName);
14+
verifySortValid(sort);
1215

1316
setCurrentId(prevCurrentId => {
1417
const gatewayId = `${destName}##${prevCurrentId}`;
18+
setGatewayId(gatewayId);
19+
1520
setGateways(prevGateways => ({
1621
...prevGateways,
1722
[gatewayId]: child
1823
}));
19-
setGatewayId(gatewayId);
24+
25+
setGatewaysSorts(prevGatewaySorts => ({
26+
...prevGatewaySorts,
27+
[gatewayId]: sort
28+
}));
2029
return prevCurrentId + 1;
2130
});
31+
2232
};
2333

2434
const removeGateway = (gatewayId) => {
@@ -55,7 +65,16 @@ function GatewayProvider({ children }) {
5565
if (destName != name) {
5666
return null;
5767
}
58-
return gateways[gatewayId];
68+
return {
69+
sort: gatewaySorts[gatewayId],
70+
gateways: gateways[gatewayId],
71+
};
72+
})
73+
.sort((a, b) => {
74+
return getSortValue(a) - getSortValue(b);
75+
})
76+
.map(sortContext => {
77+
return sortContext && sortContext.gateways;
5978
});
6079
};
6180

@@ -97,4 +116,18 @@ function verifyDestNameValid(destName) {
97116
}
98117
}
99118

100-
export default GatewayProvider;
119+
function verifySortValid(sort) {
120+
if (sort && typeof sort !== 'number') {
121+
throw new Error('sort should be a number');
122+
}
123+
}
124+
125+
function getSortValue(sortContext){
126+
if(!sortContext|| sortContext.sort === undefined) {
127+
return Number.MAX_VALUE;
128+
}
129+
return sortContext.sort;
130+
};
131+
132+
133+
export default GatewayProvider;

test/gateway.spec.js

Lines changed: 98 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import React, { useContext, useState } from 'react';
22
import { create, act } from 'react-test-renderer';
3+
import {render, within, fireEvent, waitFor, screen} from '@testing-library/react';
4+
35

46
import {
57
Gateway,
@@ -8,35 +10,109 @@ import {
810
} from '../src/index.js';
911

1012

11-
const withExposedSetState = (Component) => {
12-
let setState;
13-
let setSetState = (setStateParam) => {
14-
setState = setStateParam;
15-
};
16-
return {
17-
setState: (state) => {
18-
setState(state);
19-
},
20-
Component: (props) => <Component setSetState={setSetState} {...props} />
21-
};
22-
};
23-
13+
import { withExposedSetState } from './utils.js';
2414

2515
describe('Gateway', function () {
26-
it('throws on invalid dest name', function () {
27-
const throwingRender = () => {
28-
act(() => {
29-
create(
16+
describe('verification', () => {
17+
beforeEach(() => {
18+
jest.spyOn(console, 'error').mockImplementation(() => {});
19+
});
20+
it('throws on invalid dest name', function () {
21+
const throwingRender = () => {
22+
act(() => {
23+
create(
24+
<GatewayProvider>
25+
<div>
26+
<GatewayDest name="invaid##" />
27+
<Gateway into="invaid##" />
28+
</div>
29+
</GatewayProvider>
30+
);
31+
});
32+
};
33+
expect(throwingRender).toThrow('dest names should not have ##');
34+
});
35+
});
36+
37+
describe('sorting', () => {
38+
beforeEach(() => {
39+
jest.spyOn(console, 'error').mockImplementation(() => {});
40+
});
41+
42+
it('should render in natural order', () => {
43+
render(
44+
<GatewayProvider>
45+
<div>
46+
<section>
47+
<Gateway into="foo"> First </Gateway>
48+
<Gateway into="foo"> Second </Gateway>
49+
</section>
50+
<GatewayDest name="foo" data-testid="mydiv"/>
51+
</div>
52+
</GatewayProvider>
53+
);
54+
const textContent = screen.getByTestId('mydiv').textContent;
55+
expect(textContent).toContain('First Second');
56+
});
57+
it('should render by sort prop', () => {
58+
let rendered = render(
59+
<GatewayProvider>
60+
<div>
61+
<section>
62+
<Gateway into="foo" sort={2}> Second </Gateway>
63+
<Gateway into="foo" sort={1}> First </Gateway>
64+
</section>
65+
<GatewayDest name="foo" data-testid="mydiv"/>
66+
</div>
67+
</GatewayProvider>
68+
);
69+
const textContent = screen.getByTestId('mydiv').textContent;
70+
expect(textContent).toContain('First Second');
71+
});
72+
it('should render undefined sort prop last', () => {
73+
render(
3074
<GatewayProvider>
3175
<div>
32-
<GatewayDest name="invaid##" />
33-
<Gateway into="invaid##" />
76+
<section>
77+
<Gateway into="foo"> Third </Gateway>
78+
<Gateway into="foo" sort={2}> Second </Gateway>
79+
<Gateway into="foo" sort={1}> First </Gateway>
80+
</section>
81+
<GatewayDest name="foo" data-testid="mydiv"/>
3482
</div>
3583
</GatewayProvider>
3684
);
37-
});
38-
};
39-
expect(throwingRender).toThrow('dest names should not have ##');
85+
const textContent = screen.getByTestId('mydiv').textContent;
86+
expect(textContent).toContain('First Second Third');
87+
});
88+
it('should render undefined sort prop in natural order', () => {
89+
render(
90+
<GatewayProvider>
91+
<div>
92+
<section>
93+
<Gateway into="foo"> Second </Gateway>
94+
<Gateway into="foo"> Third </Gateway>
95+
<Gateway into="foo" sort={3}> First </Gateway>
96+
</section>
97+
<GatewayDest name="foo" data-testid="mydiv"/>
98+
</div>
99+
</GatewayProvider>
100+
);
101+
const textContent = screen.getByTestId('mydiv').textContent;
102+
expect(textContent).toContain('First Second Third');
103+
});
104+
it('should validate sort to be a number only', () => {
105+
const throwingRender = () => {
106+
act(() => {
107+
create(
108+
<GatewayProvider>
109+
<Gateway into="invalid" sort={() => {}} />
110+
</GatewayProvider>
111+
);
112+
});
113+
};
114+
expect(throwingRender).toThrow('sort should be a number');
115+
});
40116
});
41117

42118
it('should render Gateway in GatewayDest', function () {

0 commit comments

Comments
 (0)