Skip to content

Commit

Permalink
feat: add sorting
Browse files Browse the repository at this point in the history
  • Loading branch information
chardskarth committed Jul 24, 2022
1 parent df89165 commit 654c8d8
Show file tree
Hide file tree
Showing 6 changed files with 293 additions and 30 deletions.
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,20 @@ component), and they will be passed to the created element.
}
```

## GatewayDest props

| Prop Name | Description |
| -- | -- |
| `name` | name of this container. Renders all `Gateway` that have the matching `Gateway` `into` prop value. |
| `component` | React component that renders. Defaults to `div`. |
| `unmountOnEmpty` | if true, the DOM container removes itself if there are no child `Gateway` to be rendered. |

## Gateway props
| Prop Name | Description |
| -- | -- |
| `into` | name of the `GatewayDest` to render itself into. |
| `sort` | Number. Defines its sorting order among its sibling `Gateway` |

## How it works

React Gateway works very differently than most React "portals" in order to work
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"format": "jsfmt -w *.js src/ test/",
"lint": "eslint src/ test/",
"prepublish": "npm run build",
"test": "jest"
"test": "jest --verbose"
},
"keywords": [
"react",
Expand Down Expand Up @@ -47,7 +47,8 @@
"minimist": "^1.2.0",
"react": "^16.12.0",
"react-dom": "^16.12.0",
"react-test-renderer": "^16.12.0"
"react-test-renderer": "^16.12.0",
"@testing-library/react": "12.1.5"
},
"peerDependencies": {
"react": "^0.14.0 || ^15.0.0 || ^16.0.0"
Expand Down
4 changes: 2 additions & 2 deletions src/Gateway.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import React, {
import PropTypes from 'prop-types';
import GatewayContext from './GatewayContext';

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

Expand All @@ -17,7 +17,7 @@ function Gateway({ into, children }) {
gatewayId = gatewayIdParam;
setGatewayId(gatewayIdParam);
};
addGateway(into, children, onSetGatewayId);
addGateway(into, children, onSetGatewayId, sort);
return () => {
removeGateway(gatewayId);
};
Expand Down
41 changes: 37 additions & 4 deletions src/GatewayProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,33 @@ import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import GatewayContext from './GatewayContext';


function GatewayProvider({ children }) {
const [_, setCurrentId] = useState(0);
const [gateways, setGateways] = useState({});
const [gatewaySorts, setGatewaysSorts] = useState({});
const [containers, setContainer] = useState({});

const addGateway = (destName, child, setGatewayId) => {
const addGateway = (destName, child, setGatewayId, sort) => {
verifyDestNameValid(destName);
verifySortValid(sort);

setCurrentId(prevCurrentId => {
const gatewayId = `${destName}##${prevCurrentId}`;
setGatewayId(gatewayId);

setGateways(prevGateways => ({
...prevGateways,
[gatewayId]: child
}));
setGatewayId(gatewayId);

setGatewaysSorts(prevGatewaySorts => ({
...prevGatewaySorts,
[gatewayId]: sort
}));
return prevCurrentId + 1;
});

};

const removeGateway = (gatewayId) => {
Expand Down Expand Up @@ -55,7 +65,16 @@ function GatewayProvider({ children }) {
if (destName != name) {
return null;
}
return gateways[gatewayId];
return {
sort: gatewaySorts[gatewayId],
gateways: gateways[gatewayId],
};
})
.sort((a, b) => {
return getSortValue(a) - getSortValue(b);
})
.map(sortContext => {
return sortContext && sortContext.gateways;
});
};

Expand Down Expand Up @@ -97,4 +116,18 @@ function verifyDestNameValid(destName) {
}
}

export default GatewayProvider;
function verifySortValid(sort) {
if (sort && typeof sort !== 'number') {
throw new Error('sort should be a number');
}
}

function getSortValue(sortContext){
if(!sortContext|| sortContext.sort === undefined) {
return Number.MAX_VALUE;
}
return sortContext.sort;
};


export default GatewayProvider;
120 changes: 98 additions & 22 deletions test/gateway.spec.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import React, { useContext, useState } from 'react';
import { create, act } from 'react-test-renderer';
import {render, within, fireEvent, waitFor, screen} from '@testing-library/react';


import {
Gateway,
Expand All @@ -8,35 +10,109 @@ import {
} from '../src/index.js';


const withExposedSetState = (Component) => {
let setState;
let setSetState = (setStateParam) => {
setState = setStateParam;
};
return {
setState: (state) => {
setState(state);
},
Component: (props) => <Component setSetState={setSetState} {...props} />
};
};

import { withExposedSetState } from './utils.js';

describe('Gateway', function () {
it('throws on invalid dest name', function () {
const throwingRender = () => {
act(() => {
create(
describe('verification', () => {
beforeEach(() => {
jest.spyOn(console, 'error').mockImplementation(() => {});
});
it('throws on invalid dest name', function () {
const throwingRender = () => {
act(() => {
create(
<GatewayProvider>
<div>
<GatewayDest name="invaid##" />
<Gateway into="invaid##" />
</div>
</GatewayProvider>
);
});
};
expect(throwingRender).toThrow('dest names should not have ##');
});
});

describe('sorting', () => {
beforeEach(() => {
jest.spyOn(console, 'error').mockImplementation(() => {});
});

it('should render in natural order', () => {
render(
<GatewayProvider>
<div>
<section>
<Gateway into="foo"> First </Gateway>
<Gateway into="foo"> Second </Gateway>
</section>
<GatewayDest name="foo" data-testid="mydiv"/>
</div>
</GatewayProvider>
);
const textContent = screen.getByTestId('mydiv').textContent;
expect(textContent).toContain('First Second');
});
it('should render by sort prop', () => {
let rendered = render(
<GatewayProvider>
<div>
<section>
<Gateway into="foo" sort={2}> Second </Gateway>
<Gateway into="foo" sort={1}> First </Gateway>
</section>
<GatewayDest name="foo" data-testid="mydiv"/>
</div>
</GatewayProvider>
);
const textContent = screen.getByTestId('mydiv').textContent;
expect(textContent).toContain('First Second');
});
it('should render undefined sort prop last', () => {
render(
<GatewayProvider>
<div>
<GatewayDest name="invaid##" />
<Gateway into="invaid##" />
<section>
<Gateway into="foo"> Third </Gateway>
<Gateway into="foo" sort={2}> Second </Gateway>
<Gateway into="foo" sort={1}> First </Gateway>
</section>
<GatewayDest name="foo" data-testid="mydiv"/>
</div>
</GatewayProvider>
);
});
};
expect(throwingRender).toThrow('dest names should not have ##');
const textContent = screen.getByTestId('mydiv').textContent;
expect(textContent).toContain('First Second Third');
});
it('should render undefined sort prop in natural order', () => {
render(
<GatewayProvider>
<div>
<section>
<Gateway into="foo"> Second </Gateway>
<Gateway into="foo"> Third </Gateway>
<Gateway into="foo" sort={3}> First </Gateway>
</section>
<GatewayDest name="foo" data-testid="mydiv"/>
</div>
</GatewayProvider>
);
const textContent = screen.getByTestId('mydiv').textContent;
expect(textContent).toContain('First Second Third');
});
it('should validate sort to be a number only', () => {
const throwingRender = () => {
act(() => {
create(
<GatewayProvider>
<Gateway into="invalid" sort={() => {}} />
</GatewayProvider>
);
});
};
expect(throwingRender).toThrow('sort should be a number');
});
});

it('should render Gateway in GatewayDest', function () {
Expand Down
Loading

0 comments on commit 654c8d8

Please sign in to comment.