diff --git a/packages/host/app/services/store.ts b/packages/host/app/services/store.ts index 6a649e8f23..9b30e9e03a 100644 --- a/packages/host/app/services/store.ts +++ b/packages/host/app/services/store.ts @@ -15,7 +15,7 @@ import { task } from 'ember-concurrency'; import { cloneDeep } from 'lodash-es'; import { isEqual } from 'lodash-es'; -import { merge } from 'lodash-es'; +import { merge, mergeWith } from 'lodash-es'; import { TrackedObject, TrackedMap } from 'tracked-built-ins'; @@ -926,7 +926,11 @@ export default class StoreService extends Service implements StoreInterface { omitQueryFields: true, }); if (patch.attributes) { - doc.data.attributes = merge(doc.data.attributes, patch.attributes); + doc.data.attributes = mergeWith( + doc.data.attributes, + patch.attributes, + (_dest, src) => (Array.isArray(src) ? src : undefined), + ); } if (patch.relationships) { let mergedRel = mergeRelationships( diff --git a/packages/host/tests/integration/commands/patch-instance-test.gts b/packages/host/tests/integration/commands/patch-instance-test.gts index b52a7abea4..cef5e90815 100644 --- a/packages/host/tests/integration/commands/patch-instance-test.gts +++ b/packages/host/tests/integration/commands/patch-instance-test.gts @@ -215,6 +215,54 @@ module('Integration | commands | patch-instance', function (hooks) { ); }); + test('patching a containsMany field with a shorter array fully replaces it (no stale trailing items)', async function (assert) { + let patchInstanceCommand = new PatchCardInstanceCommand( + commandService.commandContext, + { + cardType: PersonDef, + }, + ); + let url = new URL(`${testRealmURL}Person/hassan`); + let saves = 0; + this.onSave((saveURL) => { + if (saveURL.href === url.href) { + saves++; + } + }); + + await patchInstanceCommand.execute({ + cardId: `${testRealmURL}Person/hassan`, + patch: { attributes: { nickNames: ['Paper', 'Pinky', 'Pix'] } }, + }); + await waitUntil(() => saves > 0, { + timeout: saveWaitTimeoutMs, + timeoutMessage: 'timed out waiting for the first save', + }); + + let savesAfterGrow = saves; + await patchInstanceCommand.execute({ + cardId: `${testRealmURL}Person/hassan`, + patch: { attributes: { nickNames: ['Paper'] } }, + }); + await waitUntil(() => saves > savesAfterGrow, { + timeout: saveWaitTimeoutMs, + timeoutMessage: 'timed out waiting for the second save', + }); + + let result = await indexQuery.instance(url); + let instance = + result && result.type === 'instance' ? result.instance : undefined; + assert.ok(instance, 'instance payload is present'); + if (!instance) { + throw new Error('expected instance payload'); + } + assert.deepEqual( + instance.attributes?.nickNames, + ['Paper'], + 'the containsMany array was fully replaced, not index-merged', + ); + }); + test('can patch a linksTo field', async function (assert) { let patchInstanceCommand = new PatchCardInstanceCommand( commandService.commandContext,