Socket
Socket
Sign inDemoInstall

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.2.1 to 0.3.0

dist/test/tree.test.js

6

CHANGELOG.md
# Changelog
### v0.3.0
- Add a way to pass additional information to `onSubmit` as a second argument.
- Add `onChange` prop to `<Form>`. Make `onChange` and `onSubmit` optional, since they probably won't co-occur.
- Add additional information to render functions for `<Form>` and `Field`s as a third argument.
### v0.2.1

@@ -4,0 +10,0 @@

@@ -157,2 +157,3 @@ "use strict";

var formState = this.props.link.formState;
var shouldShowError = this.props.formContext.shouldShowError;

@@ -165,2 +166,10 @@

moveField: this.moveChildField
}, {
touched: (0, _formState3.getExtras)(formState).meta.touched,
changed: (0, _formState3.getExtras)(formState).meta.changed,
shouldShowErrors: shouldShowError((0, _formState3.getExtras)(formState).meta),
unfilteredErrors: (0, _formState3.flatRootErrors)(formState),
asyncValidationInFlight: false, // no validations on Form
valid: (0, _formState3.isValid)(formState),
value: formState[0]
});

@@ -167,0 +176,0 @@ }

30

dist/Field.js

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

var _formState2 = require("./formState");
var _formState3 = require("./formState");

@@ -72,3 +72,3 @@ function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }

_this.props.link.onChange((0, _formState2.setChanged)((0, _formState2.validate)(_this.props.validation, [newValue, oldTree])));
_this.props.link.onChange((0, _formState3.setChanged)((0, _formState3.validate)(_this.props.validation, [newValue, oldTree])));
}, _this.onBlur = function () {

@@ -81,3 +81,3 @@ var _this$props$link$form2 = _slicedToArray(_this.props.link.formState, 2),

// TODO(zach): Not sure if we should blow away server errors here
(0, _shapedTree.mapRoot)(_formState2.setExtrasTouched, tree));
(0, _shapedTree.mapRoot)(_formState3.setExtrasTouched, tree));
}, _temp), _possibleConstructorReturn(_this, _ret);

@@ -98,3 +98,3 @@ }

var _getExtras = (0, _formState2.getExtras)(formState),
var _getExtras = (0, _formState3.getExtras)(formState),
errors = _getExtras.errors;

@@ -114,11 +114,25 @@

value: function render() {
var _props$link$formState = _slicedToArray(this.props.link.formState, 1),
value = _props$link$formState[0];
var formState = this.props.link.formState;
var _getExtras2 = (0, _formState2.getExtras)(this.props.link.formState),
var _formState2 = _slicedToArray(formState, 1),
value = _formState2[0];
var _getExtras2 = (0, _formState3.getExtras)(formState),
meta = _getExtras2.meta,
errors = _getExtras2.errors;
var shouldShowError = this.props.formContext.shouldShowError;
var flatErrors = this.props.formContext.shouldShowError(meta) ? getErrors(errors) : [];
return this.props.children(value, flatErrors, this.onChange, this.onBlur);
return this.props.children(value, flatErrors, this.onChange, this.onBlur, {
touched: meta.touched,
changed: meta.changed,
shouldShowErrors: shouldShowError(meta),
unfilteredErrors: getErrors(errors),
asyncValidationInFlight: false, // no validations on Form
valid: (0, _formState3.isValid)(formState),
value: value
});
}

@@ -125,0 +139,0 @@ }]);

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

require("./formState");
var _formState2 = require("./formState");

@@ -132,5 +132,5 @@ var _shapedTree = require("./shapedTree");

_this.onSubmit = function () {
_this.onSubmit = function (extraData) {
_this.setState({ submitted: true });
_this.props.onSubmit(_this.state.formState[0]);
_this.props.onSubmit(_this.state.formState[0], extraData);
};

@@ -140,2 +140,3 @@

_this.setState({ formState: newState, pristine: false });
_this.props.onChange(newState[0]);
};

@@ -208,3 +209,11 @@

onValidation: this.updateTreeForValidation
}, this.onSubmit)
}, this.onSubmit, {
touched: (0, _formState2.getExtras)(formState).meta.touched,
changed: (0, _formState2.getExtras)(formState).meta.changed,
shouldShowErrors: getShouldShowError(this.props.feedbackStrategy)((0, _formState2.getExtras)(formState).meta),
unfilteredErrors: (0, _formState2.flatRootErrors)(formState),
asyncValidationInFlight: false, // no validations on Form
valid: (0, _formState2.isValid)(formState),
value: formState[0]
})
);

@@ -217,2 +226,6 @@ }

Form.defaultProps = {
onChange: function onChange() {},
onSubmit: function onSubmit() {}
};
exports.default = Form;

@@ -12,2 +12,3 @@ "use strict";

exports.getExtras = getExtras;
exports.flatRootErrors = flatRootErrors;
exports.objectChild = objectChild;

@@ -25,2 +26,3 @@ exports.arrayChild = arrayChild;

exports.replaceServerErrors = replaceServerErrors;
exports.isValid = isValid;

@@ -44,2 +46,15 @@ var _shapedTree = require("./shapedTree");

function flatRootErrors(formState) {
var errors = (0, _shapedTree.getRootData)(formState[1]).errors;
var flatErrors = [];
if (errors.client !== "pending") {
flatErrors = flatErrors.concat(errors.client);
}
if (errors.server !== "unchecked") {
flatErrors = flatErrors.concat(errors.server);
}
return flatErrors;
}
function objectChild(key, formState) {

@@ -222,2 +237,16 @@ var _formState = _slicedToArray(formState, 2),

return [formState[0], (0, _shapedTree.shapedZipWith)(replaceServerErrorsExtra, serverErrors, formState[1])];
}
// Is whole tree client valid?
// TODO(zach): This will have to change with asynchronous validations. We will
// need a "pending" value as well as an "unchecked" value.
// Currently, things in the tree which are not reflected in the React tree are
// marked "pending", which means they can be valid :grimace:.
function isValid(formState) {
return (0, _shapedTree.foldMapShapedTree)(function (_ref6) {
var client = _ref6.errors.client;
return client === "pending" || client.length === 0;
}, true, function (l, r) {
return l && r;
}, formState[1]);
}

@@ -125,4 +125,16 @@ "use strict";

value: function render() {
var formState = this.props.link.formState;
var shouldShowError = this.props.formContext.shouldShowError;
var links = makeLinks(this.props.link.formState, this.onChildChange, this.onChildBlur, this.onChildValidation);
return this.props.children(links);
return this.props.children(links, {
touched: (0, _formState3.getExtras)(formState).meta.touched,
changed: (0, _formState3.getExtras)(formState).meta.changed,
shouldShowErrors: shouldShowError((0, _formState3.getExtras)(formState).meta),
unfilteredErrors: (0, _formState3.flatRootErrors)(formState),
asyncValidationInFlight: false, // no validations on Form
valid: (0, _formState3.isValid)(formState),
value: formState[0]
});
}

@@ -129,0 +141,0 @@ }]);

@@ -25,2 +25,4 @@ "use strict";

exports.mapShapedTree = mapShapedTree;
exports.foldMapShapedTree = foldMapShapedTree;
exports.getRootData = getRootData;

@@ -263,2 +265,11 @@ var _tree = require("./tree");

return (0, _tree.mapTree)(f, tree);
}
// Fold a tree inorder
function foldMapShapedTree(mapper, mempty, mappend, tree) {
return (0, _tree.foldMapTree)(mapper, mempty, mappend, tree);
}
function getRootData(tree) {
return tree.data;
}

@@ -101,2 +101,32 @@ "use strict";

});
it("Passes additional information to its render function", function () {
var formState = (0, _tools.mockFormState)(["value"]);
// $FlowFixMe
formState[1].data.errors = {
server: ["A server error"],
client: ["A client error"]
};
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).toHaveBeenCalled();
expect(renderFn).toHaveBeenCalledWith(expect.anything(), expect.anything(), expect.objectContaining({
touched: false,
changed: false,
shouldShowErrors: expect.anything(),
unfilteredErrors: expect.arrayContaining(["A server error", "A client error"]),
valid: false,
asyncValidationInFlight: false,
value: ["value"]
}));
});
});

@@ -255,3 +285,3 @@ });

addField: expect.any(Function)
}));
}), expect.anything());
});

@@ -305,3 +335,3 @@ it("validates after entry is added", function () {

removeField: expect.any(Function)
}));
}), expect.anything());
});

@@ -355,3 +385,3 @@ it("validates after entry is removed", function () {

moveField: expect.any(Function)
}));
}), expect.anything());
});

@@ -358,0 +388,0 @@ it("validates after the entry is moved", function () {

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

var _Field = require("../Field");
var _Field2 = _interopRequireDefault(_Field);
var _tools = require("./tools");

@@ -86,2 +90,3 @@

});
it("calls the link onChange with new values and correct meta", function () {

@@ -111,2 +116,3 @@ var formState = (0, _tools.mockFormState)("Hello world.");

});
it("calls the link onBlur with correct meta", function () {

@@ -132,2 +138,3 @@ var formState = (0, _tools.mockFormState)("");

});
it("flattens errors for the inner component", function () {

@@ -150,2 +157,32 @@ var formState = (0, _tools.mockFormState)("");

});
it("Passes additional information to its render function", function () {
var formState = (0, _tools.mockFormState)(10);
// $FlowFixMe
formState[1].data.errors = {
server: ["A server error"],
client: ["A client error"]
};
var link = (0, _tools.mockLink)(formState);
var renderFn = jest.fn(function () {
return null;
});
_reactTestRenderer2.default.create(React.createElement(
_Field2.default,
{ link: link },
renderFn
));
expect(renderFn).toHaveBeenCalled();
expect(renderFn).toHaveBeenCalledWith(expect.anything(), expect.anything(), expect.anything(), expect.anything(), expect.objectContaining({
touched: false,
changed: false,
shouldShowErrors: expect.anything(),
unfilteredErrors: expect.arrayContaining(["A server error", "A client error"]),
valid: false,
asyncValidationInFlight: false,
value: 10
}));
});
});

@@ -535,2 +535,30 @@ "use strict";

});
it("Passes additional information to its render function", function () {
var renderFn = jest.fn(function () {
return null;
});
_reactTestRenderer2.default.create(React.createElement(
_Form2.default,
{
initialValue: 1,
feedbackStrategy: "OnFirstTouch",
onSubmit: jest.fn(),
serverErrors: { "/": ["Server error", "Another server error"] }
},
renderFn
));
expect(renderFn).toHaveBeenCalledWith(expect.anything(), expect.anything(), expect.objectContaining({
touched: false,
changed: false,
shouldShowErrors: false,
unfilteredErrors: expect.arrayContaining(["Server error", "Another server error"]),
// Currently, only care about client errors
valid: true,
asyncValidationInFlight: false,
value: 1
}));
});
});

@@ -567,4 +595,70 @@

expect(onSubmit).toHaveBeenCalledTimes(1);
expect(onSubmit).toHaveBeenLastCalledWith(1);
expect(onSubmit).toHaveBeenLastCalledWith(1, undefined);
});
it("Calls onSubmit with extra info when submitted", function () {
var onSubmit = jest.fn();
var renderFn = jest.fn();
_reactTestRenderer2.default.create(React.createElement(
_Form2.default,
{
initialValue: 1,
feedbackStrategy: "OnFirstTouch",
onSubmit: onSubmit,
serverErrors: { "/": ["Server error", "Another server error"] }
},
renderFn
));
expect(onSubmit).toHaveBeenCalledTimes(0);
var linkOnSubmit = renderFn.mock.calls[0][1];
linkOnSubmit("extra");
expect(onSubmit).toHaveBeenCalledTimes(1);
expect(onSubmit).toHaveBeenLastCalledWith(expect.anything(), "extra");
});
it("Enforces types on onSubmit", function () {
var onSubmit = function onSubmit() {};
_reactTestRenderer2.default.create(React.createElement(
_Form2.default,
{
initialValue: 1,
feedbackStrategy: "OnFirstTouch",
onSubmit: onSubmit,
serverErrors: { "/": ["Server error", "Another server error"] }
},
function (_, onSubmit) {
// $ExpectError
onSubmit();
// $ExpectError
onSubmit("hello");
onSubmit("extra");
}
));
});
it("Calls onChange when the value is changed", function () {
var onChange = jest.fn();
var renderFn = jest.fn(function () {
return null;
});
_reactTestRenderer2.default.create(React.createElement(
_Form2.default,
{
initialValue: 1,
feedbackStrategy: "OnFirstTouch",
onChange: onChange,
serverErrors: { "/": ["Server error", "Another server error"] }
},
renderFn
));
var link = renderFn.mock.calls[0][0];
var nextFormState = (0, _tools.mockFormState)(2);
link.onChange(nextFormState);
expect(onChange).toHaveBeenCalledWith(2);
});
});

@@ -101,2 +101,32 @@ "use strict";

});
it("Passes additional information to its render function", function () {
var formState = (0, _tools.mockFormState)({ inner: "value" });
// $FlowFixMe
formState[1].data.errors = {
server: ["A server error"],
client: ["A client error"]
};
var link = (0, _tools.mockLink)(formState);
var renderFn = jest.fn(function () {
return null;
});
_reactTestRenderer2.default.create(React.createElement(
_ObjectField2.default,
{ link: link },
renderFn
));
expect(renderFn).toHaveBeenCalled();
expect(renderFn).toHaveBeenCalledWith(expect.anything(), expect.objectContaining({
touched: false,
changed: false,
shouldShowErrors: expect.anything(),
unfilteredErrors: expect.arrayContaining(["A server error", "A client error"]),
valid: false,
asyncValidationInFlight: false,
value: { inner: "value" }
}));
});
});

@@ -103,0 +133,0 @@ });

@@ -14,2 +14,3 @@ "use strict";

exports.mapTree = mapTree;
exports.foldMapTree = foldMapTree;

@@ -124,2 +125,19 @@ var _set = require("./utils/set");

}
}
// Fold a tree preorder
function foldMapTree(mapper, mempty, mappend, tree) {
if (tree.type === "leaf") {
return mapper(tree.data);
} else if (tree.type === "array") {
var foldedChildren = tree.children.reduce(function (memo, childTree) {
return mappend(memo, foldMapTree(mapper, mempty, mappend, childTree));
}, mempty);
return mappend(mapper(tree.data), foldedChildren);
} else {
var _foldedChildren = Object.keys(tree.children).reduce(function (memo, key) {
return mappend(memo, foldMapTree(mapper, mempty, mappend, tree.children[key]));
}, mempty);
return mappend(mapper(tree.data), _foldedChildren);
}
}
{
"name": "formula-one",
"version": "0.2.1",
"version": "0.3.0",
"description": "Strongly-typed React form state management",

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

@@ -5,3 +5,9 @@ // @flow strict

import type {FieldLink, Validation, Extras, ClientErrors} from "./types";
import type {
FieldLink,
Validation,
Extras,
ClientErrors,
AdditionalRenderInfo,
} from "./types";
import {cleanErrors, cleanMeta} from "./types";

@@ -28,2 +34,4 @@ import {

getExtras,
flatRootErrors,
isValid,
} from "./formState";

@@ -40,7 +48,8 @@

links: Links<E>,
{
arrayOperations: {
addField: (index: number, value: E) => void,
removeField: (index: number) => void,
moveField: (oldIndex: number, newIndex: number) => void,
}
},
additionalInfo: AdditionalRenderInfo<Array<E>>
) => React.Node,

@@ -193,2 +202,3 @@ |};

const {formState} = this.props.link;
const {shouldShowError} = this.props.formContext;

@@ -201,7 +211,19 @@ const links = makeLinks(

);
return this.props.children(links, {
addField: this.addChildField,
removeField: this.removeChildField,
moveField: this.moveChildField,
});
return this.props.children(
links,
{
addField: this.addChildField,
removeField: this.removeChildField,
moveField: this.moveChildField,
},
{
touched: getExtras(formState).meta.touched,
changed: getExtras(formState).meta.changed,
shouldShowErrors: shouldShowError(getExtras(formState).meta),
unfilteredErrors: flatRootErrors(formState),
asyncValidationInFlight: false, // no validations on Form
valid: isValid(formState),
value: formState[0],
}
);
}

@@ -208,0 +230,0 @@ }

// @flow strict
import * as React from "react";
import type {FieldLink, Validation, Err} from "./types";
import type {FieldLink, Validation, Err, AdditionalRenderInfo} from "./types";
import {mapRoot} from "./shapedTree";
import {FormContext, type FormContextPayload} from "./Form";
import {setExtrasTouched, getExtras, setChanged, validate} from "./formState";
import {
setExtrasTouched,
getExtras,
setChanged,
validate,
isValid,
} from "./formState";

@@ -17,3 +23,4 @@ type Props<T> = {|

onChange: (T) => void,
onBlur: () => void
onBlur: () => void,
additionalInfo: AdditionalRenderInfo<T>
) => React.Node,

@@ -72,8 +79,20 @@ |};

render() {
const [value] = this.props.link.formState;
const {meta, errors} = getExtras(this.props.link.formState);
const {formState} = this.props.link;
const [value] = formState;
const {meta, errors} = getExtras(formState);
const {shouldShowError} = this.props.formContext;
const flatErrors = this.props.formContext.shouldShowError(meta)
? getErrors(errors)
: [];
return this.props.children(value, flatErrors, this.onChange, this.onBlur);
return this.props.children(value, flatErrors, this.onChange, this.onBlur, {
touched: meta.touched,
changed: meta.changed,
shouldShowErrors: shouldShowError(meta),
unfilteredErrors: getErrors(errors),
asyncValidationInFlight: false, // no validations on Form
valid: isValid(formState),
value,
});
}

@@ -80,0 +99,0 @@ }

@@ -12,5 +12,6 @@ // @flow strict

ClientErrors,
AdditionalRenderInfo,
} from "./types";
import {cleanMeta, cleanErrors} from "./types";
import {type FormState} from "./formState";
import {type FormState, isValid, getExtras, flatRootErrors} from "./formState";
import {

@@ -100,3 +101,3 @@ type ShapedTree,

function getShouldShowError(strategy: FeedbackStrategy) {
function getShouldShowError(strategy: FeedbackStrategy): MetaField => boolean {
switch (strategy) {

@@ -114,9 +115,14 @@ case "Always":

type Props<T> = {
type Props<T, ExtraSubmitData> = {
// This is *only* used to intialize the form. Further changes will be ignored
+initialValue: T,
+feedbackStrategy: FeedbackStrategy,
+onSubmit: T => void,
+onSubmit: (T, ExtraSubmitData) => void,
+onChange: T => void,
+serverErrors: null | {[path: string]: Array<string>},
+children: (link: FieldLink<T>, onSubmit: () => void) => React.Node,
+children: (
link: FieldLink<T>,
onSubmit: (ExtraSubmitData) => void,
additionalInfo: AdditionalRenderInfo<T>
) => React.Node,
};

@@ -129,4 +135,15 @@ type State<T> = {

};
export default class Form<T> extends React.Component<Props<T>, State<T>> {
static getDerivedStateFromProps(props: Props<T>, state: State<T>) {
export default class Form<T, ExtraSubmitData> extends React.Component<
Props<T, ExtraSubmitData>,
State<T>
> {
static defaultProps = {
onChange: () => {},
onSubmit: () => {},
};
static getDerivedStateFromProps(
props: Props<T, ExtraSubmitData>,
state: State<T>
) {
if (props.serverErrors !== state.oldServerErrors) {

@@ -145,3 +162,3 @@ const newFormState = applyServerErrorsToFormState(

constructor(props: Props<T>) {
constructor(props: Props<T, ExtraSubmitData>) {
super(props);

@@ -165,5 +182,7 @@

onSubmit = () => {
onSubmit: (extraData: ExtraSubmitData) => void = (
extraData: ExtraSubmitData
) => {
this.setState({submitted: true});
this.props.onSubmit(this.state.formState[0]);
this.props.onSubmit(this.state.formState[0], extraData);
};

@@ -175,2 +194,3 @@

this.setState({formState: newState, pristine: false});
this.props.onChange(newState[0]);
};

@@ -219,3 +239,14 @@

},
this.onSubmit
this.onSubmit,
{
touched: getExtras(formState).meta.touched,
changed: getExtras(formState).meta.changed,
shouldShowErrors: getShouldShowError(this.props.feedbackStrategy)(
getExtras(formState).meta
),
unfilteredErrors: flatRootErrors(formState),
asyncValidationInFlight: false, // no validations on Form
valid: isValid(formState),
value: formState[0],
}
)}

@@ -222,0 +253,0 @@ </FormContext.Provider>

@@ -13,2 +13,4 @@ // @flow strict

shapedZipWith,
foldMapShapedTree,
getRootData,
} from "./shapedTree";

@@ -26,2 +28,15 @@ import type {Extras, ClientErrors, Validation, ServerErrors} from "./types";

export function flatRootErrors<T>(formState: FormState<T>): Array<string> {
const errors = getRootData(formState[1]).errors;
let flatErrors = [];
if (errors.client !== "pending") {
flatErrors = flatErrors.concat(errors.client);
}
if (errors.server !== "unchecked") {
flatErrors = flatErrors.concat(errors.server);
}
return flatErrors;
}
export function objectChild<T: {}, V>(

@@ -239,1 +254,15 @@ key: string,

}
// Is whole tree client valid?
// TODO(zach): This will have to change with asynchronous validations. We will
// need a "pending" value as well as an "unchecked" value.
// Currently, things in the tree which are not reflected in the React tree are
// marked "pending", which means they can be valid :grimace:.
export function isValid<T>(formState: FormState<T>): boolean {
return foldMapShapedTree(
({errors: {client}}) => client === "pending" || client.length === 0,
true,
(l, r) => l && r,
formState[1]
);
}

@@ -5,3 +5,9 @@ // @flow strict

import type {FieldLink, Validation, Extras, ClientErrors} from "./types";
import type {
FieldLink,
Validation,
Extras,
ClientErrors,
AdditionalRenderInfo,
} from "./types";
import {type FormContextPayload} from "./Form";

@@ -17,2 +23,4 @@ import {FormContext} from "./Form";

getExtras,
flatRootErrors,
isValid,
} from "./formState";

@@ -33,3 +41,6 @@ import {

+validation: Validation<T>,
+children: (links: Links<T>) => React.Node,
+children: (
links: Links<T>,
additionalInfo: AdditionalRenderInfo<T>
) => React.Node,
|};

@@ -130,2 +141,5 @@

render() {
const {formState} = this.props.link;
const {shouldShowError} = this.props.formContext;
const links = makeLinks(

@@ -137,3 +151,11 @@ this.props.link.formState,

);
return this.props.children(links);
return this.props.children(links, {
touched: getExtras(formState).meta.touched,
changed: getExtras(formState).meta.changed,
shouldShowErrors: shouldShowError(getExtras(formState).meta),
unfilteredErrors: flatRootErrors(formState),
asyncValidationInFlight: false, // no validations on Form
valid: isValid(formState),
value: formState[0],
});
}

@@ -140,0 +162,0 @@ }

// @flow strict
import {type Tree, type Path, leaf, strictZipWith, mapTree} from "./tree";
import {
type Tree,
type Path,
leaf,
strictZipWith,
mapTree,
foldMapTree,
} from "./tree";
import invariant from "./utils/invariant";

@@ -302,1 +309,15 @@ import {replaceAt} from "./utils/array";

}
// Fold a tree inorder
export function foldMapShapedTree<T, Node, Folded>(
mapper: Node => Folded,
mempty: Folded,
mappend: (Folded, Folded) => Folded,
tree: ShapedTree<T, Node>
): Folded {
return foldMapTree(mapper, mempty, mappend, tree);
}
export function getRootData<T, Node>(tree: ShapedTree<T, Node>): Node {
return tree.data;
}

@@ -66,2 +66,33 @@ // @flow

});
it("Passes additional information to its render function", () => {
const formState = mockFormState(["value"]);
// $FlowFixMe
formState[1].data.errors = {
server: ["A server error"],
client: ["A client error"],
};
const link = mockLink(formState);
const renderFn = jest.fn(() => null);
TestRenderer.create(<ArrayField link={link}>{renderFn}</ArrayField>);
expect(renderFn).toHaveBeenCalled();
expect(renderFn).toHaveBeenCalledWith(
expect.anything(),
expect.anything(),
expect.objectContaining({
touched: false,
changed: false,
shouldShowErrors: expect.anything(),
unfilteredErrors: expect.arrayContaining([
"A server error",
"A client error",
]),
valid: false,
asyncValidationInFlight: false,
value: ["value"],
})
);
});
});

@@ -184,3 +215,4 @@ });

addField: expect.any(Function),
})
}),
expect.anything()
);

@@ -229,3 +261,4 @@ });

removeField: expect.any(Function),
})
}),
expect.anything()
);

@@ -269,3 +302,4 @@ });

moveField: expect.any(Function),
})
}),
expect.anything()
);

@@ -272,0 +306,0 @@ });

@@ -6,2 +6,3 @@ // @flow

import Field from "../Field";
import {mockFormState, mockLink} from "./tools";

@@ -56,2 +57,3 @@ import TestField, {TestInput} from "./TestField";

});
it("calls the link onChange with new values and correct meta", () => {

@@ -78,2 +80,3 @@ const formState = mockFormState("Hello world.");

});
it("calls the link onBlur with correct meta", () => {

@@ -99,2 +102,3 @@ const formState = mockFormState("");

});
it("flattens errors for the inner component", () => {

@@ -125,2 +129,35 @@ let formState = mockFormState("");

});
it("Passes additional information to its render function", () => {
const formState = mockFormState(10);
// $FlowFixMe
formState[1].data.errors = {
server: ["A server error"],
client: ["A client error"],
};
const link = mockLink(formState);
const renderFn = jest.fn(() => null);
TestRenderer.create(<Field link={link}>{renderFn}</Field>);
expect(renderFn).toHaveBeenCalled();
expect(renderFn).toHaveBeenCalledWith(
expect.anything(),
expect.anything(),
expect.anything(),
expect.anything(),
expect.objectContaining({
touched: false,
changed: false,
shouldShowErrors: expect.anything(),
unfilteredErrors: expect.arrayContaining([
"A server error",
"A client error",
]),
valid: false,
asyncValidationInFlight: false,
value: 10,
})
);
});
});

@@ -428,2 +428,35 @@ // @flow

});
it("Passes additional information to its render function", () => {
const renderFn = jest.fn(() => null);
TestRenderer.create(
<Form
initialValue={1}
feedbackStrategy="OnFirstTouch"
onSubmit={jest.fn()}
serverErrors={{"/": ["Server error", "Another server error"]}}
>
{renderFn}
</Form>
);
expect(renderFn).toHaveBeenCalledWith(
expect.anything(),
expect.anything(),
expect.objectContaining({
touched: false,
changed: false,
shouldShowErrors: false,
unfilteredErrors: expect.arrayContaining([
"Server error",
"Another server error",
]),
// Currently, only care about client errors
valid: true,
asyncValidationInFlight: false,
value: 1,
})
);
});
});

@@ -454,4 +487,68 @@

expect(onSubmit).toHaveBeenCalledTimes(1);
expect(onSubmit).toHaveBeenLastCalledWith(1);
expect(onSubmit).toHaveBeenLastCalledWith(1, undefined);
});
it("Calls onSubmit with extra info when submitted", () => {
const onSubmit = jest.fn();
const renderFn = jest.fn();
TestRenderer.create(
<Form
initialValue={1}
feedbackStrategy="OnFirstTouch"
onSubmit={onSubmit}
serverErrors={{"/": ["Server error", "Another server error"]}}
>
{renderFn}
</Form>
);
expect(onSubmit).toHaveBeenCalledTimes(0);
const linkOnSubmit = renderFn.mock.calls[0][1];
linkOnSubmit("extra");
expect(onSubmit).toHaveBeenCalledTimes(1);
expect(onSubmit).toHaveBeenLastCalledWith(expect.anything(), "extra");
});
it("Enforces types on onSubmit", () => {
const onSubmit: (value: number, extra: "extra") => void = () => {};
TestRenderer.create(
<Form
initialValue={1}
feedbackStrategy="OnFirstTouch"
onSubmit={onSubmit}
serverErrors={{"/": ["Server error", "Another server error"]}}
>
{(_, onSubmit) => {
// $ExpectError
onSubmit();
// $ExpectError
onSubmit("hello");
onSubmit("extra");
}}
</Form>
);
});
it("Calls onChange when the value is changed", () => {
const onChange = jest.fn();
const renderFn = jest.fn(() => null);
TestRenderer.create(
<Form
initialValue={1}
feedbackStrategy="OnFirstTouch"
onChange={onChange}
serverErrors={{"/": ["Server error", "Another server error"]}}
>
{renderFn}
</Form>
);
const link = renderFn.mock.calls[0][0];
const nextFormState = mockFormState(2);
link.onChange(nextFormState);
expect(onChange).toHaveBeenCalledWith(2);
});
});

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

});
it("Passes additional information to its render function", () => {
const formState = mockFormState({inner: "value"});
// $FlowFixMe
formState[1].data.errors = {
server: ["A server error"],
client: ["A client error"],
};
const link = mockLink(formState);
const renderFn = jest.fn(() => null);
TestRenderer.create(<ObjectField link={link}>{renderFn}</ObjectField>);
expect(renderFn).toHaveBeenCalled();
expect(renderFn).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({
touched: false,
changed: false,
shouldShowErrors: expect.anything(),
unfilteredErrors: expect.arrayContaining([
"A server error",
"A client error",
]),
valid: false,
asyncValidationInFlight: false,
value: {inner: "value"},
})
);
});
});

@@ -68,0 +98,0 @@ });

@@ -142,1 +142,27 @@ // @flow strict

}
// Fold a tree preorder
export function foldMapTree<T, Folded>(
mapper: T => Folded,
mempty: Folded,
mappend: (Folded, Folded) => Folded,
tree: Tree<T>
): Folded {
if (tree.type === "leaf") {
return mapper(tree.data);
} else if (tree.type === "array") {
const foldedChildren = tree.children.reduce(
(memo, childTree) =>
mappend(memo, foldMapTree(mapper, mempty, mappend, childTree)),
mempty
);
return mappend(mapper(tree.data), foldedChildren);
} else {
const foldedChildren = Object.keys(tree.children).reduce(
(memo, key) =>
mappend(memo, foldMapTree(mapper, mempty, mappend, tree.children[key])),
mempty
);
return mappend(mapper(tree.data), foldedChildren);
}
}

@@ -42,2 +42,12 @@ // @flow strict

export type AdditionalRenderInfo<T> = {|
+touched: boolean,
+changed: boolean,
+shouldShowErrors: boolean,
+unfilteredErrors: $ReadOnlyArray<string>,
+valid: boolean,
+asyncValidationInFlight: boolean,
+value: T,
|};
export type OnChange<T> = (FormState<T>) => void;

@@ -44,0 +54,0 @@ export type OnBlur<T> = (ShapedTree<T, Extras>) => void;

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