Add status chip to file dropdown in diff view

Move file status-chip into its own component and show the chip in the
file list dropdown in diff view.

Screenshot: https://imgur.com/a/bFaRkIR
Issue: Bug 2866
Change-Id: I40b50bd0af004a96d77853339f7c8b2b49a5a372
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts
index cd43a89..c062188 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts
@@ -26,6 +26,7 @@
 import '../../shared/gr-select/gr-select';
 import '../../shared/gr-tooltip-content/gr-tooltip-content';
 import '../../shared/gr-copy-clipboard/gr-copy-clipboard';
+import '../../shared/gr-file-status-chip/gr-file-status-chip';
 import {flush} from '@polymer/polymer/lib/legacy/polymer.dom';
 import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
 import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
@@ -89,16 +90,6 @@
 const EXPAND_ALL_TIMING_LABEL = 'ExpandAllDiffs';
 const EXPAND_ALL_AVG_TIMING_LABEL = 'ExpandAllPerDiff';
 
-const FileStatus = {
-  A: 'Added',
-  C: 'Copied',
-  D: 'Deleted',
-  M: 'Modified',
-  R: 'Renamed',
-  W: 'Rewritten',
-  U: 'Unchanged',
-};
-
 const FILE_ROW_CLASS = 'file-row';
 
 export interface GrFileList {
@@ -112,7 +103,7 @@
 interface ReviewedFileInfo extends FileInfo {
   isReviewed?: boolean;
 }
-interface NormalizedFileInfo extends ReviewedFileInfo {
+export interface NormalizedFileInfo extends ReviewedFileInfo {
   __path: string;
 }
 
@@ -1123,12 +1114,6 @@
     );
   }
 
-  _computeFileStatus(
-    status?: keyof typeof FileStatus
-  ): keyof typeof FileStatus {
-    return status || 'M';
-  }
-
   _computeDiffURL(
     change?: ParsedChangeInfo,
     patchRange?: PatchRange,
@@ -1203,12 +1188,6 @@
     return classes.join(' ');
   }
 
-  _computeStatusClass(file?: NormalizedFileInfo) {
-    if (!file) return '';
-    const classStr = this._computeClass('status', file.__path);
-    return `${classStr} ${this._computeFileStatus(file.status)}`;
-  }
-
   _computePathClass(
     path: string | undefined,
     expandedFilesRecord: ElementPropertyDeepChange<GrFileList, '_expandedFiles'>
@@ -1364,17 +1343,6 @@
   }
 
   /**
-   * Get a descriptive label for use in the status indicator's tooltip and
-   * ARIA label.
-   */
-  _computeFileStatusLabel(status?: keyof typeof FileStatus) {
-    const statusCode = this._computeFileStatus(status);
-    return hasOwnProperty(FileStatus, statusCode)
-      ? FileStatus[statusCode]
-      : 'Status Unknown';
-  }
-
-  /**
    * Converts any boolean-like variable to the string 'true' or 'false'
    *
    * This method is useful when you bind aria-checked attribute to a boolean
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_html.ts b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_html.ts
index eb20675..ec985af 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_html.ts
@@ -80,27 +80,6 @@
       text-align: left;
       width: 1.5em;
     }
-    .status {
-      display: inline-block;
-      border-radius: var(--border-radius);
-      margin-left: var(--spacing-s);
-      padding: 0 var(--spacing-m);
-      color: var(--primary-text-color);
-      font-size: var(--font-size-small);
-      background-color: var(--dark-add-highlight-color);
-    }
-    .status.invisible,
-    .status.M {
-      display: none;
-    }
-    .status.D,
-    .status.R,
-    .status.W {
-      background-color: var(--dark-remove-highlight-color);
-    }
-    .status.U {
-      background-color: var(--comment-background-color);
-    }
     .file-row {
       cursor: pointer;
     }
@@ -421,14 +400,7 @@
               >
                 [[_computeTruncatedPath(file.__path)]]
               </span>
-              <span
-                class$="[[_computeStatusClass(file)]]"
-                tabindex="0"
-                title$="[[_computeFileStatusLabel(file.status)]]"
-                aria-label$="[[_computeFileStatusLabel(file.status)]]"
-              >
-                [[_computeFileStatusLabel(file.status)]]
-              </span>
+              <gr-file-status-chip file="[[file]]"></gr-file-status-chip>
               <gr-copy-clipboard
                 hide-input=""
                 text="[[file.__path]]"
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.js b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.js
index 3846f73..d0ae833 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.js
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.js
@@ -699,16 +699,6 @@
       });
     });
 
-    test('computed properties', () => {
-      assert.equal(element._computeFileStatus('A'), 'A');
-      assert.equal(element._computeFileStatus(undefined), 'M');
-      assert.equal(element._computeFileStatus(null), 'M');
-
-      assert.equal(element._computeClass('clazz', '/foo/bar/baz'), 'clazz');
-      assert.equal(element._computeClass('clazz', '/COMMIT_MSG'),
-          'clazz invisible');
-    });
-
     test('file review status', () => {
       element._reviewed = ['/COMMIT_MSG', 'myfile.txt'];
       element._filesByPath = {
@@ -761,11 +751,6 @@
       assert.isFalse(toggleExpandSpy.called);
     });
 
-    test('_computeFileStatusLabel', () => {
-      assert.equal(element._computeFileStatusLabel('A'), 'Added');
-      assert.equal(element._computeFileStatusLabel('M'), 'Modified');
-    });
-
     test('_handleFileListClick', () => {
       element._filesByPath = {
         '/COMMIT_MSG': {},
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts
index 55e3b6b..828c53d 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts
@@ -1314,6 +1314,7 @@
           files.changeFilesByPath[path],
           /* includeUnmodified= */ true
         ),
+        file: {...files.changeFilesByPath[path], __path: path},
       });
     }
     return dropdownContent;
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.js b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.js
index aa03956..72e1a8f 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.js
@@ -942,28 +942,43 @@
             mobileText: 'chell.go',
             value: 'chell.go',
             bottomText: '',
+            file: {
+              __path: 'chell.go',
+            },
           }, {
             text: 'glados.txt',
             mobileText: 'glados.txt',
             value: 'glados.txt',
             bottomText: '',
+            file: {
+              __path: 'glados.txt',
+            },
           }, {
             text: 'wheatley.md',
             mobileText: 'wheatley.md',
             value: 'wheatley.md',
             bottomText: '',
+            file: {
+              __path: 'wheatley.md',
+            },
           },
           {
             text: 'Commit message',
             mobileText: 'Commit message',
             value: '/COMMIT_MSG',
             bottomText: '',
+            file: {
+              __path: '/COMMIT_MSG',
+            },
           },
           {
             text: 'Merge list',
             mobileText: 'Merge list',
             value: '/MERGE_LIST',
             bottomText: '',
+            file: {
+              __path: '/MERGE_LIST',
+            },
           },
         ];
 
diff --git a/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list.ts b/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list.ts
index 2ea72ca..37b5348 100644
--- a/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list.ts
+++ b/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list.ts
@@ -21,6 +21,7 @@
 import '../gr-button/gr-button';
 import '../gr-date-formatter/gr-date-formatter';
 import '../gr-select/gr-select';
+import '../gr-file-status-chip/gr-file-status-chip';
 import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
 import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
 import {PolymerElement} from '@polymer/polymer/polymer-element';
@@ -28,6 +29,7 @@
 import {customElement, property, observe} from '@polymer/decorators';
 import {IronDropdownElement} from '@polymer/iron-dropdown/iron-dropdown';
 import {Timestamp} from '../../../types/common';
+import {NormalizedFileInfo} from '../../change/gr-file-list/gr-file-list';
 
 /**
  * fired when the selected value of the dropdown changes
@@ -52,6 +54,7 @@
   mobileText?: string;
   date?: Timestamp;
   disabled?: boolean;
+  file?: NormalizedFileInfo;
 }
 
 export interface GrDropdownList {
diff --git a/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list_html.ts b/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list_html.ts
index 26a6b3f..f313174 100644
--- a/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list_html.ts
+++ b/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list_html.ts
@@ -158,6 +158,9 @@
             <template is="dom-if" if="[[item.date]]">
               <gr-date-formatter date-str="[[item.date]]"></gr-date-formatter>
             </template>
+            <template is="dom-if" if="[[item.file]]">
+              <gr-file-status-chip file="[[item.file]]"></gr-file-status-chip>
+            </template>
           </div>
           <template is="dom-if" if="[[item.bottomText]]">
             <div class="bottomContent">
diff --git a/polygerrit-ui/app/elements/shared/gr-file-status-chip/gr-file-status-chip.ts b/polygerrit-ui/app/elements/shared/gr-file-status-chip/gr-file-status-chip.ts
new file mode 100644
index 0000000..9298fa3
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-file-status-chip/gr-file-status-chip.ts
@@ -0,0 +1,90 @@
+/**
+ * @license
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import '../../../styles/shared-styles';
+import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
+import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
+import {htmlTemplate} from './gr-file-status-chip_html';
+import {customElement, property} from '@polymer/decorators';
+import {PolymerElement} from '@polymer/polymer/polymer-element';
+import {SpecialFilePath} from '../../../constants/constants';
+import {NormalizedFileInfo} from '../../change/gr-file-list/gr-file-list';
+import {hasOwnProperty} from '../../../utils/common-util';
+
+const FileStatus = {
+  A: 'Added',
+  C: 'Copied',
+  D: 'Deleted',
+  M: 'Modified',
+  R: 'Renamed',
+  W: 'Rewritten',
+  U: 'Unchanged',
+};
+
+@customElement('gr-file-status-chip')
+export class GrFileStatusChip extends GestureEventListeners(
+  LegacyElementMixin(PolymerElement)
+) {
+  static get template() {
+    return htmlTemplate;
+  }
+
+  @property({type: Object})
+  file?: NormalizedFileInfo;
+
+  /**
+   * Get a descriptive label for use in the status indicator's tooltip and
+   * ARIA label.
+   */
+  _computeFileStatusLabel(status?: keyof typeof FileStatus) {
+    const statusCode = this._computeFileStatus(status);
+    return hasOwnProperty(FileStatus, statusCode)
+      ? FileStatus[statusCode]
+      : 'Status Unknown';
+  }
+
+  _computeClass(baseClass?: string, path?: string) {
+    const classes = [];
+    if (baseClass) {
+      classes.push(baseClass);
+    }
+    if (
+      path === SpecialFilePath.COMMIT_MESSAGE ||
+      path === SpecialFilePath.MERGE_LIST
+    ) {
+      classes.push('invisible');
+    }
+    return classes.join(' ');
+  }
+
+  _computeFileStatus(
+    status?: keyof typeof FileStatus
+  ): keyof typeof FileStatus {
+    return status || 'M';
+  }
+
+  _computeStatusClass(file?: NormalizedFileInfo) {
+    if (!file) return '';
+    const classStr = this._computeClass('status', file.__path);
+    return `${classStr} ${this._computeFileStatus(file.status)}`;
+  }
+}
+
+declare global {
+  interface HTMLElementTagNameMap {
+    'gr-file-status-chip': GrFileStatusChip;
+  }
+}
diff --git a/polygerrit-ui/app/elements/shared/gr-file-status-chip/gr-file-status-chip_html.ts b/polygerrit-ui/app/elements/shared/gr-file-status-chip/gr-file-status-chip_html.ts
new file mode 100644
index 0000000..9e49868
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-file-status-chip/gr-file-status-chip_html.ts
@@ -0,0 +1,51 @@
+/**
+ * @license
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import {html} from '@polymer/polymer/lib/utils/html-tag';
+
+export const htmlTemplate = html`
+  <style include="shared-styles">
+    .status {
+      display: inline-block;
+      border-radius: var(--border-radius);
+      margin-left: var(--spacing-s);
+      padding: 0 var(--spacing-m);
+      color: var(--primary-text-color);
+      font-size: var(--font-size-small);
+      background-color: var(--dark-add-highlight-color);
+    }
+    .status.invisible,
+    .status.M {
+      display: none;
+    }
+    .status.D,
+    .status.R,
+    .status.W {
+      background-color: var(--dark-remove-highlight-color);
+    }
+    .status.U {
+      background-color: var(--comment-background-color);
+    }
+  </style>
+  <span
+    class$="[[_computeStatusClass(file)]]"
+    tabindex="0"
+    title$="[[_computeFileStatusLabel(file.status)]]"
+    aria-label$="[[_computeFileStatusLabel(file.status)]]"
+  >
+    [[_computeFileStatusLabel(file.status)]]
+  </span>
+`;
diff --git a/polygerrit-ui/app/elements/shared/gr-file-status-chip/gr-file-status-chip_test.ts b/polygerrit-ui/app/elements/shared/gr-file-status-chip/gr-file-status-chip_test.ts
new file mode 100644
index 0000000..0abc85f
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-file-status-chip/gr-file-status-chip_test.ts
@@ -0,0 +1,46 @@
+/**
+ * @license
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import '../../../test/common-test-setup-karma';
+import './gr-file-status-chip';
+import {GrFileStatusChip} from './gr-file-status-chip';
+
+const fixture = fixtureFromElement('gr-file-status-chip');
+
+suite('gr-file-status-chip tests', () => {
+  let element: GrFileStatusChip;
+
+  setup(() => {
+    element = fixture.instantiate();
+  });
+
+  test('computed properties', () => {
+    assert.equal(element._computeFileStatus('A'), 'A');
+    assert.equal(element._computeFileStatus(undefined), 'M');
+
+    assert.equal(element._computeClass('clazz', '/foo/bar/baz'), 'clazz');
+    assert.equal(
+      element._computeClass('clazz', '/COMMIT_MSG'),
+      'clazz invisible'
+    );
+  });
+
+  test('_computeFileStatusLabel', () => {
+    assert.equal(element._computeFileStatusLabel('A'), 'Added');
+    assert.equal(element._computeFileStatusLabel('M'), 'Modified');
+  });
+});