@homebound/form-state 2.2.9 → 2.2.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/FormStateApp.js +5 -5
- package/dist/formState.d.ts +3 -59
- package/dist/formState.js +7 -115
- package/dist/formState.test.js +34 -391
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/useFormState.d.ts +57 -0
- package/dist/useFormState.js +123 -0
- package/dist/useFormState.test.d.ts +1 -0
- package/dist/useFormState.test.js +474 -0
- package/package.json +7 -3
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
4
|
+
const rtl_utils_1 = require("@homebound/rtl-utils");
|
|
5
|
+
const mobx_1 = require("mobx");
|
|
6
|
+
const mobx_react_1 = require("mobx-react");
|
|
7
|
+
const react_1 = require("react");
|
|
8
|
+
const formState_1 = require("./formState");
|
|
9
|
+
const useFormState_1 = require("./useFormState");
|
|
10
|
+
describe("useFormState", () => {
|
|
11
|
+
it("calls init.map if init.input is defined", async () => {
|
|
12
|
+
// Given a component
|
|
13
|
+
function TestComponent() {
|
|
14
|
+
const config = { firstName: { type: "value" } };
|
|
15
|
+
// And we have query data that may or may not be defined
|
|
16
|
+
const data = Math.random() >= 0 ? { firstName: "bob" } : undefined;
|
|
17
|
+
// Then the lambda is passed the "de-undefined" data
|
|
18
|
+
const form = useFormState_1.useFormState({
|
|
19
|
+
config,
|
|
20
|
+
init: { input: data, map: (d) => ({ firstName: d.firstName }) },
|
|
21
|
+
});
|
|
22
|
+
return jsx_runtime_1.jsx("div", { children: form.firstName.value }, void 0);
|
|
23
|
+
}
|
|
24
|
+
const r = await rtl_utils_1.render(jsx_runtime_1.jsx(TestComponent, {}, void 0));
|
|
25
|
+
expect(r.baseElement).toHaveTextContent("bob");
|
|
26
|
+
});
|
|
27
|
+
it("memoizes on init.input being an array", async () => {
|
|
28
|
+
const config = { firstName: { type: "value" } };
|
|
29
|
+
function TestComponent() {
|
|
30
|
+
const [, setTick] = react_1.useState(0);
|
|
31
|
+
const form = useFormState_1.useFormState({
|
|
32
|
+
config,
|
|
33
|
+
init: { input: ["a", "b"], map: ([a, b]) => ({ firstName: a + b }) },
|
|
34
|
+
});
|
|
35
|
+
const onClick = () => [form.firstName.set("fred"), setTick(1)];
|
|
36
|
+
return (jsx_runtime_1.jsxs("div", { children: [jsx_runtime_1.jsx("button", { "data-testid": "change", onClick: onClick }, void 0),
|
|
37
|
+
jsx_runtime_1.jsx("div", Object.assign({ "data-testid": "firstName" }, { children: form.firstName.value }), void 0)] }, void 0));
|
|
38
|
+
}
|
|
39
|
+
const r = await rtl_utils_1.render(jsx_runtime_1.jsx(TestComponent, {}, void 0));
|
|
40
|
+
expect(r.firstName()).toHaveTextContent("ab");
|
|
41
|
+
rtl_utils_1.click(r.change);
|
|
42
|
+
expect(r.firstName()).toHaveTextContent("fred");
|
|
43
|
+
});
|
|
44
|
+
it("uses default if init.input is undefined", async () => {
|
|
45
|
+
// Given a component
|
|
46
|
+
function TestComponent() {
|
|
47
|
+
const config = { firstName: { type: "value" } };
|
|
48
|
+
// And we have query data that may or may not be defined (but is actually undefined)
|
|
49
|
+
const data = Math.random() >= 0 ? undefined : { firstName: "bob" };
|
|
50
|
+
const form = useFormState_1.useFormState({
|
|
51
|
+
config,
|
|
52
|
+
init: { input: data, map: (d) => ({ firstName: d.firstName }) },
|
|
53
|
+
});
|
|
54
|
+
return jsx_runtime_1.jsx("div", { children: form.firstName.value }, void 0);
|
|
55
|
+
}
|
|
56
|
+
const r = await rtl_utils_1.render(jsx_runtime_1.jsx(TestComponent, {}, void 0));
|
|
57
|
+
// Then we init.map wasn't called, and we used {} instead
|
|
58
|
+
expect(r.baseElement.textContent).toEqual("");
|
|
59
|
+
});
|
|
60
|
+
it("uses custom init.ifUndefined if init.input is undefined", async () => {
|
|
61
|
+
// Given a component
|
|
62
|
+
function TestComponent() {
|
|
63
|
+
const config = {
|
|
64
|
+
id: { type: "value" },
|
|
65
|
+
firstName: { type: "value" },
|
|
66
|
+
};
|
|
67
|
+
// And we have query data that may or may not be defined (but is actually undefined)
|
|
68
|
+
const data = Math.random() >= 0 ? undefined : { firstName: "bob" };
|
|
69
|
+
const form = useFormState_1.useFormState({
|
|
70
|
+
config,
|
|
71
|
+
// And we pass `ifUndefined`
|
|
72
|
+
init: {
|
|
73
|
+
input: data,
|
|
74
|
+
map: (d) => ({ firstName: d.firstName }),
|
|
75
|
+
ifUndefined: { firstName: "default" },
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
return (jsx_runtime_1.jsxs("div", { children: [jsx_runtime_1.jsx("div", Object.assign({ "data-testid": "firstName" }, { children: form.firstName.value }), void 0),
|
|
79
|
+
jsx_runtime_1.jsx("div", Object.assign({ "data-testid": "changedValue" }, { children: JSON.stringify(form.changedValue) }), void 0)] }, void 0));
|
|
80
|
+
}
|
|
81
|
+
const r = await rtl_utils_1.render(jsx_runtime_1.jsx(TestComponent, {}, void 0));
|
|
82
|
+
// Then we use the ifUndefined value
|
|
83
|
+
expect(r.firstName.textContent).toEqual("default");
|
|
84
|
+
expect(r.changedValue.textContent).toEqual(JSON.stringify({ firstName: "default" }));
|
|
85
|
+
});
|
|
86
|
+
it("uses init if set as a value", async () => {
|
|
87
|
+
const config = { firstName: { type: "value" } };
|
|
88
|
+
function TestComponent() {
|
|
89
|
+
const [, setTick] = react_1.useState(0);
|
|
90
|
+
const form = useFormState_1.useFormState({
|
|
91
|
+
config,
|
|
92
|
+
// That's using a raw init value
|
|
93
|
+
init: { firstName: "bob" },
|
|
94
|
+
});
|
|
95
|
+
return (jsx_runtime_1.jsxs("div", { children: [jsx_runtime_1.jsx("button", { "data-testid": "change", onClick: () => {
|
|
96
|
+
// When that value changes
|
|
97
|
+
form.firstName.set("fred");
|
|
98
|
+
// And also we re-render the component
|
|
99
|
+
setTick(1);
|
|
100
|
+
} }, void 0),
|
|
101
|
+
jsx_runtime_1.jsx("div", Object.assign({ "data-testid": "firstName" }, { children: form.firstName.value }), void 0)] }, void 0));
|
|
102
|
+
}
|
|
103
|
+
const r = await rtl_utils_1.render(jsx_runtime_1.jsx(TestComponent, {}, void 0));
|
|
104
|
+
expect(r.firstName()).toHaveTextContent("bob");
|
|
105
|
+
rtl_utils_1.click(r.change);
|
|
106
|
+
// Then the change didn't get dropped due to init being unstable
|
|
107
|
+
expect(r.firstName()).toHaveTextContent("fred");
|
|
108
|
+
});
|
|
109
|
+
it("doesn't required an init value", async () => {
|
|
110
|
+
function TestComponent() {
|
|
111
|
+
const config = { firstName: { type: "value" } };
|
|
112
|
+
const form = useFormState_1.useFormState({ config });
|
|
113
|
+
return jsx_runtime_1.jsx("div", { children: form.firstName.value }, void 0);
|
|
114
|
+
}
|
|
115
|
+
const r = await rtl_utils_1.render(jsx_runtime_1.jsx(TestComponent, {}, void 0));
|
|
116
|
+
expect(r.baseElement.textContent).toEqual("");
|
|
117
|
+
});
|
|
118
|
+
it("keeps local changed values when a query refreshes", async () => {
|
|
119
|
+
// Given a component
|
|
120
|
+
function TestComponent() {
|
|
121
|
+
const config = authorWithAddressAndBooksConfig;
|
|
122
|
+
// And we have two sets of data
|
|
123
|
+
const data1 = {
|
|
124
|
+
id: "a:1",
|
|
125
|
+
firstName: "f1",
|
|
126
|
+
lastName: "l1",
|
|
127
|
+
address: { id: "address:1", street: "s1", city: "c1" },
|
|
128
|
+
books: [
|
|
129
|
+
{ id: "b:1", title: "a1" },
|
|
130
|
+
{ id: "b:2", title: "b1" },
|
|
131
|
+
],
|
|
132
|
+
};
|
|
133
|
+
const data2 = {
|
|
134
|
+
id: "a:1",
|
|
135
|
+
firstName: "f2",
|
|
136
|
+
lastName: "l2",
|
|
137
|
+
address: { id: "address:1", street: "s2", city: "c2" },
|
|
138
|
+
books: [
|
|
139
|
+
{ id: "b:1", title: "a2" },
|
|
140
|
+
{ id: "b:2", title: "b2" },
|
|
141
|
+
{ id: "b:3", title: "b3" },
|
|
142
|
+
],
|
|
143
|
+
};
|
|
144
|
+
// Eventually our local values are saved
|
|
145
|
+
const data3 = {
|
|
146
|
+
id: "a:1",
|
|
147
|
+
firstName: "local",
|
|
148
|
+
lastName: "l2",
|
|
149
|
+
address: { id: "address:1", street: "local", city: "c2" },
|
|
150
|
+
books: [
|
|
151
|
+
{ id: "b:1", title: "local" },
|
|
152
|
+
{ id: "b:2", title: "b2" },
|
|
153
|
+
{ id: "b:3", title: "b3" },
|
|
154
|
+
],
|
|
155
|
+
};
|
|
156
|
+
// And we start out with data1
|
|
157
|
+
const [data, setData] = react_1.useState(data1);
|
|
158
|
+
const form = useFormState_1.useFormState({ config, init: { input: data, map: (d) => d } });
|
|
159
|
+
function makeLocalChanges() {
|
|
160
|
+
form.firstName.value = "local";
|
|
161
|
+
form.address.street.value = "local";
|
|
162
|
+
form.books.rows[0].title.value = "local";
|
|
163
|
+
}
|
|
164
|
+
return (jsx_runtime_1.jsx(mobx_react_1.Observer, { children: () => (jsx_runtime_1.jsxs("div", { children: [jsx_runtime_1.jsx("div", Object.assign({ "data-testid": "firstName" }, { children: form.firstName.value }), void 0),
|
|
165
|
+
jsx_runtime_1.jsx("div", Object.assign({ "data-testid": "lastName" }, { children: form.lastName.value }), void 0),
|
|
166
|
+
jsx_runtime_1.jsx("div", Object.assign({ "data-testid": "street" }, { children: form.address.street.value }), void 0),
|
|
167
|
+
jsx_runtime_1.jsx("div", Object.assign({ "data-testid": "city" }, { children: form.address.city.value }), void 0),
|
|
168
|
+
jsx_runtime_1.jsx("div", Object.assign({ "data-testid": "title1" }, { children: form.books.rows[0].title.value }), void 0),
|
|
169
|
+
jsx_runtime_1.jsx("div", Object.assign({ "data-testid": "title2" }, { children: form.books.rows[1].title.value }), void 0),
|
|
170
|
+
jsx_runtime_1.jsx("div", Object.assign({ "data-testid": "booksLength" }, { children: form.books.rows.length }), void 0),
|
|
171
|
+
jsx_runtime_1.jsx("button", { "data-testid": "makeLocalChanges", onClick: makeLocalChanges }, void 0),
|
|
172
|
+
jsx_runtime_1.jsx("button", { "data-testid": "refreshData", onClick: () => setData(data2) }, void 0),
|
|
173
|
+
jsx_runtime_1.jsx("button", { "data-testid": "saveData", onClick: () => setData(data3) }, void 0),
|
|
174
|
+
jsx_runtime_1.jsx("div", Object.assign({ "data-testid": "changedValue" }, { children: JSON.stringify(form.changedValue) }), void 0)] }, void 0)) }, void 0));
|
|
175
|
+
}
|
|
176
|
+
// And we start out with the initial query data
|
|
177
|
+
const r = await rtl_utils_1.render(jsx_runtime_1.jsx(TestComponent, {}, void 0));
|
|
178
|
+
expect(r.firstName().textContent).toEqual("f1");
|
|
179
|
+
expect(r.street().textContent).toEqual("s1");
|
|
180
|
+
expect(r.title1().textContent).toEqual("a1");
|
|
181
|
+
// When we make some local changes
|
|
182
|
+
rtl_utils_1.click(r.makeLocalChanges);
|
|
183
|
+
// Then we see them
|
|
184
|
+
expect(r.firstName().textContent).toEqual("local");
|
|
185
|
+
expect(r.street().textContent).toEqual("local");
|
|
186
|
+
expect(r.title1().textContent).toEqual("local");
|
|
187
|
+
expect(JSON.parse(r.changedValue().textContent)).toEqual({
|
|
188
|
+
id: "a:1",
|
|
189
|
+
address: { id: "address:1", street: "local" },
|
|
190
|
+
books: [{ id: "b:1", title: "local" }, { id: "b:2" }],
|
|
191
|
+
firstName: "local",
|
|
192
|
+
});
|
|
193
|
+
// And when the new query is ran i.e. due to a cache refresh
|
|
194
|
+
rtl_utils_1.click(r.refreshData);
|
|
195
|
+
// Then we kept our local changes
|
|
196
|
+
expect(r.firstName().textContent).toEqual("local");
|
|
197
|
+
expect(r.street().textContent).toEqual("local");
|
|
198
|
+
expect(r.title1().textContent).toEqual("local");
|
|
199
|
+
// But we also see the new data for fields we have not changed
|
|
200
|
+
expect(r.lastName().textContent).toEqual("l2");
|
|
201
|
+
expect(r.city().textContent).toEqual("c2");
|
|
202
|
+
expect(r.title2().textContent).toEqual("b2");
|
|
203
|
+
expect(r.booksLength().textContent).toEqual("3");
|
|
204
|
+
expect(JSON.parse(r.changedValue().textContent)).toEqual({
|
|
205
|
+
id: "a:1",
|
|
206
|
+
address: { id: "address:1", street: "local" },
|
|
207
|
+
books: [{ id: "b:1", title: "local" }, { id: "b:2" }, { id: "b:3" }],
|
|
208
|
+
firstName: "local",
|
|
209
|
+
});
|
|
210
|
+
// And then when our mutation results come back
|
|
211
|
+
rtl_utils_1.click(r.saveData);
|
|
212
|
+
// Then changedValue doesn't show our local changes anymore
|
|
213
|
+
expect(JSON.parse(r.changedValue().textContent)).toEqual({
|
|
214
|
+
id: "a:1",
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
it("useFormState can accept new data while read only", async () => {
|
|
218
|
+
// Given a component
|
|
219
|
+
function TestComponent() {
|
|
220
|
+
const config = authorWithAddressAndBooksConfig;
|
|
221
|
+
// And we have two sets of data
|
|
222
|
+
const data1 = {
|
|
223
|
+
firstName: "f1",
|
|
224
|
+
address: { street: "s1" },
|
|
225
|
+
books: [{ title: "a1" }],
|
|
226
|
+
};
|
|
227
|
+
const data2 = {
|
|
228
|
+
firstName: "f2",
|
|
229
|
+
address: { street: "s2" },
|
|
230
|
+
books: [{ title: "a2" }],
|
|
231
|
+
};
|
|
232
|
+
// And we start out with data1
|
|
233
|
+
const [data, setData] = react_1.useState(data1);
|
|
234
|
+
const form = useFormState_1.useFormState({
|
|
235
|
+
config,
|
|
236
|
+
init: { input: data, map: (d) => d },
|
|
237
|
+
// And the form is read only
|
|
238
|
+
readOnly: true,
|
|
239
|
+
});
|
|
240
|
+
return (jsx_runtime_1.jsx(mobx_react_1.Observer, { children: () => (jsx_runtime_1.jsxs("div", { children: [jsx_runtime_1.jsx("div", Object.assign({ "data-testid": "firstName" }, { children: form.firstName.value }), void 0),
|
|
241
|
+
jsx_runtime_1.jsx("div", Object.assign({ "data-testid": "street" }, { children: form.address.street.value }), void 0),
|
|
242
|
+
jsx_runtime_1.jsx("div", Object.assign({ "data-testid": "title1" }, { children: form.books.rows[0].title.value }), void 0),
|
|
243
|
+
jsx_runtime_1.jsx("button", { "data-testid": "refreshData", onClick: () => setData(data2) }, void 0)] }, void 0)) }, void 0));
|
|
244
|
+
}
|
|
245
|
+
// And we start out with the initial query data
|
|
246
|
+
const r = await rtl_utils_1.render(jsx_runtime_1.jsx(TestComponent, {}, void 0));
|
|
247
|
+
expect(r.firstName().textContent).toEqual("f1");
|
|
248
|
+
expect(r.street().textContent).toEqual("s1");
|
|
249
|
+
expect(r.title1().textContent).toEqual("a1");
|
|
250
|
+
// When the new query is ran i.e. due to a cache refresh
|
|
251
|
+
rtl_utils_1.click(r.refreshData);
|
|
252
|
+
// Then we see the latest data
|
|
253
|
+
expect(r.firstName().textContent).toEqual("f2");
|
|
254
|
+
expect(r.street().textContent).toEqual("s2");
|
|
255
|
+
expect(r.title1().textContent).toEqual("a2");
|
|
256
|
+
});
|
|
257
|
+
it("useFormState can accept new data with computed fields", async () => {
|
|
258
|
+
// Given a component
|
|
259
|
+
function TestComponent() {
|
|
260
|
+
// And it's using a class/mobx proxy as the basis for the data
|
|
261
|
+
class AuthorRow {
|
|
262
|
+
constructor(firstName, lastName) {
|
|
263
|
+
this.firstName = firstName;
|
|
264
|
+
this.lastName = lastName;
|
|
265
|
+
}
|
|
266
|
+
get fullName() {
|
|
267
|
+
return this.firstName + " " + this.lastName;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
// And we have two sets of data
|
|
271
|
+
const data1 = { firstName: "f1", lastName: "l1" };
|
|
272
|
+
const data2 = { firstName: "f2", lastName: "l2" };
|
|
273
|
+
// And we start out with data1
|
|
274
|
+
const [data, setData] = react_1.useState(data1);
|
|
275
|
+
const author = react_1.useMemo(() => new AuthorRow(data.firstName, data.lastName), [data]);
|
|
276
|
+
const config = react_1.useMemo(() => ({
|
|
277
|
+
firstName: { type: "value" },
|
|
278
|
+
lastName: { type: "value" },
|
|
279
|
+
fullName: { type: "value", computed: true },
|
|
280
|
+
}), []);
|
|
281
|
+
const form = useFormState_1.useFormState({ config, init: { input: author, map: (a) => a } });
|
|
282
|
+
return (jsx_runtime_1.jsx(mobx_react_1.Observer, { children: () => (jsx_runtime_1.jsxs("div", { children: [jsx_runtime_1.jsx("div", Object.assign({ "data-testid": "firstName" }, { children: form.firstName.value }), void 0),
|
|
283
|
+
jsx_runtime_1.jsx("div", Object.assign({ "data-testid": "fullName" }, { children: form.fullName.value }), void 0),
|
|
284
|
+
jsx_runtime_1.jsx("button", { "data-testid": "refreshData", onClick: () => setData(data2) }, void 0)] }, void 0)) }, void 0));
|
|
285
|
+
}
|
|
286
|
+
// And we start out with the initial query data
|
|
287
|
+
const r = await rtl_utils_1.render(jsx_runtime_1.jsx(TestComponent, {}, void 0));
|
|
288
|
+
expect(r.firstName().textContent).toEqual("f1");
|
|
289
|
+
expect(r.fullName().textContent).toEqual("f1 l1");
|
|
290
|
+
// When the new query is ran i.e. due to a cache refresh
|
|
291
|
+
rtl_utils_1.click(r.refreshData);
|
|
292
|
+
expect(r.firstName().textContent).toEqual("f2");
|
|
293
|
+
expect(r.fullName().textContent).toEqual("f2 l2");
|
|
294
|
+
});
|
|
295
|
+
it("can trigger auto save for fields in list that were initially undefined", async () => {
|
|
296
|
+
const autoSave = jest.fn();
|
|
297
|
+
// Given a component
|
|
298
|
+
function TestComponent() {
|
|
299
|
+
// When the data is initially undefined
|
|
300
|
+
const [data, setData] = react_1.useState();
|
|
301
|
+
const data2 = { books: [{ title: "Title 1" }] };
|
|
302
|
+
const form = useFormState_1.useFormState({
|
|
303
|
+
config: authorWithBooksConfig,
|
|
304
|
+
init: { input: data, map: (d) => d, ifUndefined: { books: [] } },
|
|
305
|
+
autoSave,
|
|
306
|
+
});
|
|
307
|
+
return (jsx_runtime_1.jsx(mobx_react_1.Observer, { children: () => (jsx_runtime_1.jsxs("div", { children: [jsx_runtime_1.jsx("button", { "data-testid": "refreshData", onClick: () => setData(data2) }, void 0),
|
|
308
|
+
jsx_runtime_1.jsx("button", { "data-testid": "add", onClick: () => form.books.add({ title: "New Book" }) }, void 0),
|
|
309
|
+
jsx_runtime_1.jsx("button", { "data-testid": "blurBookOne", onClick: () => form.books.rows[0].title.blur() }, void 0),
|
|
310
|
+
jsx_runtime_1.jsx("button", { "data-testid": "blurBookTwo", onClick: () => form.books.rows[1].title.blur() }, void 0)] }, void 0)) }, void 0));
|
|
311
|
+
}
|
|
312
|
+
// Given a formState with `onBlur` set
|
|
313
|
+
const r = await rtl_utils_1.render(jsx_runtime_1.jsx(TestComponent, {}, void 0));
|
|
314
|
+
// When the data/child is now available
|
|
315
|
+
rtl_utils_1.click(r.refreshData);
|
|
316
|
+
// And the field is blurred
|
|
317
|
+
rtl_utils_1.click(r.blurBookOne);
|
|
318
|
+
// Then we don't auto-save because nothing has changed
|
|
319
|
+
expect(autoSave).toBeCalledTimes(0);
|
|
320
|
+
// And when adding a new book
|
|
321
|
+
rtl_utils_1.click(r.add);
|
|
322
|
+
// We autoSave the new row right away (because we don't have any validation rules
|
|
323
|
+
// that say the new row can't be empty)
|
|
324
|
+
expect(autoSave).toBeCalledTimes(1);
|
|
325
|
+
// And the new book is blurred
|
|
326
|
+
rtl_utils_1.click(r.blurBookTwo);
|
|
327
|
+
// Then we auto save again
|
|
328
|
+
expect(autoSave).toBeCalledTimes(2);
|
|
329
|
+
});
|
|
330
|
+
it("does not infinite loop when calling set inside of auto-save", async () => {
|
|
331
|
+
// Given a component
|
|
332
|
+
let autoSaves = 0;
|
|
333
|
+
function TestComponent() {
|
|
334
|
+
const config = authorConfig;
|
|
335
|
+
const data = { firstName: "f1", lastName: "f1" };
|
|
336
|
+
const form = useFormState_1.useFormState({
|
|
337
|
+
config,
|
|
338
|
+
init: data,
|
|
339
|
+
// And there is reactive business logic in the `autoSave` method
|
|
340
|
+
autoSave(state) {
|
|
341
|
+
state.lastName.set("l2");
|
|
342
|
+
autoSaves++;
|
|
343
|
+
},
|
|
344
|
+
});
|
|
345
|
+
return (jsx_runtime_1.jsx("div", { children: jsx_runtime_1.jsx("button", { "data-testid": "setFirst", onClick: () => form.firstName.set("f2") }, void 0) }, void 0));
|
|
346
|
+
}
|
|
347
|
+
const r = await rtl_utils_1.render(jsx_runtime_1.jsx(TestComponent, {}, void 0));
|
|
348
|
+
// When we change firstName
|
|
349
|
+
rtl_utils_1.click(r.setFirst);
|
|
350
|
+
// When autoSave didn't infinite loop
|
|
351
|
+
expect(autoSaves).toEqual(1);
|
|
352
|
+
});
|
|
353
|
+
describe("pickFields", () => {
|
|
354
|
+
it("can pick a value field", () => {
|
|
355
|
+
const a = useFormState_1.pickFields(
|
|
356
|
+
//
|
|
357
|
+
{ firstName: { type: "value" } }, { firstName: "a", b: "ignored" });
|
|
358
|
+
expect(a).toMatchInlineSnapshot(`
|
|
359
|
+
Object {
|
|
360
|
+
"firstName": "a",
|
|
361
|
+
}
|
|
362
|
+
`);
|
|
363
|
+
});
|
|
364
|
+
it("can pick an unset object fields", () => {
|
|
365
|
+
const a = useFormState_1.pickFields(authorWithAddressConfig, { firstName: "a", b: "ignored" });
|
|
366
|
+
expect(a).toMatchInlineSnapshot(`
|
|
367
|
+
Object {
|
|
368
|
+
"address": undefined,
|
|
369
|
+
"firstName": "a",
|
|
370
|
+
"id": undefined,
|
|
371
|
+
"lastName": undefined,
|
|
372
|
+
}
|
|
373
|
+
`);
|
|
374
|
+
});
|
|
375
|
+
it("can pick a set object field", () => {
|
|
376
|
+
const a = useFormState_1.pickFields(authorWithAddressConfig, { firstName: "a", b: "ignored", address: {} });
|
|
377
|
+
expect(a).toMatchInlineSnapshot(`
|
|
378
|
+
Object {
|
|
379
|
+
"address": Object {
|
|
380
|
+
"city": undefined,
|
|
381
|
+
"street": undefined,
|
|
382
|
+
},
|
|
383
|
+
"firstName": "a",
|
|
384
|
+
"id": undefined,
|
|
385
|
+
"lastName": undefined,
|
|
386
|
+
}
|
|
387
|
+
`);
|
|
388
|
+
});
|
|
389
|
+
it("can pick an unset list field", () => {
|
|
390
|
+
const a = useFormState_1.pickFields(authorWithBooksConfig, { firstName: "a", b: "ignored" });
|
|
391
|
+
expect(a).toMatchInlineSnapshot(`
|
|
392
|
+
Object {
|
|
393
|
+
"books": undefined,
|
|
394
|
+
"firstName": "a",
|
|
395
|
+
"id": undefined,
|
|
396
|
+
"lastName": undefined,
|
|
397
|
+
}
|
|
398
|
+
`);
|
|
399
|
+
});
|
|
400
|
+
it("can pick a set list field", () => {
|
|
401
|
+
const a = useFormState_1.pickFields(authorWithBooksConfig, { firstName: "a", b: "ignored", books: [{}] });
|
|
402
|
+
expect(a).toMatchInlineSnapshot(`
|
|
403
|
+
Object {
|
|
404
|
+
"books": Array [
|
|
405
|
+
Object {
|
|
406
|
+
"classification": undefined,
|
|
407
|
+
"id": undefined,
|
|
408
|
+
"title": undefined,
|
|
409
|
+
},
|
|
410
|
+
],
|
|
411
|
+
"firstName": "a",
|
|
412
|
+
"id": undefined,
|
|
413
|
+
"lastName": undefined,
|
|
414
|
+
}
|
|
415
|
+
`);
|
|
416
|
+
});
|
|
417
|
+
it("can pick a set observable list field", () => {
|
|
418
|
+
const books = mobx_1.observable([]);
|
|
419
|
+
const a = useFormState_1.pickFields(authorWithBooksConfig, { firstName: "a", b: "ignored", books });
|
|
420
|
+
expect(mobx_1.isObservable(a.books)).toEqual(true);
|
|
421
|
+
});
|
|
422
|
+
});
|
|
423
|
+
});
|
|
424
|
+
const authorConfig = {
|
|
425
|
+
id: { type: "value" },
|
|
426
|
+
firstName: { type: "value" },
|
|
427
|
+
lastName: { type: "value" },
|
|
428
|
+
};
|
|
429
|
+
const authorWithAddressConfig = {
|
|
430
|
+
id: { type: "value" },
|
|
431
|
+
firstName: { type: "value" },
|
|
432
|
+
lastName: { type: "value" },
|
|
433
|
+
address: {
|
|
434
|
+
type: "object",
|
|
435
|
+
config: {
|
|
436
|
+
street: { type: "value", rules: [formState_1.required] },
|
|
437
|
+
city: { type: "value" },
|
|
438
|
+
},
|
|
439
|
+
},
|
|
440
|
+
};
|
|
441
|
+
const authorWithBooksConfig = {
|
|
442
|
+
id: { type: "value" },
|
|
443
|
+
firstName: { type: "value" },
|
|
444
|
+
lastName: { type: "value" },
|
|
445
|
+
books: {
|
|
446
|
+
type: "list",
|
|
447
|
+
config: {
|
|
448
|
+
id: { type: "value" },
|
|
449
|
+
title: { type: "value", rules: [formState_1.required] },
|
|
450
|
+
classification: { type: "value" },
|
|
451
|
+
},
|
|
452
|
+
},
|
|
453
|
+
};
|
|
454
|
+
const authorWithAddressAndBooksConfig = {
|
|
455
|
+
id: { type: "value" },
|
|
456
|
+
firstName: { type: "value" },
|
|
457
|
+
lastName: { type: "value" },
|
|
458
|
+
address: {
|
|
459
|
+
type: "object",
|
|
460
|
+
config: {
|
|
461
|
+
id: { type: "value" },
|
|
462
|
+
street: { type: "value", rules: [formState_1.required] },
|
|
463
|
+
city: { type: "value" },
|
|
464
|
+
},
|
|
465
|
+
},
|
|
466
|
+
books: {
|
|
467
|
+
type: "list",
|
|
468
|
+
config: {
|
|
469
|
+
id: { type: "value" },
|
|
470
|
+
title: { type: "value", rules: [formState_1.required] },
|
|
471
|
+
classification: { type: "value" },
|
|
472
|
+
},
|
|
473
|
+
},
|
|
474
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@homebound/form-state",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.13",
|
|
4
4
|
"author": "Homebound",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -21,9 +21,13 @@
|
|
|
21
21
|
"format": "prettier --loglevel warn --write \"**/*.{ts,tsx,css,md}\"",
|
|
22
22
|
"semantic-release": "semantic-release"
|
|
23
23
|
},
|
|
24
|
-
"
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"fast-deep-equal": "^3.1.3",
|
|
26
|
+
"is-plain-object": "^5.0.0",
|
|
25
27
|
"mobx": "^6.3.2",
|
|
26
|
-
"mobx-react": "^7.2.0"
|
|
28
|
+
"mobx-react": "^7.2.0"
|
|
29
|
+
},
|
|
30
|
+
"peerDependencies": {
|
|
27
31
|
"react": ">=16"
|
|
28
32
|
},
|
|
29
33
|
"devDependencies": {
|