Merge branch 'add-filter-by-my-reaction' into 'master'
Add filter by my reaction Closes #35618 See merge request !12962
This commit is contained in:
commit
be8e2b9c1c
|
@ -85,6 +85,13 @@ class DropDown {
|
||||||
const renderableList = this.list.querySelector('ul[data-dynamic]') || this.list;
|
const renderableList = this.list.querySelector('ul[data-dynamic]') || this.list;
|
||||||
|
|
||||||
renderableList.innerHTML = children.join('');
|
renderableList.innerHTML = children.join('');
|
||||||
|
|
||||||
|
const listEvent = new CustomEvent('render.dl', {
|
||||||
|
detail: {
|
||||||
|
list: this,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
this.list.dispatchEvent(listEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderChildren(data) {
|
renderChildren(data) {
|
||||||
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
/* global Flash */
|
||||||
|
|
||||||
|
import Ajax from '~/droplab/plugins/ajax';
|
||||||
|
import Filter from '~/droplab/plugins/filter';
|
||||||
|
import './filtered_search_dropdown';
|
||||||
|
|
||||||
|
class DropdownEmoji extends gl.FilteredSearchDropdown {
|
||||||
|
constructor(options = {}) {
|
||||||
|
super(options);
|
||||||
|
this.config = {
|
||||||
|
Ajax: {
|
||||||
|
endpoint: `${gon.relative_url_root || ''}/autocomplete/award_emojis`,
|
||||||
|
method: 'setData',
|
||||||
|
loadingTemplate: this.loadingTemplate,
|
||||||
|
onError() {
|
||||||
|
/* eslint-disable no-new */
|
||||||
|
new Flash('An error occured fetching the dropdown data.');
|
||||||
|
/* eslint-enable no-new */
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Filter: {
|
||||||
|
template: 'name',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
import(/* webpackChunkName: 'emoji' */ '~/emoji')
|
||||||
|
.then(({ glEmojiTag }) => { this.glEmojiTag = glEmojiTag; })
|
||||||
|
.catch(() => { /* ignore error and leave emoji name in the search bar */ });
|
||||||
|
|
||||||
|
this.unbindEvents();
|
||||||
|
this.bindEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
bindEvents() {
|
||||||
|
super.bindEvents();
|
||||||
|
|
||||||
|
this.listRenderedWrapper = this.listRendered.bind(this);
|
||||||
|
this.dropdown.addEventListener('render.dl', this.listRenderedWrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
unbindEvents() {
|
||||||
|
this.dropdown.removeEventListener('render.dl', this.listRenderedWrapper);
|
||||||
|
super.unbindEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
listRendered() {
|
||||||
|
this.replaceEmojiElement();
|
||||||
|
}
|
||||||
|
|
||||||
|
itemClicked(e) {
|
||||||
|
super.itemClicked(e, (selected) => {
|
||||||
|
const name = selected.querySelector('.js-data-value').innerText.trim();
|
||||||
|
return gl.DropdownUtils.getEscapedText(name);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderContent(forceShowList = false) {
|
||||||
|
this.droplab.changeHookList(this.hookId, this.dropdown, [Ajax, Filter], this.config);
|
||||||
|
super.renderContent(forceShowList);
|
||||||
|
}
|
||||||
|
|
||||||
|
replaceEmojiElement() {
|
||||||
|
if (!this.glEmojiTag) return;
|
||||||
|
|
||||||
|
// Replace empty gl-emoji tag to real content
|
||||||
|
const dropdownItems = [...this.dropdown.querySelectorAll('.filter-dropdown-item')];
|
||||||
|
dropdownItems.forEach((dropdownItem) => {
|
||||||
|
const name = dropdownItem.querySelector('.js-data-value').innerText;
|
||||||
|
const emojiTag = this.glEmojiTag(name);
|
||||||
|
const emojiElement = dropdownItem.querySelector('gl-emoji');
|
||||||
|
emojiElement.outerHTML = emojiTag;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this.droplab
|
||||||
|
.addHook(this.input, this.dropdown, [Ajax, Filter], this.config).init();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.gl = window.gl || {};
|
||||||
|
gl.DropdownEmoji = DropdownEmoji;
|
|
@ -61,7 +61,7 @@ class DropdownHint extends gl.FilteredSearchDropdown {
|
||||||
.map(tokenKey => ({
|
.map(tokenKey => ({
|
||||||
icon: `fa-${tokenKey.icon}`,
|
icon: `fa-${tokenKey.icon}`,
|
||||||
hint: tokenKey.key,
|
hint: tokenKey.key,
|
||||||
tag: `<${tokenKey.symbol}${tokenKey.key}>`,
|
tag: `<${tokenKey.tag}>`,
|
||||||
type: tokenKey.type,
|
type: tokenKey.type,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import './dropdown_emoji';
|
||||||
import './dropdown_hint';
|
import './dropdown_hint';
|
||||||
import './dropdown_non_user';
|
import './dropdown_non_user';
|
||||||
import './dropdown_user';
|
import './dropdown_user';
|
||||||
|
|
|
@ -58,6 +58,11 @@ class FilteredSearchDropdownManager {
|
||||||
},
|
},
|
||||||
element: this.container.querySelector('#js-dropdown-label'),
|
element: this.container.querySelector('#js-dropdown-label'),
|
||||||
},
|
},
|
||||||
|
'my-reaction': {
|
||||||
|
reference: null,
|
||||||
|
gl: 'DropdownEmoji',
|
||||||
|
element: this.container.querySelector('#js-dropdown-my-reaction'),
|
||||||
|
},
|
||||||
hint: {
|
hint: {
|
||||||
reference: null,
|
reference: null,
|
||||||
gl: 'DropdownHint',
|
gl: 'DropdownHint',
|
||||||
|
|
|
@ -439,8 +439,13 @@ class FilteredSearchManager {
|
||||||
const match = this.filteredSearchTokenKeys.searchByKeyParam(keyParam);
|
const match = this.filteredSearchTokenKeys.searchByKeyParam(keyParam);
|
||||||
|
|
||||||
if (match) {
|
if (match) {
|
||||||
const indexOf = keyParam.indexOf('_');
|
// Use lastIndexOf because the token key is allowed to contain underscore
|
||||||
const sanitizedKey = indexOf !== -1 ? keyParam.slice(0, keyParam.indexOf('_')) : keyParam;
|
// e.g. 'my_reaction' is the token key of 'my_reaction_emoji'
|
||||||
|
const lastIndexOf = keyParam.lastIndexOf('_');
|
||||||
|
let sanitizedKey = lastIndexOf !== -1 ? keyParam.slice(0, lastIndexOf) : keyParam;
|
||||||
|
// Replace underscore with hyphen in the sanitizedkey.
|
||||||
|
// e.g. 'my_reaction' => 'my-reaction'
|
||||||
|
sanitizedKey = sanitizedKey.replace('_', '-');
|
||||||
const symbol = match.symbol;
|
const symbol = match.symbol;
|
||||||
let quotationsToUse = '';
|
let quotationsToUse = '';
|
||||||
|
|
||||||
|
@ -515,7 +520,10 @@ class FilteredSearchManager {
|
||||||
const condition = this.filteredSearchTokenKeys
|
const condition = this.filteredSearchTokenKeys
|
||||||
.searchByConditionKeyValue(token.key, token.value.toLowerCase());
|
.searchByConditionKeyValue(token.key, token.value.toLowerCase());
|
||||||
const { param } = this.filteredSearchTokenKeys.searchByKey(token.key) || {};
|
const { param } = this.filteredSearchTokenKeys.searchByKey(token.key) || {};
|
||||||
const keyParam = param ? `${token.key}_${param}` : token.key;
|
// Replace hyphen with underscore to use as request parameter
|
||||||
|
// e.g. 'my-reaction' => 'my_reaction'
|
||||||
|
const underscoredKey = token.key.replace('-', '_');
|
||||||
|
const keyParam = param ? `${underscoredKey}_${param}` : underscoredKey;
|
||||||
let tokenPath = '';
|
let tokenPath = '';
|
||||||
|
|
||||||
if (condition) {
|
if (condition) {
|
||||||
|
|
|
@ -4,26 +4,42 @@ const tokenKeys = [{
|
||||||
param: 'username',
|
param: 'username',
|
||||||
symbol: '@',
|
symbol: '@',
|
||||||
icon: 'pencil',
|
icon: 'pencil',
|
||||||
|
tag: '@author',
|
||||||
}, {
|
}, {
|
||||||
key: 'assignee',
|
key: 'assignee',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
param: 'username',
|
param: 'username',
|
||||||
symbol: '@',
|
symbol: '@',
|
||||||
icon: 'user',
|
icon: 'user',
|
||||||
|
tag: '@assignee',
|
||||||
}, {
|
}, {
|
||||||
key: 'milestone',
|
key: 'milestone',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
param: 'title',
|
param: 'title',
|
||||||
symbol: '%',
|
symbol: '%',
|
||||||
icon: 'clock-o',
|
icon: 'clock-o',
|
||||||
|
tag: '%milestone',
|
||||||
}, {
|
}, {
|
||||||
key: 'label',
|
key: 'label',
|
||||||
type: 'array',
|
type: 'array',
|
||||||
param: 'name[]',
|
param: 'name[]',
|
||||||
symbol: '~',
|
symbol: '~',
|
||||||
icon: 'tag',
|
icon: 'tag',
|
||||||
|
tag: '~label',
|
||||||
}];
|
}];
|
||||||
|
|
||||||
|
if (gon.current_user_id) {
|
||||||
|
// Appending tokenkeys only logged-in
|
||||||
|
tokenKeys.push({
|
||||||
|
key: 'my-reaction',
|
||||||
|
type: 'string',
|
||||||
|
param: 'emoji',
|
||||||
|
symbol: '',
|
||||||
|
icon: 'thumbs-up',
|
||||||
|
tag: 'emoji',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const alternativeTokenKeys = [{
|
const alternativeTokenKeys = [{
|
||||||
key: 'label',
|
key: 'label',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
@ -84,6 +100,10 @@ class FilteredSearchTokenKeys {
|
||||||
return tokenKeysWithAlternative.find((tokenKey) => {
|
return tokenKeysWithAlternative.find((tokenKey) => {
|
||||||
let tokenKeyParam = tokenKey.key;
|
let tokenKeyParam = tokenKey.key;
|
||||||
|
|
||||||
|
// Replace hyphen with underscore to compare keyParam with tokenKeyParam
|
||||||
|
// e.g. 'my-reaction' => 'my_reaction'
|
||||||
|
tokenKeyParam = tokenKeyParam.replace('-', '_');
|
||||||
|
|
||||||
if (tokenKey.param) {
|
if (tokenKey.param) {
|
||||||
tokenKeyParam += `_${tokenKey.param}`;
|
tokenKeyParam += `_${tokenKey.param}`;
|
||||||
}
|
}
|
||||||
|
|
|
@ -132,6 +132,23 @@ class FilteredSearchVisualTokens {
|
||||||
.catch(() => { });
|
.catch(() => { });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static updateEmojiTokenAppearance(tokenValueContainer, tokenValueElement, tokenValue) {
|
||||||
|
const container = tokenValueContainer;
|
||||||
|
const element = tokenValueElement;
|
||||||
|
|
||||||
|
return import(/* webpackChunkName: 'emoji' */ '../emoji')
|
||||||
|
.then((Emoji) => {
|
||||||
|
if (!Emoji.isEmojiNameValid(tokenValue)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
container.dataset.originalValue = tokenValue;
|
||||||
|
element.innerHTML = Emoji.glEmojiTag(tokenValue);
|
||||||
|
})
|
||||||
|
// ignore error and leave emoji name in the search bar
|
||||||
|
.catch(() => { });
|
||||||
|
}
|
||||||
|
|
||||||
static renderVisualTokenValue(parentElement, tokenName, tokenValue) {
|
static renderVisualTokenValue(parentElement, tokenName, tokenValue) {
|
||||||
const tokenValueContainer = parentElement.querySelector('.value-container');
|
const tokenValueContainer = parentElement.querySelector('.value-container');
|
||||||
const tokenValueElement = tokenValueContainer.querySelector('.value');
|
const tokenValueElement = tokenValueContainer.querySelector('.value');
|
||||||
|
@ -144,6 +161,10 @@ class FilteredSearchVisualTokens {
|
||||||
FilteredSearchVisualTokens.updateUserTokenAppearance(
|
FilteredSearchVisualTokens.updateUserTokenAppearance(
|
||||||
tokenValueContainer, tokenValueElement, tokenValue,
|
tokenValueContainer, tokenValueElement, tokenValue,
|
||||||
);
|
);
|
||||||
|
} else if (tokenType === 'my-reaction') {
|
||||||
|
FilteredSearchVisualTokens.updateEmojiTokenAppearance(
|
||||||
|
tokenValueContainer, tokenValueElement, tokenValue,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -225,6 +225,18 @@
|
||||||
color: $common-gray-dark;
|
color: $common-gray-dark;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gl-emoji {
|
||||||
|
display: inline-block;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
vertical-align: inherit;
|
||||||
|
|
||||||
|
img {
|
||||||
|
height: 18px;
|
||||||
|
width: 18px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.form-control {
|
.form-control {
|
||||||
position: relative;
|
position: relative;
|
||||||
min-width: 200px;
|
min-width: 200px;
|
||||||
|
@ -277,7 +289,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.filtered-search-input-dropdown-menu {
|
.filtered-search-input-dropdown-menu {
|
||||||
max-height: 225px;
|
max-height: 260px;
|
||||||
max-width: 280px;
|
max-width: 280px;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
class AutocompleteController < ApplicationController
|
class AutocompleteController < ApplicationController
|
||||||
skip_before_action :authenticate_user!, only: [:users]
|
AWARD_EMOJI_MAX = 100
|
||||||
|
|
||||||
|
skip_before_action :authenticate_user!, only: [:users, :award_emojis]
|
||||||
before_action :load_project, only: [:users]
|
before_action :load_project, only: [:users]
|
||||||
before_action :find_users, only: [:users]
|
before_action :find_users, only: [:users]
|
||||||
|
|
||||||
|
@ -48,6 +50,20 @@ class AutocompleteController < ApplicationController
|
||||||
render json: projects.to_json(only: [:id, :name_with_namespace], methods: :name_with_namespace)
|
render json: projects.to_json(only: [:id, :name_with_namespace], methods: :name_with_namespace)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def award_emojis
|
||||||
|
emoji_with_count = AwardEmoji
|
||||||
|
.limit(AWARD_EMOJI_MAX)
|
||||||
|
.where(user: current_user)
|
||||||
|
.group(:name)
|
||||||
|
.order(count: :desc, name: :asc)
|
||||||
|
.count
|
||||||
|
|
||||||
|
# Transform from hash to array to guarantee json order
|
||||||
|
# e.g. { 'thumbsup' => 2, 'thumbsdown' = 1 }
|
||||||
|
# => [{ name: 'thumbsup' }, { name: 'thumbsdown' }]
|
||||||
|
render json: emoji_with_count.map { |k, v| { name: k } }
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def find_users
|
def find_users
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
# sort: string
|
# sort: string
|
||||||
# non_archived: boolean
|
# non_archived: boolean
|
||||||
# iids: integer[]
|
# iids: integer[]
|
||||||
|
# my_reaction_emoji: string
|
||||||
#
|
#
|
||||||
class IssuableFinder
|
class IssuableFinder
|
||||||
include CreatedAtFilter
|
include CreatedAtFilter
|
||||||
|
@ -46,6 +47,7 @@ class IssuableFinder
|
||||||
items = by_iids(items)
|
items = by_iids(items)
|
||||||
items = by_milestone(items)
|
items = by_milestone(items)
|
||||||
items = by_label(items)
|
items = by_label(items)
|
||||||
|
items = by_my_reaction_emoji(items)
|
||||||
|
|
||||||
# Filtering by project HAS TO be the last because we use the project IDs yielded by the issuable query thus far
|
# Filtering by project HAS TO be the last because we use the project IDs yielded by the issuable query thus far
|
||||||
items = by_project(items)
|
items = by_project(items)
|
||||||
|
@ -371,6 +373,14 @@ class IssuableFinder
|
||||||
items
|
items
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def by_my_reaction_emoji(items)
|
||||||
|
if params[:my_reaction_emoji].present? && current_user
|
||||||
|
items = items.awarded(current_user, params[:my_reaction_emoji])
|
||||||
|
end
|
||||||
|
|
||||||
|
items
|
||||||
|
end
|
||||||
|
|
||||||
def by_due_date(items)
|
def by_due_date(items)
|
||||||
if due_date?
|
if due_date?
|
||||||
if filter_by_no_due_date?
|
if filter_by_no_due_date?
|
||||||
|
|
|
@ -11,6 +11,21 @@ module Awardable
|
||||||
end
|
end
|
||||||
|
|
||||||
module ClassMethods
|
module ClassMethods
|
||||||
|
def awarded(user, name)
|
||||||
|
sql = <<~EOL
|
||||||
|
EXISTS (
|
||||||
|
SELECT TRUE
|
||||||
|
FROM award_emoji
|
||||||
|
WHERE user_id = :user_id AND
|
||||||
|
name = :name AND
|
||||||
|
awardable_type = :awardable_type AND
|
||||||
|
awardable_id = #{self.arel_table.name}.id
|
||||||
|
)
|
||||||
|
EOL
|
||||||
|
|
||||||
|
where(sql, user_id: user.id, name: name, awardable_type: self.name)
|
||||||
|
end
|
||||||
|
|
||||||
def order_upvotes_desc
|
def order_upvotes_desc
|
||||||
order_votes_desc(AwardEmoji::UPVOTE_NAME)
|
order_votes_desc(AwardEmoji::UPVOTE_NAME)
|
||||||
end
|
end
|
||||||
|
|
|
@ -93,6 +93,13 @@
|
||||||
%span.dropdown-label-box{ style: 'background: {{color}}' }
|
%span.dropdown-label-box{ style: 'background: {{color}}' }
|
||||||
%span.label-title.js-data-value
|
%span.label-title.js-data-value
|
||||||
{{title}}
|
{{title}}
|
||||||
|
#js-dropdown-my-reaction.filtered-search-input-dropdown-menu.dropdown-menu
|
||||||
|
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
|
||||||
|
%li.filter-dropdown-item
|
||||||
|
%button.btn.btn-link
|
||||||
|
%gl-emoji
|
||||||
|
%span.js-data-value.prepend-left-10
|
||||||
|
{{name}}
|
||||||
%button.clear-search.hidden{ type: 'button' }
|
%button.clear-search.hidden{ type: 'button' }
|
||||||
= icon('times')
|
= icon('times')
|
||||||
.filter-dropdown-container
|
.filter-dropdown-container
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
---
|
||||||
|
title: Add my reaction filter to search bar
|
||||||
|
merge_request: 12962
|
||||||
|
author: Hiroyuki Sato
|
|
@ -27,6 +27,7 @@ Rails.application.routes.draw do
|
||||||
get '/autocomplete/users' => 'autocomplete#users'
|
get '/autocomplete/users' => 'autocomplete#users'
|
||||||
get '/autocomplete/users/:id' => 'autocomplete#user'
|
get '/autocomplete/users/:id' => 'autocomplete#user'
|
||||||
get '/autocomplete/projects' => 'autocomplete#projects'
|
get '/autocomplete/projects' => 'autocomplete#projects'
|
||||||
|
get '/autocomplete/award_emojis' => 'autocomplete#award_emojis'
|
||||||
|
|
||||||
# Search
|
# Search
|
||||||
get 'search' => 'search#show'
|
get 'search' => 'search#show'
|
||||||
|
|
|
@ -339,4 +339,42 @@ describe AutocompleteController do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'GET award_emojis' do
|
||||||
|
let(:user2) { create(:user) }
|
||||||
|
let!(:award_emoji1) { create_list(:award_emoji, 2, user: user, name: 'thumbsup') }
|
||||||
|
let!(:award_emoji2) { create_list(:award_emoji, 1, user: user, name: 'thumbsdown') }
|
||||||
|
let!(:award_emoji3) { create_list(:award_emoji, 3, user: user, name: 'star') }
|
||||||
|
let!(:award_emoji4) { create_list(:award_emoji, 1, user: user, name: 'tea') }
|
||||||
|
|
||||||
|
context 'unauthorized user' do
|
||||||
|
it 'returns empty json' do
|
||||||
|
get :award_emojis
|
||||||
|
|
||||||
|
expect(json_response).to be_empty
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'sign in as user without award emoji' do
|
||||||
|
it 'returns empty json' do
|
||||||
|
sign_in(user2)
|
||||||
|
get :award_emojis
|
||||||
|
|
||||||
|
expect(json_response).to be_empty
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'sign in as user with award emoji' do
|
||||||
|
it 'returns json sorted by name count' do
|
||||||
|
sign_in(user)
|
||||||
|
get :award_emojis
|
||||||
|
|
||||||
|
expect(json_response.count).to eq 4
|
||||||
|
expect(json_response[0]).to match('name' => 'star')
|
||||||
|
expect(json_response[1]).to match('name' => 'thumbsup')
|
||||||
|
expect(json_response[2]).to match('name' => 'tea')
|
||||||
|
expect(json_response[3]).to match('name' => 'thumbsdown')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -204,6 +204,12 @@ describe 'Dropdown assignee', :js do
|
||||||
|
|
||||||
expect(page).to have_css(js_dropdown_assignee, visible: true)
|
expect(page).to have_css(js_dropdown_assignee, visible: true)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'opens assignee dropdown with existing my-reaction' do
|
||||||
|
filtered_search.set('my-reaction:star assignee:')
|
||||||
|
|
||||||
|
expect(page).to have_css(js_dropdown_assignee, visible: true)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'caching requests' do
|
describe 'caching requests' do
|
||||||
|
|
|
@ -0,0 +1,182 @@
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
describe 'Dropdown emoji', js: true do
|
||||||
|
include FilteredSearchHelpers
|
||||||
|
|
||||||
|
let!(:project) { create(:project, :public) }
|
||||||
|
let!(:user) { create(:user, name: 'administrator', username: 'root') }
|
||||||
|
let!(:issue) { create(:issue, project: project) }
|
||||||
|
let!(:award_emoji_star) { create(:award_emoji, name: 'star', user: user, awardable: issue) }
|
||||||
|
let(:filtered_search) { find('.filtered-search') }
|
||||||
|
let(:js_dropdown_emoji) { '#js-dropdown-my-reaction' }
|
||||||
|
|
||||||
|
def send_keys_to_filtered_search(input)
|
||||||
|
input.split("").each do |i|
|
||||||
|
filtered_search.send_keys(i)
|
||||||
|
end
|
||||||
|
|
||||||
|
sleep 0.5
|
||||||
|
wait_for_requests
|
||||||
|
end
|
||||||
|
|
||||||
|
def dropdown_emoji_size
|
||||||
|
page.all('#js-dropdown-my-reaction .filter-dropdown .filter-dropdown-item').size
|
||||||
|
end
|
||||||
|
|
||||||
|
def click_emoji(text)
|
||||||
|
find('#js-dropdown-my-reaction .filter-dropdown .filter-dropdown-item', text: text).click
|
||||||
|
end
|
||||||
|
|
||||||
|
before do
|
||||||
|
project.team << [user, :master]
|
||||||
|
create_list(:award_emoji, 2, user: user, name: 'thumbsup')
|
||||||
|
create_list(:award_emoji, 1, user: user, name: 'thumbsdown')
|
||||||
|
create_list(:award_emoji, 3, user: user, name: 'star')
|
||||||
|
create_list(:award_emoji, 1, user: user, name: 'tea')
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when user not logged in' do
|
||||||
|
before do
|
||||||
|
visit project_issues_path(project)
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'behavior' do
|
||||||
|
it 'does not open when the search bar has my-reaction:' do
|
||||||
|
filtered_search.set('my-reaction:')
|
||||||
|
|
||||||
|
expect(page).not_to have_css(js_dropdown_emoji)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when user loggged in' do
|
||||||
|
before do
|
||||||
|
sign_in(user)
|
||||||
|
|
||||||
|
visit project_issues_path(project)
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'behavior' do
|
||||||
|
it 'opens when the search bar has my-reaction:' do
|
||||||
|
filtered_search.set('my-reaction:')
|
||||||
|
|
||||||
|
expect(page).to have_css(js_dropdown_emoji, visible: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'closes when the search bar is unfocused' do
|
||||||
|
find('body').click()
|
||||||
|
|
||||||
|
expect(page).to have_css(js_dropdown_emoji, visible: false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should show loading indicator when opened' do
|
||||||
|
filtered_search.set('my-reaction:')
|
||||||
|
|
||||||
|
expect(page).to have_css('#js-dropdown-my-reaction .filter-dropdown-loading', visible: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should hide loading indicator when loaded' do
|
||||||
|
send_keys_to_filtered_search('my-reaction:')
|
||||||
|
|
||||||
|
expect(page).not_to have_css('#js-dropdown-my-reaction .filter-dropdown-loading')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should load all the emojis when opened' do
|
||||||
|
send_keys_to_filtered_search('my-reaction:')
|
||||||
|
|
||||||
|
expect(dropdown_emoji_size).to eq(4)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'shows the most populated emoji at top of dropdown' do
|
||||||
|
send_keys_to_filtered_search('my-reaction:')
|
||||||
|
|
||||||
|
expect(first('#js-dropdown-my-reaction li')).to have_content(award_emoji_star.name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'filtering' do
|
||||||
|
before do
|
||||||
|
filtered_search.set('my-reaction')
|
||||||
|
send_keys_to_filtered_search(':')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'filters by name' do
|
||||||
|
send_keys_to_filtered_search('up')
|
||||||
|
|
||||||
|
expect(dropdown_emoji_size).to eq(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'filters by case insensitive name' do
|
||||||
|
send_keys_to_filtered_search('Up')
|
||||||
|
|
||||||
|
expect(dropdown_emoji_size).to eq(1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'selecting from dropdown' do
|
||||||
|
before do
|
||||||
|
filtered_search.set('my-reaction')
|
||||||
|
send_keys_to_filtered_search(':')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'fills in the my-reaction name' do
|
||||||
|
click_emoji('thumbsup')
|
||||||
|
|
||||||
|
wait_for_requests
|
||||||
|
|
||||||
|
expect(page).to have_css(js_dropdown_emoji, visible: false)
|
||||||
|
expect_tokens([emoji_token('thumbsup')])
|
||||||
|
expect_filtered_search_input_empty
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'input has existing content' do
|
||||||
|
it 'opens my-reaction dropdown with existing search term' do
|
||||||
|
filtered_search.set('searchTerm my-reaction:')
|
||||||
|
|
||||||
|
expect(page).to have_css(js_dropdown_emoji, visible: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'opens my-reaction dropdown with existing assignee' do
|
||||||
|
filtered_search.set('assignee:@user my-reaction:')
|
||||||
|
|
||||||
|
expect(page).to have_css(js_dropdown_emoji, visible: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'opens my-reaction dropdown with existing label' do
|
||||||
|
filtered_search.set('label:~bug my-reaction:')
|
||||||
|
|
||||||
|
expect(page).to have_css(js_dropdown_emoji, visible: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'opens my-reaction dropdown with existing milestone' do
|
||||||
|
filtered_search.set('milestone:%v1.0 my-reaction:')
|
||||||
|
|
||||||
|
expect(page).to have_css(js_dropdown_emoji, visible: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'opens my-reaction dropdown with existing my-reaction' do
|
||||||
|
filtered_search.set('my-reaction:star my-reaction:')
|
||||||
|
|
||||||
|
expect(page).to have_css(js_dropdown_emoji, visible: true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'caching requests' do
|
||||||
|
it 'caches requests after the first load' do
|
||||||
|
filtered_search.set('my-reaction')
|
||||||
|
send_keys_to_filtered_search(':')
|
||||||
|
initial_size = dropdown_emoji_size
|
||||||
|
|
||||||
|
expect(initial_size).to be > 0
|
||||||
|
|
||||||
|
create_list(:award_emoji, 1, user: user, name: 'smile')
|
||||||
|
find('.filtered-search-box .clear-search').click
|
||||||
|
filtered_search.set('my-reaction')
|
||||||
|
send_keys_to_filtered_search(':')
|
||||||
|
|
||||||
|
expect(dropdown_emoji_size).to eq(initial_size)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -3,7 +3,7 @@ require 'rails_helper'
|
||||||
describe 'Dropdown hint', :js do
|
describe 'Dropdown hint', :js do
|
||||||
include FilteredSearchHelpers
|
include FilteredSearchHelpers
|
||||||
|
|
||||||
let!(:project) { create(:project) }
|
let!(:project) { create(:project, :public) }
|
||||||
let!(:user) { create(:user) }
|
let!(:user) { create(:user) }
|
||||||
let(:filtered_search) { find('.filtered-search') }
|
let(:filtered_search) { find('.filtered-search') }
|
||||||
let(:js_dropdown_hint) { '#js-dropdown-hint' }
|
let(:js_dropdown_hint) { '#js-dropdown-hint' }
|
||||||
|
@ -14,8 +14,23 @@ describe 'Dropdown hint', :js do
|
||||||
|
|
||||||
before do
|
before do
|
||||||
project.team << [user, :master]
|
project.team << [user, :master]
|
||||||
sign_in(user)
|
|
||||||
create(:issue, project: project)
|
create(:issue, project: project)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when user not logged in' do
|
||||||
|
before do
|
||||||
|
visit project_issues_path(project)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not exist my-reaction dropdown item' do
|
||||||
|
expect(page).to have_css(js_dropdown_hint, visible: false)
|
||||||
|
expect(page).not_to have_content('my-reaction')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when user logged in' do
|
||||||
|
before do
|
||||||
|
sign_in(user)
|
||||||
|
|
||||||
visit project_issues_path(project)
|
visit project_issues_path(project)
|
||||||
end
|
end
|
||||||
|
@ -50,7 +65,7 @@ describe 'Dropdown hint', :js do
|
||||||
it 'filters with text' do
|
it 'filters with text' do
|
||||||
filtered_search.set('a')
|
filtered_search.set('a')
|
||||||
|
|
||||||
expect(find(js_dropdown_hint)).to have_selector('.filter-dropdown .filter-dropdown-item', count: 3)
|
expect(find(js_dropdown_hint)).to have_selector('.filter-dropdown .filter-dropdown-item', count: 4)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -94,6 +109,15 @@ describe 'Dropdown hint', :js do
|
||||||
expect_tokens([{ name: 'label' }])
|
expect_tokens([{ name: 'label' }])
|
||||||
expect_filtered_search_input_empty
|
expect_filtered_search_input_empty
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'opens the emoji dropdown when you click on my-reaction' do
|
||||||
|
click_hint('my-reaction')
|
||||||
|
|
||||||
|
expect(page).to have_css(js_dropdown_hint, visible: false)
|
||||||
|
expect(page).to have_css('#js-dropdown-my-reaction', visible: true)
|
||||||
|
expect_tokens([{ name: 'my-reaction' }])
|
||||||
|
expect_filtered_search_input_empty
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'selecting from dropdown with some input' do
|
describe 'selecting from dropdown with some input' do
|
||||||
|
@ -136,6 +160,16 @@ describe 'Dropdown hint', :js do
|
||||||
expect_tokens([{ name: 'label' }])
|
expect_tokens([{ name: 'label' }])
|
||||||
expect_filtered_search_input_empty
|
expect_filtered_search_input_empty
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'opens the emoji dropdown when you click on my-reaction' do
|
||||||
|
filtered_search.set('my')
|
||||||
|
click_hint('my-reaction')
|
||||||
|
|
||||||
|
expect(page).to have_css(js_dropdown_hint, visible: false)
|
||||||
|
expect(page).to have_css('#js-dropdown-my-reaction', visible: true)
|
||||||
|
expect_tokens([{ name: 'my-reaction' }])
|
||||||
|
expect_filtered_search_input_empty
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'reselecting from dropdown' do
|
describe 'reselecting from dropdown' do
|
||||||
|
@ -174,5 +208,15 @@ describe 'Dropdown hint', :js do
|
||||||
expect_tokens([{ name: 'label' }])
|
expect_tokens([{ name: 'label' }])
|
||||||
expect_filtered_search_input_empty
|
expect_filtered_search_input_empty
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'reuses existing emoji text' do
|
||||||
|
filtered_search.send_keys('my-reaction:')
|
||||||
|
filtered_search.send_keys(:backspace)
|
||||||
|
click_hint('my-reaction')
|
||||||
|
|
||||||
|
expect_tokens([{ name: 'my-reaction' }])
|
||||||
|
expect_filtered_search_input_empty
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -270,6 +270,12 @@ describe 'Dropdown label', js: true do
|
||||||
|
|
||||||
expect(page).to have_css(js_dropdown_label)
|
expect(page).to have_css(js_dropdown_label)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'opens label dropdown with existing my-reaction' do
|
||||||
|
filtered_search.set('my-reaction:star label:')
|
||||||
|
|
||||||
|
expect(page).to have_css(js_dropdown_label)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'caching requests' do
|
describe 'caching requests' do
|
||||||
|
|
|
@ -242,6 +242,12 @@ describe 'Dropdown milestone', :js do
|
||||||
|
|
||||||
expect(page).to have_css(js_dropdown_milestone, visible: true)
|
expect(page).to have_css(js_dropdown_milestone, visible: true)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'opens milestone dropdown with existing my-reaction' do
|
||||||
|
filtered_search.set('my-reaction:star milestone:')
|
||||||
|
|
||||||
|
expect(page).to have_css(js_dropdown_milestone, visible: true)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'caching requests' do
|
describe 'caching requests' do
|
||||||
|
|
|
@ -100,7 +100,7 @@ describe 'Search bar', js: true do
|
||||||
find('.filtered-search-box .clear-search').click
|
find('.filtered-search-box .clear-search').click
|
||||||
filtered_search.click
|
filtered_search.click
|
||||||
|
|
||||||
expect(find('#js-dropdown-hint')).to have_selector('.filter-dropdown .filter-dropdown-item', count: 4)
|
expect(find('#js-dropdown-hint')).to have_selector('.filter-dropdown .filter-dropdown-item', count: 5)
|
||||||
expect(get_left_style(find('#js-dropdown-hint')['style'])).to eq(hint_offset)
|
expect(get_left_style(find('#js-dropdown-hint')['style'])).to eq(hint_offset)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -10,6 +10,9 @@ describe IssuesFinder do
|
||||||
set(:issue1) { create(:issue, author: user, assignees: [user], project: project1, milestone: milestone, title: 'gitlab', created_at: 1.week.ago) }
|
set(:issue1) { create(:issue, author: user, assignees: [user], project: project1, milestone: milestone, title: 'gitlab', created_at: 1.week.ago) }
|
||||||
set(:issue2) { create(:issue, author: user, assignees: [user], project: project2, description: 'gitlab') }
|
set(:issue2) { create(:issue, author: user, assignees: [user], project: project2, description: 'gitlab') }
|
||||||
set(:issue3) { create(:issue, author: user2, assignees: [user2], project: project2, title: 'tanuki', description: 'tanuki', created_at: 1.week.from_now) }
|
set(:issue3) { create(:issue, author: user2, assignees: [user2], project: project2, title: 'tanuki', description: 'tanuki', created_at: 1.week.from_now) }
|
||||||
|
set(:award_emoji1) { create(:award_emoji, name: 'thumbsup', user: user, awardable: issue1) }
|
||||||
|
set(:award_emoji2) { create(:award_emoji, name: 'thumbsup', user: user2, awardable: issue2) }
|
||||||
|
set(:award_emoji3) { create(:award_emoji, name: 'thumbsdown', user: user, awardable: issue3) }
|
||||||
|
|
||||||
describe '#execute' do
|
describe '#execute' do
|
||||||
set(:closed_issue) { create(:issue, author: user2, assignees: [user2], project: project2, state: 'closed') }
|
set(:closed_issue) { create(:issue, author: user2, assignees: [user2], project: project2, state: 'closed') }
|
||||||
|
@ -26,6 +29,10 @@ describe IssuesFinder do
|
||||||
issue1
|
issue1
|
||||||
issue2
|
issue2
|
||||||
issue3
|
issue3
|
||||||
|
|
||||||
|
award_emoji1
|
||||||
|
award_emoji2
|
||||||
|
award_emoji3
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'scope: all' do
|
context 'scope: all' do
|
||||||
|
@ -250,6 +257,34 @@ describe IssuesFinder do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'filtering by reaction name' do
|
||||||
|
context 'user searches by "thumbsup" reaction' do
|
||||||
|
let(:params) { { my_reaction_emoji: 'thumbsup' } }
|
||||||
|
|
||||||
|
it 'returns issues that the user thumbsup to' do
|
||||||
|
expect(issues).to contain_exactly(issue1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'user2 searches by "thumbsup" reaction' do
|
||||||
|
let(:search_user) { user2 }
|
||||||
|
|
||||||
|
let(:params) { { my_reaction_emoji: 'thumbsup' } }
|
||||||
|
|
||||||
|
it 'returns issues that the user2 thumbsup to' do
|
||||||
|
expect(issues).to contain_exactly(issue2)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'user searches by "thumbsdown" reaction' do
|
||||||
|
let(:params) { { my_reaction_emoji: 'thumbsdown' } }
|
||||||
|
|
||||||
|
it 'returns issues that the user thumbsdown to' do
|
||||||
|
expect(issues).to contain_exactly(issue3)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'when the user is unauthorized' do
|
context 'when the user is unauthorized' do
|
||||||
let(:search_user) { nil }
|
let(:search_user) { nil }
|
||||||
|
|
||||||
|
|
|
@ -351,14 +351,17 @@ describe('DropDown', function () {
|
||||||
|
|
||||||
describe('render', function () {
|
describe('render', function () {
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
this.list = { querySelector: () => {} };
|
this.list = { querySelector: () => {}, dispatchEvent: () => {} };
|
||||||
this.dropdown = { renderChildren: () => {}, list: this.list };
|
this.dropdown = { renderChildren: () => {}, list: this.list };
|
||||||
this.renderableList = {};
|
this.renderableList = {};
|
||||||
this.data = [0, 1];
|
this.data = [0, 1];
|
||||||
|
this.customEvent = {};
|
||||||
|
|
||||||
spyOn(this.dropdown, 'renderChildren').and.callFake(data => data);
|
spyOn(this.dropdown, 'renderChildren').and.callFake(data => data);
|
||||||
spyOn(this.list, 'querySelector').and.returnValue(this.renderableList);
|
spyOn(this.list, 'querySelector').and.returnValue(this.renderableList);
|
||||||
|
spyOn(this.list, 'dispatchEvent');
|
||||||
spyOn(this.data, 'map').and.callThrough();
|
spyOn(this.data, 'map').and.callThrough();
|
||||||
|
spyOn(window, 'CustomEvent').and.returnValue(this.customEvent);
|
||||||
|
|
||||||
DropDown.prototype.render.call(this.dropdown, this.data);
|
DropDown.prototype.render.call(this.dropdown, this.data);
|
||||||
});
|
});
|
||||||
|
@ -375,6 +378,14 @@ describe('DropDown', function () {
|
||||||
expect(this.renderableList.innerHTML).toBe('01');
|
expect(this.renderableList.innerHTML).toBe('01');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should call render.dl', function () {
|
||||||
|
expect(window.CustomEvent).toHaveBeenCalledWith('render.dl', jasmine.any(Object));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call dispatchEvent with the customEvent', function () {
|
||||||
|
expect(this.list.dispatchEvent).toHaveBeenCalledWith(this.customEvent);
|
||||||
|
});
|
||||||
|
|
||||||
describe('if no data argument is passed', function () {
|
describe('if no data argument is passed', function () {
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
this.data.map.calls.reset();
|
this.data.map.calls.reset();
|
||||||
|
@ -394,7 +405,7 @@ describe('DropDown', function () {
|
||||||
|
|
||||||
describe('if no dynamic list is present', function () {
|
describe('if no dynamic list is present', function () {
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
this.list = { querySelector: () => {} };
|
this.list = { querySelector: () => {}, dispatchEvent: () => {} };
|
||||||
this.dropdown = { renderChildren: () => {}, list: this.list };
|
this.dropdown = { renderChildren: () => {}, list: this.list };
|
||||||
this.data = [0, 1];
|
this.data = [0, 1];
|
||||||
|
|
||||||
|
|
|
@ -12,11 +12,9 @@ describe Awardable do
|
||||||
|
|
||||||
describe "ClassMethods" do
|
describe "ClassMethods" do
|
||||||
let!(:issue2) { create(:issue) }
|
let!(:issue2) { create(:issue) }
|
||||||
|
let!(:award_emoji2) { create(:award_emoji, awardable: issue2) }
|
||||||
|
|
||||||
before do
|
describe "orders" do
|
||||||
create(:award_emoji, awardable: issue2)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "orders on upvotes" do
|
it "orders on upvotes" do
|
||||||
expect(Issue.order_upvotes_desc.to_a).to eq [issue2, issue]
|
expect(Issue.order_upvotes_desc.to_a).to eq [issue2, issue]
|
||||||
end
|
end
|
||||||
|
@ -26,6 +24,16 @@ describe Awardable do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe ".awarded" do
|
||||||
|
it "filters by user and emoji name" do
|
||||||
|
expect(Issue.awarded(award_emoji.user, "thumbsup")).to be_empty
|
||||||
|
expect(Issue.awarded(award_emoji.user, "thumbsdown")).to eq [issue]
|
||||||
|
expect(Issue.awarded(award_emoji2.user, "thumbsup")).to eq [issue2]
|
||||||
|
expect(Issue.awarded(award_emoji2.user, "thumbsdown")).to be_empty
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "#upvotes" do
|
describe "#upvotes" do
|
||||||
it "counts the number of upvotes" do
|
it "counts the number of upvotes" do
|
||||||
expect(issue.upvotes).to be 0
|
expect(issue.upvotes).to be 0
|
||||||
|
|
|
@ -58,11 +58,17 @@ module FilteredSearchHelpers
|
||||||
page.all(:css, '.tokens-container li .selectable').each_with_index do |el, index|
|
page.all(:css, '.tokens-container li .selectable').each_with_index do |el, index|
|
||||||
token_name = tokens[index][:name]
|
token_name = tokens[index][:name]
|
||||||
token_value = tokens[index][:value]
|
token_value = tokens[index][:value]
|
||||||
|
token_emoji = tokens[index][:emoji_name]
|
||||||
|
|
||||||
expect(el.find('.name')).to have_content(token_name)
|
expect(el.find('.name')).to have_content(token_name)
|
||||||
if token_value
|
if token_value
|
||||||
expect(el.find('.value')).to have_content(token_value)
|
expect(el.find('.value')).to have_content(token_value)
|
||||||
end
|
end
|
||||||
|
# gl-emoji content is blank when the emoji unicode is not supported
|
||||||
|
if token_emoji
|
||||||
|
selector = %(gl-emoji[data-name="#{token_emoji}"])
|
||||||
|
expect(el.find('.value')).to have_css(selector)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -89,6 +95,10 @@ module FilteredSearchHelpers
|
||||||
create_token('Label', label_name, symbol)
|
create_token('Label', label_name, symbol)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def emoji_token(emoji_name = nil)
|
||||||
|
{ name: 'My-Reaction', emoji_name: emoji_name }
|
||||||
|
end
|
||||||
|
|
||||||
def default_placeholder
|
def default_placeholder
|
||||||
'Search or filter results...'
|
'Search or filter results...'
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue