Commit 4e38d88d authored by Kamil Trzcinski's avatar Kamil Trzcinski

Merge remote-tracking branch 'origin/master' into rename-ci-commit-phase-2

parents 8562cb5f 4d4a9b7c
...@@ -2,39 +2,47 @@ class @AwardsHandler ...@@ -2,39 +2,47 @@ class @AwardsHandler
constructor: -> constructor: ->
@aliases = emojiAliases() @aliases = gl.emojiAliases()
$(document) $(document)
.off 'click', '.js-add-award' .off 'click', '.js-add-award'
.on 'click', '.js-add-award', (event) => .on 'click', '.js-add-award', (e) =>
event.stopPropagation() e.stopPropagation()
event.preventDefault() e.preventDefault()
@showEmojiMenu $(event.currentTarget) @showEmojiMenu $(e.currentTarget)
$('html').on 'click', (event) -> $('html').on 'click', (e) ->
unless $(event.target).closest('.emoji-menu').length $target = $ e.target
unless $target.closest('.emoji-menu-content').length
$('.js-awards-block.current').removeClass 'current'
unless $target.closest('.emoji-menu').length
if $('.emoji-menu').is(':visible') if $('.emoji-menu').is(':visible')
$('.js-add-award.is-active').removeClass 'is-active' $('.js-add-award.is-active').removeClass 'is-active'
$('.emoji-menu').removeClass 'is-visible' $('.emoji-menu').removeClass 'is-visible'
$(document) $(document)
.off 'click', '.js-emoji-btn' .off 'click', '.js-emoji-btn'
.on 'click', '.js-emoji-btn', @handleClick .on 'click', '.js-emoji-btn', (e) =>
e.preventDefault()
handleClick: (e) => $target = $ e.currentTarget
emoji = $target.find('.icon').data 'emoji'
e.preventDefault() $target.closest('.js-awards-block').addClass 'current'
@addAward @getVotesBlock(), @getAwardUrl(), emoji
emoji = $(e.currentTarget).find('.icon').data 'emoji'
@getVotesBlock().addClass 'js-awards-block'
@addAward @getAwardUrl(), emoji
showEmojiMenu: ($addBtn) -> showEmojiMenu: ($addBtn) ->
$menu = $('.emoji-menu') $menu = $ '.emoji-menu'
if $addBtn.hasClass 'js-note-emoji'
$addBtn.parents('.note').find('.js-awards-block').addClass 'current'
else
$addBtn.closest('.js-awards-block').addClass 'current'
if $menu.length if $menu.length
$holder = $addBtn.closest('.js-award-holder') $holder = $addBtn.closest('.js-award-holder')
...@@ -51,7 +59,7 @@ class @AwardsHandler ...@@ -51,7 +59,7 @@ class @AwardsHandler
$('#emoji_search').focus() $('#emoji_search').focus()
else else
$addBtn.addClass 'is-loading is-active' $addBtn.addClass 'is-loading is-active'
url = $addBtn.data 'award-menu-url' url = @getAwardMenuUrl()
@createEmojiMenu url, => @createEmojiMenu url, =>
$addBtn.removeClass 'is-loading' $addBtn.removeClass 'is-loading'
...@@ -68,12 +76,13 @@ class @AwardsHandler ...@@ -68,12 +76,13 @@ class @AwardsHandler
createEmojiMenu: (awardMenuUrl, callback) -> createEmojiMenu: (awardMenuUrl, callback) ->
$.get awardMenuUrl, (response) => $.get awardMenuUrl, (response) ->
$('body').append response $('body').append response
callback() callback()
positionMenu: ($menu, $addBtn) -> positionMenu: ($menu, $addBtn) ->
position = $addBtn.data('position') position = $addBtn.data('position')
# The menu could potentially be off-screen or in a hidden overflow element # The menu could potentially be off-screen or in a hidden overflow element
...@@ -91,88 +100,114 @@ class @AwardsHandler ...@@ -91,88 +100,114 @@ class @AwardsHandler
$menu.css(css) $menu.css(css)
addAward: (awardUrl, emoji, checkMutuality = yes) -> addAward: (votesBlock, awardUrl, emoji, checkMutuality = yes, callback) ->
emoji = @normilizeEmojiName(emoji) emoji = @normilizeEmojiName emoji
@postEmoji awardUrl, emoji, =>
@addAwardToEmojiBar(emoji, checkMutuality)
$('.js-awards-block-current').removeClass 'js-awards-block-current' @postEmoji awardUrl, emoji, =>
@addAwardToEmojiBar votesBlock, emoji, checkMutuality
callback?()
$('.emoji-menu').removeClass 'is-visible' $('.emoji-menu').removeClass 'is-visible'
addAwardToEmojiBar: (emoji, checkForMutuality = yes) -> addAwardToEmojiBar: (votesBlock, emoji, checkForMutuality = yes) ->
@checkMutuality emoji if checkForMutuality @checkMutuality votesBlock, emoji if checkForMutuality
@addEmojiToFrequentlyUsedList(emoji) @addEmojiToFrequentlyUsedList emoji
emoji = @normilizeEmojiName(emoji) emoji = @normilizeEmojiName emoji
$emojiBtn = @findEmojiIcon(emoji).parent() $emojiButton = @findEmojiIcon(votesBlock, emoji).parent()
if $emojiBtn.length > 0 if $emojiButton.length > 0
if @isActive($emojiBtn) if @isActive $emojiButton
@decrementCounter($emojiBtn, emoji) @decrementCounter $emojiButton, emoji
else else
counter = $emojiBtn.find('.js-counter') counter = $emojiButton.find '.js-counter'
counter.text(parseInt(counter.text()) + 1) counter.text parseInt(counter.text()) + 1
$emojiBtn.addClass('active') $emojiButton.addClass 'active'
@addMeToUserList(emoji) @addMeToUserList votesBlock, emoji
@animateEmoji $emojiButton
else else
@createEmoji(emoji) votesBlock.removeClass 'hidden'
@createEmoji votesBlock, emoji
getVotesBlock: -> return $ '.awards.js-awards-block' getVotesBlock: ->
currentBlock = $ '.js-awards-block.current'
return if currentBlock.length then currentBlock else $('.js-awards-block').eq 0
getAwardUrl: -> @getVotesBlock().data 'award-url'
getAwardUrl: -> return @getVotesBlock().data 'award-url'
checkMutuality: (emoji) ->
checkMutuality: (votesBlock, emoji) ->
awardUrl = @getAwardUrl() awardUrl = @getAwardUrl()
if emoji in [ 'thumbsup', 'thumbsdown' ] if emoji in [ 'thumbsup', 'thumbsdown' ]
mutualVote = if emoji is 'thumbsup' then 'thumbsdown' else 'thumbsup' mutualVote = if emoji is 'thumbsup' then 'thumbsdown' else 'thumbsup'
$emojiButton = votesBlock.find("[data-emoji=#{mutualVote}]").parent()
isAlreadyVoted = $emojiButton.hasClass 'active'
if isAlreadyVoted
@showEmojiLoader $emojiButton
@addAward votesBlock, awardUrl, mutualVote, no, ->
$emojiButton.removeClass 'is-loading'
isAlreadyVoted = $("[data-emoji=#{mutualVote}]").parent().hasClass 'active'
@addAward awardUrl, mutualVote, no if isAlreadyVoted
showEmojiLoader: ($emojiButton) ->
isActive: ($emojiBtn) -> $emojiBtn.hasClass 'active' $loader = $emojiButton.find '.fa-spinner'
unless $loader.length
$emojiButton.append '<i class="fa fa-spinner fa-spin award-control-icon award-control-icon-loading"></i>'
decrementCounter: ($emojiBtn, emoji) -> $emojiButton.addClass 'is-loading'
isntNoteBody = $emojiBtn.closest('.note-body').length is 0
counter = $('.js-counter', $emojiBtn)
counterNumber = parseInt(counter.text())
if !isntNoteBody
# If this is a note body, we just hide the award emoji row like the initial state isActive: ($emojiButton) -> $emojiButton.hasClass 'active'
$emojiBtn.closest('.js-awards-block').addClass 'hidden'
decrementCounter: ($emojiButton, emoji) ->
counter = $ '.js-counter', $emojiButton
counterNumber = parseInt counter.text(), 10
if counterNumber > 1 if counterNumber > 1
counter.text(counterNumber - 1) counter.text counterNumber - 1
@removeMeFromUserList($emojiBtn, emoji) @removeMeFromUserList $emojiButton, emoji
else if (emoji == 'thumbsup' || emoji == 'thumbsdown') && isntNoteBody else if emoji is 'thumbsup' or emoji is 'thumbsdown'
$emojiBtn.tooltip('destroy') $emojiButton.tooltip 'destroy'
counter.text('0') counter.text '0'
@removeMeFromUserList($emojiBtn, emoji) @removeMeFromUserList $emojiButton, emoji
@removeEmoji $emojiButton if $emojiButton.parents('.note').length
else else
$emojiBtn.tooltip('destroy') @removeEmoji $emojiButton
$emojiBtn.remove()
$emojiButton.removeClass 'active'
$emojiBtn.removeClass('active')
removeEmoji: ($emojiButton) ->
$emojiButton.tooltip('destroy')
$emojiButton.remove()
$votesBlock = @getVotesBlock()
if $votesBlock.find('.js-emoji-btn').length is 0
$votesBlock.addClass 'hidden'
getAwardTooltip: ($awardBlock) -> getAwardTooltip: ($awardBlock) ->
return $awardBlock.attr('data-original-title') or $awardBlock.attr('data-title') return $awardBlock.attr('data-original-title') or $awardBlock.attr('data-title') or ''
removeMeFromUserList: ($emojiBtn, emoji) -> removeMeFromUserList: ($emojiButton, emoji) ->
awardBlock = $emojiBtn awardBlock = $emojiButton
originalTitle = @getAwardTooltip awardBlock originalTitle = @getAwardTooltip awardBlock
authors = originalTitle.split ', ' authors = originalTitle.split ', '
...@@ -183,117 +218,134 @@ class @AwardsHandler ...@@ -183,117 +218,134 @@ class @AwardsHandler
awardBlock awardBlock
.closest '.js-emoji-btn' .closest '.js-emoji-btn'
.removeData 'original-title' .removeData 'original-title'
.removeData 'title'
.attr 'data-original-title', newAuthors .attr 'data-original-title', newAuthors
.attr 'data-title', newAuthors
@resetTooltip(awardBlock) @resetTooltip awardBlock
addMeToUserList: (emoji) -> addMeToUserList: (votesBlock, emoji) ->
awardBlock = @findEmojiIcon(emoji).parent() awardBlock = @findEmojiIcon(votesBlock, emoji).parent()
origTitle = @getAwardTooltip awardBlock origTitle = @getAwardTooltip awardBlock
users = [] users = []
if origTitle if origTitle
users = origTitle.trim().split(', ') users = origTitle.trim().split ', '
users.push('me') users.push 'me'
awardBlock.attr('title', users.join(', ')) awardBlock.attr 'title', users.join ', '
@resetTooltip(awardBlock) @resetTooltip awardBlock
resetTooltip: (award) -> resetTooltip: (award) ->
award.tooltip('destroy')
award.tooltip 'destroy'
# 'destroy' call is asynchronous and there is no appropriate callback on it, this is why we need to set timeout. # 'destroy' call is asynchronous and there is no appropriate callback on it, this is why we need to set timeout.
setTimeout (-> cb = -> award.tooltip()
award.tooltip() setTimeout cb, 200
), 200
createEmoji_: (emoji) -> createEmoji_: (votesBlock, emoji) ->
emojiCssClass = @resolveNameToCssClass emoji emojiCssClass = @resolveNameToCssClass emoji
buttonHtml = "<button class='btn award-control js-emoji-btn has-tooltip active' title='me' data-placement='bottom'>
buttonHtml = "<button class='btn award-control js-emoji-btn has-tooltip active' title='me' data-placement='bottom'>
<div class='icon emoji-icon #{emojiCssClass}' data-emoji='#{emoji}'></div> <div class='icon emoji-icon #{emojiCssClass}' data-emoji='#{emoji}'></div>
<span class='award-control-text js-counter'>1</span> <span class='award-control-text js-counter'>1</span>
</button>" </button>"
emoji_node = $(buttonHtml) $emojiButton = $ buttonHtml
.insertBefore '.js-awards-block .js-award-holder:not(.js-award-action-btn)' $emojiButton
.insertBefore votesBlock.find '.js-award-holder'
.find '.emoji-icon' .find '.emoji-icon'
.data 'emoji', emoji .data 'emoji', emoji
@animateEmoji $emojiButton
$('.award-control').tooltip() $('.award-control').tooltip()
votesBlock.removeClass 'current'
animateEmoji: ($emoji) ->
$currentBlock = $ '.js-awards-block' className = 'pulse animated'
if $currentBlock.is '.hidden' $emoji.addClass className
$currentBlock.removeClass 'hidden' setTimeout (-> $emoji.removeClass className), 321
createEmoji: (emoji) -> createEmoji: (votesBlock, emoji) ->
return @createEmoji_ emoji if $('.emoji-menu').length if $('.emoji-menu').length
return @createEmoji_ votesBlock, emoji
awardMenuUrl = gl.awardMenuUrl or '/emojis' @createEmojiMenu @getAwardMenuUrl(), => @createEmoji_ votesBlock, emoji
@createEmojiMenu awardMenuUrl, => @createEmoji emoji
getAwardMenuUrl: -> return gl.awardMenuUrl
resolveNameToCssClass: (emoji) -> resolveNameToCssClass: (emoji) ->
emoji_icon = $(".emoji-menu-content [data-emoji='#{emoji}']") emojiIcon = $ ".emoji-menu-content [data-emoji='#{emoji}']"
if emoji_icon.length > 0 if emojiIcon.length > 0
unicodeName = emoji_icon.data('unicode-name') unicodeName = emojiIcon.data 'unicode-name'
else else
# Find by alias # Find by alias
unicodeName = $(".emoji-menu-content [data-aliases*=':#{emoji}:']").data('unicode-name') unicodeName = $(".emoji-menu-content [data-aliases*=':#{emoji}:']").data 'unicode-name'
return "emoji-#{unicodeName}" return "emoji-#{unicodeName}"
postEmoji: (awardUrl, emoji, callback) -> postEmoji: (awardUrl, emoji, callback) ->
$.post awardUrl, { name: emoji }, (data) -> $.post awardUrl, { name: emoji }, (data) ->
if data.ok callback() if data.ok
callback.call()
findEmojiIcon: (votesBlock, emoji) ->
return votesBlock.find ".js-emoji-btn [data-emoji='#{emoji}']"
findEmojiIcon: (emoji) ->
$(".js-awards-block.awards > .js-emoji-btn [data-emoji='#{emoji}']")
scrollToAwards: -> scrollToAwards: ->
$('body, html').animate({
scrollTop: $('.awards').offset().top - 80
}, 200)
normilizeEmojiName: (emoji) -> options = scrollTop: $('.awards').offset().top - 110
@aliases[emoji] || emoji $('body, html').animate options, 200
normilizeEmojiName: (emoji) -> return @aliases[emoji] or emoji
addEmojiToFrequentlyUsedList: (emoji) -> addEmojiToFrequentlyUsedList: (emoji) ->
frequently_used_emojis = @getFrequentlyUsedEmojis()
frequently_used_emojis.push(emoji) frequentlyUsedEmojis = @getFrequentlyUsedEmojis()
$.cookie('frequently_used_emojis', frequently_used_emojis.join(','), { expires: 365 }) frequentlyUsedEmojis.push emoji
$.cookie 'frequently_used_emojis', frequentlyUsedEmojis.join(','), { expires: 365 }
getFrequentlyUsedEmojis: -> getFrequentlyUsedEmojis: ->
frequently_used_emojis = ($.cookie('frequently_used_emojis') || '').split(',')
_.compact(_.uniq(frequently_used_emojis)) frequentlyUsedEmojis = ($.cookie('frequently_used_emojis') or '').split(',')
return _.compact _.uniq frequentlyUsedEmojis
renderFrequentlyUsedBlock: -> renderFrequentlyUsedBlock: ->
if $.cookie('frequently_used_emojis')
frequently_used_emojis = @getFrequentlyUsedEmojis() if $.cookie 'frequently_used_emojis'
frequentlyUsedEmojis = @getFrequentlyUsedEmojis()
ul = $("<ul class='clearfix emoji-menu-list'>") ul = $("<ul class='clearfix emoji-menu-list'>")
for emoji in frequently_used_emojis for emoji in frequentlyUsedEmojis
$(".emoji-menu-content [data-emoji='#{emoji}']").closest('li').clone().appendTo(ul) $(".emoji-menu-content [data-emoji='#{emoji}']").closest('li').clone().appendTo(ul)
$('input.emoji-search').after(ul).after($('<h5>').text('Frequently used')) $('input.emoji-search').after(ul).after($('<h5>').text('Frequently used'))
setupSearch: -> setupSearch: ->
$('input.emoji-search').on 'keyup', (ev) => $('input.emoji-search').on 'keyup', (ev) =>
term = $(ev.target).val() term = $(ev.target).val()
...@@ -310,5 +362,7 @@ class @AwardsHandler ...@@ -310,5 +362,7 @@ class @AwardsHandler
else else
$('.emoji-menu-content').children().show() $('.emoji-menu-content').children().show()
searchEmojis: (term)->
searchEmojis: (term) ->
$(".emoji-menu-content [data-emoji*='#{term}']").closest('li').clone() $(".emoji-menu-content [data-emoji*='#{term}']").closest('li').clone()
...@@ -23,7 +23,7 @@ class Dispatcher ...@@ -23,7 +23,7 @@ class Dispatcher
new Issue() new Issue()
shortcut_handler = new ShortcutsIssuable() shortcut_handler = new ShortcutsIssuable()
new ZenMode() new ZenMode()
window.awardsHandler = new AwardsHandler() gl.awardsHandler = new AwardsHandler()
when 'projects:milestones:show', 'groups:milestones:show', 'dashboard:milestones:show' when 'projects:milestones:show', 'groups:milestones:show', 'dashboard:milestones:show'
new Milestone() new Milestone()
when 'dashboard:todos:index' when 'dashboard:todos:index'
...@@ -54,7 +54,7 @@ class Dispatcher ...@@ -54,7 +54,7 @@ class Dispatcher
new Diff() new Diff()
shortcut_handler = new ShortcutsIssuable(true) shortcut_handler = new ShortcutsIssuable(true)
new ZenMode() new ZenMode()
window.awardsHandler = new AwardsHandler() gl.awardsHandler = new AwardsHandler()
when "projects:merge_requests:diffs" when "projects:merge_requests:diffs"
new Diff() new Diff()
new ZenMode() new ZenMode()
......
window.emojiAliases = -> gl.emojiAliases = ->
JSON.parse('<%= Gitlab::AwardEmoji.aliases.to_json %>') JSON.parse('<%= Gitlab::AwardEmoji.aliases.to_json %>')
...@@ -162,13 +162,14 @@ class @Notes ...@@ -162,13 +162,14 @@ class @Notes
renderNote: (note) -> renderNote: (note) ->
unless note.valid unless note.valid
if note.award if note.award
flash = new Flash('You have already used this award emoji!', 'alert') flash = new Flash('You have already awarded this emoji!', 'alert')
flash.pinTo('.header-content') flash.pinTo('.header-content')
return return
if note.award if note.award
awardsHandler.addAwardToEmojiBar(note.name) votesBlock = $('.js-awards-block').eq 0
awardsHandler.scrollToAwards() gl.awardsHandler.addAwardToEmojiBar votesBlock, note.name
gl.awardsHandler.scrollToAwards()
# render note if it not present in loaded list # render note if it not present in loaded list
# or skip if rendered # or skip if rendered
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
padding: 0; padding: 0;
.timeline-entry { .timeline-entry {
padding: $gl-padding $gl-btn-padding; padding: $gl-padding $gl-btn-padding 11px;
border-color: $table-border-color; border-color: $table-border-color;
color: $gl-gray; color: $gl-gray;
border-bottom: 1px solid $border-white-light; border-bottom: 1px solid $border-white-light;
......
...@@ -95,6 +95,7 @@ ...@@ -95,6 +95,7 @@
.award-control { .award-control {
margin-right: 5px; margin-right: 5px;
margin-bottom: 5px;
padding-left: 5px; padding-left: 5px;
padding-right: 5px; padding-right: 5px;
line-height: 20px; line-height: 20px;
...@@ -108,7 +109,8 @@ ...@@ -108,7 +109,8 @@
} }
&.is-loading { &.is-loading {
.award-control-icon-normal { .award-control-icon-normal,
.emoji-icon {
display: none; display: none;
} }
......
...@@ -69,6 +69,10 @@ ul.notes { ...@@ -69,6 +69,10 @@ ul.notes {
.note-edit-form { .note-edit-form {
display: block; display: block;
&.current-note-edit-form + .note-awards {
display: none;
}
} }
} }
...@@ -116,10 +120,38 @@ ul.notes { ...@@ -116,10 +120,38 @@ ul.notes {
} }
} }
.note-awards {
.js-awards-block {
padding: 2px;
margin-top: 10px;
}
.award-control {
font-size: 13px;
padding: 2px 5px;
}
}
.note-header { .note-header {
padding-bottom: 3px; padding-bottom: 3px;
} }
.note-emoji-button {
.fa-spinner {
display: none;
}
&.is-loading {
.fa-smile-o {
display: none;
}
.fa-spinner {
display: inline-block;
}
}
}
} }
} }
......
...@@ -9,13 +9,22 @@ module ToggleAwardEmoji ...@@ -9,13 +9,22 @@ module ToggleAwardEmoji
name = params.require(:name) name = params.require(:name)
awardable.toggle_award_emoji(name, current_user) awardable.toggle_award_emoji(name, current_user)
TodoService.new.new_award_emoji(awardable, current_user) TodoService.new.new_award_emoji(to_todoable(awardable), current_user)
render json: { ok: true } render json: { ok: true }
end end
private private
def to_todoable(awardable)
case awardable
when Note
awardable.noteable
else
awardable
end
end
def awardable def awardable
raise NotImplementedError raise NotImplementedError
end end
......
class Projects::NotesController < Projects::ApplicationController class Projects::NotesController < Projects::ApplicationController
include ToggleAwardEmoji
# Authorize # Authorize
before_action :authorize_read_note! before_action :authorize_read_note!
before_action :authorize_create_note!, only: [:create] before_action :authorize_create_note!, only: [:create]
...@@ -61,6 +63,7 @@ class Projects::NotesController < Projects::ApplicationController ...@@ -61,6 +63,7 @@ class Projects::NotesController < Projects::ApplicationController
def note def note
@note ||= @project.notes.find(params[:id]) @note ||= @project.notes.find(params[:id])
end end
alias_method :awardable, :note
def note_to_html(note) def note_to_html(note)
render_to_string( render_to_string(
......
...@@ -3,6 +3,7 @@ class Note < ActiveRecord::Base ...@@ -3,6 +3,7 @@ class Note < ActiveRecord::Base
include Gitlab::CurrentSettings include Gitlab::CurrentSettings
include Participable include Participable
include Mentionable include Mentionable
include Awardable
default_value_for :system, false default_value_for :system, false
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
gl.awardMenuUrl = "#{emojis_path}" gl.awardMenuUrl = "#{emojis_path}"
.award-menu-holder.js-award-holder .award-menu-holder.js-award-holder
%button.btn.award-control.js-add-award{ type: "button", data: { award_menu_url: emojis_path } } %button.btn.award-control.js-add-award{ type: "button" }
= icon('smile-o', class: "award-control-icon award-control-icon-normal") = icon('smile-o', class: "award-control-icon award-control-icon-normal")
= icon('spinner spin', class: "award-control-icon award-control-icon-loading") = icon('spinner spin', class: "award-control-icon award-control-icon-loading")
%span.award-control-text %span.award-control-text
......
...@@ -68,9 +68,9 @@ ...@@ -68,9 +68,9 @@
#related-branches{ data: { url: related_branches_namespace_project_issue_url(@project.namespace, @project, @issue) } } #related-branches{ data: { url: related_branches_namespace_project_issue_url(@project.namespace, @project, @issue) } }
// This element is filled in using JavaScript. // This element is filled in using JavaScript.
.content-block.content-block-small .content-block.content-block-small
= render 'new_branch' = render 'new_branch'
= render 'award_emoji/awards_block', awardable: @issue, inline: true = render 'award_emoji/awards_block', awardable: @issue, inline: true
%section.issuable-discussion %section.issuable-discussion
= render 'projects/issues/discussion' = render 'projects/issues/discussion'
......
...@@ -22,6 +22,9 @@ ...@@ -22,6 +22,9 @@
%span.note-role %span.note-role
= access = access
- if note_editable - if note_editable
= link_to '#', title: 'Award Emoji', class: 'note-action-button note-emoji-button js-add-award js-note-emoji', data: { position: 'right' } do
= icon('spinner spin')
= icon('smile-o')
= link_to '#', title: 'Edit comment', class: 'note-action-button js-note-edit' do = link_to '#', title: 'Edit comment', class: 'note-action-button js-note-edit' do
= icon('pencil') = icon('pencil')
= link_to namespace_project_note_path(note.project.namespace, note.project, note), title: 'Remove comment', method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: 'note-action-button js-note-delete danger' do = link_to namespace_project_note_path(note.project.namespace, note.project, note), title: 'Remove comment', method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: 'note-action-button js-note-delete danger' do
...@@ -30,9 +33,11 @@ ...@@ -30,9 +33,11 @@
.note-text .note-text
= preserve do = preserve do
= markdown(note.note, pipeline: :note, cache_key: [note, "note"], author: note.author) = markdown(note.note, pipeline: :note, cache_key: [note, "note"], author: note.author)
= edited_time_ago_with_tooltip(note, placement: 'bottom', html_class: 'note_edited_ago', include_author: true)
- if note_editable - if note_editable
= render 'projects/notes/edit_form', note: note = render 'projects/notes/edit_form', note: note
= edited_time_ago_with_tooltip(note, placement: 'bottom', html_class: 'note_edited_ago', include_author: true) .note-awards
= render 'award_emoji/awards_block', awardable: note, inline: false
- if note.attachment.url - if note.attachment.url
.note-attachment .note-attachment
......
...@@ -758,6 +758,7 @@ Rails.application.routes.draw do ...@@ -758,6 +758,7 @@ Rails.application.routes.draw do
resources :notes, only: [:index, :create, :destroy, :update], constraints: { id: /\d+/ } do resources :notes, only: [:index, :create, :destroy, :update], constraints: { id: /\d+/ } do
member do member do
post :toggle_award_emoji
delete :delete_attachment delete :delete_attachment
end end
end end
......
require('spec_helper')
describe Projects::NotesController do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:issue) { create(:issue, project: project) }
let(:note) { create(:note, noteable: issue, project: project) }
describe 'POST #toggle_award_emoji' do
before do
sign_in(user)
project.team << [user, :developer]
end
it "toggles the award emoji" do
expect do
post(:toggle_award_emoji, namespace_id: project.namespace.path,
project_id: project.path, id: note.id, name: "thumbsup")
end.to change { note.award_emoji.count }.by(1)
expect(response.status).to eq(200)
end
it "removes the already awarded emoji" do
post(:toggle_award_emoji, namespace_id: project.namespace.path,
project_id: project.path, id: note.id, name: "thumbsup")
expect do
post(:toggle_award_emoji, namespace_id: project.namespace.path,
project_id: project.path, id: note.id, name: "thumbsup")
end.to change { AwardEmoji.count }.by(-1)
expect(response.status).to eq(200)
end
end
end
#= require awards_handler
#= require jquery
#= require jquery.cookie
#= require ./fixtures/emoji_menu
awardsHandler = null
window.gl or= {}
gl.emojiAliases = -> return { '+1': 'thumbsup', '-1': 'thumbsdown' }
gl.awardMenuUrl = '/emojis'
lazyAssert = (done, assertFn) ->
setTimeout -> # Maybe jasmine.clock here?
assertFn()
done()
, 333
describe 'AwardsHandler', ->
fixture.preload 'awards_handler.html'
beforeEach ->
fixture.load 'awards_handler.html'
awardsHandler = new AwardsHandler
spyOn(awardsHandler, 'postEmoji').and.callFake (url, emoji, cb) => cb()
spyOn(jQuery, 'get').and.callFake (req, cb) ->
expect(req).toBe '/emojis'
cb window.emojiMenu
describe '::showEmojiMenu', ->
it 'should show emoji menu when Add emoji button clicked', (done) ->
$('.js-add-award').eq(0).click()
lazyAssert done, ->
$emojiMenu = $ '.emoji-menu'
expect($emojiMenu.length).toBe 1
expect($emojiMenu.hasClass('is-visible')).toBe yes
expect($emojiMenu.find('#emoji_search').length).toBe 1
expect($('.js-awards-block.current').length).toBe 1
it 'should also show emoji menu for the smiley icon in notes', (done) ->
$('.note-action-button').click()
lazyAssert done, ->
$emojiMenu = $ '.emoji-menu'
expect($emojiMenu.length).toBe 1
it 'should remove emoji menu when body is clicked', (done) ->
$('.js-add-award').eq(0).click()
lazyAssert done, ->
$emojiMenu = $('.emoji-menu')
$('body').click()
expect($emojiMenu.length).toBe 1
expect($emojiMenu.hasClass('is-visible')).toBe no
expect($('.js-awards-block.current').length).toBe 0
describe '::addAwardToEmojiBar', ->
it 'should add emoji to votes block', ->
$votesBlock = $('.js-awards-block').eq 0
awardsHandler.addAwardToEmojiBar $votesBlock, 'heart', no
$emojiButton = $votesBlock.find '[data-emoji=heart]'
expect($emojiButton.length).toBe 1
expect($emojiButton.next('.js-counter').text()).toBe '1'
expect($votesBlock.hasClass('hidden')).toBe no
it 'should remove the emoji when we click again', ->
$votesBlock = $('.js-awards-block').eq 0
awardsHandler.addAwardToEmojiBar $votesBlock, 'heart', no
awardsHandler.addAwardToEmojiBar $votesBlock, 'heart', no
$emojiButton = $votesBlock.find '[data-emoji=heart]'
expect($emojiButton.length).toBe 0
it 'should decrement the emoji counter', ->
$votesBlock = $('.js-awards-block').eq 0
awardsHandler.addAwardToEmojiBar $votesBlock, 'heart', no
$emojiButton = $votesBlock.find '[data-emoji=heart]'
$emojiButton.next('.js-counter').text 5
awardsHandler.addAwardToEmojiBar $votesBlock, 'heart', no
expect($emojiButton.length).toBe 1
expect($emojiButton.next('.js-counter').text()).toBe '4'
describe '::getAwardUrl', ->
it 'should return the url for request', ->
expect(awardsHandler.getAwardUrl()).toBe '/gitlab-org/gitlab-test/issues/8/toggle_award_emoji'
describe '::addAward and ::checkMutuality', ->
it 'should handle :+1: and :-1: mutuality', ->
awardUrl = awardsHandler.getAwardUrl()
$votesBlock = $('.js-awards-block').eq 0
$thumbsUpEmoji = $votesBlock.find('[data-emoji=thumbsup]').parent()
$thumbsDownEmoji = $votesBlock.find('[data-emoji=thumbsdown]').parent()
awardsHandler.addAward $votesBlock, awardUrl, 'thumbsup', no
expect($thumbsUpEmoji.hasClass('active')).toBe yes
expect($thumbsDownEmoji.hasClass('active')).toBe no
$thumbsUpEmoji.tooltip()
$thumbsDownEmoji.tooltip()
awardsHandler.addAward $votesBlock, awardUrl, 'thumbsdown', yes
expect($thumbsUpEmoji.hasClass('active')).toBe no
expect($thumbsDownEmoji.hasClass('active')).toBe yes
describe '::removeEmoji', ->
it 'should remove emoji', ->
awardUrl = awardsHandler.getAwardUrl()
$votesBlock = $('.js-awards-block').eq 0
awardsHandler.addAward $votesBlock, awardUrl, 'fire', no
expect($votesBlock.find('[data-emoji=fire]').length).toBe 1
awardsHandler.removeEmoji $votesBlock.find('[data-emoji=fire]').closest('button')
expect($votesBlock.find('[data-emoji=fire]').length).toBe 0
describe 'search', ->
it 'should filter the emoji', ->
$('.js-add-award').eq(0).click()
expect($('[data-emoji=angel]').is(':visible')).toBe yes
expect($('[data-emoji=anger]').is(':visible')).toBe yes
$('#emoji_search').val('ali').trigger 'keyup'
expect($('[data-emoji=angel]').is(':visible')).toBe no
expect($('[data-emoji=anger]').is(':visible')).toBe no
expect($('[data-emoji=alien]').is(':visible')).toBe yes
expect($('h5.emoji-search').is(':visible')).toBe yes
describe 'emoji menu', ->
selector = '[data-emoji=sunglasses]'
openEmojiMenuAndAddEmoji = ->
$('.js-add-award').eq(0).click()
$menu = $ '.emoji-menu'
$block = $ '.js-awards-block'
$emoji = $menu.find ".emoji-menu-list-item #{selector}"
expect($emoji.length).toBe 1
expect($block.find(selector).length).toBe 0
$emoji.click()
expect($menu.hasClass('.is-visible')).toBe no
expect($block.find(selector).length).toBe 1
it 'should add selected emoji to awards block', ->
openEmojiMenuAndAddEmoji()
it 'should remove already selected emoji', ->
openEmojiMenuAndAddEmoji()
$('.js-add-award').eq(0).click()
$block = $ '.js-awards-block'
$emoji = $('.emoji-menu').find ".emoji-menu-list-item #{selector}"
$emoji.click()
expect($block.find(selector).length).toBe 0
...@@ -14,17 +14,17 @@ describe 'Quick Submit behavior', -> ...@@ -14,17 +14,17 @@ describe 'Quick Submit behavior', ->
} }
it 'does not respond to other keyCodes', -> it 'does not respond to other keyCodes', ->
$('input').trigger(keydownEvent(keyCode: 32)) $('input.quick-submit-input').trigger(keydownEvent(keyCode: 32))
expect(@spies.submit).not.toHaveBeenTriggered() expect(@spies.submit).not.toHaveBeenTriggered()
it 'does not respond to Enter alone', -> it 'does not respond to Enter alone', ->
$('input').trigger(keydownEvent(ctrlKey: false, metaKey: false)) $('input.quick-submit-input').trigger(keydownEvent(ctrlKey: false, metaKey: false))
expect(@spies.submit).not.toHaveBeenTriggered() expect(@spies.submit).not.toHaveBeenTriggered()
it 'does not respond to repeated events', -> it 'does not respond to repeated events', ->
$('input').trigger(keydownEvent(repeat: true)) $('input.quick-submit-input').trigger(keydownEvent(repeat: true))
expect(@spies.submit).not.toHaveBeenTriggered() expect(@spies.submit).not.toHaveBeenTriggered()
...@@ -38,26 +38,26 @@ describe 'Quick Submit behavior', -> ...@@ -38,26 +38,26 @@ describe 'Quick Submit behavior', ->
# only run the tests that apply to the current platform # only run the tests that apply to the current platform
if navigator.userAgent.match(/Macintosh/) if navigator.userAgent.match(/Macintosh/)
it 'responds to Meta+Enter', -> it 'responds to Meta+Enter', ->
$('input').trigger(keydownEvent()) $('input.quick-submit-input').trigger(keydownEvent())
expect(@spies.submit).toHaveBeenTriggered() expect(@spies.submit).toHaveBeenTriggered()
it 'excludes other modifier keys', -> it 'excludes other modifier keys', ->
$('input').trigger(keydownEvent(altKey: true)) $('input.quick-submit-input').trigger(keydownEvent(altKey: true))
$('input').trigger(keydownEvent(ctrlKey: true)) $('input.quick-submit-input').trigger(keydownEvent(ctrlKey: true))
$('input').trigger(keydownEvent(shiftKey: true)) $('input.quick-submit-input').trigger(keydownEvent(shiftKey: true))
expect(@spies.submit).not.toHaveBeenTriggered() expect(@spies.submit).not.toHaveBeenTriggered()
else else
it 'responds to Ctrl+Enter', -> it 'responds to Ctrl+Enter', ->
$('input').trigger(keydownEvent()) $('input.quick-submit-input').trigger(keydownEvent())
expect(@spies.submit).toHaveBeenTriggered() expect(@spies.submit).toHaveBeenTriggered()
it 'excludes other modifier keys', -> it 'excludes other modifier keys', ->
$('input').trigger(keydownEvent(altKey: true)) $('input.quick-submit-input').trigger(keydownEvent(altKey: true))
$('input').trigger(keydownEvent(metaKey: true)) $('input.quick-submit-input').trigger(keydownEvent(metaKey: true))
$('input').trigger(keydownEvent(shiftKey: true)) $('input.quick-submit-input').trigger(keydownEvent(shiftKey: true))
expect(@spies.submit).not.toHaveBeenTriggered() expect(@spies.submit).not.toHaveBeenTriggered()
......
.issue-details.issuable-details
.detail-page-description.content-block
%h2.title Quibusdam sint officiis earum molestiae ipsa autem voluptatem nisi rem.
.description.js-task-list-container.is-task-list-enabled
.wiki
%p Qui exercitationem magnam optio quae fuga earum odio.
%textarea.hidden.js-task-list-field Qui exercitationem magnam optio quae fuga earum odio.
%small.edited-text
.content-block.content-block-small
.awards.js-awards-block{"data-award-url" => "/gitlab-org/gitlab-test/issues/8/toggle_award_emoji"}
%button.award-control.btn.js-emoji-btn{"data-placement" => "bottom", "data-title" => "", :type => "button"}
.icon.emoji-icon.emoji-1F44D{"data-aliases" => "", "data-emoji" => "thumbsup", "data-unicode-name" => "1F44D", :title => "thumbsup"}
%span.award-control-text.js-counter 0
%button.award-control.btn.js-emoji-btn{"data-placement" => "bottom", "data-title" => "", :type => "button"}
.icon.emoji-icon.emoji-1F44E{"data-aliases" => "", "data-emoji" => "thumbsdown", "data-unicode-name" => "1F44E", :title => "thumbsdown"}
%span.award-control-text.js-counter 0
.award-menu-holder.js-award-holder
%button.btn.award-control.js-add-award{:type => "button"}
%i.fa.fa-smile-o.award-control-icon.award-control-icon-normal
%i.fa.fa-spinner.fa-spin.award-control-icon.award-control-icon-loading
%span.award-control-text Add
%section.issuable-discussion
#notes
%ul#notes-list.notes.main-notes-list.timeline
%li#note_348.note.note-row-348.timeline-entry{"data-author-id" => "18", "data-editable" => ""}
.timeline-entry-inner
.timeline-icon
%a{:href => "/u/agustin"}
%img.avatar.s40{:alt => "", :src => "#"}/
.timeline-content
.note-header
%a.author_link{:href => "/u/agustin"}
%span.author Brenna Stokes
.inline.note-headline-light
@agustin commented
%a{:href => "#note_348"}
%time 11 days ago
.note-actions
%span.note-role Reporter
%a.note-action-button.note-emoji-button.js-add-award.js-note-emoji{"data-position" => "right", :href => "#", :title => "Award Emoji"}
%i.fa.fa-spinner.fa-spin
%i.fa.fa-smile-o
.js-task-list-container.note-body.is-task-list-enabled
.note-text
%p Suscipit sunt quia quisquam sed eveniet ipsam.
.note-awards
.awards.hidden.js-awards-block{"data-award-url" => "/gitlab-org/gitlab-test/notes/348/toggle_award_emoji"}
.award-menu-holder.js-award-holder
%button.btn.award-control.js-add-award{:type => "button"}
%i.fa.fa-smile-o.award-control-icon.award-control-icon-normal
%i.fa.fa-spinner.fa-spin.award-control-icon.award-control-icon-loading
%span.award-control-text Add
%form.js-quick-submit{ action: '/foo' } %form.js-quick-submit{ action: '/foo' }
%input{ type: 'text' } %input{ type: 'text', class: 'quick-submit-input'}
%textarea %textarea
%input{ type: 'submit'} Submit %input{ type: 'submit'} Submit
......
window.emojiMenu = """
<div class='emoji-menu'>
<div class='emoji-menu-content'>
<input type="text" name="emoji_search" id="emoji_search" value="" class="emoji-search search-input form-control" />
<h5 class='emoji-menu-title'>
Emoticons
</h5>
<ul class='clearfix emoji-menu-list'>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F47D" title="alien" data-aliases="" data-emoji="alien" data-unicode-name="1F47D"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F47C" title="angel" data-aliases="" data-emoji="angel" data-unicode-name="1F47C"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F4A2" title="anger" data-aliases="" data-emoji="anger" data-unicode-name="1F4A2"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F620" title="angry" data-aliases="" data-emoji="angry" data-unicode-name="1F620"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F627" title="anguished" data-aliases="" data-emoji="anguished" data-unicode-name="1F627"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F632" title="astonished" data-aliases="" data-emoji="astonished" data-unicode-name="1F632"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F45F" title="athletic_shoe" data-aliases="" data-emoji="athletic_shoe" data-unicode-name="1F45F"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F476" title="baby" data-aliases="" data-emoji="baby" data-unicode-name="1F476"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F459" title="bikini" data-aliases="" data-emoji="bikini" data-unicode-name="1F459"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F499" title="blue_heart" data-aliases="" data-emoji="blue_heart" data-unicode-name="1F499"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F60A" title="blush" data-aliases="" data-emoji="blush" data-unicode-name="1F60A"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F4A5" title="boom" data-aliases="" data-emoji="boom" data-unicode-name="1F4A5"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F462" title="boot" data-aliases="" data-emoji="boot" data-unicode-name="1F462"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F647" title="bow" data-aliases="" data-emoji="bow" data-unicode-name="1F647"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F466" title="boy" data-aliases="" data-emoji="boy" data-unicode-name="1F466"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F470" title="bride_with_veil" data-aliases="" data-emoji="bride_with_veil" data-unicode-name="1F470"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F4BC" title="briefcase" data-aliases="" data-emoji="briefcase" data-unicode-name="1F4BC"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F494" title="broken_heart" data-aliases="" data-emoji="broken_heart" data-unicode-name="1F494"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F464" title="bust_in_silhouette" data-aliases="" data-emoji="bust_in_silhouette" data-unicode-name="1F464"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F465" title="busts_in_silhouette" data-aliases="" data-emoji="busts_in_silhouette" data-unicode-name="1F465"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F44F" title="clap" data-aliases="" data-emoji="clap" data-unicode-name="1F44F"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F302" title="closed_umbrella" data-aliases="" data-emoji="closed_umbrella" data-unicode-name="1F302"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F630" title="cold_sweat" data-aliases="" data-emoji="cold_sweat" data-unicode-name="1F630"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F616" title="confounded" data-aliases="" data-emoji="confounded" data-unicode-name="1F616"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F615" title="confused" data-aliases="" data-emoji="confused" data-unicode-name="1F615"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F477" title="construction_worker" data-aliases="" data-emoji="construction_worker" data-unicode-name="1F477"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F46E" title="cop" data-aliases="" data-emoji="cop" data-unicode-name="1F46E"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F46B" title="couple" data-aliases="" data-emoji="couple" data-unicode-name="1F46B"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F491" title="couple_with_heart" data-aliases="" data-emoji="couple_with_heart" data-unicode-name="1F491"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F48F" title="couplekiss" data-aliases="" data-emoji="couplekiss" data-unicode-name="1F48F"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F451" title="crown" data-aliases="" data-emoji="crown" data-unicode-name="1F451"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F622" title="cry" data-aliases="" data-emoji="cry" data-unicode-name="1F622"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F63F" title="crying_cat_face" data-aliases="" data-emoji="crying_cat_face" data-unicode-name="1F63F"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F498" title="cupid" data-aliases="" data-emoji="cupid" data-unicode-name="1F498"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F483" title="dancer" data-aliases="" data-emoji="dancer" data-unicode-name="1F483"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F46F" title="dancers" data-aliases="" data-emoji="dancers" data-unicode-name="1F46F"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F4A8" title="dash" data-aliases="" data-emoji="dash" data-unicode-name="1F4A8"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F61E" title="disappointed" data-aliases="" data-emoji="disappointed" data-unicode-name="1F61E"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F625" title="disappointed_relieved" data-aliases="" data-emoji="disappointed_relieved" data-unicode-name="1F625"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F4AB" title="dizzy" data-aliases="" data-emoji="dizzy" data-unicode-name="1F4AB"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F635" title="dizzy_face" data-aliases="" data-emoji="dizzy_face" data-unicode-name="1F635"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F457" title="dress" data-aliases="" data-emoji="dress" data-unicode-name="1F457"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F4A7" title="droplet" data-aliases="" data-emoji="droplet" data-unicode-name="1F4A7"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F442" title="ear" data-aliases="" data-emoji="ear" data-unicode-name="1F442"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F611" title="expressionless" data-aliases="" data-emoji="expressionless" data-unicode-name="1F611"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F453" title="eyeglasses" data-aliases="" data-emoji="eyeglasses" data-unicode-name="1F453"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F440" title="eyes" data-aliases="" data-emoji="eyes" data-unicode-name="1F440"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F46A" title="family" data-aliases="" data-emoji="family" data-unicode-name="1F46A"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F628" title="fearful" data-aliases="" data-emoji="fearful" data-unicode-name="1F628"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F525" title="fire" data-aliases=":flame:" data-emoji="fire" data-unicode-name="1F525"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-270A" title="fist" data-aliases="" data-emoji="fist" data-unicode-name="270A"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F633" title="flushed" data-aliases="" data-emoji="flushed" data-unicode-name="1F633"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F463" title="footprints" data-aliases="" data-emoji="footprints" data-unicode-name="1F463"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F626" title="frowning" data-aliases=":anguished:" data-emoji="frowning" data-unicode-name="1F626"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F48E" title="gem" data-aliases="" data-emoji="gem" data-unicode-name="1F48E"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F467" title="girl" data-aliases="" data-emoji="girl" data-unicode-name="1F467"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F49A" title="green_heart" data-aliases="" data-emoji="green_heart" data-unicode-name="1F49A"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F62C" title="grimacing" data-aliases="" data-emoji="grimacing" data-unicode-name="1F62C"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F601" title="grin" data-aliases="" data-emoji="grin" data-unicode-name="1F601"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F600" title="grinning" data-aliases="" data-emoji="grinning" data-unicode-name="1F600"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F482" title="guardsman" data-aliases="" data-emoji="guardsman" data-unicode-name="1F482"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F487" title="haircut" data-aliases="" data-emoji="haircut" data-unicode-name="1F487"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F45C" title="handbag" data-aliases="" data-emoji="handbag" data-unicode-name="1F45C"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F649" title="hear_no_evil" data-aliases="" data-emoji="hear_no_evil" data-unicode-name="1F649"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-2764" title="heart" data-aliases="" data-emoji="heart" data-unicode-name="2764"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F60D" title="heart_eyes" data-aliases="" data-emoji="heart_eyes" data-unicode-name="1F60D"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F63B" title="heart_eyes_cat" data-aliases="" data-emoji="heart_eyes_cat" data-unicode-name="1F63B"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F493" title="heartbeat" data-aliases="" data-emoji="heartbeat" data-unicode-name="1F493"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F497" title="heartpulse" data-aliases="" data-emoji="heartpulse" data-unicode-name="1F497"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F460" title="high_heel" data-aliases="" data-emoji="high_heel" data-unicode-name="1F460"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F62F" title="hushed" data-aliases="" data-emoji="hushed" data-unicode-name="1F62F"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F47F" title="imp" data-aliases="" data-emoji="imp" data-unicode-name="1F47F"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F481" title="information_desk_person" data-aliases="" data-emoji="information_desk_person" data-unicode-name="1F481"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F607" title="innocent" data-aliases="" data-emoji="innocent" data-unicode-name="1F607"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F47A" title="japanese_goblin" data-aliases="" data-emoji="japanese_goblin" data-unicode-name="1F47A"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F479" title="japanese_ogre" data-aliases="" data-emoji="japanese_ogre" data-unicode-name="1F479"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F456" title="jeans" data-aliases="" data-emoji="jeans" data-unicode-name="1F456"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F602" title="joy" data-aliases="" data-emoji="joy" data-unicode-name="1F602"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F639" title="joy_cat" data-aliases="" data-emoji="joy_cat" data-unicode-name="1F639"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F458" title="kimono" data-aliases="" data-emoji="kimono" data-unicode-name="1F458"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F48B" title="kiss" data-aliases="" data-emoji="kiss" data-unicode-name="1F48B"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F617" title="kissing" data-aliases="" data-emoji="kissing" data-unicode-name="1F617"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F63D" title="kissing_cat" data-aliases="" data-emoji="kissing_cat" data-unicode-name="1F63D"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F61A" title="kissing_closed_eyes" data-aliases="" data-emoji="kissing_closed_eyes" data-unicode-name="1F61A"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F618" title="kissing_heart" data-aliases="" data-emoji="kissing_heart" data-unicode-name="1F618"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F619" title="kissing_smiling_eyes" data-aliases="" data-emoji="kissing_smiling_eyes" data-unicode-name="1F619"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F606" title="laughing" data-aliases=":satisfied:" data-emoji="laughing" data-unicode-name="1F606"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F444" title="lips" data-aliases="" data-emoji="lips" data-unicode-name="1F444"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F484" title="lipstick" data-aliases="" data-emoji="lipstick" data-unicode-name="1F484"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F48C" title="love_letter" data-aliases="" data-emoji="love_letter" data-unicode-name="1F48C"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F468" title="man" data-aliases="" data-emoji="man" data-unicode-name="1F468"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F472" title="man_with_gua_pi_mao" data-aliases="" data-emoji="man_with_gua_pi_mao" data-unicode-name="1F472"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F473" title="man_with_turban" data-aliases="" data-emoji="man_with_turban" data-unicode-name="1F473"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F45E" title="mans_shoe" data-aliases="" data-emoji="mans_shoe" data-unicode-name="1F45E"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F637" title="mask" data-aliases="" data-emoji="mask" data-unicode-name="1F637"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F486" title="massage" data-aliases="" data-emoji="massage" data-unicode-name="1F486"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F4AA" title="muscle" data-aliases="" data-emoji="muscle" data-unicode-name="1F4AA"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F485" title="nail_care" data-aliases="" data-emoji="nail_care" data-unicode-name="1F485"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F454" title="necktie" data-aliases="" data-emoji="necktie" data-unicode-name="1F454"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F610" title="neutral_face" data-aliases="" data-emoji="neutral_face" data-unicode-name="1F610"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F645" title="no_good" data-aliases="" data-emoji="no_good" data-unicode-name="1F645"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F636" title="no_mouth" data-aliases="" data-emoji="no_mouth" data-unicode-name="1F636"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F443" title="nose" data-aliases="" data-emoji="nose" data-unicode-name="1F443"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F44C" title="ok_hand" data-aliases="" data-emoji="ok_hand" data-unicode-name="1F44C"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F646" title="ok_woman" data-aliases="" data-emoji="ok_woman" data-unicode-name="1F646"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F474" title="older_man" data-aliases="" data-emoji="older_man" data-unicode-name="1F474"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F475" title="older_woman" data-aliases=":grandma:" data-emoji="older_woman" data-unicode-name="1F475"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F450" title="open_hands" data-aliases="" data-emoji="open_hands" data-unicode-name="1F450"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F62E" title="open_mouth" data-aliases="" data-emoji="open_mouth" data-unicode-name="1F62E"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F614" title="pensive" data-aliases="" data-emoji="pensive" data-unicode-name="1F614"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F623" title="persevere" data-aliases="" data-emoji="persevere" data-unicode-name="1F623"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F64D" title="person_frowning" data-aliases="" data-emoji="person_frowning" data-unicode-name="1F64D"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F471" title="person_with_blond_hair" data-aliases="" data-emoji="person_with_blond_hair" data-unicode-name="1F471"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F64E" title="person_with_pouting_face" data-aliases="" data-emoji="person_with_pouting_face" data-unicode-name="1F64E"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F447" title="point_down" data-aliases="" data-emoji="point_down" data-unicode-name="1F447"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F448" title="point_left" data-aliases="" data-emoji="point_left" data-unicode-name="1F448"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F449" title="point_right" data-aliases="" data-emoji="point_right" data-unicode-name="1F449"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-261D" title="point_up" data-aliases="" data-emoji="point_up" data-unicode-name="261D"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F446" title="point_up_2" data-aliases="" data-emoji="point_up_2" data-unicode-name="1F446"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F4A9" title="poop" data-aliases=":shit: :hankey: :poo:" data-emoji="poop" data-unicode-name="1F4A9"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F45D" title="pouch" data-aliases="" data-emoji="pouch" data-unicode-name="1F45D"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F63E" title="pouting_cat" data-aliases="" data-emoji="pouting_cat" data-unicode-name="1F63E"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F64F" title="pray" data-aliases="" data-emoji="pray" data-unicode-name="1F64F"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F478" title="princess" data-aliases="" data-emoji="princess" data-unicode-name="1F478"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F44A" title="punch" data-aliases="" data-emoji="punch" data-unicode-name="1F44A"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F49C" title="purple_heart" data-aliases="" data-emoji="purple_heart" data-unicode-name="1F49C"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F45B" title="purse" data-aliases="" data-emoji="purse" data-unicode-name="1F45B"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F621" title="rage" data-aliases="" data-emoji="rage" data-unicode-name="1F621"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-270B" title="raised_hand" data-aliases="" data-emoji="raised_hand" data-unicode-name="270B"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F64C" title="raised_hands" data-aliases="" data-emoji="raised_hands" data-unicode-name="1F64C"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F64B" title="raising_hand" data-aliases="" data-emoji="raising_hand" data-unicode-name="1F64B"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-263A" title="relaxed" data-aliases="" data-emoji="relaxed" data-unicode-name="263A"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F60C" title="relieved" data-aliases="" data-emoji="relieved" data-unicode-name="1F60C"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F49E" title="revolving_hearts" data-aliases="" data-emoji="revolving_hearts" data-unicode-name="1F49E"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F380" title="ribbon" data-aliases="" data-emoji="ribbon" data-unicode-name="1F380"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F48D" title="ring" data-aliases="" data-emoji="ring" data-unicode-name="1F48D"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F3C3" title="runner" data-aliases="" data-emoji="runner" data-unicode-name="1F3C3"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F3BD" title="running_shirt_with_sash" data-aliases="" data-emoji="running_shirt_with_sash" data-unicode-name="1F3BD"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F461" title="sandal" data-aliases="" data-emoji="sandal" data-unicode-name="1F461"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F631" title="scream" data-aliases="" data-emoji="scream" data-unicode-name="1F631"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F640" title="scream_cat" data-aliases="" data-emoji="scream_cat" data-unicode-name="1F640"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F648" title="see_no_evil" data-aliases="" data-emoji="see_no_evil" data-unicode-name="1F648"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F455" title="shirt" data-aliases="" data-emoji="shirt" data-unicode-name="1F455"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F480" title="skull" data-aliases=":skeleton:" data-emoji="skull" data-unicode-name="1F480"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F634" title="sleeping" data-aliases="" data-emoji="sleeping" data-unicode-name="1F634"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F62A" title="sleepy" data-aliases="" data-emoji="sleepy" data-unicode-name="1F62A"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F604" title="smile" data-aliases="" data-emoji="smile" data-unicode-name="1F604"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F638" title="smile_cat" data-aliases="" data-emoji="smile_cat" data-unicode-name="1F638"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F603" title="smiley" data-aliases="" data-emoji="smiley" data-unicode-name="1F603"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F63A" title="smiley_cat" data-aliases="" data-emoji="smiley_cat" data-unicode-name="1F63A"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F608" title="smiling_imp" data-aliases="" data-emoji="smiling_imp" data-unicode-name="1F608"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F60F" title="smirk" data-aliases="" data-emoji="smirk" data-unicode-name="1F60F"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F63C" title="smirk_cat" data-aliases="" data-emoji="smirk_cat" data-unicode-name="1F63C"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F62D" title="sob" data-aliases="" data-emoji="sob" data-unicode-name="1F62D"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-2728" title="sparkles" data-aliases="" data-emoji="sparkles" data-unicode-name="2728"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F496" title="sparkling_heart" data-aliases="" data-emoji="sparkling_heart" data-unicode-name="1F496"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F64A" title="speak_no_evil" data-aliases="" data-emoji="speak_no_evil" data-unicode-name="1F64A"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F4AC" title="speech_balloon" data-aliases="" data-emoji="speech_balloon" data-unicode-name="1F4AC"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F31F" title="star2" data-aliases="" data-emoji="star2" data-unicode-name="1F31F"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F61B" title="stuck_out_tongue" data-aliases="" data-emoji="stuck_out_tongue" data-unicode-name="1F61B"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F61D" title="stuck_out_tongue_closed_eyes" data-aliases="" data-emoji="stuck_out_tongue_closed_eyes" data-unicode-name="1F61D"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F61C" title="stuck_out_tongue_winking_eye" data-aliases="" data-emoji="stuck_out_tongue_winking_eye" data-unicode-name="1F61C"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F60E" title="sunglasses" data-aliases="" data-emoji="sunglasses" data-unicode-name="1F60E"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F613" title="sweat" data-aliases="" data-emoji="sweat" data-unicode-name="1F613"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F4A6" title="sweat_drops" data-aliases="" data-emoji="sweat_drops" data-unicode-name="1F4A6"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F605" title="sweat_smile" data-aliases="" data-emoji="sweat_smile" data-unicode-name="1F605"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F4AD" title="thought_balloon" data-aliases="" data-emoji="thought_balloon" data-unicode-name="1F4AD"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F44E" title="thumbsdown" data-aliases=":-1:" data-emoji="thumbsdown" data-unicode-name="1F44E"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F44D" title="thumbsup" data-aliases=":+1:" data-emoji="thumbsup" data-unicode-name="1F44D"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F62B" title="tired_face" data-aliases="" data-emoji="tired_face" data-unicode-name="1F62B"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F445" title="tongue" data-aliases="" data-emoji="tongue" data-unicode-name="1F445"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F3A9" title="tophat" data-aliases="" data-emoji="tophat" data-unicode-name="1F3A9"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F624" title="triumph" data-aliases="" data-emoji="triumph" data-unicode-name="1F624"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F495" title="two_hearts" data-aliases="" data-emoji="two_hearts" data-unicode-name="1F495"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F46C" title="two_men_holding_hands" data-aliases="" data-emoji="two_men_holding_hands" data-unicode-name="1F46C"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F46D" title="two_women_holding_hands" data-aliases="" data-emoji="two_women_holding_hands" data-unicode-name="1F46D"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F612" title="unamused" data-aliases="" data-emoji="unamused" data-unicode-name="1F612"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-270C" title="v" data-aliases="" data-emoji="v" data-unicode-name="270C"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F6B6" title="walking" data-aliases="" data-emoji="walking" data-unicode-name="1F6B6"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F44B" title="wave" data-aliases="" data-emoji="wave" data-unicode-name="1F44B"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F629" title="weary" data-aliases="" data-emoji="weary" data-unicode-name="1F629"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F609" title="wink" data-aliases="" data-emoji="wink" data-unicode-name="1F609"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F469" title="woman" data-aliases="" data-emoji="woman" data-unicode-name="1F469"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F45A" title="womans_clothes" data-aliases="" data-emoji="womans_clothes" data-unicode-name="1F45A"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F452" title="womans_hat" data-aliases="" data-emoji="womans_hat" data-unicode-name="1F452"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F61F" title="worried" data-aliases="" data-emoji="worried" data-unicode-name="1F61F"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F49B" title="yellow_heart" data-aliases="" data-emoji="yellow_heart" data-unicode-name="1F49B"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F60B" title="yum" data-aliases="" data-emoji="yum" data-unicode-name="1F60B"></div>
</button>
</li>
<li class='pull-left text-center emoji-menu-list-item'>
<button class='emoji-menu-btn text-center js-emoji-btn' type='button'>
<div class="icon emoji-icon emoji-1F4A4" title="zzz" data-aliases="" data-emoji="zzz" data-unicode-name="1F4A4"></div>
</button>
</li>
</ul>
</div>
</div>
"""
#= require jquery-ui #= require jquery-ui/autocomplete
#= require new_branch_form #= require new_branch_form
describe 'Branch', -> describe 'Branch', ->
......
...@@ -9,6 +9,16 @@ describe Note, models: true do ...@@ -9,6 +9,16 @@ describe Note, models: true do
it { is_expected.to have_many(:todos).dependent(:destroy) } it { is_expected.to have_many(:todos).dependent(:destroy) }
end end
describe 'modules' do
subject { described_class }
it { is_expected.to include_module(Participable) }
it { is_expected.to include_module(Mentionable) }
it { is_expected.to include_module(Awardable) }
it { is_expected.to include_module(Gitlab::CurrentSettings) }
end
describe 'validation' do describe 'validation' do
it { is_expected.to validate_presence_of(:note) } it { is_expected.to validate_presence_of(:note) }
it { is_expected.to validate_presence_of(:project) } it { is_expected.to validate_presence_of(:project) }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment