Merge "Disable Cherry pick button if no change is selected"
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.ts b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.ts
index 05e3c7f..eb12e1d 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.ts
@@ -144,7 +144,8 @@
   @state()
   latestCommitter?: GitPersonInfo;
 
-  private selectedChangeIds = new Set<ChangeInfoId>();
+  @state()
+  selectedChangeIds = new Set<ChangeInfoId>();
 
   private readonly restApiService = getAppContext().restApiService;
 
@@ -261,12 +262,8 @@
       <gr-dialog
         confirm-label="Cherry Pick"
         .cancelLabel=${this.computeCancelLabel()}
-        ?disabled=${this.computeDisableCherryPick(
-          this.cherryPickType,
-          this.duplicateProjectChanges,
-          this.statuses,
-          this.branch
-        )}
+        ?disabled=${this.computeDisableCherryPick()}
+        title=${this.computeTitleForCherryPick()}
         @confirm=${this.handleConfirmTap}
         @cancel=${this.handleCancelTap}
       >
@@ -438,6 +435,23 @@
     `;
   }
 
+  // private but compute in tests
+  computeTitleForCherryPick() {
+    if (!this.computeDisableCherryPick()) {
+      return 'Cherry pick changes';
+    }
+    if (this.noChangesSelected())
+      return 'Disabled because no changes are selected';
+
+    if (
+      this.cherryPickType === CherryPickType.TOPIC &&
+      this.duplicateProjectChanges
+    ) {
+      return 'Duplicate projects selected';
+    }
+    return 'Cherry pick button disabled';
+  }
+
   containsDuplicateProject(changes: (ChangeInfo | ParsedChangeInfo)[]) {
     const projects: {[projectName: string]: boolean} = {};
     for (let i = 0; i < changes.length; i++) {
@@ -481,6 +495,8 @@
       this.selectedChangeIds.has(change.id)
     );
     this.duplicateProjectChanges = this.containsDuplicateProject(changes);
+    // Explicitly trigger a re-render
+    this.selectedChangeIds = new Set(this.selectedChangeIds);
   }
 
   private computeTopicErrorMessage() {
@@ -533,18 +549,24 @@
     return isRunningChange ? 'Close' : 'Cancel';
   }
 
-  private computeDisableCherryPick(
-    cherryPickType: CherryPickType,
-    duplicateProjectChanges: boolean,
-    statuses: Statuses,
-    branch: BranchName
-  ) {
-    if (!branch) return true;
+  private noChangesSelected() {
+    const changes = this.changes.filter(change =>
+      this.selectedChangeIds.has(change.id)
+    );
+    if (this.cherryPickType === CherryPickType.TOPIC && changes.length === 0)
+      return true;
+    return false;
+  }
+
+  private computeDisableCherryPick() {
+    if (!this.branch) return true;
+    if (this.noChangesSelected()) return true;
     const duplicateProject =
-      cherryPickType === CherryPickType.TOPIC && duplicateProjectChanges;
+      this.cherryPickType === CherryPickType.TOPIC &&
+      this.duplicateProjectChanges;
     if (duplicateProject) return true;
-    if (!statuses) return false;
-    const isRunningChange = Object.values(statuses).some(
+    if (!this.statuses) return false;
+    const isRunningChange = Object.values(this.statuses).some(
       v => v.status === ProgressStatus.RUNNING
     );
     return isRunningChange;
@@ -600,8 +622,6 @@
       this.selectedChangeIds.has(change.id)
     );
     if (!changes.length) {
-      const errorSpan = this.shadowRoot?.querySelector('.error-message');
-      errorSpan!.innerHTML = 'No change selected';
       return;
     }
     const topic = this.generateRandomCherryPickTopic(changes[0]);
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog_test.ts b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog_test.ts
index 8f81710..b0cc4d9 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog_test.ts
@@ -105,7 +105,12 @@
     assert.shadowDom.equal(
       element,
       /* HTML */ `
-        <gr-dialog confirm-label="Cherry Pick" disabled="" role="dialog">
+        <gr-dialog
+          confirm-label="Cherry Pick"
+          disabled=""
+          role="dialog"
+          title="Cherry pick button disabled"
+        >
           <div class="header title" slot="header">
             Cherry Pick Change to Another Branch
           </div>
@@ -238,31 +243,31 @@
       assert.equal(checkboxes.length, 2);
       assert.isTrue(checkboxes[0].checked);
       checkboxes[0].click();
+      assert.isTrue(checkboxes[1].checked);
       queryAndAssert<GrDialog>(element, 'gr-dialog').confirmButton!.click();
       await element.updateComplete;
       assert.equal(executeChangeActionStub.callCount, 1);
       assert.isTrue(duplicateChangesStub.called);
     });
 
-    test('deselecting all change shows error message', async () => {
+    test('deselecting all change disables the confirm button', async () => {
       element.branch = 'master' as BranchName;
       await element.updateComplete;
-      const executeChangeActionStub = stubRestApi(
-        'executeChangeAction'
-      ).resolves(new Response());
       const checkboxes = queryAll<HTMLInputElement>(
         element,
         'input[type="checkbox"]'
       );
       assert.equal(checkboxes.length, 2);
+      assert.equal(element.cherryPickType, CHERRY_PICK_TYPES.TOPIC);
       checkboxes[0].click();
       checkboxes[1].click();
-      queryAndAssert<GrDialog>(element, 'gr-dialog').confirmButton!.click();
       await element.updateComplete;
-      assert.equal(executeChangeActionStub.callCount, 0);
+      assert.isTrue(
+        queryAndAssert<GrDialog>(element, 'gr-dialog').confirmButton?.disabled
+      );
       assert.equal(
-        queryAndAssert<HTMLElement>(element, '.error-message').innerText,
-        'No change selected'
+        queryAndAssert<GrDialog>(element, 'gr-dialog').title,
+        'Disabled because no changes are selected'
       );
     });