Merge "Do not split DELTA groups in context control which are not common."
diff --git a/polygerrit-ui/app/embed/diff/gr-context-controls/gr-context-controls.ts b/polygerrit-ui/app/embed/diff/gr-context-controls/gr-context-controls.ts
index 79feca0..1ec5262e 100644
--- a/polygerrit-ui/app/embed/diff/gr-context-controls/gr-context-controls.ts
+++ b/polygerrit-ui/app/embed/diff/gr-context-controls/gr-context-controls.ts
@@ -380,7 +380,7 @@
     let classes = 'contextControlButton showContext ';
 
     if (type === ContextButtonType.ALL) {
-      if (this.group.hasDeltaGroup()) {
+      if (this.group.hasNonCommonDeltaGroup()) {
         text = '+ Unrelated changes';
         ariaLabel = 'Show unrelated changes';
         classes += ' unrelatedChanges ';
@@ -499,7 +499,7 @@
    * Creates a container div with partial (+10) expansion buttons (above and/or below).
    */
   private createPartialExpansionButtons() {
-    if (!this.showPartialLinks() || this.group?.hasDeltaGroup()) {
+    if (!this.showPartialLinks() || this.group?.hasNonCommonDeltaGroup()) {
       return undefined;
     }
     let aboveButton;
@@ -532,7 +532,7 @@
       !this.showPartialLinks() ||
       !this.renderPreferences?.use_block_expansion ||
       this.group?.hasSkipGroup() ||
-      this.group?.hasDeltaGroup()
+      this.group?.hasNonCommonDeltaGroup()
     ) {
       return undefined;
     }
diff --git a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-group.ts b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-group.ts
index 0739562..c7e9f44 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-group.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-group.ts
@@ -165,7 +165,8 @@
  * Groups where all lines are before or all lines are after the split will be
  * retained as is and put into the first or second list respectively. Groups
  * with some lines before and some lines after the split will be split into
- * two groups, which will be put into the first and second list.
+ * two groups, which will be put into the first and second list. Groups with
+ * type DELTA which are not common will not be split.
  *
  * @param split A line number offset relative to the first group's
  *     start line at which the groups should be split.
@@ -179,31 +180,41 @@
   if (groups.length === 0) return [[], []];
   const leftSplit = groups[0].lineRange.left.start_line + split;
   const rightSplit = groups[0].lineRange.right.start_line + split;
-
+  let isSplitDone = false;
   const beforeGroups = [];
   const afterGroups = [];
   for (const group of groups) {
-    const isCompletelyBefore =
-      group.lineRange.left.end_line < leftSplit ||
-      group.lineRange.right.end_line < rightSplit;
-    const isCompletelyAfter =
-      leftSplit <= group.lineRange.left.start_line ||
-      rightSplit <= group.lineRange.right.start_line;
-    if (isCompletelyBefore) {
-      beforeGroups.push(group);
-    } else if (isCompletelyAfter) {
+    if (isSplitDone) {
       afterGroups.push(group);
+    } else if (
+      group.type === GrDiffGroupType.DELTA &&
+      !group.ignoredWhitespaceOnly
+    ) {
+      beforeGroups.push(group);
     } else {
-      const {beforeSplit, afterSplit} = splitGroupInTwo(
-        group,
-        leftSplit,
-        rightSplit
-      );
-      if (beforeSplit) {
-        beforeGroups.push(beforeSplit);
-      }
-      if (afterSplit) {
-        afterGroups.push(afterSplit);
+      const isCompletelyBefore =
+        group.lineRange.left.end_line < leftSplit ||
+        group.lineRange.right.end_line < rightSplit;
+      const isCompletelyAfter =
+        leftSplit <= group.lineRange.left.start_line ||
+        rightSplit <= group.lineRange.right.start_line;
+      if (isCompletelyBefore) {
+        beforeGroups.push(group);
+      } else if (isCompletelyAfter) {
+        afterGroups.push(group);
+      } else {
+        const {beforeSplit, afterSplit} = splitGroupInTwo(
+          group,
+          leftSplit,
+          rightSplit
+        );
+        if (beforeSplit) {
+          beforeGroups.push(beforeSplit);
+        }
+        if (afterSplit) {
+          afterGroups.push(afterSplit);
+        }
+        isSplitDone = true;
       }
     }
   }
@@ -438,9 +449,13 @@
     );
   }
 
-  /** Returns true if it contains a DELTA group. */
-  hasDeltaGroup() {
-    return this.contextGroups?.some(g => g.type === GrDiffGroupType.DELTA);
+  /** Returns true if it contains a DELTA group excluding whitespace only
+   * changes.
+   */
+  hasNonCommonDeltaGroup() {
+    return this.contextGroups?.some(
+      g => g.type === GrDiffGroupType.DELTA && !g.ignoredWhitespaceOnly
+    );
   }
 
   containsLine(side: Side, line: LineNumber) {
diff --git a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-group_test.ts b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-group_test.ts
index bbbb4ad..c8446f0 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-group_test.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-group_test.ts
@@ -97,6 +97,8 @@
 
   suite('hideInContextControl', () => {
     let groups: GrDiffGroup[];
+    let groupsWithDelta: GrDiffGroup[];
+    let groupsWithWhiteSpaceOnlyChange: GrDiffGroup[];
     setup(() => {
       groups = [
         new GrDiffGroup({
@@ -108,6 +110,46 @@
           ],
         }),
         new GrDiffGroup({
+          type: GrDiffGroupType.BOTH,
+          lines: [
+            new GrDiffLine(GrDiffLineType.BOTH, 8, 10),
+            new GrDiffLine(GrDiffLineType.BOTH, 9, 11),
+            new GrDiffLine(GrDiffLineType.BOTH, 10, 12),
+            new GrDiffLine(GrDiffLineType.BOTH, 11, 13),
+          ],
+        }),
+        new GrDiffGroup({
+          type: GrDiffGroupType.BOTH,
+          lines: [
+            new GrDiffLine(GrDiffLineType.BOTH, 12, 14),
+            new GrDiffLine(GrDiffLineType.BOTH, 13, 15),
+            new GrDiffLine(GrDiffLineType.BOTH, 14, 16),
+          ],
+        }),
+      ];
+
+      groupsWithWhiteSpaceOnlyChange = [
+        groups[0],
+        new GrDiffGroup({
+          type: GrDiffGroupType.DELTA,
+          lines: [
+            new GrDiffLine(GrDiffLineType.REMOVE, 8),
+            new GrDiffLine(GrDiffLineType.ADD, 0, 10),
+            new GrDiffLine(GrDiffLineType.REMOVE, 9),
+            new GrDiffLine(GrDiffLineType.ADD, 0, 11),
+            new GrDiffLine(GrDiffLineType.REMOVE, 10),
+            new GrDiffLine(GrDiffLineType.ADD, 0, 12),
+            new GrDiffLine(GrDiffLineType.REMOVE, 11),
+            new GrDiffLine(GrDiffLineType.ADD, 0, 13),
+          ],
+          ignoredWhitespaceOnly: true,
+        }),
+        groups[2],
+      ];
+
+      groupsWithDelta = [
+        groups[0],
+        new GrDiffGroup({
           type: GrDiffGroupType.DELTA,
           lines: [
             new GrDiffLine(GrDiffLineType.REMOVE, 8),
@@ -120,14 +162,7 @@
             new GrDiffLine(GrDiffLineType.ADD, 0, 13),
           ],
         }),
-        new GrDiffGroup({
-          type: GrDiffGroupType.BOTH,
-          lines: [
-            new GrDiffLine(GrDiffLineType.BOTH, 12, 14),
-            new GrDiffLine(GrDiffLineType.BOTH, 13, 15),
-            new GrDiffLine(GrDiffLineType.BOTH, 14, 16),
-          ],
-        }),
+        groups[2],
       ];
     });
 
@@ -144,29 +179,33 @@
       assert.equal(collapsedGroups[2], groups[2]);
     });
 
+    test('does not hides when split is at delta group in context control', () => {
+      const collapsedGroups = hideInContextControl(groupsWithDelta, 3, 7);
+      assert.equal(collapsedGroups.length, 3);
+
+      assert.equal(collapsedGroups[0], groupsWithDelta[0]);
+      assert.equal(collapsedGroups[1], groupsWithDelta[1]);
+      assert.equal(collapsedGroups[2], groupsWithDelta[2]);
+    });
+
     test('splits partially hidden groups', () => {
       const collapsedGroups = hideInContextControl(groups, 4, 8);
       assert.equal(collapsedGroups.length, 4);
       assert.equal(collapsedGroups[0], groups[0]);
 
-      assert.equal(collapsedGroups[1].type, GrDiffGroupType.DELTA);
-      assert.deepEqual(collapsedGroups[1].adds, [groups[1].adds[0]]);
-      assert.deepEqual(collapsedGroups[1].removes, [groups[1].removes[0]]);
+      assert.equal(collapsedGroups[1].type, GrDiffGroupType.BOTH);
+      assert.deepEqual(collapsedGroups[1].lines, [groups[1].lines[0]]);
 
       assert.equal(collapsedGroups[2].type, GrDiffGroupType.CONTEXT_CONTROL);
       assert.equal(collapsedGroups[2].contextGroups.length, 2);
 
       assert.equal(
         collapsedGroups[2].contextGroups[0].type,
-        GrDiffGroupType.DELTA
+        GrDiffGroupType.BOTH
       );
       assert.deepEqual(
-        collapsedGroups[2].contextGroups[0].adds,
-        groups[1].adds.slice(1)
-      );
-      assert.deepEqual(
-        collapsedGroups[2].contextGroups[0].removes,
-        groups[1].removes.slice(1)
+        collapsedGroups[2].contextGroups[0].lines,
+        groups[1].lines.slice(1)
       );
 
       assert.equal(
@@ -181,6 +220,54 @@
       assert.deepEqual(collapsedGroups[3].lines, groups[2].lines.slice(1));
     });
 
+    test('splits partially hidden common delta groups', () => {
+      const collapsedGroups = hideInContextControl(
+        groupsWithWhiteSpaceOnlyChange,
+        4,
+        8
+      );
+      assert.equal(collapsedGroups.length, 4);
+      assert.equal(collapsedGroups[0], groupsWithWhiteSpaceOnlyChange[0]);
+
+      assert.equal(collapsedGroups[1].type, GrDiffGroupType.DELTA);
+      assert.deepEqual(collapsedGroups[1].adds, [
+        groupsWithWhiteSpaceOnlyChange[1].adds[0],
+      ]);
+      assert.deepEqual(collapsedGroups[1].removes, [
+        groupsWithWhiteSpaceOnlyChange[1].removes[0],
+      ]);
+
+      assert.equal(collapsedGroups[2].type, GrDiffGroupType.CONTEXT_CONTROL);
+      assert.equal(collapsedGroups[2].contextGroups.length, 2);
+
+      assert.equal(
+        collapsedGroups[2].contextGroups[0].type,
+        GrDiffGroupType.DELTA
+      );
+      assert.deepEqual(
+        collapsedGroups[2].contextGroups[0].adds,
+        groupsWithWhiteSpaceOnlyChange[1].adds.slice(1)
+      );
+      assert.deepEqual(
+        collapsedGroups[2].contextGroups[0].removes,
+        groupsWithWhiteSpaceOnlyChange[1].removes.slice(1)
+      );
+
+      assert.equal(
+        collapsedGroups[2].contextGroups[1].type,
+        GrDiffGroupType.BOTH
+      );
+      assert.deepEqual(collapsedGroups[2].contextGroups[1].lines, [
+        groupsWithWhiteSpaceOnlyChange[2].lines[0],
+      ]);
+
+      assert.equal(collapsedGroups[3].type, GrDiffGroupType.BOTH);
+      assert.deepEqual(
+        collapsedGroups[3].lines,
+        groupsWithWhiteSpaceOnlyChange[2].lines.slice(1)
+      );
+    });
+
     suite('with skip chunks', () => {
       setup(() => {
         const skipGroup = new GrDiffGroup({