From 3c668fa04fd7e0a1d925e9666eb727ed3e83d145 Mon Sep 17 00:00:00 2001
From: Bryce Johnson <bryce@gitlab.com>
Date: Sat, 15 Apr 2017 19:38:07 -0400
Subject: [PATCH 1/3] Consolidate user avatar Vue logic

---
 .../boards/components/issue_card_inner.js     | 27 +++----
 .../components/stage_code_component.js        |  7 +-
 .../components/stage_issue_component.js       |  8 +-
 .../components/stage_plan_component.js        |  9 ++-
 .../components/stage_production_component.js  |  8 +-
 .../components/stage_review_component.js      |  8 +-
 .../components/stage_staging_component.js     |  7 +-
 .../components/diff_note_avatars.js           | 28 ++++---
 .../components/environment_item.vue           | 18 ++---
 .../pipelines/components/pipeline_url.js      | 22 ++---
 .../vue_shared/components/commit.js           | 20 ++---
 .../user_avatar/user_avatar_image.vue         | 79 ++++++++++++++++++
 .../user_avatar/user_avatar_link.vue          | 80 +++++++++++++++++++
 .../user_avatar/user_avatar_size_mixin.js     | 13 +++
 .../user_avatar/user_avatar_svg.vue           | 42 ++++++++++
 app/assets/stylesheets/framework/icons.scss   |  4 +
 .../stylesheets/pages/environments.scss       |  4 -
 config/webpack.config.js                      |  5 +-
 .../merge_requests/diff_notes_avatars_spec.rb |  2 +-
 spec/javascripts/boards/issue_card_spec.js    |  2 +-
 .../pipelines/pipeline_url_spec.js            |  2 +-
 .../vue_shared/components/commit_spec.js      |  2 +-
 .../components/pipelines_table_row_spec.js    |  4 +-
 .../components/user_avatar_image_spec.js      | 60 ++++++++++++++
 .../components/user_avatar_link_spec.js       | 58 ++++++++++++++
 .../components/user_avatar_size_mixin_spec.js | 37 +++++++++
 .../components/user_avatar_svg_spec.js        | 35 ++++++++
 27 files changed, 513 insertions(+), 78 deletions(-)
 create mode 100644 app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_image.vue
 create mode 100644 app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_link.vue
 create mode 100644 app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_size_mixin.js
 create mode 100644 app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_svg.vue
 create mode 100644 spec/javascripts/vue_shared/components/user_avatar_image_spec.js
 create mode 100644 spec/javascripts/vue_shared/components/user_avatar_link_spec.js
 create mode 100644 spec/javascripts/vue_shared/components/user_avatar_size_mixin_spec.js
 create mode 100644 spec/javascripts/vue_shared/components/user_avatar_svg_spec.js

diff --git a/app/assets/javascripts/boards/components/issue_card_inner.js b/app/assets/javascripts/boards/components/issue_card_inner.js
index 710207db0c7..4699ef5a51c 100644
--- a/app/assets/javascripts/boards/components/issue_card_inner.js
+++ b/app/assets/javascripts/boards/components/issue_card_inner.js
@@ -1,4 +1,5 @@
 import Vue from 'vue';
+import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
 import eventHub from '../eventhub';
 
 const Store = gl.issueBoards.BoardsStore;
@@ -38,6 +39,9 @@ gl.issueBoards.IssueCardInner = Vue.extend({
       maxCounter: 99,
     };
   },
+  components: {
+    userAvatarLink,
+  },
   computed: {
     numberOverLimit() {
       return this.issue.assignees.length - this.limitBeforeCounter;
@@ -146,23 +150,16 @@ gl.issueBoards.IssueCardInner = Vue.extend({
           </span>
         </h4>
         <div class="card-assignee">
-          <a
-            class="has-tooltip js-no-trigger"
-            :href="assigneeUrl(assignee)"
-            :title="assigneeUrlTitle(assignee)"
+          <user-avatar-link
             v-for="(assignee, index) in issue.assignees"
             v-if="shouldRenderAssignee(index)"
-            data-container="body"
-            data-placement="bottom"
-          >
-            <img
-              class="avatar avatar-inline s20"
-              :src="assignee.avatar"
-              width="20"
-              height="20"
-              :alt="avatarUrlTitle(assignee)"
-            />
-          </a>
+            class="js-no-trigger"
+            :link-href="assigneeUrl(assignee)"
+            :img-alt="avatarUrlTitle(assignee)"
+            :img-src="assignee.avatar"
+            :tooltip-text="assigneeUrlTitle(assignee)"
+            tooltip-placement="bottom"
+          />
           <span
             class="avatar-counter has-tooltip"
             :title="assigneeCounterTooltip"
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_code_component.js b/app/assets/javascripts/cycle_analytics/components/stage_code_component.js
index 0d9ad197abf..eeb61826ace 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_code_component.js
+++ b/app/assets/javascripts/cycle_analytics/components/stage_code_component.js
@@ -1,6 +1,7 @@
 /* eslint-disable no-param-reassign */
 
 import Vue from 'vue';
+import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
 
 const global = window.gl || (window.gl = {});
 global.cycleAnalytics = global.cycleAnalytics || {};
@@ -10,6 +11,9 @@ global.cycleAnalytics.StageCodeComponent = Vue.extend({
     items: Array,
     stage: Object,
   },
+  components: {
+    userAvatarImage,
+  },
   template: `
     <div>
       <div class="events-description">
@@ -19,7 +23,8 @@ global.cycleAnalytics.StageCodeComponent = Vue.extend({
       <ul class="stage-event-list">
         <li v-for="mergeRequest in items" class="stage-event-item">
           <div class="item-details">
-            <img class="avatar" :src="mergeRequest.author.avatarUrl">
+            <!-- FIXME: Pass an alt attribute here for accessibility -->
+            <user-avatar-image :img-src="mergeRequest.author.avatarUrl"/>
             <h5 class="item-title merge-merquest-title">
               <a :href="mergeRequest.url">
                 {{ mergeRequest.title }}
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_issue_component.js b/app/assets/javascripts/cycle_analytics/components/stage_issue_component.js
index ad285874643..09fb390787d 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_issue_component.js
+++ b/app/assets/javascripts/cycle_analytics/components/stage_issue_component.js
@@ -1,6 +1,6 @@
 /* eslint-disable no-param-reassign */
-
 import Vue from 'vue';
+import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
 
 const global = window.gl || (window.gl = {});
 global.cycleAnalytics = global.cycleAnalytics || {};
@@ -10,6 +10,9 @@ global.cycleAnalytics.StageIssueComponent = Vue.extend({
     items: Array,
     stage: Object,
   },
+  components: {
+    userAvatarImage,
+  },
   template: `
     <div>
       <div class="events-description">
@@ -19,7 +22,8 @@ global.cycleAnalytics.StageIssueComponent = Vue.extend({
       <ul class="stage-event-list">
         <li v-for="issue in items" class="stage-event-item">
           <div class="item-details">
-            <img class="avatar" :src="issue.author.avatarUrl">
+            <!-- FIXME: Pass an alt attribute here for accessibility -->
+            <user-avatar-image :img-src="issue.author.avatarUrl"/>
             <h5 class="item-title issue-title">
               <a class="issue-title" :href="issue.url">
                 {{ issue.title }}
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_plan_component.js b/app/assets/javascripts/cycle_analytics/components/stage_plan_component.js
index dec1704395e..cd7a94b67c1 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_plan_component.js
+++ b/app/assets/javascripts/cycle_analytics/components/stage_plan_component.js
@@ -1,5 +1,6 @@
 /* eslint-disable no-param-reassign */
 import Vue from 'vue';
+import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
 import iconCommit from '../svg/icon_commit.svg';
 
 const global = window.gl || (window.gl = {});
@@ -10,11 +11,12 @@ global.cycleAnalytics.StagePlanComponent = Vue.extend({
     items: Array,
     stage: Object,
   },
-
+  components: {
+    userAvatarImage,
+  },
   data() {
     return { iconCommit };
   },
-
   template: `
     <div>
       <div class="events-description">
@@ -24,7 +26,8 @@ global.cycleAnalytics.StagePlanComponent = Vue.extend({
       <ul class="stage-event-list">
         <li v-for="commit in items" class="stage-event-item">
           <div class="item-details item-conmmit-component">
-            <img class="avatar" :src="commit.author.avatarUrl">
+            <!-- FIXME: Pass an alt attribute here for accessibility -->
+            <user-avatar-image :img-src="commit.author.avatarUrl"/>
             <h5 class="item-title commit-title">
               <a :href="commit.commitUrl">
                 {{ commit.title }}
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_production_component.js b/app/assets/javascripts/cycle_analytics/components/stage_production_component.js
index a14ebc3ece9..bdf86b4ff3c 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_production_component.js
+++ b/app/assets/javascripts/cycle_analytics/components/stage_production_component.js
@@ -1,6 +1,6 @@
 /* eslint-disable no-param-reassign */
-
 import Vue from 'vue';
+import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
 
 const global = window.gl || (window.gl = {});
 global.cycleAnalytics = global.cycleAnalytics || {};
@@ -10,6 +10,9 @@ global.cycleAnalytics.StageProductionComponent = Vue.extend({
     items: Array,
     stage: Object,
   },
+  components: {
+    userAvatarImage,
+  },
   template: `
     <div>
       <div class="events-description">
@@ -19,7 +22,8 @@ global.cycleAnalytics.StageProductionComponent = Vue.extend({
       <ul class="stage-event-list">
         <li v-for="issue in items" class="stage-event-item">
           <div class="item-details">
-            <img class="avatar" :src="issue.author.avatarUrl">
+            <!-- FIXME: Pass an alt attribute here for accessibility -->
+            <user-avatar-image :img-src="issue.author.avatarUrl"/>
             <h5 class="item-title issue-title">
               <a class="issue-title" :href="issue.url">
                 {{ issue.title }}
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_review_component.js b/app/assets/javascripts/cycle_analytics/components/stage_review_component.js
index 1a5bf9bc0b5..cfb7a4ab576 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_review_component.js
+++ b/app/assets/javascripts/cycle_analytics/components/stage_review_component.js
@@ -1,6 +1,6 @@
 /* eslint-disable no-param-reassign */
-
 import Vue from 'vue';
+import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
 
 const global = window.gl || (window.gl = {});
 global.cycleAnalytics = global.cycleAnalytics || {};
@@ -10,6 +10,9 @@ global.cycleAnalytics.StageReviewComponent = Vue.extend({
     items: Array,
     stage: Object,
   },
+  components: {
+    userAvatarImage,
+  },
   template: `
     <div>
       <div class="events-description">
@@ -19,7 +22,8 @@ global.cycleAnalytics.StageReviewComponent = Vue.extend({
       <ul class="stage-event-list">
         <li v-for="mergeRequest in items" class="stage-event-item">
           <div class="item-details">
-            <img class="avatar" :src="mergeRequest.author.avatarUrl">
+            <!-- FIXME: Pass an alt attribute here for accessibility -->
+            <user-avatar-image :img-src="mergeRequest.author.avatarUrl"/>
             <h5 class="item-title merge-merquest-title">
               <a :href="mergeRequest.url">
                 {{ mergeRequest.title }}
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_staging_component.js b/app/assets/javascripts/cycle_analytics/components/stage_staging_component.js
index 1f7c673b1d4..97a849c4feb 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_staging_component.js
+++ b/app/assets/javascripts/cycle_analytics/components/stage_staging_component.js
@@ -1,5 +1,6 @@
 /* eslint-disable no-param-reassign */
 import Vue from 'vue';
+import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
 import iconBranch from '../svg/icon_branch.svg';
 
 const global = window.gl || (window.gl = {});
@@ -13,6 +14,9 @@ global.cycleAnalytics.StageStagingComponent = Vue.extend({
   data() {
     return { iconBranch };
   },
+  components: {
+    userAvatarImage,
+  },
   template: `
     <div>
       <div class="events-description">
@@ -22,7 +26,8 @@ global.cycleAnalytics.StageStagingComponent = Vue.extend({
       <ul class="stage-event-list">
         <li v-for="build in items" class="stage-event-item item-build-component">
           <div class="item-details">
-            <img class="avatar" :src="build.author.avatarUrl">
+            <!-- FIXME: Pass an alt attribute here for accessibility -->
+            <user-avatar-image :img-src="build.author.avatarUrl"/>
             <h5 class="item-title">
               <a :href="build.url" class="pipeline-id">#{{ build.id }}</a>
               <i class="fa fa-code-fork"></i>
diff --git a/app/assets/javascripts/diff_notes/components/diff_note_avatars.js b/app/assets/javascripts/diff_notes/components/diff_note_avatars.js
index 5f533b5761c..517bdb6be09 100644
--- a/app/assets/javascripts/diff_notes/components/diff_note_avatars.js
+++ b/app/assets/javascripts/diff_notes/components/diff_note_avatars.js
@@ -3,6 +3,7 @@
 
 import Vue from 'vue';
 import collapseIcon from '../icons/collapse_icon.svg';
+import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
 
 const DiffNoteAvatars = Vue.extend({
   props: ['discussionId'],
@@ -15,22 +16,24 @@ const DiffNoteAvatars = Vue.extend({
       collapseIcon,
     };
   },
+  components: {
+    userAvatarImage,
+  },
   template: `
     <div class="diff-comment-avatar-holders"
       v-show="notesCount !== 0">
       <div v-if="!isVisible">
-        <img v-for="note in notesSubset"
-          class="avatar diff-comment-avatar has-tooltip js-diff-comment-avatar"
-          width="19"
-          height="19"
-          role="button"
-          data-container="body"
-          data-placement="top"
-          data-html="true"
+        <!-- FIXME: Pass an alt attribute here for accessibility -->
+        <user-avatar-image
+          v-for="note in notesSubset"
+          class="diff-comment-avatar js-diff-comment-avatar"
+          @click.native="clickedAvatar($event)"
+          :img-src="note.authorAvatar"
+          :tooltip-text="getTooltipText(note)"
           :data-line-type="lineType"
-          :title="note.authorName + ': ' + note.noteTruncated"
-          :src="note.authorAvatar"
-          @click="clickedAvatar($event)" />
+          :size="19"
+          data-html="true"
+        />
         <span v-if="notesCount > shownAvatars"
           class="diff-comments-more-count has-tooltip js-diff-comment-avatar"
           data-container="body"
@@ -150,6 +153,9 @@ const DiffNoteAvatars = Vue.extend({
     setDiscussionVisible() {
       this.isVisible = $(`.diffs .notes[data-discussion-id="${this.discussion.id}"]`).is(':visible');
     },
+    getTooltipText(note) {
+      return `${note.authorName}: ${note.noteTruncated}`;
+    },
   },
 });
 
diff --git a/app/assets/javascripts/environments/components/environment_item.vue b/app/assets/javascripts/environments/components/environment_item.vue
index 1f01629aa1b..012ff1f975b 100644
--- a/app/assets/javascripts/environments/components/environment_item.vue
+++ b/app/assets/javascripts/environments/components/environment_item.vue
@@ -1,6 +1,7 @@
 <script>
 import Timeago from 'timeago.js';
 import _ from 'underscore';
+import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
 import '../../lib/utils/text_utility';
 import ActionsComponent from './environment_actions.vue';
 import ExternalUrlComponent from './environment_external_url.vue';
@@ -20,6 +21,7 @@ const timeagoInstance = new Timeago();
 
 export default {
   components: {
+    userAvatarLink,
     'commit-component': CommitComponent,
     'actions-component': ActionsComponent,
     'external-url-component': ExternalUrlComponent,
@@ -468,15 +470,13 @@ export default {
 
       <span v-if="!model.isFolder && deploymentHasUser">
         by
-        <a
-          :href="deploymentUser.web_url"
-          class="js-deploy-user-container">
-          <img
-            class="avatar has-tooltip s20"
-            :src="deploymentUser.avatar_url"
-            :alt="userImageAltDescription"
-            :title="deploymentUser.username" />
-        </a>
+        <user-avatar-link
+          class="js-deploy-user-container"
+          :link-href="deploymentUser.web_url"
+          :img-src="deploymentUser.avatar_url"
+          :img-alt="userImageAltDescription"
+          :tooltip-text="deploymentUser.username"
+        />
       </span>
     </td>
 
diff --git a/app/assets/javascripts/pipelines/components/pipeline_url.js b/app/assets/javascripts/pipelines/components/pipeline_url.js
index ea8aaca6c9c..7cd2e0f9366 100644
--- a/app/assets/javascripts/pipelines/components/pipeline_url.js
+++ b/app/assets/javascripts/pipelines/components/pipeline_url.js
@@ -1,3 +1,5 @@
+import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
+
 export default {
   props: [
     'pipeline',
@@ -7,6 +9,9 @@ export default {
       return !!this.pipeline.user;
     },
   },
+  components: {
+    userAvatarLink,
+  },
   template: `
     <td>
       <a
@@ -15,18 +20,13 @@ export default {
         <span class="pipeline-id">#{{pipeline.id}}</span>
       </a>
       <span>by</span>
-      <a
-        class="js-pipeline-url-user"
+      <user-avatar-link
         v-if="user"
-        :href="pipeline.user.web_url">
-        <img
-          v-if="user"
-          class="avatar has-tooltip s20 "
-          :title="pipeline.user.name"
-          data-container="body"
-          :src="pipeline.user.avatar_url"
-        >
-      </a>
+        class="js-pipeline-url-user"
+        :link-href="pipeline.user.web_url"
+        :img-src="pipeline.user.avatar_url"
+        :tooltip-text="pipeline.user.name"
+      />
       <span
         v-if="!user"
         class="js-pipeline-url-api api">
diff --git a/app/assets/javascripts/vue_shared/components/commit.js b/app/assets/javascripts/vue_shared/components/commit.js
index 9b060a0a35f..23bc5fbc034 100644
--- a/app/assets/javascripts/vue_shared/components/commit.js
+++ b/app/assets/javascripts/vue_shared/components/commit.js
@@ -1,4 +1,5 @@
 import commitIconSvg from 'icons/_icon_commit.svg';
+import userAvatarLink from './user_avatar/user_avatar_link.vue';
 
 export default {
   props: {
@@ -110,6 +111,9 @@ export default {
     return { commitIconSvg };
   },
 
+  components: {
+    userAvatarLink,
+  },
   template: `
     <div class="branch-commit">
 
@@ -133,16 +137,14 @@ export default {
 
       <p class="commit-title">
         <span v-if="title">
-          <a v-if="hasAuthor"
+          <user-avatar-link
+            v-if="hasAuthor"
             class="avatar-image-container"
-            :href="author.web_url">
-            <img
-              class="avatar has-tooltip s20"
-              :src="author.avatar_url"
-              :alt="userImageAltDescription"
-              :title="author.username" />
-          </a>
-
+            :link-href="author.web_url"
+            :img-src="author.avatar_url"
+            :img-alt="userImageAltDescription"
+            :tooltip-text="author.username"
+          />
           <a class="commit-row-message"
             :href="commitUrl">
             {{title}}
diff --git a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_image.vue b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_image.vue
new file mode 100644
index 00000000000..4891e7f927c
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_image.vue
@@ -0,0 +1,79 @@
+<script>
+
+/* This is a re-usable vue component for rendering a user avatar that
+  does not need to link to the user's profile. The image and an optional
+  tooltip can be configured by props passed to this component.
+
+  Sample configuration:
+
+  <user-avatar-image
+    :img-src="userAvatarSrc"
+    :img-alt="tooltipText"
+    :tooltip-text="tooltipText"
+    tooltip-placement="top"
+  />
+
+*/
+
+import defaultAvatarUrl from 'images/no_avatar.png';
+import UserAvatarSizeMixin from './user_avatar_size_mixin';
+import TooltipMixin from '../../mixins/tooltip';
+
+export default {
+  name: 'UserAvatarImage',
+  mixins: [UserAvatarSizeMixin, TooltipMixin],
+  props: {
+    imgSrc: {
+      type: String,
+      required: false,
+      default: defaultAvatarUrl,
+    },
+    cssClasses: {
+      type: String,
+      required: false,
+      default: '',
+    },
+    imgAlt: {
+      type: String,
+      required: false,
+      default: 'user avatar',
+    },
+    size: {
+      type: Number,
+      required: false,
+      default: 20,
+    },
+    tooltipText: {
+      type: String,
+      required: false,
+      default: '',
+    },
+    tooltipPlacement: {
+      type: String,
+      required: false,
+      default: 'top',
+    },
+  },
+  computed: {
+    tooltipContainer() {
+      return this.tooltipText ? 'body' : null;
+    },
+    imgCssClasses() {
+      return `avatar ${this.avatarSizeClass} ${this.cssClasses}`;
+    },
+  },
+};
+</script>
+
+<template>
+  <img
+    :class="imgCssClasses"
+    :src="imgSrc"
+    :style="avatarSizeStylesMap"
+    :alt="imgAlt"
+    :data-container="tooltipContainer"
+    :data-placement="tooltipPlacement"
+    :title="tooltipText"
+    ref="tooltip"
+  />
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_link.vue b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_link.vue
new file mode 100644
index 00000000000..95898d54cf7
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_link.vue
@@ -0,0 +1,80 @@
+<script>
+
+/* This is a re-usable vue component for rendering a user avatar wrapped in
+  a clickable link (likely to the user's profile). The link, image, and
+  tooltip can be configured by props passed to this component.
+
+  Sample configuration:
+
+  <user-avatar-link
+    :link-href="userProfileUrl"
+    :img-src="userAvatarSrc"
+    :img-alt="tooltipText"
+    :img-size="20"
+    :tooltip-text="tooltipText"
+    tooltip-placement="top"
+  />
+
+*/
+
+import userAvatarImage from './user_avatar_image.vue';
+
+export default {
+  name: 'UserAvatarLink',
+  components: {
+    userAvatarImage,
+  },
+  props: {
+    linkHref: {
+      type: String,
+      required: false,
+      default: '',
+    },
+    imgSrc: {
+      type: String,
+      required: false,
+      default: '',
+    },
+    imgAlt: {
+      type: String,
+      required: false,
+      default: '',
+    },
+    imgCssClasses: {
+      type: String,
+      required: false,
+      default: '',
+    },
+    imgSize: {
+      type: Number,
+      required: false,
+      default: 20,
+    },
+    tooltipText: {
+      type: String,
+      required: false,
+      default: '',
+    },
+    tooltipPlacement: {
+      type: String,
+      required: false,
+      default: 'top',
+    },
+  },
+};
+</script>
+
+<template>
+  <a
+    class="user-avatar-link"
+    :href="linkHref">
+    <user-avatar-image
+      :img-src="imgSrc"
+      :img-alt="imgAlt"
+      :css-classes="imgCssClasses"
+      :size="imgSize"
+      :tooltip-text="tooltipText"
+      :tooltip-placement="tooltipPlacement"
+    />
+  </a>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_size_mixin.js b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_size_mixin.js
new file mode 100644
index 00000000000..b6155ffd28e
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_size_mixin.js
@@ -0,0 +1,13 @@
+export default {
+  computed: {
+    avatarSizeStylesMap() {
+      return {
+        width: `${this.size}px`,
+        height: `${this.size}px`,
+      };
+    },
+    avatarSizeClass() {
+      return `s${this.size}`;
+    },
+  },
+};
diff --git a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_svg.vue b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_svg.vue
new file mode 100644
index 00000000000..39b4d37c91e
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_svg.vue
@@ -0,0 +1,42 @@
+<script>
+
+/* This is a re-usable vue component for rendering a user avatar svg (typically
+  for a blank state). It will receive styles comparable to the user avatar,
+  but no image is loaded, it isn't wrapped in a link, and tooltips aren't supported.
+  The svg and avatar size can be configured by props passed to this component.
+
+  Sample configuration:
+
+  <user-avatar-svg
+    :svg="potentialApproverSvg"
+    :size="20"
+  />
+
+*/
+
+import UserAvatarSizeMixin from './user_avatar_size_mixin';
+
+export default {
+  mixins: [UserAvatarSizeMixin],
+  props: {
+    svg: {
+      type: String,
+      required: true,
+    },
+    size: {
+      type: Number,
+      required: false,
+      default: 20,
+    },
+  },
+};
+</script>
+
+<template>
+  <svg
+    :class="avatarSizeClass"
+    :style="avatarSizeStylesMap"
+    v-html="svg">
+  </svg>
+</template>
+
diff --git a/app/assets/stylesheets/framework/icons.scss b/app/assets/stylesheets/framework/icons.scss
index 1b7d4e42258..ef864e8f6a9 100644
--- a/app/assets/stylesheets/framework/icons.scss
+++ b/app/assets/stylesheets/framework/icons.scss
@@ -65,3 +65,7 @@
     text-decoration: none;
   }
 }
+
+.user-avatar-link {
+  text-decoration: none;
+}
diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss
index a42ae7e55a5..48d3b7b1d07 100644
--- a/app/assets/stylesheets/pages/environments.scss
+++ b/app/assets/stylesheets/pages/environments.scss
@@ -68,10 +68,6 @@
       margin: 0;
     }
 
-    .avatar-image-container {
-      text-decoration: none;
-    }
-
     .icon-play {
       height: 13px;
       width: 12px;
diff --git a/config/webpack.config.js b/config/webpack.config.js
index 0781017c89f..7bc225968de 100644
--- a/config/webpack.config.js
+++ b/config/webpack.config.js
@@ -90,9 +90,9 @@ var config = {
         loader: 'raw-loader',
       },
       {
-        test: /\.gif$/,
+        test: /\.(gif|png)$/,
         loader: 'url-loader',
-        query: { mimetype: 'image/gif' },
+        options: { limit: 2048 },
       },
       {
         test: /\.(worker\.js|pdf|bmpr)$/,
@@ -190,6 +190,7 @@ var config = {
       'emojis':         path.join(ROOT_PATH, 'fixtures/emojis'),
       'empty_states':   path.join(ROOT_PATH, 'app/views/shared/empty_states'),
       'icons':          path.join(ROOT_PATH, 'app/views/shared/icons'),
+      'images':         path.join(ROOT_PATH, 'app/assets/images'),
       'vendor':         path.join(ROOT_PATH, 'vendor/assets/javascripts'),
       'vue$':           'vue/dist/vue.esm.js',
     }
diff --git a/spec/features/merge_requests/diff_notes_avatars_spec.rb b/spec/features/merge_requests/diff_notes_avatars_spec.rb
index b2e170513c4..ccf047d3efa 100644
--- a/spec/features/merge_requests/diff_notes_avatars_spec.rb
+++ b/spec/features/merge_requests/diff_notes_avatars_spec.rb
@@ -91,7 +91,7 @@ feature 'Diff note avatars', feature: true, js: true do
         page.within find("[id='#{position.line_code(project.repository)}']") do
           find('.diff-notes-collapse').click
 
-          expect(first('img.js-diff-comment-avatar')["title"]).to eq("#{note.author.name}: #{note.note.truncate(17)}")
+          expect(first('img.js-diff-comment-avatar')["data-original-title"]).to eq("#{note.author.name}: #{note.note.truncate(17)}")
         end
       end
 
diff --git a/spec/javascripts/boards/issue_card_spec.js b/spec/javascripts/boards/issue_card_spec.js
index fddde799d01..bd9b4fbfdd3 100644
--- a/spec/javascripts/boards/issue_card_spec.js
+++ b/spec/javascripts/boards/issue_card_spec.js
@@ -129,7 +129,7 @@ describe('Issue card component', () => {
 
       it('sets title', () => {
         expect(
-          component.$el.querySelector('.card-assignee a').getAttribute('title'),
+          component.$el.querySelector('.card-assignee img').getAttribute('data-original-title'),
         ).toContain(`Assigned to ${user.name}`);
       });
 
diff --git a/spec/javascripts/pipelines/pipeline_url_spec.js b/spec/javascripts/pipelines/pipeline_url_spec.js
index 53931d67ad7..0bcc3905702 100644
--- a/spec/javascripts/pipelines/pipeline_url_spec.js
+++ b/spec/javascripts/pipelines/pipeline_url_spec.js
@@ -60,7 +60,7 @@ describe('Pipeline Url Component', () => {
     expect(
       component.$el.querySelector('.js-pipeline-url-user').getAttribute('href'),
     ).toEqual(mockData.pipeline.user.web_url);
-    expect(image.getAttribute('title')).toEqual(mockData.pipeline.user.name);
+    expect(image.getAttribute('data-original-title')).toEqual(mockData.pipeline.user.name);
     expect(image.getAttribute('src')).toEqual(mockData.pipeline.user.avatar_url);
   });
 
diff --git a/spec/javascripts/vue_shared/components/commit_spec.js b/spec/javascripts/vue_shared/components/commit_spec.js
index 242010ba688..0638483e7aa 100644
--- a/spec/javascripts/vue_shared/components/commit_spec.js
+++ b/spec/javascripts/vue_shared/components/commit_spec.js
@@ -86,7 +86,7 @@ describe('Commit component', () => {
 
       it('Should render the author avatar with title and alt attributes', () => {
         expect(
-          component.$el.querySelector('.commit-title .avatar-image-container img').getAttribute('title'),
+          component.$el.querySelector('.commit-title .avatar-image-container img').getAttribute('data-original-title'),
         ).toContain(props.author.username);
         expect(
           component.$el.querySelector('.commit-title .avatar-image-container img').getAttribute('alt'),
diff --git a/spec/javascripts/vue_shared/components/pipelines_table_row_spec.js b/spec/javascripts/vue_shared/components/pipelines_table_row_spec.js
index 14280751053..286118917e8 100644
--- a/spec/javascripts/vue_shared/components/pipelines_table_row_spec.js
+++ b/spec/javascripts/vue_shared/components/pipelines_table_row_spec.js
@@ -79,7 +79,7 @@ describe('Pipelines Table Row', () => {
         ).toEqual(pipeline.user.web_url);
 
         expect(
-          component.$el.querySelector('td:nth-child(2) img').getAttribute('title'),
+          component.$el.querySelector('td:nth-child(2) img').getAttribute('data-original-title'),
         ).toEqual(pipeline.user.name);
       });
     });
@@ -102,7 +102,7 @@ describe('Pipelines Table Row', () => {
       }
 
       const commitAuthorLink = commitAuthorElement.getAttribute('href');
-      const commitAuthorName = commitAuthorElement.querySelector('img.avatar').getAttribute('title');
+      const commitAuthorName = commitAuthorElement.querySelector('img.avatar').getAttribute('data-original-title');
 
       return { commitAuthorElement, commitAuthorLink, commitAuthorName };
     };
diff --git a/spec/javascripts/vue_shared/components/user_avatar_image_spec.js b/spec/javascripts/vue_shared/components/user_avatar_image_spec.js
new file mode 100644
index 00000000000..0b5ec736b1e
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/user_avatar_image_spec.js
@@ -0,0 +1,60 @@
+import Vue from 'vue';
+import UserAvatarImage from '~/vue_shared/components/user_avatar/user_avatar_image.vue';
+
+const UserAvatarImageComponent = Vue.extend(UserAvatarImage);
+
+describe('User Avatar Image Component', function () {
+  describe('Initialization', function () {
+    beforeEach(function () {
+      this.propsData = {
+        size: 99,
+        imgSrc: 'myavatarurl.com',
+        imgAlt: 'mydisplayname',
+        cssClasses: 'myextraavatarclass',
+        tooltipText: 'tooltip text',
+        tooltipPlacement: 'bottom',
+      };
+
+      this.userAvatarImage = new UserAvatarImageComponent({
+        propsData: this.propsData,
+      }).$mount();
+
+      this.imageElement = this.userAvatarImage.$el.outerHTML;
+    });
+
+    it('should return a defined Vue component', function () {
+      expect(this.userAvatarImage).toBeDefined();
+    });
+
+    it('should have <img> as a child element', function () {
+      const componentImgTag = this.userAvatarImage.$el.outerHTML;
+      expect(componentImgTag).toContain('<img');
+    });
+
+    it('should return neccessary props as defined', function () {
+      _.each(this.propsData, (val, key) => {
+        expect(this.userAvatarImage[key]).toBeDefined();
+      });
+    });
+
+    it('should properly compute tooltipContainer', function () {
+      expect(this.userAvatarImage.tooltipContainer).toBe('body');
+    });
+
+    it('should properly render tooltipContainer', function () {
+      expect(this.imageElement).toContain('data-container="body"');
+    });
+
+    it('should properly compute avatarSizeClass', function () {
+      expect(this.userAvatarImage.avatarSizeClass).toBe('s99');
+    });
+
+    it('should properly compute imgCssClasses', function () {
+      expect(this.userAvatarImage.imgCssClasses).toBe('avatar s99 myextraavatarclass');
+    });
+
+    it('should properly render imgCssClasses', function () {
+      expect(this.imageElement).toContain('avatar s99 myextraavatarclass');
+    });
+  });
+});
diff --git a/spec/javascripts/vue_shared/components/user_avatar_link_spec.js b/spec/javascripts/vue_shared/components/user_avatar_link_spec.js
new file mode 100644
index 00000000000..770daa9f0de
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/user_avatar_link_spec.js
@@ -0,0 +1,58 @@
+import Vue from 'vue';
+import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
+
+describe('User Avatar Link Component', function () {
+  beforeEach(function () {
+    this.propsData = {
+      linkHref: 'myavatarurl.com',
+      imgSize: 99,
+      imgSrc: 'myavatarurl.com',
+      imgAlt: 'mydisplayname',
+      imgCssClasses: 'myextraavatarclass',
+      tooltipText: 'tooltip text',
+      tooltipPlacement: 'bottom',
+    };
+
+    const UserAvatarLinkComponent = Vue.extend(UserAvatarLink);
+
+    this.userAvatarLink = new UserAvatarLinkComponent({
+      propsData: this.propsData,
+    }).$mount();
+
+    this.userAvatarImage = this.userAvatarLink.$children[0];
+  });
+
+  it('should return a defined Vue component', function () {
+    expect(this.userAvatarLink).toBeDefined();
+  });
+
+  it('should have user-avatar-image registered as child component', function () {
+    expect(this.userAvatarLink.$options.components.userAvatarImage).toBeDefined();
+  });
+
+  it('user-avatar-link should have user-avatar-image as child component', function () {
+    expect(this.userAvatarImage).toBeDefined();
+  });
+
+  it('should render <a> as a child element', function () {
+    const componentLinkTag = this.userAvatarLink.$el.outerHTML;
+    expect(componentLinkTag).toContain('<a');
+  });
+
+  it('should have <img> as a child element', function () {
+    const componentImgTag = this.userAvatarLink.$el.outerHTML;
+    expect(componentImgTag).toContain('<img');
+  });
+
+  it('should return neccessary props as defined', function () {
+    _.each(this.propsData, (val, key) => {
+      expect(this.userAvatarLink[key]).toBeDefined();
+    });
+  });
+
+  it('should include props in the rendered output', function () {
+    _.each(this.propsData, (val) => {
+      expect(this.userAvatarLink.$el.outerHTML).toContain(val);
+    });
+  });
+});
diff --git a/spec/javascripts/vue_shared/components/user_avatar_size_mixin_spec.js b/spec/javascripts/vue_shared/components/user_avatar_size_mixin_spec.js
new file mode 100644
index 00000000000..b37813cdb3d
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/user_avatar_size_mixin_spec.js
@@ -0,0 +1,37 @@
+import Vue from 'vue';
+import UserAvatarSizeMixin from '~/vue_shared/components/user_avatar/user_avatar_size_mixin';
+
+describe('User Avatar Size Mixin', () => {
+  beforeEach(() => {
+    this.vueInstance = new Vue({
+      data: {
+        size: 99,
+      },
+      mixins: [UserAvatarSizeMixin],
+    });
+  });
+
+  describe('#avatarSizeClass', () => {
+    it('should be a defined computed value', () => {
+      expect(this.vueInstance.avatarSizeClass).toBeDefined();
+    });
+
+    it('should correctly transform size into the class name', () => {
+      expect(this.vueInstance.avatarSizeClass).toBe('s99');
+    });
+  });
+
+  describe('#avatarSizeStylesMap', () => {
+    it('should be a defined computed value', () => {
+      expect(this.vueInstance.avatarSizeStylesMap).toBeDefined();
+    });
+
+    it('should return a correctly formatted styles width', () => {
+      expect(this.vueInstance.avatarSizeStylesMap.width).toBe('99px');
+    });
+
+    it('should return a correctly formatted styles height', () => {
+      expect(this.vueInstance.avatarSizeStylesMap.height).toBe('99px');
+    });
+  });
+});
diff --git a/spec/javascripts/vue_shared/components/user_avatar_svg_spec.js b/spec/javascripts/vue_shared/components/user_avatar_svg_spec.js
new file mode 100644
index 00000000000..809886c5dbd
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/user_avatar_svg_spec.js
@@ -0,0 +1,35 @@
+import Vue from 'vue';
+import UserAvatarSvg from '~/vue_shared/components/user_avatar/user_avatar_svg.vue';
+import avatarSvg from 'icons/_icon_random.svg';
+
+const UserAvatarSvgComponent = Vue.extend(UserAvatarSvg);
+
+describe('User Avatar Svg Component', function () {
+  describe('Initialization', function () {
+    beforeEach(function () {
+      this.propsData = {
+        size: 99,
+        svg: avatarSvg,
+      };
+
+      this.userAvatarSvg = new UserAvatarSvgComponent({
+        propsData: this.propsData,
+      }).$mount();
+    });
+
+    it('should return a defined Vue component', function () {
+      expect(this.userAvatarSvg).toBeDefined();
+    });
+
+    it('should have <svg> as a child element', function () {
+      expect(this.userAvatarSvg.$el.tagName).toEqual('svg');
+      expect(this.userAvatarSvg.$el.innerHTML).toContain('<path');
+    });
+
+    it('should return neccessary props as defined', function () {
+      _.each(this.propsData, (val, key) => {
+        expect(this.userAvatarSvg[key]).toBeDefined();
+      });
+    });
+  });
+});
-- 
GitLab


From b73959a94bcace3d0af7819f7621b308980c49d9 Mon Sep 17 00:00:00 2001
From: Bryce Johnson <bryce@gitlab.com>
Date: Wed, 17 May 2017 11:22:26 -0400
Subject: [PATCH 2/3] Fixes per feedback on user avatar components.

---
 .../user_avatar/user_avatar_image.vue         | 13 ++++---
 .../user_avatar/user_avatar_size_mixin.js     | 13 -------
 .../user_avatar/user_avatar_svg.vue           | 11 ++++--
 .../components/user_avatar_image_spec.js      | 26 +++++--------
 .../components/user_avatar_link_spec.js       | 12 +-----
 .../components/user_avatar_size_mixin_spec.js | 37 -------------------
 .../components/user_avatar_svg_spec.js        |  6 ---
 7 files changed, 26 insertions(+), 92 deletions(-)
 delete mode 100644 app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_size_mixin.js
 delete mode 100644 spec/javascripts/vue_shared/components/user_avatar_size_mixin_spec.js

diff --git a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_image.vue b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_image.vue
index 4891e7f927c..b8db6afda12 100644
--- a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_image.vue
+++ b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_image.vue
@@ -16,12 +16,11 @@
 */
 
 import defaultAvatarUrl from 'images/no_avatar.png';
-import UserAvatarSizeMixin from './user_avatar_size_mixin';
 import TooltipMixin from '../../mixins/tooltip';
 
 export default {
   name: 'UserAvatarImage',
-  mixins: [UserAvatarSizeMixin, TooltipMixin],
+  mixins: [TooltipMixin],
   props: {
     imgSrc: {
       type: String,
@@ -58,8 +57,8 @@ export default {
     tooltipContainer() {
       return this.tooltipText ? 'body' : null;
     },
-    imgCssClasses() {
-      return `avatar ${this.avatarSizeClass} ${this.cssClasses}`;
+    avatarSizeClass() {
+      return `s${this.size}`;
     },
   },
 };
@@ -67,9 +66,11 @@ export default {
 
 <template>
   <img
-    :class="imgCssClasses"
+    class="avatar"
+    :class="[avatarSizeClass, cssClasses]"
     :src="imgSrc"
-    :style="avatarSizeStylesMap"
+    :width="size"
+    :height="size"
     :alt="imgAlt"
     :data-container="tooltipContainer"
     :data-placement="tooltipPlacement"
diff --git a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_size_mixin.js b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_size_mixin.js
deleted file mode 100644
index b6155ffd28e..00000000000
--- a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_size_mixin.js
+++ /dev/null
@@ -1,13 +0,0 @@
-export default {
-  computed: {
-    avatarSizeStylesMap() {
-      return {
-        width: `${this.size}px`,
-        height: `${this.size}px`,
-      };
-    },
-    avatarSizeClass() {
-      return `s${this.size}`;
-    },
-  },
-};
diff --git a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_svg.vue b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_svg.vue
index 39b4d37c91e..d2ff2ac006e 100644
--- a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_svg.vue
+++ b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_svg.vue
@@ -14,10 +14,7 @@
 
 */
 
-import UserAvatarSizeMixin from './user_avatar_size_mixin';
-
 export default {
-  mixins: [UserAvatarSizeMixin],
   props: {
     svg: {
       type: String,
@@ -29,13 +26,19 @@ export default {
       default: 20,
     },
   },
+  computed: {
+    avatarSizeClass() {
+      return `s${this.size}`;
+    },
+  },
 };
 </script>
 
 <template>
   <svg
     :class="avatarSizeClass"
-    :style="avatarSizeStylesMap"
+    :height="size"
+    :width="size"
     v-html="svg">
   </svg>
 </template>
diff --git a/spec/javascripts/vue_shared/components/user_avatar_image_spec.js b/spec/javascripts/vue_shared/components/user_avatar_image_spec.js
index 0b5ec736b1e..8daa7610274 100644
--- a/spec/javascripts/vue_shared/components/user_avatar_image_spec.js
+++ b/spec/javascripts/vue_shared/components/user_avatar_image_spec.js
@@ -18,8 +18,6 @@ describe('User Avatar Image Component', function () {
       this.userAvatarImage = new UserAvatarImageComponent({
         propsData: this.propsData,
       }).$mount();
-
-      this.imageElement = this.userAvatarImage.$el.outerHTML;
     });
 
     it('should return a defined Vue component', function () {
@@ -27,14 +25,7 @@ describe('User Avatar Image Component', function () {
     });
 
     it('should have <img> as a child element', function () {
-      const componentImgTag = this.userAvatarImage.$el.outerHTML;
-      expect(componentImgTag).toContain('<img');
-    });
-
-    it('should return neccessary props as defined', function () {
-      _.each(this.propsData, (val, key) => {
-        expect(this.userAvatarImage[key]).toBeDefined();
-      });
+      expect(this.userAvatarImage.$el.tagName).toBe('IMG');
     });
 
     it('should properly compute tooltipContainer', function () {
@@ -42,19 +33,22 @@ describe('User Avatar Image Component', function () {
     });
 
     it('should properly render tooltipContainer', function () {
-      expect(this.imageElement).toContain('data-container="body"');
+      expect(this.userAvatarImage.$el.getAttribute('data-container')).toBe('body');
     });
 
     it('should properly compute avatarSizeClass', function () {
       expect(this.userAvatarImage.avatarSizeClass).toBe('s99');
     });
 
-    it('should properly compute imgCssClasses', function () {
-      expect(this.userAvatarImage.imgCssClasses).toBe('avatar s99 myextraavatarclass');
-    });
+    it('should properly render img css', function () {
+      const classList = this.userAvatarImage.$el.classList;
+      const containsAvatar = classList.contains('avatar');
+      const containsSizeClass = classList.contains('s99');
+      const containsCustomClass = classList.contains('myextraavatarclass');
 
-    it('should properly render imgCssClasses', function () {
-      expect(this.imageElement).toContain('avatar s99 myextraavatarclass');
+      expect(containsAvatar).toBe(true);
+      expect(containsSizeClass).toBe(true);
+      expect(containsCustomClass).toBe(true);
     });
   });
 });
diff --git a/spec/javascripts/vue_shared/components/user_avatar_link_spec.js b/spec/javascripts/vue_shared/components/user_avatar_link_spec.js
index 770daa9f0de..52e450e9ba5 100644
--- a/spec/javascripts/vue_shared/components/user_avatar_link_spec.js
+++ b/spec/javascripts/vue_shared/components/user_avatar_link_spec.js
@@ -35,13 +35,11 @@ describe('User Avatar Link Component', function () {
   });
 
   it('should render <a> as a child element', function () {
-    const componentLinkTag = this.userAvatarLink.$el.outerHTML;
-    expect(componentLinkTag).toContain('<a');
+    expect(this.userAvatarLink.$el.tagName).toBe('A');
   });
 
   it('should have <img> as a child element', function () {
-    const componentImgTag = this.userAvatarLink.$el.outerHTML;
-    expect(componentImgTag).toContain('<img');
+    expect(this.userAvatarLink.$el.querySelector('img')).not.toBeNull();
   });
 
   it('should return neccessary props as defined', function () {
@@ -49,10 +47,4 @@ describe('User Avatar Link Component', function () {
       expect(this.userAvatarLink[key]).toBeDefined();
     });
   });
-
-  it('should include props in the rendered output', function () {
-    _.each(this.propsData, (val) => {
-      expect(this.userAvatarLink.$el.outerHTML).toContain(val);
-    });
-  });
 });
diff --git a/spec/javascripts/vue_shared/components/user_avatar_size_mixin_spec.js b/spec/javascripts/vue_shared/components/user_avatar_size_mixin_spec.js
deleted file mode 100644
index b37813cdb3d..00000000000
--- a/spec/javascripts/vue_shared/components/user_avatar_size_mixin_spec.js
+++ /dev/null
@@ -1,37 +0,0 @@
-import Vue from 'vue';
-import UserAvatarSizeMixin from '~/vue_shared/components/user_avatar/user_avatar_size_mixin';
-
-describe('User Avatar Size Mixin', () => {
-  beforeEach(() => {
-    this.vueInstance = new Vue({
-      data: {
-        size: 99,
-      },
-      mixins: [UserAvatarSizeMixin],
-    });
-  });
-
-  describe('#avatarSizeClass', () => {
-    it('should be a defined computed value', () => {
-      expect(this.vueInstance.avatarSizeClass).toBeDefined();
-    });
-
-    it('should correctly transform size into the class name', () => {
-      expect(this.vueInstance.avatarSizeClass).toBe('s99');
-    });
-  });
-
-  describe('#avatarSizeStylesMap', () => {
-    it('should be a defined computed value', () => {
-      expect(this.vueInstance.avatarSizeStylesMap).toBeDefined();
-    });
-
-    it('should return a correctly formatted styles width', () => {
-      expect(this.vueInstance.avatarSizeStylesMap.width).toBe('99px');
-    });
-
-    it('should return a correctly formatted styles height', () => {
-      expect(this.vueInstance.avatarSizeStylesMap.height).toBe('99px');
-    });
-  });
-});
diff --git a/spec/javascripts/vue_shared/components/user_avatar_svg_spec.js b/spec/javascripts/vue_shared/components/user_avatar_svg_spec.js
index 809886c5dbd..b8d639ffbec 100644
--- a/spec/javascripts/vue_shared/components/user_avatar_svg_spec.js
+++ b/spec/javascripts/vue_shared/components/user_avatar_svg_spec.js
@@ -25,11 +25,5 @@ describe('User Avatar Svg Component', function () {
       expect(this.userAvatarSvg.$el.tagName).toEqual('svg');
       expect(this.userAvatarSvg.$el.innerHTML).toContain('<path');
     });
-
-    it('should return neccessary props as defined', function () {
-      _.each(this.propsData, (val, key) => {
-        expect(this.userAvatarSvg[key]).toBeDefined();
-      });
-    });
   });
 });
-- 
GitLab


From 5573768213850a977cae75b500765d3cb7c5406c Mon Sep 17 00:00:00 2001
From: Bryce Johnson <bryce@gitlab.com>
Date: Thu, 18 May 2017 20:06:04 -0400
Subject: [PATCH 3/3] Add s selectors for supported avatars.

---
 app/assets/stylesheets/framework/avatar.scss | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/app/assets/stylesheets/framework/avatar.scss b/app/assets/stylesheets/framework/avatar.scss
index 91c1ebd5a7d..4ae2b164d2e 100644
--- a/app/assets/stylesheets/framework/avatar.scss
+++ b/app/assets/stylesheets/framework/avatar.scss
@@ -10,6 +10,8 @@
   border-radius: $avatar_radius;
   border: 1px solid $avatar-border;
   &.s16 { @include avatar-size(16px, 6px); }
+  &.s18 { @include avatar-size(18px, 6px); }
+  &.s19 { @include avatar-size(19px, 6px); }
   &.s20 { @include avatar-size(20px, 7px); }
   &.s24 { @include avatar-size(24px, 8px); }
   &.s26 { @include avatar-size(26px, 8px); }
-- 
GitLab