Fix order of merging of old checks into new checks

Change-Id: Icdcb6542ebc0e19279418055643dd8a108395bbc
diff --git a/gr-checks/gr-checks-view.js b/gr-checks/gr-checks-view.js
index c0d32a1..1ddd1ca 100644
--- a/gr-checks/gr-checks-view.js
+++ b/gr-checks/gr-checks-view.js
@@ -134,6 +134,27 @@
       )
     },
 
+
+    /**
+     * Merge new checks into old checks to maintain showCheckMessage
+     * property
+     * Loop over checks to make sure no new checks are missed
+     * Merge new check object into prev check
+     * Remove any check that is not returned the next time
+     * Ensure message is updated
+     */
+    _updateChecks(checks) {
+      return checks.map(
+        (check) => {
+          const prevCheck = this._checks.find(
+            (c) => { return c.checker_uuid === check.checker_uuid }
+          )
+          if (!prevCheck) return Object.assign({}, check);
+          return Object.assign({}, prevCheck, check,
+            {showCheckMessage: prevCheck.showCheckMessage});
+        });
+    },
+
     /**
      * @param {!Defs.Change} change
      * @param {!Defs.Revision} revision
@@ -144,20 +165,11 @@
 
       getChecks(change._number, revision._number).then(checks => {
         if (checks && checks.length) {
-          checks.sort(this._orderChecks);
+          checks.sort((a, b) => this._orderChecks(a, b));
           if (!this._checks) {
             this._checks = checks;
           } else {
-            // Merge checks & this_checks to keep showCheckMessage property
-            this._checks = checks.map(
-              (check) => {
-                const prevCheck = this._checks.find(
-                  (c) => { return c.checker_uuid === check.checker_uuid }
-                )
-                if (!prevCheck) return check;
-                return Object.assign({}, check, prevCheck)
-              }
-            )
+            this._checks = this._updateChecks(checks);
           }
           this.set('_status', LoadingStatus.RESULTS);
         } else {
diff --git a/gr-checks/gr-checks-view_test.html b/gr-checks/gr-checks-view_test.html
index 0e133df..1f54281 100644
--- a/gr-checks/gr-checks-view_test.html
+++ b/gr-checks/gr-checks-view_test.html
@@ -8,6 +8,20 @@
 <title>gr-checks-item</title>
 <link rel="import" href="gr-checks-view.html">
 
+
+<!-- Gr-overlay does not exist in the test framework
+It is expected to be provided by Gerrit core -->
+<dom-module id="gr-overlay">
+  <template>
+  </template>
+  <script>
+    Polymer({
+      is: 'gr-overlay',
+      refit() {}
+    });
+  </script>
+</dom-module>
+
 <test-fixture id="basic">
   <template is="dom-template">
     <gr-checks-view
@@ -27,134 +41,276 @@
     logUrl: 'http://example.com/test-log-url',
     startTime: "2019-02-06T22:25:19.269Z",
     finishTime: "2019-02-06T22:25:44.574Z",
-    checker_name: "test checker"
+    checker_name: "test checker",
+    state: "RUNNING",
+    checker_status: "ENABLED",
+    blocking: [],
+    checker_description: "No-op jobs for testing purposes",
+    message: "added test file\n\nChange-Id: I8df212a28ae23cc239afd10ee4f506887e03ab70\n",
+    showCheckMessage: undefined,
+    checker_uuid: "gerritforge:codestyle-a6a0e4682515f3521897c5f950d1394f4619d928",
   };
-const REVISION = {
-  "kind": "REWORK",
-  "_number": 3,
-  "created": "2018-05-15 21:56:13.000000000",
-  "uploader": {
-    "_account_id": 1000000,
-  },
-  "ref": "refs/changes/00/1000/1",
-  "commit": {
-    "parents": [],
-    "subject": "added test file",
-    "message": "added test file\n\nChange-Id: I8df212a28ae23cc239afd10ee4f506887e03ab70\n",
-    "commit": "1c9a1dfd38ea51dc7880f3ddf669100710f0c91b"
-  },
-};
+  const CHECK2 = {
+    checkId: 'test-check-id2',
+    logUrl: 'http://example.com/test-log-url',
+    startTime: "2019-02-06T22:25:19.269Z",
+    finishTime: "2019-02-06T22:25:44.574Z",
+    checker_name: "test checker2",
+    state: "RUNNING",
+    checker_status: "ENABLED",
+    blocking: [],
+    checker_description: "No-op jobs for testing purposes 2",
+    message: "added test file\n\nChange-Id: I8df212a28ae23cc239afd10ee4f506887e03ab70\n",
+    showCheckMessage: undefined,
+    checker_uuid: "gerritforge:polygerrit-a6a0e4682515f3521897c5f950d1394f4619d928",
+  };
+  const REVISION = {
+    "kind": "REWORK",
+    "_number": 3,
+    "created": "2018-05-15 21:56:13.000000000",
+    "uploader": {
+      "_account_id": 1000000,
+    },
+    "ref": "refs/changes/00/1000/1",
+    "commit": {
+      "parents": [],
+      "subject": "added test file",
+      "message": "added test file\n\nChange-Id: I8df212a28ae23cc239afd10ee4f506887e03ab70\n",
+      "commit": "1c9a1dfd38ea51dc7880f3ddf669100710f0c91b"
+    },
+  };
 
-suite('gr-checks-view tests', () => {
-  let element;
-  let sandbox;
-  let getChecksSpy;
-  let getChecksResolve;
-  let retryCheckSpy;
-  let isConfiguredSpy;
-  let isConfiguredResolve;
-  let getAccountSpy, getAccountPromise, getAccountResolve;
-  let fetchJSONSpy, fetchJSONPromise, fetchJSONResolve;
-  let getAccountCapabilitiesSpy, getAccountCapabilitiesPromise,
-    getAccountCapabilitiesResolve;
+  suite('gr-checks-view tests', () => {
+    let element;
+    let sandbox;
+    let getChecksSpy;
+    let getChecksResolve;
+    let retryCheckSpy;
+    let isConfiguredSpy;
+    let isConfiguredResolve;
+    let getAccountSpy, getAccountPromise, getAccountResolve;
+    let fetchJSONSpy, fetchJSONPromise, fetchJSONResolve;
+    let getAccountCapabilitiesSpy, getAccountCapabilitiesPromise,
+      getAccountCapabilitiesResolve;
 
-  setup((done) => {
-    sandbox = sinon.sandbox.create();
-
-    getChecksSpy = sinon.stub();
-    const getChecksPromise = new Promise((resolve, reject) => {
-      getChecksResolve = resolve;
-    });
-    getChecksSpy.returns(getChecksPromise);
-
-    isConfiguredSpy = sinon.stub();
-    const isConfiguredPromise = new Promise((resolve, reject) => {
-      isConfiguredResolve = resolve;
-    });
-    isConfiguredSpy.returns(isConfiguredPromise);
-
-    retryCheckSpy = sinon.stub();
-    retryCheckSpy.returns(Promise.resolve());
-
-    const plugin = {};
-    getAccountSpy = sinon.stub();
-    const getAccountPromise = new Promise((resolve, reject) => {
-      getAccountResolve = resolve;
-    })
-    getAccountSpy.returns(getAccountPromise);
-
-    fetchJSONSpy = sinon.stub();
-    const fetchJSONPromise = new Promise((resolve, reject) => {
-      fetchJSONResolve = resolve;
-    })
-    fetchJSONSpy.returns(fetchJSONPromise)
-
-    getAccountCapabilitiesSpy = sinon.stub();
-    const getAccountCapabilitiesPromise = new Promise((resolve, reject) => {
-      getAccountCapabilitiesResolve = resolve;
-    })
-    getAccountCapabilitiesSpy.returns(getAccountCapabilitiesPromise);
-
-    plugin.restApi = () => ({
-      getAccount: getAccountSpy,
-      fetchJSON: fetchJSONSpy,
-      getAccountCapabilities: getAccountCapabilitiesSpy
-    });
-
-    element = fixture('basic', {
-      retryCheck: retryCheckSpy,
-      getChecks: getChecksSpy,
-      isConfigured: isConfiguredSpy,
-      change: {
-        'project': 'test-repository',
-        '_number': 2,
-        'revisions': {
-          'first-sha': "test-revision",
-          'second-sha': REVISION,
-        },
-      },
-      plugin: plugin,
-      revision: REVISION,
-    });
-    flush(done);
-  });
-
-  teardown(() => { sandbox.restore(); });
-
-  test('renders loading', () => {
-    // Element also contains the hidden gr-overlay hence use includes
-    assert(element.textContent.trim().includes('Loading...'));
-  });
-
-  test('queries the checks', () => {
-    assert.isTrue(getChecksSpy.called);
-    assert.isTrue(getChecksSpy.calledWith(2, 3));
-  });
-
-  suite('no checks returned', () => {
     setup((done) => {
-      getChecksResolve([]);
+      sandbox = sinon.sandbox.create();
+
+      getChecksSpy = sinon.stub();
+      const getChecksPromise = new Promise((resolve, reject) => {
+        getChecksResolve = resolve;
+      });
+      getChecksSpy.returns(getChecksPromise);
+
+      isConfiguredSpy = sinon.stub();
+      const isConfiguredPromise = new Promise((resolve, reject) => {
+        isConfiguredResolve = resolve;
+      });
+      isConfiguredSpy.returns(isConfiguredPromise);
+
+      retryCheckSpy = sinon.stub();
+      retryCheckSpy.returns(Promise.resolve());
+
+      const plugin = {};
+      getAccountSpy = sinon.stub();
+      const getAccountPromise = new Promise((resolve, reject) => {
+        getAccountResolve = resolve;
+      })
+      getAccountSpy.returns(getAccountPromise);
+
+      fetchJSONSpy = sinon.stub();
+      const fetchJSONPromise = new Promise((resolve, reject) => {
+        fetchJSONResolve = resolve;
+      })
+      fetchJSONSpy.returns(fetchJSONPromise)
+
+      getAccountCapabilitiesSpy = sinon.stub();
+      const getAccountCapabilitiesPromise = new Promise((resolve, reject) => {
+        getAccountCapabilitiesResolve = resolve;
+      })
+      getAccountCapabilitiesSpy.returns(getAccountCapabilitiesPromise);
+
+      plugin.restApi = () => ({
+        getAccount: getAccountSpy,
+        fetchJSON: fetchJSONSpy,
+        getAccountCapabilities: getAccountCapabilitiesSpy
+      });
+      element = fixture('basic', {
+        retryCheck: retryCheckSpy,
+        getChecks: getChecksSpy,
+        isConfigured: isConfiguredSpy,
+        change: {
+          'project': 'test-repository',
+          '_number': 2,
+          'revisions': {
+            'first-sha': "test-revision",
+            'second-sha': REVISION,
+          },
+        },
+        plugin: plugin,
+        revision: REVISION,
+      });
       flush(done);
     });
 
-    test('it calls to check if the checks are configured', () => {
-      assert.isTrue(isConfiguredSpy.called);
-      assert.isTrue(isConfiguredSpy.calledWith('test-repository'));
+    teardown(() => { sandbox.restore(); });
+
+    test('renders loading', () => {
+      // Element also contains the hidden gr-overlay hence use includes
+      assert(element.textContent.trim().includes('Loading...'));
     });
 
-    test('no configure button renders', () => {
-      assert(!element.$$('gr-button'));
-    })
+    test('queries the checks', () => {
+      assert.isTrue(getChecksSpy.called);
+      assert.isTrue(getChecksSpy.calledWith(2, 3));
+    });
 
-    suite('not configured', () => {
+    suite('no checks returned', () => {
       setup((done) => {
-        isConfiguredResolve(false);
+        getChecksResolve([]);
         flush(done);
       });
 
-      test('renders checks not configured', () => {
-        const header = element.$$('h2');
-        assert.equal(header.textContent.trim(), 'Code review checks not configured');
+      test('it calls to check if the checks are configured', () => {
+        assert.isTrue(isConfiguredSpy.called);
+        assert.isTrue(isConfiguredSpy.calledWith('test-repository'));
+      });
+
+      test('no configure button renders', () => {
+        assert(!element.$$('gr-button'));
+      });
+
+      suite('not configured', () => {
+        setup((done) => {
+          isConfiguredResolve(false);
+          flush(done);
+        });
+
+        test('renders checks not configured', () => {
+          const header = element.$$('h2');
+          assert.equal(header.textContent.trim(), 'Code review checks not configured');
+        });
+
+        suite('create checker capability false', () => {
+          setup((done) => {
+            getAccountResolve(true);
+            getAccountCapabilitiesResolve({'checks-administrateCheckers': false});
+            flush(done);
+          });
+          test('checker button does not render', () => {
+            assert(!element.$$('gr-button'));
+          });
+        });
+
+        suite('create checker capability true', () => {
+          setup((done) => {
+            getAccountResolve(true);
+            getAccountCapabilitiesResolve({'checks-administrateCheckers': true});
+            flush(done);
+          });
+          test('checker button renders', () => {
+            assert(element.$$('gr-button'));
+          });
+        });
+
+      });
+
+      suite('no checks ran', () => {
+        setup((done) => {
+          isConfiguredResolve(true);
+          flush(done);
+        });
+
+        test('renders checks not configured', () => {
+          const header = element.$$('h2');
+          assert.equal(header.textContent.trim(), 'No checks ran for this code review');
+        });
+      });
+    });
+
+    suite('checks updated properly', () => {
+      setup((done) => {
+        element._checks = [CHECK1, CHECK2];
+        flush(done);
+      });
+
+      test('message is updated', () => {
+        const NEW_CHECKS = [Object.assign({}, CHECK1),
+          Object.assign({}, CHECK2)];
+        NEW_CHECKS[0].message = "New message 1";
+        NEW_CHECKS[1].message = "New message 2";
+        const EXPECTED_CHECKS = [Object.assign({}, CHECK1),
+          Object.assign({}, CHECK2)];
+        EXPECTED_CHECKS[0].message = "New message 1";
+        EXPECTED_CHECKS[1].message = "New message 2";
+        const UPDATED_CHECKS = element._updateChecks(NEW_CHECKS);
+        assert.equal(UPDATED_CHECKS[0].message, NEW_CHECKS[0].message);
+        assert.equal(UPDATED_CHECKS[1].message, NEW_CHECKS[1].message);
+      });
+
+      test('total checks updated if one is deleted', () => {
+        const NEW_CHECKS = [Object.assign({}, CHECK1)];
+        const EXPECTED_CHECKS = [Object.assign({}, CHECK1)];
+        const UPDATED_CHECKS = element._updateChecks(NEW_CHECKS);
+        assert.equal(UPDATED_CHECKS.length, 1);
+        assert.deepEqual(UPDATED_CHECKS, EXPECTED_CHECKS);
+      });
+
+      test('status is updated', () => {
+        const NEW_CHECKS = [Object.assign({}, CHECK1),
+          Object.assign({}, CHECK2)];
+        NEW_CHECKS[0].state = "SUCCESSFUL";
+        NEW_CHECKS[1].state = "FAILED";
+        const UPDATED_CHECKS = element._updateChecks(NEW_CHECKS);
+        assert.deepEqual(UPDATED_CHECKS, NEW_CHECKS);
+      });
+
+      test('showMessage property is retained', () => {
+
+        element._checks[0].showCheckMessage = true;
+        element._checks[1].showCheckMessage = false;
+
+        const NEW_CHECKS = [Object.assign({}, CHECK1),
+          Object.assign({}, CHECK2)];
+        const UPDATED_CHECKS = element._updateChecks(NEW_CHECKS);
+        assert.equal(UPDATED_CHECKS[0].showCheckMessage,
+          CHECK1.showCheckMessage);
+        assert.equal(UPDATED_CHECKS[1].showCheckMessage,
+          CHECK2.showCheckMessage);
+
+        element._checks[0].showCheckMessage = undefined;
+        element._checks[1].showCheckMessage = undefined;
+
+      });
+
+      test('message is not shown if new check has no message', () => {
+        const NEW_CHECKS = [Object.assign({}, CHECK1),
+          Object.assign({}, CHECK2)];
+        NEW_CHECKS[0].message = '';
+        const UPDATED_CHECKS = element._updateChecks(NEW_CHECKS);
+        assert.equal(UPDATED_CHECKS[0].message, NEW_CHECKS[0].message);
+      });
+
+    });
+
+    suite('with checks', () => {
+      setup(done => {
+        getChecksResolve([CHECK1, CHECK1, CHECK1]);
+        flush(done);
+      });
+
+      test('it calls to check if the checks are configured', () => {
+        assert.isFalse(isConfiguredSpy.called);
+      });
+
+      test('renders the header', () => {
+        const header = element.$$('header > h3');
+        assert.equal(header.textContent.trim(), 'Latest checks for Patchset 3');
+      });
+
+      test('renders a table of all the checks', () => {
+        const tbody = element.$$('table > tbody');
+        assert.lengthOf(tbody.querySelectorAll('gr-checks-item'), 3)
       });
 
       suite('create checker capability false', () => {
@@ -180,62 +336,6 @@
       });
 
     });
-
-    suite('no checks ran', () => {
-      setup((done) => {
-        isConfiguredResolve(true);
-        flush(done);
-      });
-
-      test('renders checks not configured', () => {
-        const header = element.$$('h2');
-        assert.equal(header.textContent.trim(), 'No checks ran for this code review');
-      });
-    });
   });
 
-  suite('with checks', () => {
-    setup(done => {
-      getChecksResolve([CHECK1, CHECK1, CHECK1]);
-      flush(done);
-    });
-
-    test('it calls to check if the checks are configured', () => {
-      assert.isFalse(isConfiguredSpy.called);
-    });
-
-    test('renders the header', () => {
-      const header = element.$$('header > h3');
-      assert.equal(header.textContent.trim(), 'Latest checks for Patchset 3');
-    });
-
-    test('renders a table of all the checks', () => {
-      const tbody = element.$$('table > tbody');
-      assert.lengthOf(tbody.querySelectorAll('gr-checks-item'), 3)
-    });
-
-    suite('create checker capability false', () => {
-        setup((done) => {
-          getAccountResolve(true);
-          getAccountCapabilitiesResolve({'checks-administrateCheckers': false});
-          flush(done);
-        });
-        test('checker button does not render', () => {
-          assert(!element.$$('gr-button'));
-        });
-      });
-
-      suite('create checker capability true', () => {
-        setup((done) => {
-          getAccountResolve(true);
-          getAccountCapabilitiesResolve({'checks-administrateCheckers': true});
-          flush(done);
-        });
-        test('checker button renders', () => {
-          assert(element.$$('gr-button'));
-        });
-      });
-
-  });
-});
 </script>