diff --git a/jest.config.js b/jest.config.js index 96733b5f..b695422a 100644 --- a/jest.config.js +++ b/jest.config.js @@ -8,5 +8,13 @@ module.exports = { "!packages/dataparcels-docs/**" ], testMatch: ["**/__test__/**/*-test.js?(x)"], - testURL: 'http://localhost' + testURL: 'http://localhost', + coverageThreshold: { + global: { + branches: 90, + functions: 90, + lines: 90, + statements: 90 + } + } }; diff --git a/packages/dataparcels-docs/src/examples/EditingArraysSortableHoc.jsx b/packages/dataparcels-docs/src/examples/EditingArraysDrag.jsx similarity index 56% rename from packages/dataparcels-docs/src/examples/EditingArraysSortableHoc.jsx rename to packages/dataparcels-docs/src/examples/EditingArraysDrag.jsx index 9eb4a1c3..9bb43410 100644 --- a/packages/dataparcels-docs/src/examples/EditingArraysSortableHoc.jsx +++ b/packages/dataparcels-docs/src/examples/EditingArraysDrag.jsx @@ -1,7 +1,7 @@ import React from 'react'; import ParcelHoc from 'react-dataparcels/ParcelHoc'; import ParcelBoundary from 'react-dataparcels/ParcelBoundary'; -import {SortableContainer, SortableElement} from 'react-sortable-hoc'; +import ParcelDrag from 'react-dataparcels-drag'; import ExampleHoc from 'component/ExampleHoc'; const FruitListParcelHoc = ParcelHoc({ @@ -13,35 +13,20 @@ const FruitListParcelHoc = ParcelHoc({ ] }); -const SortableFruitItem = SortableElement(({fruitParcel}) => { - return +const SortableFruitList = ParcelDrag({ + element: (fruitParcel) => {(parcel) =>
} -
; -}); - -const SortableFruitList = SortableContainer(({fruitListParcel}) => { - return
- {fruitListParcel.toArray((fruitParcel, index) => { - return ; - })} -
; +
}); const FruitListEditor = (props) => { let {fruitListParcel} = props; return
- fruitListParcel.move(oldIndex, newIndex)} - /> +
; }; diff --git a/packages/dataparcels-docs/src/pages/examples/editing-arrays.md b/packages/dataparcels-docs/src/pages/examples/editing-arrays.md index 97274987..7e827f76 100644 --- a/packages/dataparcels-docs/src/pages/examples/editing-arrays.md +++ b/packages/dataparcels-docs/src/pages/examples/editing-arrays.md @@ -1,7 +1,7 @@ import Link from 'gatsby-link'; import EditingArrays from 'examples/EditingArrays'; import EditingArraysFlipMove from 'examples/EditingArraysFlipMove'; -import EditingArraysSortableHoc from 'examples/EditingArraysSortableHoc'; +import EditingArraysDrag from 'examples/EditingArraysDrag'; Dataparcels has a powerful set of methods for manipulating indexed data types, such as arrays. This example demonstrates an editor that allows the user to edit, append to and sort the elements in an array of strings. @@ -52,17 +52,19 @@ export default FruitListParcelHoc(FruitListEditor); For the full list of methods you can use on indexed data types, see Indexed Change Methods and Element Change Methods in the Parcel API reference. -## Drag and drop with react-sortable-hoc +## Drag and drop with react-dataparcels-drag -Dataparcels' plays nicely with [react-sortable-hoc](https://github.com/clauderic/react-sortable-hoc). Drag items up and fown to change their order. +Drag and drop is easy using [react-dataparcels-drag](https://www.npmjs.com/package/react-dataparcels-drag), which is a slim wrapper around [react-sortable-hoc](https://github.com/clauderic/react-sortable-hoc). Drag items up and fown to change their order. - +THe `react-dataparcels-drag` HOC attempts to keep a very similar API to `react-sortable-hoc`, and therefore its usage is a little different compared to the other HOCs in `react-dataparcels`. + + ```js import React from 'react'; import ParcelHoc from 'react-dataparcels/ParcelHoc'; import ParcelBoundary from 'react-dataparcels/ParcelBoundary'; -import {SortableContainer, SortableElement} from 'react-sortable-hoc'; +import ParcelDrag from 'react-dataparcels-drag'; const FruitListParcelHoc = ParcelHoc({ name: "fruitListParcel", @@ -73,35 +75,20 @@ const FruitListParcelHoc = ParcelHoc({ ] }); -const SortableFruitItem = SortableElement(({fruitParcel}) => { - return - {(parcel) =>
+const SortableFruitList = ParcelDrag({ + element: (fruitParcel) => + {(parcel) =>
} -
; -}); - -const SortableFruitList = SortableContainer(({fruitListParcel}) => { - return
- {fruitListParcel.toArray((fruitParcel, index) => { - return ; - })} -
; + }); const FruitListEditor = (props) => { let {fruitListParcel} = props; return
- fruitListParcel.move(oldIndex, newIndex)} - /> +
; }; @@ -120,7 +107,6 @@ import React from 'react'; import FlipMove from 'react-flip-move'; import ParcelHoc from 'react-dataparcels/ParcelHoc'; import ParcelBoundary from 'react-dataparcels/ParcelBoundary'; -import ExampleHoc from 'component/ExampleHoc'; const FruitListEditor = (props) => { let {fruitListParcel} = props; @@ -149,6 +135,6 @@ const FruitListParcelHoc = ParcelHoc({ name: "fruitListParcel" }); -export default FruitListParcelHoc(ExampleHoc(FruitListEditor)); +export default FruitListParcelHoc(FruitListEditor); ``` diff --git a/packages/react-dataparcels-drag/README.md b/packages/react-dataparcels-drag/README.md new file mode 100644 index 00000000..4e54d20d --- /dev/null +++ b/packages/react-dataparcels-drag/README.md @@ -0,0 +1,14 @@ +![dataparcels](https://user-images.githubusercontent.com/345320/48319791-4eece200-e666-11e8-8b19-252cd1135ae2.png) + + +[![CircleCI](https://circleci.com/gh/blueflag/dataparcels/tree/master.svg?style=shield)](https://circleci.com/gh/blueflag/dataparcels/tree/master) + +A plugin for [`react-dataparcels`](https://www.npmjs.com/package/react-dataparcels) that adds drag and drop re-ordering of elements, using the wonderful [react-sortable-hoc](https://github.com/clauderic/react-sortable-hoc). + +## Example + +**[See the example](https://dataparcels.blueflag.codes/examples/editing-arrays)** + +## Packages + +Get [`react-dataparcels-drag`](https://www.npmjs.com/package/react-dataparcels-drag). diff --git a/packages/react-dataparcels-drag/__test__/Exports-test.js b/packages/react-dataparcels-drag/__test__/Exports-test.js new file mode 100644 index 00000000..9ed38e32 --- /dev/null +++ b/packages/react-dataparcels-drag/__test__/Exports-test.js @@ -0,0 +1,9 @@ +// @flow + +// dataparcels exports +import Drag from '../src/index'; +import InternalDrag from '../src/Drag'; + +test('index should export Drag', () => { + expect(Drag).toBe(InternalDrag); +}); diff --git a/packages/react-dataparcels-drag/package.json b/packages/react-dataparcels-drag/package.json new file mode 100644 index 00000000..17b927c7 --- /dev/null +++ b/packages/react-dataparcels-drag/package.json @@ -0,0 +1,32 @@ +{ + "name": "react-dataparcels-drag", + "version": "0.18.0-2", + "description": "A plugin for react-dataparcels that adds drag and drop re-ordering of elements.", + "main": "lib/index.js", + "license": "UNLICENSED", + "author": "Damien Clarke", + "repository": { + "type": "git", + "url": "git+https://github.com/blueflag/dataparcels.git" + }, + "files": [ + "lib" + ], + "bugs": { + "url": "https://github.com/blueflag/dataparcels/issues" + }, + "private": false, + "scripts": { + "build": "rm -rf lib && NODE_ENV=production babel src --out-dir lib --ignore '**/__test__/*.js'", + "build-all": "yarn build", + "watch": "yarn run build -w" + }, + "dependencies": { + "babel-runtime": "6.23.0", + "react-sortable-hoc": "1.4.0" + }, + "peerDependencies": { + "react": "16.4.2", + "react-dataparcels": "^0.18.0-2" + } +} diff --git a/packages/react-dataparcels-drag/src/Drag.js b/packages/react-dataparcels-drag/src/Drag.js new file mode 100644 index 00000000..bccba709 --- /dev/null +++ b/packages/react-dataparcels-drag/src/Drag.js @@ -0,0 +1,47 @@ +// @flow + +import type {ComponentType} from 'react'; +import type {Node} from 'react'; +import type Parcel from 'react-dataparcels'; + +import React from 'react'; +import {SortableContainer} from 'react-sortable-hoc'; +import {SortableElement} from 'react-sortable-hoc'; + +type Config = { + element: (parcel: Parcel, rest: *) => Node, + container?: ComponentType<*> +}; + +type Props = { + parcel: Parcel, + onSortEnd?: ({oldIndex: number, newIndex: number}) => void +}; + +export default ({element, container, ...configRest}: Config) => { + let Container = container || 'div'; + let ConfiguredElement = SortableElement(({parcel, ...rest}) => element(parcel, rest)); + let ConfiguredContainer = SortableContainer(({parcel}) => + {parcel.toArray((elementParcel, index) => )} + ); + + return ({parcel, onSortEnd, ...rest}: Props): Node => { + if(!parcel.isIndexed()) { + throw new Error(`react-dataparcels-drag's parcel prop must be of type indexed`); + } + return { + let {oldIndex, newIndex} = param; + parcel.move(oldIndex, newIndex); + onSortEnd && onSortEnd(param); + }} + {...configRest} + {...rest} + />; + }; +}; diff --git a/packages/react-dataparcels-drag/src/__test__/Drag-test.js b/packages/react-dataparcels-drag/src/__test__/Drag-test.js new file mode 100644 index 00000000..beebed12 --- /dev/null +++ b/packages/react-dataparcels-drag/src/__test__/Drag-test.js @@ -0,0 +1,151 @@ +// // @flow +import React from 'react'; +import Parcel from 'react-dataparcels'; +import Drag from '../Drag'; + +test('Drag must pass props correctly', () => { + + let handleChange = jest.fn(); + + let parcel = new Parcel({ + value: [1,2,3], + handleChange + }); + + let MyDrag = Drag({ + element: () =>
+ }); + + // $FlowFixMe + let wrapper = shallow(, {disableLifecycleMethods: true}); + + // first level in + let props1 = wrapper.props(); + expect(props1.parcel).toBe(parcel); + props1.onSortEnd({oldIndex: 0, newIndex: 2}); + expect(handleChange.mock.calls[0][0].value).toEqual([2,3,1]); + + // second level in + let props2 = wrapper.dive().props(); + expect(props2.parcel).toBe(parcel); + + // third level in + let props3 = wrapper.dive().dive().props(); + expect(props3.children.length).toBe(3); +}); + +test('Drag should throw if parcel is not indexed', () => { + + let parcel = new Parcel({ + value: {abc: 123} + }); + + let MyDrag = Drag({ + element: () =>
+ }); + + expect(() => { + // $FlowFixMe + shallow(, {disableLifecycleMethods: true}); + }).toThrow(`react-dataparcels-drag's parcel prop must be of type indexed`); +}); + +test('Drag must accept onSortEnd and still call internal onSortEnd', () => { + + let handleChange = jest.fn(); + + let parcel = new Parcel({ + value: [1,2,3], + handleChange + }); + + let MyDrag = Drag({ + element: () =>
+ }); + + let onSortEnd = jest.fn(); + + // $FlowFixMe + let wrapper = shallow(, {disableLifecycleMethods: true}); + + let props = wrapper.props(); + let sortEndArg = {oldIndex: 0, newIndex: 2}; + + expect(props.parcel).toBe(parcel); + props.onSortEnd(sortEndArg); + expect(handleChange.mock.calls[0][0].value).toEqual([2,3,1]); + expect(onSortEnd.mock.calls[0][0]).toEqual(sortEndArg); +}); + + +test('Drag must accept additional props and pass them to react-sortable-hoc as props', () => { + + let parcel = new Parcel({ + value: [1,2,3] + }); + + let MyDrag = Drag({ + element: () =>
+ }); + + // $FlowFixMe + let wrapper = shallow(, {disableLifecycleMethods: true}); + + let props = wrapper.props(); + expect(props.woo).toBe(123); +}); + +test('Drag must accept additional config and pass them to react-sortable-hoc as props', () => { + + let parcel = new Parcel({ + value: [1,2,3] + }); + + let MyDrag = Drag({ + element: () =>
, + woo: 123 + }); + + // $FlowFixMe + let wrapper = shallow(, {disableLifecycleMethods: true}); + + let props = wrapper.props(); + expect(props.woo).toBe(123); +}); + +test('Drag must prefer props over config', () => { + + let parcel = new Parcel({ + value: [1,2,3] + }); + + let MyDrag = Drag({ + element: () =>
, + woo: 123 + }); + + // $FlowFixMe + let wrapper = shallow(, {disableLifecycleMethods: true}); + + let props = wrapper.props(); + expect(props.woo).toBe(456); +}); + +test('Drag must render elements and pass parcels to them', () => { + + let value = [1,2,3]; + + let parcel = new Parcel({ + value + }); + + let element = jest.fn(() =>
); + + let MyDrag = Drag({ + element + }); + + // $FlowFixMe + let wrapper = mount(); + expect(element.mock.calls.map(call => call[0].value)).toEqual([1,2,3]); +}); diff --git a/packages/react-dataparcels-drag/src/index.js b/packages/react-dataparcels-drag/src/index.js new file mode 100644 index 00000000..a5099964 --- /dev/null +++ b/packages/react-dataparcels-drag/src/index.js @@ -0,0 +1,4 @@ +// @flow + +import Drag from './Drag'; +export default Drag; diff --git a/packages/react-dataparcels/package.json b/packages/react-dataparcels/package.json index 58115fd8..91fb4567 100644 --- a/packages/react-dataparcels/package.json +++ b/packages/react-dataparcels/package.json @@ -25,5 +25,8 @@ "babel-runtime": "6.23.0", "dataparcels": "^0.18.0-2", "unmutable": "^0.39.0" + }, + "peerDependencies": { + "react": "16.4.2" } } diff --git a/yarn.lock b/yarn.lock index 7c2cbccb..d08aeb59 100644 --- a/yarn.lock +++ b/yarn.lock @@ -97,8 +97,8 @@ js-tokens "^3.0.0" "@babel/runtime@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.2.0.tgz#b03e42eeddf5898e00646e4c840fa07ba8dcad7f" + version "7.3.1" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.3.1.tgz#574b03e8e8a9898eaf4a872a92ea20b7846f6f2a" dependencies: regenerator-runtime "^0.12.0" @@ -11190,7 +11190,7 @@ react-side-effect@^1.1.0: exenv "^1.2.1" shallowequal "^1.0.1" -react-sortable-hoc@^1.4.0: +react-sortable-hoc@1.4.0, react-sortable-hoc@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/react-sortable-hoc/-/react-sortable-hoc-1.4.0.tgz#b477ce700ba755754200a1dabd36e588e2f5608d" dependencies: