@homebound/form-state 2.26.10 → 2.26.12

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.
@@ -208,6 +208,8 @@ parentInstance, parentState, key, rules, listConfig, config, strictOrder, maybeA
208
208
  //
209
209
  // Only do this if one of our client-side objects is missing an id
210
210
  const incomingByHash = idKey && currentItems.some((item) => !item[idKey]) && (0, utils_1.groupBy)(incomingItems, hashWithoutId);
211
+ // Watch for deletions
212
+ const hasOpKey = Object.keys(listConfig.config).includes("op");
211
213
  // First, process all current items
212
214
  for (const currentItem of currentItems) {
213
215
  const childState = rowMap.get(currentItem); // We'll always have a child state for `currentItems`
@@ -221,12 +223,20 @@ parentInstance, parentState, key, rules, listConfig, config, strictOrder, maybeA
221
223
  // Once matched, we don't this to be an addedRow anymore
222
224
  addedRows.delete(currentItem);
223
225
  }
224
- else if (childState.dirty || addedRows.has(currentItem)) {
225
- // If no incoming, but we're dirty, keep the WIP change
226
- mergedItems.push(currentItem);
226
+ else if (!childState.dirty && !addedRows.has(currentItem)) {
227
+ // Local is not dirty/added, and it's not upstream, so let it get removed
228
+ }
229
+ else if (hasOpKey && currentItem.op === "delete") {
230
+ // We were locally marked as deleted, and not finding a match is the server acking that we're gone
231
+ }
232
+ else if (currentItems.length === incomingItems.length &&
233
+ !!idKey &&
234
+ currentItem[idKey] === undefined) {
235
+ // Assume our newly-assigned id is coming back
227
236
  }
228
237
  else {
229
- // Local is not dirty, and it's not upstream, so remove it
238
+ // If no incoming, but we're dirty (see above) and not deleted (see above), keep the WIP change
239
+ mergedItems.push(currentItem);
230
240
  }
231
241
  }
232
242
  // Then, add any incoming items that don't exist in current
@@ -205,6 +205,80 @@ describe("formState", () => {
205
205
  // And we're not longer dirty
206
206
  expect(state.books.dirty).toBe(false);
207
207
  });
208
+ it("list value can refresh and not drop new children that are just data", () => {
209
+ const config = configBuilders_1.f.config({
210
+ firstName: configBuilders_1.f.value(),
211
+ tags: configBuilders_1.f.list({ combo: configBuilders_1.f.value() }),
212
+ });
213
+ const state = (0, objectField_1.createObjectState)(config, { firstName: "a1", tags: [] });
214
+ // And we add a new child on the client-side (and it doesn't have an id)
215
+ state.tags.add({ combo: ["b1", "b2"] });
216
+ // When we refresh it with the original list undefined
217
+ state.set({ tags: [] }, { refreshing: true });
218
+ // Then it keeps the new child
219
+ expect(state.tags.value).toEqual([{ combo: ["b1", "b2"] }]);
220
+ // And it's still considered changed
221
+ expect(state.tags.dirty).toBe(true);
222
+ expect(state.tags.originalValue).toEqual([]);
223
+ // And we add an additional child
224
+ state.tags.add({ combo: ["c1", "c2"] });
225
+ // And a refresh brings back the 1st child acked from the server
226
+ state.set({ tags: [{ combo: ["b1", "b2"] }] }, { refreshing: true });
227
+ // Then we still have 2 children
228
+ expect(state.tags.value).toEqual([{ combo: ["b1", "b2"] }, { combo: ["c1", "c2"] }]);
229
+ // And we're still dirty
230
+ expect(state.tags.dirty).toBe(true);
231
+ // When the server finally acks both children
232
+ state.set({ tags: [{ combo: ["b1", "b2"] }, { combo: ["c1", "c2"] }] }, { refreshing: true });
233
+ // Then we're finally clean
234
+ expect(state.tags.dirty).toBe(false);
235
+ });
236
+ it("list value can refresh and drop children that were deleted", () => {
237
+ // Given a list field that is currently populated
238
+ const a1 = {
239
+ id: "a:1",
240
+ firstName: "a1",
241
+ books: [
242
+ { id: "b:1", title: "b1", op: "include" },
243
+ { id: "b:2", title: "b2", op: "include" },
244
+ ],
245
+ };
246
+ // And incremental deletion is enabled
247
+ const config = configBuilders_1.f.config({
248
+ id: configBuilders_1.f.value(),
249
+ books: configBuilders_1.f.list({ id: configBuilders_1.f.value(), title: configBuilders_1.f.value(), op: configBuilders_1.f.value() }),
250
+ });
251
+ const state = (0, objectField_1.createObjectState)(config, a1);
252
+ // When we delete the 1st book
253
+ state.books.rows[0].op.set("delete");
254
+ // When we refresh it only the 2nd book
255
+ state.set({ books: [{ id: "b:2", title: "b2" }] }, { refreshing: true });
256
+ // Then we only have 1 book
257
+ expect(state.books.value).toEqual([{ id: "b:2", title: "b2", op: "include" }]);
258
+ // And we're not dirty
259
+ expect(state.books.dirty).toBe(false);
260
+ });
261
+ it("list value can refresh and merge children that were added", () => {
262
+ // Given a list field that is currently populated with 1 child
263
+ const a1 = { id: "a:1", books: [{ id: "b:1", title: "b1" }] };
264
+ const state = createAuthorInputState(a1);
265
+ // When we add a new book
266
+ state.books.add({ title: "b2" });
267
+ // When we refresh it with both books, and the 2nd book now has its id assigned
268
+ state.set({
269
+ books: [
270
+ { id: "b:1", title: "b1" },
271
+ { id: "b:2", title: "b2" },
272
+ ],
273
+ }, { refreshing: true });
274
+ // Then wes till have two books
275
+ expect(state.books.value).toEqual([
276
+ { id: "b:1", title: "b1" },
277
+ { id: "b:2", title: "b2" },
278
+ ]);
279
+ // And we're not dirty
280
+ expect(state.books.dirty).toBe(false);
281
+ });
208
282
  it("list value can observe changes", () => {
209
283
  const b1 = { title: "t1" };
210
284
  const a1 = { firstName: "a1", books: [b1] };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@homebound/form-state",
3
- "version": "2.26.10",
3
+ "version": "2.26.12",
4
4
  "author": "Homebound",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",