Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

formula-one

Package Overview
Dependencies
Maintainers
1
Versions
44
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

formula-one - npm Package Compare versions

Comparing version 0.9.0-alpha.1 to 0.9.0-alpha.2

dist/test/utils/array.test.js

22

CHANGELOG.md

@@ -5,7 +5,23 @@ # Changelog

- Add `customChange` prop to `ObjectField` and `ArrayField`. This allows changes in one part of the object to affect other parts of the form. Currently, no metadata is preserved if a `customChange` function is used. This will be addressed in a future API. _Warning_: Rendering a component which calls `onChange` during mount will result in an infinite render loop.
<br>
<br>
- Add `customChange` prop to `ObjectField` and `ArrayField`. This allows changes in one part of the object to affect other parts of the form. Currently, no metadata is preserved (all fields are marked **changed** and **touched**) if a `customChange` function is used. This will be addressed in a future API.
**Warning**: Rendering a component which calls `onChange` during mount under a non-null returning `customChange` will result in an infinite render loop.
**Warning**: returning non-null from `customChange` forces a remount of all children. This can cause unintended consequences such as loss of focus on inputs. This will be fixed in a future 0.9 release.
- Add `addFields` and `filterFields` array manipulators. These are currently necessary due to the non-atomic nature of the current `addField` and `removeField` manipulators. They will be made atomic in a future version.
The API is:
```jsx
// A type indicating a range to be inserted at an index
type Span<E> = [number, $ReadOnlyArray<E>];
// A way to atomicly add fields to an ArrayField<E>
addFields: (spans: $ReadOnlyArray<Span<E>) => void;
// A way to remove fields from an ArrayField<E>
filterFields: (predicate: (item: E, index: number) => boolean) => void
```
### v0.8.2

@@ -12,0 +28,0 @@

@@ -122,3 +122,3 @@ "use strict";

_this.props.link.onChange((0, _formState3.validate)(_this.props.validation, (0, _formState3.setChanged)((0, _formState3.setTouched)([newValue, newTree]))));
}, _this._removeChildField = function (index) {
}, _this._addChildFields = function (spans) {
var _this$props$link$form3 = _slicedToArray(_this.props.link.formState, 2),

@@ -128,7 +128,21 @@ oldValue = _this$props$link$form3[0],

var newValue = (0, _array.removeAt)(index, oldValue);
var newTree = (0, _shapedTree.dangerouslySetChildren)((0, _array.removeAt)(index, (0, _shapedTree.shapedArrayChildren)(oldTree)), oldTree);
var cleanNode = {
errors: _types.cleanErrors,
meta: _types.cleanMeta
};
var newValue = (0, _array.insertSpans)(spans, oldValue);
var newNodeSpans = spans.map(function (_ref2) {
var _ref3 = _slicedToArray(_ref2, 2),
index = _ref3[0],
content = _ref3[1];
return [index, content.map(function (v) {
return (0, _shapedTree.treeFromValue)(v, cleanNode);
})];
});
var newTree = (0, _shapedTree.dangerouslySetChildren)((0, _array.insertSpans)(newNodeSpans, (0, _shapedTree.shapedArrayChildren)(oldTree)), oldTree);
_this.props.link.onChange((0, _formState3.validate)(_this.props.validation, (0, _formState3.setChanged)((0, _formState3.setTouched)([newValue, newTree]))));
}, _this._moveChildField = function (from, to) {
}, _this._filterChildFields = function (predicate) {
var _this$props$link$form4 = _slicedToArray(_this.props.link.formState, 2),

@@ -138,2 +152,31 @@ oldValue = _this$props$link$form4[0],

var zipped = (0, _array.zip)(oldValue, (0, _shapedTree.shapedArrayChildren)(oldTree));
var _unzip = (0, _array.unzip)(zipped.filter(function (_ref4, i) {
var _ref5 = _slicedToArray(_ref4, 1),
value = _ref5[0];
return predicate(value, i);
})),
_unzip2 = _slicedToArray(_unzip, 2),
newValue = _unzip2[0],
newChildren = _unzip2[1];
var newTree = (0, _shapedTree.dangerouslySetChildren)(newChildren, oldTree);
_this.props.link.onChange((0, _formState3.validate)(_this.props.validation, (0, _formState3.setChanged)((0, _formState3.setTouched)([newValue, newTree]))));
}, _this._removeChildField = function (index) {
var _this$props$link$form5 = _slicedToArray(_this.props.link.formState, 2),
oldValue = _this$props$link$form5[0],
oldTree = _this$props$link$form5[1];
var newValue = (0, _array.removeAt)(index, oldValue);
var newTree = (0, _shapedTree.dangerouslySetChildren)((0, _array.removeAt)(index, (0, _shapedTree.shapedArrayChildren)(oldTree)), oldTree);
_this.props.link.onChange((0, _formState3.validate)(_this.props.validation, (0, _formState3.setChanged)((0, _formState3.setTouched)([newValue, newTree]))));
}, _this._moveChildField = function (from, to) {
var _this$props$link$form6 = _slicedToArray(_this.props.link.formState, 2),
oldValue = _this$props$link$form6[0],
oldTree = _this$props$link$form6[1];
var newValue = (0, _array.moveFromTo)(from, to, oldValue);

@@ -172,4 +215,4 @@ var newTree = (0, _shapedTree.dangerouslySetChildren)((0, _array.moveFromTo)(from, to, (0, _shapedTree.shapedArrayChildren)(oldTree)), oldTree);

value: function forceChildRemount() {
this.setState(function (_ref2) {
var nonce = _ref2.nonce;
this.setState(function (_ref6) {
var nonce = _ref6.nonce;
return { nonce: nonce + 1 };

@@ -192,3 +235,5 @@ });

removeField: this._removeChildField,
moveField: this._moveChildField
moveField: this._moveChildField,
addFields: this._addChildFields,
filterFields: this._filterChildFields
}, {

@@ -195,0 +240,0 @@ touched: (0, _formState3.getExtras)(formState).meta.touched,

@@ -468,2 +468,104 @@ "use strict";

});
describe("addFields", function () {
it("exposes addFields to add an entry", function () {
var formStateValue = ["one", "two", "three"];
var formState = (0, _tools.mockFormState)(formStateValue);
var link = (0, _tools.mockLink)(formState);
var renderFn = jest.fn(function () {
return null;
});
_reactTestRenderer2.default.create(React.createElement(
_ArrayField2.default,
{ link: link },
renderFn
));
expect(renderFn).toHaveBeenCalledWith(expect.anything(), expect.objectContaining({
addFields: expect.any(Function)
}), expect.anything());
});
it("validates after entry is added", function () {
var formStateValue = ["one", "two", "three"];
var formState = (0, _tools.mockFormState)(formStateValue);
var link = (0, _tools.mockLink)(formState);
var renderFn = jest.fn(function () {
return null;
});
var validation = jest.fn(function () {
return ["an error"];
});
_reactTestRenderer2.default.create(React.createElement(
_ArrayField2.default,
{ validation: validation, link: link },
renderFn
));
expect(validation).toHaveBeenCalledTimes(1);
var _renderFn$mock$calls$4 = _slicedToArray(renderFn.mock.calls[0], 2),
_ = _renderFn$mock$calls$4[0],
addFields = _renderFn$mock$calls$4[1].addFields;
addFields([[0, ["negative one", "zero"]], [3, ["four", "five"]]]);
expect(validation).toHaveBeenCalledTimes(2);
expect(validation).toHaveBeenLastCalledWith(["negative one", "zero", "one", "two", "three", "four", "five"]);
});
});
describe("filterFields", function () {
it("exposes addFields to add an entry", function () {
var formStateValue = ["one", "two", "three"];
var formState = (0, _tools.mockFormState)(formStateValue);
var link = (0, _tools.mockLink)(formState);
var renderFn = jest.fn(function () {
return null;
});
_reactTestRenderer2.default.create(React.createElement(
_ArrayField2.default,
{ link: link },
renderFn
));
expect(renderFn).toHaveBeenCalledWith(expect.anything(), expect.objectContaining({
filterFields: expect.any(Function)
}), expect.anything());
});
it("validates after entry is added", function () {
var formStateValue = ["one", "two", "three", "four", "five"];
var formState = (0, _tools.mockFormState)(formStateValue);
var link = (0, _tools.mockLink)(formState);
var renderFn = jest.fn(function () {
return null;
});
var validation = jest.fn(function () {
return ["an error"];
});
_reactTestRenderer2.default.create(React.createElement(
_ArrayField2.default,
{ validation: validation, link: link },
renderFn
));
expect(validation).toHaveBeenCalledTimes(1);
var _renderFn$mock$calls$5 = _slicedToArray(renderFn.mock.calls[0], 2),
_ = _renderFn$mock$calls$5[0],
filterFields = _renderFn$mock$calls$5[1].filterFields;
// remove numbers without "o" and the fourth element
filterFields(function (v, i) {
return v.indexOf("o") !== -1 && i !== 3;
});
expect(validation).toHaveBeenCalledTimes(2);
expect(validation).toHaveBeenLastCalledWith(["one", "two"]);
});
});
});

@@ -470,0 +572,0 @@

@@ -6,2 +6,5 @@ "use strict";

});
var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
exports.removeAt = removeAt;

@@ -11,3 +14,6 @@ exports.replaceAt = replaceAt;

exports.moveFromTo = moveFromTo;
exports.insertSpans = insertSpans;
exports.zipWith = zipWith;
exports.zip = zip;
exports.unzip = unzip;

@@ -35,2 +41,44 @@ function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }

function insertSpans(spans, arr) {
// no duplicated indices are allowed, ECMAScript Array.sort is not stable by spec
var indexSet = new Set(spans.map(function (_ref) {
var _ref2 = _slicedToArray(_ref, 1),
i = _ref2[0];
return i;
}));
if (indexSet.size !== spans.length) {
throw new Error("You cannot insert two spans at the same index. Combine the values of the spans.");
}
// sort spans by insertion position
var spansCopy = [].concat(_toConsumableArray(spans));
spansCopy.sort(function (_ref3, _ref4) {
var _ref6 = _slicedToArray(_ref3, 1),
i = _ref6[0];
var _ref5 = _slicedToArray(_ref4, 1),
j = _ref5[0];
return i - j;
});
// build the new array in one pass
var ret = [];
var lastIndexInsertedAt = 0;
spansCopy.forEach(function (_ref7) {
var _ref8 = _slicedToArray(_ref7, 2),
index = _ref8[0],
contents = _ref8[1];
// All the content before this
ret = ret.concat(arr.slice(lastIndexInsertedAt, index));
ret = ret.concat(contents);
lastIndexInsertedAt = index;
});
ret = ret.concat(arr.slice(lastIndexInsertedAt));
return ret;
}
// Strict on length

@@ -46,2 +94,21 @@ function zipWith(f, left, right) {

return ret;
}
function zip(left, right) {
return zipWith(function (l, r) {
return [l, r];
}, left, right);
}
function unzip(zipped) {
var ret = [[], []];
for (var i = 0; i < zipped.length; i += 1) {
var _zipped$i = _slicedToArray(zipped[i], 2),
left = _zipped$i[0],
right = _zipped$i[1];
ret[0].push(left);
ret[1].push(right);
}
return ret;
}

2

package.json
{
"name": "formula-one",
"version": "0.9.0-alpha.1",
"version": "0.9.0-alpha.2",
"description": "Strongly-typed React form state management",

@@ -5,0 +5,0 @@ "author": "Zach Gotsch",

@@ -23,3 +23,10 @@ // @flow strict

} from "./shapedTree";
import {removeAt, moveFromTo, insertAt} from "./utils/array";
import {
removeAt,
moveFromTo,
insertAt,
insertSpans,
zip,
unzip,
} from "./utils/array";
import {FormContext, type FormContextPayload} from "./Form";

@@ -54,2 +61,4 @@ import {

moveField: (oldIndex: number, newIndex: number) => void,
addFields: (spans: $ReadOnlyArray<[number, $ReadOnlyArray<E>]>) => void,
filterFields: (predicate: (E, number) => boolean) => void,
},

@@ -205,2 +214,50 @@ additionalInfo: AdditionalRenderInfo<Array<E>>

_addChildFields: (
spans: $ReadOnlyArray<[number, $ReadOnlyArray<E>]>
) => void = spans => {
const [oldValue, oldTree] = this.props.link.formState;
const cleanNode = {
errors: cleanErrors,
meta: cleanMeta,
};
const newValue = insertSpans(spans, oldValue);
const newNodeSpans: Array<
[number, $ReadOnlyArray<ShapedTree<E, Extras>>]
> = spans.map(([index, content]) => [
index,
content.map(v => treeFromValue(v, cleanNode)),
]);
const newTree = dangerouslySetChildren(
insertSpans(newNodeSpans, shapedArrayChildren(oldTree)),
oldTree
);
this.props.link.onChange(
validate(
this.props.validation,
setChanged(setTouched([newValue, newTree]))
)
);
};
_filterChildFields: (
predicate: (E, number) => boolean
) => void = predicate => {
const [oldValue, oldTree] = this.props.link.formState;
const zipped = zip(oldValue, shapedArrayChildren(oldTree));
const [newValue, newChildren] = unzip(
zipped.filter(([value], i) => predicate(value, i))
);
const newTree = dangerouslySetChildren(newChildren, oldTree);
this.props.link.onChange(
validate(
this.props.validation,
setChanged(setTouched([newValue, newTree]))
)
);
};
_removeChildField = (index: number) => {

@@ -257,2 +314,4 @@ const [oldValue, oldTree] = this.props.link.formState;

moveField: this._moveChildField,
addFields: this._addChildFields,
filterFields: this._filterChildFields,
},

@@ -259,0 +318,0 @@ {

@@ -361,2 +361,91 @@ // @flow

});
describe("addFields", () => {
it("exposes addFields to add an entry", () => {
const formStateValue = ["one", "two", "three"];
const formState = mockFormState(formStateValue);
const link = mockLink(formState);
const renderFn = jest.fn(() => null);
TestRenderer.create(<ArrayField link={link}>{renderFn}</ArrayField>);
expect(renderFn).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({
addFields: expect.any(Function),
}),
expect.anything()
);
});
it("validates after entry is added", () => {
const formStateValue = ["one", "two", "three"];
const formState = mockFormState(formStateValue);
const link = mockLink(formState);
const renderFn = jest.fn(() => null);
const validation = jest.fn(() => ["an error"]);
TestRenderer.create(
<ArrayField validation={validation} link={link}>
{renderFn}
</ArrayField>
);
expect(validation).toHaveBeenCalledTimes(1);
const [_, {addFields}] = renderFn.mock.calls[0];
addFields([[0, ["negative one", "zero"]], [3, ["four", "five"]]]);
expect(validation).toHaveBeenCalledTimes(2);
expect(validation).toHaveBeenLastCalledWith([
"negative one",
"zero",
"one",
"two",
"three",
"four",
"five",
]);
});
});
describe("filterFields", () => {
it("exposes addFields to add an entry", () => {
const formStateValue = ["one", "two", "three"];
const formState = mockFormState(formStateValue);
const link = mockLink(formState);
const renderFn = jest.fn(() => null);
TestRenderer.create(<ArrayField link={link}>{renderFn}</ArrayField>);
expect(renderFn).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({
filterFields: expect.any(Function),
}),
expect.anything()
);
});
it("validates after entry is added", () => {
const formStateValue = ["one", "two", "three", "four", "five"];
const formState = mockFormState(formStateValue);
const link = mockLink(formState);
const renderFn = jest.fn(() => null);
const validation = jest.fn(() => ["an error"]);
TestRenderer.create(
<ArrayField validation={validation} link={link}>
{renderFn}
</ArrayField>
);
expect(validation).toHaveBeenCalledTimes(1);
const [_, {filterFields}] = renderFn.mock.calls[0];
// remove numbers without "o" and the fourth element
filterFields((v, i) => v.indexOf("o") !== -1 && i !== 3);
expect(validation).toHaveBeenCalledTimes(2);
expect(validation).toHaveBeenLastCalledWith(["one", "two"]);
});
});
});

@@ -363,0 +452,0 @@

@@ -32,2 +32,32 @@ // @flow strict

export function insertSpans<E>(
spans: $ReadOnlyArray<[number, $ReadOnlyArray<E>]>,
arr: $ReadOnlyArray<E>
): Array<E> {
// no duplicated indices are allowed, ECMAScript Array.sort is not stable by spec
const indexSet = new Set(spans.map(([i]) => i));
if (indexSet.size !== spans.length) {
throw new Error(
"You cannot insert two spans at the same index. Combine the values of the spans."
);
}
// sort spans by insertion position
const spansCopy = [...spans];
spansCopy.sort(([i], [j]) => i - j);
// build the new array in one pass
let ret = [];
let lastIndexInsertedAt = 0;
spansCopy.forEach(([index, contents]) => {
// All the content before this
ret = ret.concat(arr.slice(lastIndexInsertedAt, index));
ret = ret.concat(contents);
lastIndexInsertedAt = index;
});
ret = ret.concat(arr.slice(lastIndexInsertedAt));
return ret;
}
// Strict on length

@@ -48,1 +78,20 @@ export function zipWith<A, B, C>(

}
export function zip<A, B>(
left: $ReadOnlyArray<A>,
right: $ReadOnlyArray<B>
): Array<[A, B]> {
return zipWith((l, r) => [l, r], left, right);
}
export function unzip<A, B>(
zipped: $ReadOnlyArray<[A, B]>
): [Array<A>, Array<B>] {
const ret = [[], []];
for (let i = 0; i < zipped.length; i += 1) {
const [left, right] = zipped[i];
ret[0].push(left);
ret[1].push(right);
}
return ret;
}
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc