Retain keys from the dashboard request in the change object

When calling sync we pass in the ChangeInfo object returned from
the dashboard query to load the changes.
This query includes the Submit Requirements property that we don't
want repeated in the sync() call for detailedChange hence retain
the keys found in the original request and overwrite the duplicate
keys from the new change object received in the sync call.

Release-Notes: skip
Change-Id: I676123d35d4ff12058918d2cd0033f55e99486c6
diff --git a/polygerrit-ui/app/models/bulk-actions/bulk-actions-model.ts b/polygerrit-ui/app/models/bulk-actions/bulk-actions-model.ts
index 5ab1617..79809a0 100644
--- a/polygerrit-ui/app/models/bulk-actions/bulk-actions-model.ts
+++ b/polygerrit-ui/app/models/bulk-actions/bulk-actions-model.ts
@@ -144,7 +144,8 @@
     if (newChanges !== newCurrent.allChanges) return;
     const allDetailedChanges = new Map(newChanges);
     for (const change of changeDetails ?? []) {
-      allDetailedChanges.set(change._number, change);
+      const originalChange = changes.find(c => c._number === change._number);
+      allDetailedChanges.set(change._number, {...originalChange, ...change});
     }
     this.setState({
       ...newCurrent,
diff --git a/polygerrit-ui/app/models/bulk-actions/bulk-actions-model_test.ts b/polygerrit-ui/app/models/bulk-actions/bulk-actions-model_test.ts
index a2c4bac..f77c3ff 100644
--- a/polygerrit-ui/app/models/bulk-actions/bulk-actions-model_test.ts
+++ b/polygerrit-ui/app/models/bulk-actions/bulk-actions-model_test.ts
@@ -10,6 +10,7 @@
   NumericChangeId,
   ChangeStatus,
   HttpMethod,
+  SubmitRequirementStatus,
 } from '../../api/rest-api';
 import {BulkActionsModel, LoadingState} from './bulk-actions-model';
 import {getAppContext} from '../../services/app-context';
@@ -216,6 +217,49 @@
     );
   });
 
+  test('sync retains keys from original change', async () => {
+    const c1 = createChange();
+    c1._number = 1 as NumericChangeId;
+    c1.submit_requirements = [
+      {
+        name: 'a',
+        status: SubmitRequirementStatus.FORCED,
+        submittability_expression_result: {
+          expression: 'b',
+        },
+      },
+    ];
+
+    stubRestApi('getDetailedChangesWithActions').callsFake(() => {
+      const change = createChange();
+      change._number = 1 as NumericChangeId;
+      change.actions = {abandon: {}};
+      assert.isNotOk(change.submit_requirements);
+      return Promise.resolve([change]);
+    });
+
+    bulkActionsModel.sync([c1]);
+
+    await waitUntilObserved(
+      bulkActionsModel.loadingState$,
+      s => s === LoadingState.LOADED
+    );
+
+    const changeAfterSync = bulkActionsModel
+      .getState()
+      .allChanges.get(1 as NumericChangeId);
+    assert.deepEqual(changeAfterSync!.submit_requirements, [
+      {
+        name: 'a',
+        status: SubmitRequirementStatus.FORCED,
+        submittability_expression_result: {
+          expression: 'b',
+        },
+      },
+    ]);
+    assert.deepEqual(changeAfterSync!.actions, {abandon: {}});
+  });
+
   test('sync ignores outdated fetch responses', async () => {
     const c1 = createChange();
     c1._number = 1 as NumericChangeId;