Commit 08a09c6b authored by Alfredo Sumaran's avatar Alfredo Sumaran

Remove IIFEs in filtered_search_bundle.js

parent eeaeb275
......@@ -2,82 +2,80 @@ import Filter from '~/droplab/plugins/filter';
require('./filtered_search_dropdown');
(() => {
class DropdownHint extends gl.FilteredSearchDropdown {
constructor(droplab, dropdown, input, filter) {
super(droplab, dropdown, input, filter);
this.config = {
Filter: {
template: 'hint',
filterFunction: gl.DropdownUtils.filterHint.bind(null, input),
},
};
}
itemClicked(e) {
const { selected } = e.detail;
class DropdownHint extends gl.FilteredSearchDropdown {
constructor(droplab, dropdown, input, filter) {
super(droplab, dropdown, input, filter);
this.config = {
Filter: {
template: 'hint',
filterFunction: gl.DropdownUtils.filterHint.bind(null, input),
},
};
}
if (selected.tagName === 'LI') {
if (selected.hasAttribute('data-value')) {
this.dismissDropdown();
} else if (selected.getAttribute('data-action') === 'submit') {
this.dismissDropdown();
this.dispatchFormSubmitEvent();
} else {
const token = selected.querySelector('.js-filter-hint').innerText.trim();
const tag = selected.querySelector('.js-filter-tag').innerText.trim();
itemClicked(e) {
const { selected } = e.detail;
if (tag.length) {
// Get previous input values in the input field and convert them into visual tokens
const previousInputValues = this.input.value.split(' ');
const searchTerms = [];
if (selected.tagName === 'LI') {
if (selected.hasAttribute('data-value')) {
this.dismissDropdown();
} else if (selected.getAttribute('data-action') === 'submit') {
this.dismissDropdown();
this.dispatchFormSubmitEvent();
} else {
const token = selected.querySelector('.js-filter-hint').innerText.trim();
const tag = selected.querySelector('.js-filter-tag').innerText.trim();
previousInputValues.forEach((value, index) => {
searchTerms.push(value);
if (tag.length) {
// Get previous input values in the input field and convert them into visual tokens
const previousInputValues = this.input.value.split(' ');
const searchTerms = [];
if (index === previousInputValues.length - 1
&& token.indexOf(value.toLowerCase()) !== -1) {
searchTerms.pop();
}
});
previousInputValues.forEach((value, index) => {
searchTerms.push(value);
if (searchTerms.length > 0) {
gl.FilteredSearchVisualTokens.addSearchVisualToken(searchTerms.join(' '));
if (index === previousInputValues.length - 1
&& token.indexOf(value.toLowerCase()) !== -1) {
searchTerms.pop();
}
});
gl.FilteredSearchDropdownManager.addWordToInput(token.replace(':', ''), '', false, this.container);
if (searchTerms.length > 0) {
gl.FilteredSearchVisualTokens.addSearchVisualToken(searchTerms.join(' '));
}
this.dismissDropdown();
this.dispatchInputEvent();
gl.FilteredSearchDropdownManager.addWordToInput(token.replace(':', ''), '', false, this.container);
}
this.dismissDropdown();
this.dispatchInputEvent();
}
}
}
renderContent() {
const dropdownData = [];
renderContent() {
const dropdownData = [];
[].forEach.call(this.input.closest('.filtered-search-box-input-container').querySelectorAll('.dropdown-menu'), (dropdownMenu) => {
const { icon, hint, tag, type } = dropdownMenu.dataset;
if (icon && hint && tag) {
dropdownData.push(
Object.assign({
icon: `fa-${icon}`,
hint,
tag: `<${tag}>`,
}, type && { type }),
);
}
});
[].forEach.call(this.input.closest('.filtered-search-box-input-container').querySelectorAll('.dropdown-menu'), (dropdownMenu) => {
const { icon, hint, tag, type } = dropdownMenu.dataset;
if (icon && hint && tag) {
dropdownData.push(
Object.assign({
icon: `fa-${icon}`,
hint,
tag: `<${tag}>`,
}, type && { type }),
);
}
});
this.droplab.changeHookList(this.hookId, this.dropdown, [Filter], this.config);
this.droplab.setData(this.hookId, dropdownData);
}
this.droplab.changeHookList(this.hookId, this.dropdown, [Filter], this.config);
this.droplab.setData(this.hookId, dropdownData);
}
init() {
this.droplab.addHook(this.input, this.dropdown, [Filter], this.config).init();
}
init() {
this.droplab.addHook(this.input, this.dropdown, [Filter], this.config).init();
}
}
window.gl = window.gl || {};
gl.DropdownHint = DropdownHint;
})();
window.gl = window.gl || {};
gl.DropdownHint = DropdownHint;
......@@ -5,48 +5,46 @@ import Filter from '~/droplab/plugins/filter';
require('./filtered_search_dropdown');
(() => {
class DropdownNonUser extends gl.FilteredSearchDropdown {
constructor(droplab, dropdown, input, filter, endpoint, symbol) {
super(droplab, dropdown, input, filter);
this.symbol = symbol;
this.config = {
Ajax: {
endpoint,
method: 'setData',
loadingTemplate: this.loadingTemplate,
onError() {
/* eslint-disable no-new */
new Flash('An error occured fetching the dropdown data.');
/* eslint-enable no-new */
},
class DropdownNonUser extends gl.FilteredSearchDropdown {
constructor(droplab, dropdown, input, filter, endpoint, symbol) {
super(droplab, dropdown, input, filter);
this.symbol = symbol;
this.config = {
Ajax: {
endpoint,
method: 'setData',
loadingTemplate: this.loadingTemplate,
onError() {
/* eslint-disable no-new */
new Flash('An error occured fetching the dropdown data.');
/* eslint-enable no-new */
},
Filter: {
filterFunction: gl.DropdownUtils.filterWithSymbol.bind(null, this.symbol, input),
template: 'title',
},
};
}
},
Filter: {
filterFunction: gl.DropdownUtils.filterWithSymbol.bind(null, this.symbol, input),
template: 'title',
},
};
}
itemClicked(e) {
super.itemClicked(e, (selected) => {
const title = selected.querySelector('.js-data-value').innerText.trim();
return `${this.symbol}${gl.DropdownUtils.getEscapedText(title)}`;
});
}
itemClicked(e) {
super.itemClicked(e, (selected) => {
const title = selected.querySelector('.js-data-value').innerText.trim();
return `${this.symbol}${gl.DropdownUtils.getEscapedText(title)}`;
});
}
renderContent(forceShowList = false) {
this.droplab
.changeHookList(this.hookId, this.dropdown, [Ajax, Filter], this.config);
super.renderContent(forceShowList);
}
renderContent(forceShowList = false) {
this.droplab
.changeHookList(this.hookId, this.dropdown, [Ajax, Filter], this.config);
super.renderContent(forceShowList);
}
init() {
this.droplab
.addHook(this.input, this.dropdown, [Ajax, Filter], this.config).init();
}
init() {
this.droplab
.addHook(this.input, this.dropdown, [Ajax, Filter], this.config).init();
}
}
window.gl = window.gl || {};
gl.DropdownNonUser = DropdownNonUser;
})();
window.gl = window.gl || {};
gl.DropdownNonUser = DropdownNonUser;
......@@ -4,69 +4,67 @@ import AjaxFilter from '~/droplab/plugins/ajax_filter';
require('./filtered_search_dropdown');
(() => {
class DropdownUser extends gl.FilteredSearchDropdown {
constructor(droplab, dropdown, input, filter) {
super(droplab, dropdown, input, filter);
this.config = {
AjaxFilter: {
endpoint: `${gon.relative_url_root || ''}/autocomplete/users.json`,
searchKey: 'search',
params: {
per_page: 20,
active: true,
project_id: this.getProjectId(),
current_user: true,
},
searchValueFunction: this.getSearchInput.bind(this),
loadingTemplate: this.loadingTemplate,
onError() {
/* eslint-disable no-new */
new Flash('An error occured fetching the dropdown data.');
/* eslint-enable no-new */
},
class DropdownUser extends gl.FilteredSearchDropdown {
constructor(droplab, dropdown, input, filter) {
super(droplab, dropdown, input, filter);
this.config = {
AjaxFilter: {
endpoint: `${gon.relative_url_root || ''}/autocomplete/users.json`,
searchKey: 'search',
params: {
per_page: 20,
active: true,
project_id: this.getProjectId(),
current_user: true,
},
};
}
itemClicked(e) {
super.itemClicked(e,
selected => selected.querySelector('.dropdown-light-content').innerText.trim());
}
renderContent(forceShowList = false) {
this.droplab.changeHookList(this.hookId, this.dropdown, [AjaxFilter], this.config);
super.renderContent(forceShowList);
}
searchValueFunction: this.getSearchInput.bind(this),
loadingTemplate: this.loadingTemplate,
onError() {
/* eslint-disable no-new */
new Flash('An error occured fetching the dropdown data.');
/* eslint-enable no-new */
},
},
};
}
getProjectId() {
return this.input.getAttribute('data-project-id');
}
itemClicked(e) {
super.itemClicked(e,
selected => selected.querySelector('.dropdown-light-content').innerText.trim());
}
getSearchInput() {
const query = gl.DropdownUtils.getSearchInput(this.input);
const { lastToken } = gl.FilteredSearchTokenizer.processTokens(query);
renderContent(forceShowList = false) {
this.droplab.changeHookList(this.hookId, this.dropdown, [AjaxFilter], this.config);
super.renderContent(forceShowList);
}
let value = lastToken || '';
getProjectId() {
return this.input.getAttribute('data-project-id');
}
if (value[0] === '@') {
value = value.slice(1);
}
getSearchInput() {
const query = gl.DropdownUtils.getSearchInput(this.input);
const { lastToken } = gl.FilteredSearchTokenizer.processTokens(query);
// Removes the first character if it is a quotation so that we can search
// with multiple words
if (value[0] === '"' || value[0] === '\'') {
value = value.slice(1);
}
let value = lastToken || '';
return value;
if (value[0] === '@') {
value = value.slice(1);
}
init() {
this.droplab.addHook(this.input, this.dropdown, [AjaxFilter], this.config).init();
// Removes the first character if it is a quotation so that we can search
// with multiple words
if (value[0] === '"' || value[0] === '\'') {
value = value.slice(1);
}
return value;
}
init() {
this.droplab.addHook(this.input, this.dropdown, [AjaxFilter], this.config).init();
}
}
window.gl = window.gl || {};
gl.DropdownUser = DropdownUser;
})();
window.gl = window.gl || {};
gl.DropdownUser = DropdownUser;
import FilteredSearchContainer from './container';
(() => {
class DropdownUtils {
static getEscapedText(text) {
let escapedText = text;
const hasSpace = text.indexOf(' ') !== -1;
const hasDoubleQuote = text.indexOf('"') !== -1;
// Encapsulate value with quotes if it has spaces
// Known side effect: values's with both single and double quotes
// won't escape properly
if (hasSpace) {
if (hasDoubleQuote) {
escapedText = `'${text}'`;
} else {
// Encapsulate singleQuotes or if it hasSpace
escapedText = `"${text}"`;
}
class DropdownUtils {
static getEscapedText(text) {
let escapedText = text;
const hasSpace = text.indexOf(' ') !== -1;
const hasDoubleQuote = text.indexOf('"') !== -1;
// Encapsulate value with quotes if it has spaces
// Known side effect: values's with both single and double quotes
// won't escape properly
if (hasSpace) {
if (hasDoubleQuote) {
escapedText = `'${text}'`;
} else {
// Encapsulate singleQuotes or if it hasSpace
escapedText = `"${text}"`;
}
return escapedText;
}
static filterWithSymbol(filterSymbol, input, item) {
const updatedItem = item;
const searchInput = gl.DropdownUtils.getSearchInput(input);
return escapedText;
}
static filterWithSymbol(filterSymbol, input, item) {
const updatedItem = item;
const searchInput = gl.DropdownUtils.getSearchInput(input);
const title = updatedItem.title.toLowerCase();
let value = searchInput.toLowerCase();
let symbol = '';
const title = updatedItem.title.toLowerCase();
let value = searchInput.toLowerCase();
let symbol = '';
// Remove the symbol for filter
if (value[0] === filterSymbol) {
symbol = value[0];
value = value.slice(1);
}
// Remove the symbol for filter
if (value[0] === filterSymbol) {
symbol = value[0];
value = value.slice(1);
}
// Removes the first character if it is a quotation so that we can search
// with multiple words
if ((value[0] === '"' || value[0] === '\'') && title.indexOf(' ') !== -1) {
value = value.slice(1);
}
// Removes the first character if it is a quotation so that we can search
// with multiple words
if ((value[0] === '"' || value[0] === '\'') && title.indexOf(' ') !== -1) {
value = value.slice(1);
}
// Eg. filterSymbol = ~ for labels
const matchWithoutSymbol = symbol === filterSymbol && title.indexOf(value) !== -1;
const match = title.indexOf(`${symbol}${value}`) !== -1;
// Eg. filterSymbol = ~ for labels
const matchWithoutSymbol = symbol === filterSymbol && title.indexOf(value) !== -1;
const match = title.indexOf(`${symbol}${value}`) !== -1;
updatedItem.droplab_hidden = !match && !matchWithoutSymbol;
updatedItem.droplab_hidden = !match && !matchWithoutSymbol;
return updatedItem;
}
return updatedItem;
static filterHint(input, item) {
const updatedItem = item;
const searchInput = gl.DropdownUtils.getSearchQuery(input);
const { lastToken, tokens } = gl.FilteredSearchTokenizer.processTokens(searchInput);
const lastKey = lastToken.key || lastToken || '';
const allowMultiple = item.type === 'array';
const itemInExistingTokens = tokens.some(t => t.key === item.hint);
if (!allowMultiple && itemInExistingTokens) {
updatedItem.droplab_hidden = true;
} else if (!lastKey || searchInput.split('').last() === ' ') {
updatedItem.droplab_hidden = false;
} else if (lastKey) {
const split = lastKey.split(':');
const tokenName = split[0].split(' ').last();
const match = updatedItem.hint.indexOf(tokenName.toLowerCase()) === -1;
updatedItem.droplab_hidden = tokenName ? match : false;
}
static filterHint(input, item) {
const updatedItem = item;
const searchInput = gl.DropdownUtils.getSearchQuery(input);
const { lastToken, tokens } = gl.FilteredSearchTokenizer.processTokens(searchInput);
const lastKey = lastToken.key || lastToken || '';
const allowMultiple = item.type === 'array';
const itemInExistingTokens = tokens.some(t => t.key === item.hint);
if (!allowMultiple && itemInExistingTokens) {
updatedItem.droplab_hidden = true;
} else if (!lastKey || searchInput.split('').last() === ' ') {
updatedItem.droplab_hidden = false;
} else if (lastKey) {
const split = lastKey.split(':');
const tokenName = split[0].split(' ').last();
const match = updatedItem.hint.indexOf(tokenName.toLowerCase()) === -1;
updatedItem.droplab_hidden = tokenName ? match : false;
}
return updatedItem;
}
static setDataValueIfSelected(filter, selected) {
const dataValue = selected.getAttribute('data-value');
return updatedItem;
if (dataValue) {
gl.FilteredSearchDropdownManager.addWordToInput(filter, dataValue, true);
}
static setDataValueIfSelected(filter, selected) {
const dataValue = selected.getAttribute('data-value');
// Return boolean based on whether it was set
return dataValue !== null;
}
if (dataValue) {
gl.FilteredSearchDropdownManager.addWordToInput(filter, dataValue, true);
}
// Determines the full search query (visual tokens + input)
static getSearchQuery(untilInput = false) {
const container = FilteredSearchContainer.container;
const tokens = [].slice.call(container.querySelectorAll('.tokens-container li'));
const values = [];
// Return boolean based on whether it was set
return dataValue !== null;
if (untilInput) {
const inputIndex = _.findIndex(tokens, t => t.classList.contains('input-token'));
// Add one to include input-token to the tokens array
tokens.splice(inputIndex + 1);
}
// Determines the full search query (visual tokens + input)
static getSearchQuery(untilInput = false) {
const container = FilteredSearchContainer.container;
const tokens = [].slice.call(container.querySelectorAll('.tokens-container li'));
const values = [];
tokens.forEach((token) => {
if (token.classList.contains('js-visual-token')) {
const name = token.querySelector('.name');
const value = token.querySelector('.value');
const symbol = value && value.dataset.symbol ? value.dataset.symbol : '';
let valueText = '';
if (untilInput) {
const inputIndex = _.findIndex(tokens, t => t.classList.contains('input-token'));
// Add one to include input-token to the tokens array
tokens.splice(inputIndex + 1);
}
if (value && value.innerText) {
valueText = value.innerText;
}
tokens.forEach((token) => {
if (token.classList.contains('js-visual-token')) {
const name = token.querySelector('.name');
const value = token.querySelector('.value');
const symbol = value && value.dataset.symbol ? value.dataset.symbol : '';
let valueText = '';
if (value && value.innerText) {
valueText = value.innerText;
}
if (token.className.indexOf('filtered-search-token') !== -1) {
values.push(`${name.innerText.toLowerCase()}:${symbol}${valueText}`);
} else {
values.push(name.innerText);
}
} else if (token.classList.contains('input-token')) {
const { isLastVisualTokenValid } =
gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
const input = FilteredSearchContainer.container.querySelector('.filtered-search');
const inputValue = input && input.value;
if (isLastVisualTokenValid) {
values.push(inputValue);
} else {
const previous = values.pop();
values.push(`${previous}${inputValue}`);
}
if (token.className.indexOf('filtered-search-token') !== -1) {
values.push(`${name.innerText.toLowerCase()}:${symbol}${valueText}`);
} else {
values.push(name.innerText);
}
});
} else if (token.classList.contains('input-token')) {
const { isLastVisualTokenValid } =
gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
return values
.map(value => value.trim())
.join(' ');
}
const input = FilteredSearchContainer.container.querySelector('.filtered-search');
const inputValue = input && input.value;
static getSearchInput(filteredSearchInput) {
const inputValue = filteredSearchInput.value;
const { right } = gl.DropdownUtils.getInputSelectionPosition(filteredSearchInput);
if (isLastVisualTokenValid) {
values.push(inputValue);
} else {
const previous = values.pop();
values.push(`${previous}${inputValue}`);
}
}
});
return inputValue.slice(0, right);
}
return values
.map(value => value.trim())
.join(' ');
}
static getInputSelectionPosition(input) {
const selectionStart = input.selectionStart;
let inputValue = input.value;
// Replace all spaces inside quote marks with underscores
// (will continue to match entire string until an end quote is found if any)
// This helps with matching the beginning & end of a token:key
inputValue = inputValue.replace(/(('[^']*'{0,1})|("[^"]*"{0,1})|:\s+)/g, str => str.replace(/\s/g, '_'));
// Get the right position for the word selected
// Regex matches first space
let right = inputValue.slice(selectionStart).search(/\s/);
if (right >= 0) {
right += selectionStart;
} else if (right < 0) {
right = inputValue.length;
}
static getSearchInput(filteredSearchInput) {
const inputValue = filteredSearchInput.value;
const { right } = gl.DropdownUtils.getInputSelectionPosition(filteredSearchInput);
// Get the left position for the word selected
// Regex matches last non-whitespace character
let left = inputValue.slice(0, right).search(/\S+$/);
return inputValue.slice(0, right);
}
if (selectionStart === 0) {
left = 0;
} else if (selectionStart === inputValue.length && left < 0) {
left = inputValue.length;
} else if (left < 0) {
left = selectionStart;
}
static getInputSelectionPosition(input) {
const selectionStart = input.selectionStart;
let inputValue = input.value;
// Replace all spaces inside quote marks with underscores
// (will continue to match entire string until an end quote is found if any)
// This helps with matching the beginning & end of a token:key
inputValue = inputValue.replace(/(('[^']*'{0,1})|("[^"]*"{0,1})|:\s+)/g, str => str.replace(/\s/g, '_'));
// Get the right position for the word selected
// Regex matches first space
let right = inputValue.slice(selectionStart).search(/\s/);
if (right >= 0) {
right += selectionStart;
} else if (right < 0) {
right = inputValue.length;
}
// Get the left position for the word selected
// Regex matches last non-whitespace character
let left = inputValue.slice(0, right).search(/\S+$/);
return {
left,
right,
};
if (selectionStart === 0) {
left = 0;
} else if (selectionStart === inputValue.length && left < 0) {
left = inputValue.length;
} else if (left < 0) {
left = selectionStart;
}
return {
left,
right,
};
}
}
window.gl = window.gl || {};
gl.DropdownUtils = DropdownUtils;
})();
window.gl = window.gl || {};
gl.DropdownUtils = DropdownUtils;
(() => {
const DATA_DROPDOWN_TRIGGER = 'data-dropdown-trigger';
class FilteredSearchDropdown {
constructor(droplab, dropdown, input, filter) {
this.droplab = droplab;
this.hookId = input && input.id;
this.input = input;
this.filter = filter;
this.dropdown = dropdown;
this.loadingTemplate = `<div class="filter-dropdown-loading">
<i class="fa fa-spinner fa-spin"></i>
</div>`;
this.bindEvents();
}
bindEvents() {
this.itemClickedWrapper = this.itemClicked.bind(this);
this.dropdown.addEventListener('click.dl', this.itemClickedWrapper);
}
const DATA_DROPDOWN_TRIGGER = 'data-dropdown-trigger';
class FilteredSearchDropdown {
constructor(droplab, dropdown, input, filter) {
this.droplab = droplab;
this.hookId = input && input.id;
this.input = input;
this.filter = filter;
this.dropdown = dropdown;
this.loadingTemplate = `<div class="filter-dropdown-loading">
<i class="fa fa-spinner fa-spin"></i>
</div>`;
this.bindEvents();
}
unbindEvents() {
this.dropdown.removeEventListener('click.dl', this.itemClickedWrapper);
}
bindEvents() {
this.itemClickedWrapper = this.itemClicked.bind(this);
this.dropdown.addEventListener('click.dl', this.itemClickedWrapper);
}
getCurrentHook() {
return this.droplab.hooks.filter(h => h.id === this.hookId)[0] || null;
}
unbindEvents() {
this.dropdown.removeEventListener('click.dl', this.itemClickedWrapper);
}
itemClicked(e, getValueFunction) {
const { selected } = e.detail;
getCurrentHook() {
return this.droplab.hooks.filter(h => h.id === this.hookId)[0] || null;
}
if (selected.tagName === 'LI' && selected.innerHTML) {
const dataValueSet = gl.DropdownUtils.setDataValueIfSelected(this.filter, selected);
itemClicked(e, getValueFunction) {
const { selected } = e.detail;
if (!dataValueSet) {
const value = getValueFunction(selected);
gl.FilteredSearchDropdownManager.addWordToInput(this.filter, value, true);
}
if (selected.tagName === 'LI' && selected.innerHTML) {
const dataValueSet = gl.DropdownUtils.setDataValueIfSelected(this.filter, selected);
this.resetFilters();
this.dismissDropdown();
this.dispatchInputEvent();
if (!dataValueSet) {
const value = getValueFunction(selected);
gl.FilteredSearchDropdownManager.addWordToInput(this.filter, value, true);
}
}
setAsDropdown() {
this.input.setAttribute(DATA_DROPDOWN_TRIGGER, `#${this.dropdown.id}`);
this.resetFilters();
this.dismissDropdown();
this.dispatchInputEvent();
}
}
setOffset(offset = 0) {
if (window.innerWidth > 480) {
this.dropdown.style.left = `${offset}px`;
} else {
this.dropdown.style.left = '0px';
}
setAsDropdown() {
this.input.setAttribute(DATA_DROPDOWN_TRIGGER, `#${this.dropdown.id}`);
}
setOffset(offset = 0) {
if (window.innerWidth > 480) {
this.dropdown.style.left = `${offset}px`;
} else {
this.dropdown.style.left = '0px';
}
}
renderContent(forceShowList = false) {
const currentHook = this.getCurrentHook();
if (forceShowList && currentHook && currentHook.list.hidden) {
currentHook.list.show();
}
renderContent(forceShowList = false) {
const currentHook = this.getCurrentHook();
if (forceShowList && currentHook && currentHook.list.hidden) {
currentHook.list.show();
}
}
render(forceRenderContent = false, forceShowList = false) {
this.setAsDropdown();
render(forceRenderContent = false, forceShowList = false) {
this.setAsDropdown();
const currentHook = this.getCurrentHook();
const firstTimeInitialized = currentHook === null;
const currentHook = this.getCurrentHook();
const firstTimeInitialized = currentHook === null;
if (firstTimeInitialized || forceRenderContent) {
this.renderContent(forceShowList);
} else if (currentHook.list.list.id !== this.dropdown.id) {
this.renderContent(forceShowList);
}
if (firstTimeInitialized || forceRenderContent) {
this.renderContent(forceShowList);
} else if (currentHook.list.list.id !== this.dropdown.id) {
this.renderContent(forceShowList);
}
}
dismissDropdown() {
// Focusing on the input will dismiss dropdown
// (default droplab functionality)
this.input.focus();
}
dismissDropdown() {
// Focusing on the input will dismiss dropdown
// (default droplab functionality)
this.input.focus();
}
dispatchInputEvent() {
// Propogate input change to FilteredSearchDropdownManager
// so that it can determine which dropdowns to open
this.input.dispatchEvent(new CustomEvent('input', {
bubbles: true,
cancelable: true,
}));
}
dispatchInputEvent() {
// Propogate input change to FilteredSearchDropdownManager
// so that it can determine which dropdowns to open
this.input.dispatchEvent(new CustomEvent('input', {
bubbles: true,
cancelable: true,
}));
}
dispatchFormSubmitEvent() {
// dispatchEvent() is necessary as form.submit() does not
// trigger event handlers
this.input.form.dispatchEvent(new Event('submit'));
}
dispatchFormSubmitEvent() {
// dispatchEvent() is necessary as form.submit() does not
// trigger event handlers
this.input.form.dispatchEvent(new Event('submit'));
}
hideDropdown() {
const currentHook = this.getCurrentHook();
if (currentHook) {
currentHook.list.hide();
}
hideDropdown() {
const currentHook = this.getCurrentHook();
if (currentHook) {
currentHook.list.hide();
}
}
resetFilters() {
const hook = this.getCurrentHook();
if (hook) {
const data = hook.list.data || [];
const results = data.map((o) => {
const updated = o;
updated.droplab_hidden = false;
return updated;
});
hook.list.render(results);
}
resetFilters() {
const hook = this.getCurrentHook();
if (hook) {
const data = hook.list.data || [];
const results = data.map((o) => {
const updated = o;
updated.droplab_hidden = false;
return updated;
});
hook.list.render(results);
}
}
}
window.gl = window.gl || {};
gl.FilteredSearchDropdown = FilteredSearchDropdown;
})();
window.gl = window.gl || {};
gl.FilteredSearchDropdown = FilteredSearchDropdown;
import DropLab from '~/droplab/drop_lab';
import FilteredSearchContainer from './container';
(() => {
class FilteredSearchDropdownManager {
constructor(baseEndpoint = '', page) {
this.container = FilteredSearchContainer.container;
this.baseEndpoint = baseEndpoint.replace(/\/$/, '');
this.tokenizer = gl.FilteredSearchTokenizer;
this.filteredSearchTokenKeys = gl.FilteredSearchTokenKeys;
this.filteredSearchInput = this.container.querySelector('.filtered-search');
this.page = page;
this.setupMapping();
this.cleanupWrapper = this.cleanup.bind(this);
document.addEventListener('beforeunload', this.cleanupWrapper);
class FilteredSearchDropdownManager {
constructor(baseEndpoint = '', page) {
this.container = FilteredSearchContainer.container;
this.baseEndpoint = baseEndpoint.replace(/\/$/, '');
this.tokenizer = gl.FilteredSearchTokenizer;
this.filteredSearchTokenKeys = gl.FilteredSearchTokenKeys;
this.filteredSearchInput = this.container.querySelector('.filtered-search');
this.page = page;
this.setupMapping();
this.cleanupWrapper = this.cleanup.bind(this);
document.addEventListener('beforeunload', this.cleanupWrapper);
}
cleanup() {
if (this.droplab) {
this.droplab.destroy();
this.droplab = null;
}
cleanup() {
if (this.droplab) {
this.droplab.destroy();
this.droplab = null;
}
this.setupMapping();
this.setupMapping();
document.removeEventListener('beforeunload', this.cleanupWrapper);
}
document.removeEventListener('beforeunload', this.cleanupWrapper);
}
setupMapping() {
this.mapping = {
author: {
reference: null,
gl: 'DropdownUser',
element: this.container.querySelector('#js-dropdown-author'),
},
assignee: {
reference: null,
gl: 'DropdownUser',
element: this.container.querySelector('#js-dropdown-assignee'),
},
milestone: {
reference: null,
gl: 'DropdownNonUser',
extraArguments: [`${this.baseEndpoint}/milestones.json`, '%'],
element: this.container.querySelector('#js-dropdown-milestone'),
},
label: {
reference: null,
gl: 'DropdownNonUser',
extraArguments: [`${this.baseEndpoint}/labels.json`, '~'],
element: this.container.querySelector('#js-dropdown-label'),
},
hint: {
reference: null,
gl: 'DropdownHint',
element: this.container.querySelector('#js-dropdown-hint'),
},
};
}
setupMapping() {
this.mapping = {
author: {
reference: null,
gl: 'DropdownUser',
element: this.container.querySelector('#js-dropdown-author'),
},
assignee: {
reference: null,
gl: 'DropdownUser',
element: this.container.querySelector('#js-dropdown-assignee'),
},
milestone: {
reference: null,
gl: 'DropdownNonUser',
extraArguments: [`${this.baseEndpoint}/milestones.json`, '%'],
element: this.container.querySelector('#js-dropdown-milestone'),
},
label: {
reference: null,
gl: 'DropdownNonUser',
extraArguments: [`${this.baseEndpoint}/labels.json`, '~'],
element: this.container.querySelector('#js-dropdown-label'),
},
hint: {
reference: null,
gl: 'DropdownHint',
element: this.container.querySelector('#js-dropdown-hint'),
},
};
static addWordToInput(tokenName, tokenValue = '', clicked = false) {
const input = FilteredSearchContainer.container.querySelector('.filtered-search');
gl.FilteredSearchVisualTokens.addFilterVisualToken(tokenName, tokenValue);
input.value = '';
if (clicked) {
gl.FilteredSearchVisualTokens.moveInputToTheRight();
}
}
static addWordToInput(tokenName, tokenValue = '', clicked = false) {
const input = FilteredSearchContainer.container.querySelector('.filtered-search');
updateCurrentDropdownOffset() {
this.updateDropdownOffset(this.currentDropdown);
}
gl.FilteredSearchVisualTokens.addFilterVisualToken(tokenName, tokenValue);
input.value = '';
updateDropdownOffset(key) {
// Always align dropdown with the input field
let offset = this.filteredSearchInput.getBoundingClientRect().left - this.container.querySelector('.scroll-container').getBoundingClientRect().left;
if (clicked) {
gl.FilteredSearchVisualTokens.moveInputToTheRight();
}
}
const maxInputWidth = 240;
const currentDropdownWidth = this.mapping[key].element.clientWidth || maxInputWidth;
updateCurrentDropdownOffset() {
this.updateDropdownOffset(this.currentDropdown);
// Make sure offset never exceeds the input container
const offsetMaxWidth = this.container.querySelector('.scroll-container').clientWidth - currentDropdownWidth;
if (offsetMaxWidth < offset) {
offset = offsetMaxWidth;
}
updateDropdownOffset(key) {
// Always align dropdown with the input field
let offset = this.filteredSearchInput.getBoundingClientRect().left - this.container.querySelector('.scroll-container').getBoundingClientRect().left;
this.mapping[key].reference.setOffset(offset);
}
const maxInputWidth = 240;
const currentDropdownWidth = this.mapping[key].element.clientWidth || maxInputWidth;
load(key, firstLoad = false) {
const mappingKey = this.mapping[key];
const glClass = mappingKey.gl;
const element = mappingKey.element;
let forceShowList = false;
// Make sure offset never exceeds the input container
const offsetMaxWidth = this.container.querySelector('.scroll-container').clientWidth - currentDropdownWidth;
if (offsetMaxWidth < offset) {
offset = offsetMaxWidth;
}
if (!mappingKey.reference) {
const dl = this.droplab;
const defaultArguments = [null, dl, element, this.filteredSearchInput, key];
const glArguments = defaultArguments.concat(mappingKey.extraArguments || []);
this.mapping[key].reference.setOffset(offset);
// Passing glArguments to `new gl[glClass](<arguments>)`
mappingKey.reference = new (Function.prototype.bind.apply(gl[glClass], glArguments))();
}
load(key, firstLoad = false) {
const mappingKey = this.mapping[key];
const glClass = mappingKey.gl;
const element = mappingKey.element;
let forceShowList = false;
if (!mappingKey.reference) {
const dl = this.droplab;
const defaultArguments = [null, dl, element, this.filteredSearchInput, key];
const glArguments = defaultArguments.concat(mappingKey.extraArguments || []);
if (firstLoad) {
mappingKey.reference.init();
}
// Passing glArguments to `new gl[glClass](<arguments>)`
mappingKey.reference = new (Function.prototype.bind.apply(gl[glClass], glArguments))();
}
if (this.currentDropdown === 'hint') {
// Force the dropdown to show if it was clicked from the hint dropdown
forceShowList = true;
}
if (firstLoad) {
mappingKey.reference.init();
}
this.updateDropdownOffset(key);
mappingKey.reference.render(firstLoad, forceShowList);
if (this.currentDropdown === 'hint') {
// Force the dropdown to show if it was clicked from the hint dropdown
forceShowList = true;
}
this.currentDropdown = key;
}
this.updateDropdownOffset(key);
mappingKey.reference.render(firstLoad, forceShowList);
loadDropdown(dropdownName = '') {
let firstLoad = false;
this.currentDropdown = key;
if (!this.droplab) {
firstLoad = true;
this.droplab = new DropLab();
}
loadDropdown(dropdownName = '') {
let firstLoad = false;
const match = this.filteredSearchTokenKeys.searchByKey(dropdownName.toLowerCase());
const shouldOpenFilterDropdown = match && this.currentDropdown !== match.key
&& this.mapping[match.key];
const shouldOpenHintDropdown = !match && this.currentDropdown !== 'hint';
if (!this.droplab) {
firstLoad = true;
this.droplab = new DropLab();
}
if (shouldOpenFilterDropdown || shouldOpenHintDropdown) {
const key = match && match.key ? match.key : 'hint';
this.load(key, firstLoad);
}
}
const match = this.filteredSearchTokenKeys.searchByKey(dropdownName.toLowerCase());
const shouldOpenFilterDropdown = match && this.currentDropdown !== match.key
&& this.mapping[match.key];
const shouldOpenHintDropdown = !match && this.currentDropdown !== 'hint';
setDropdown() {
const query = gl.DropdownUtils.getSearchQuery(true);
const { lastToken, searchToken } = this.tokenizer.processTokens(query);
if (shouldOpenFilterDropdown || shouldOpenHintDropdown) {
const key = match && match.key ? match.key : 'hint';
this.load(key, firstLoad);
}
if (this.currentDropdown) {
this.updateCurrentDropdownOffset();
}
setDropdown() {
const query = gl.DropdownUtils.getSearchQuery(true);
const { lastToken, searchToken } = this.tokenizer.processTokens(query);
if (this.currentDropdown) {
this.updateCurrentDropdownOffset();
}
if (lastToken === searchToken && lastToken !== null) {
// Token is not fully initialized yet because it has no value
// Eg. token = 'label:'
const split = lastToken.split(':');
const dropdownName = split[0].split(' ').last();
this.loadDropdown(split.length > 1 ? dropdownName : '');
} else if (lastToken) {
// Token has been initialized into an object because it has a value
this.loadDropdown(lastToken.key);
} else {
this.loadDropdown('hint');
}
if (lastToken === searchToken && lastToken !== null) {
// Token is not fully initialized yet because it has no value
// Eg. token = 'label:'
const split = lastToken.split(':');
const dropdownName = split[0].split(' ').last();
this.loadDropdown(split.length > 1 ? dropdownName : '');
} else if (lastToken) {
// Token has been initialized into an object because it has a value
this.loadDropdown(lastToken.key);
} else {
this.loadDropdown('hint');
}
}
resetDropdowns() {
if (!this.currentDropdown) {
return;
}
resetDropdowns() {
if (!this.currentDropdown) {
return;
}
// Force current dropdown to hide
this.mapping[this.currentDropdown].reference.hideDropdown();
// Force current dropdown to hide
this.mapping[this.currentDropdown].reference.hideDropdown();
// Re-Load dropdown
this.setDropdown();
// Re-Load dropdown
this.setDropdown();
// Reset filters for current dropdown
this.mapping[this.currentDropdown].reference.resetFilters();
// Reset filters for current dropdown
this.mapping[this.currentDropdown].reference.resetFilters();
// Reposition dropdown so that it is aligned with cursor
this.updateDropdownOffset(this.currentDropdown);
}
// Reposition dropdown so that it is aligned with cursor
this.updateDropdownOffset(this.currentDropdown);
}
destroyDroplab() {
this.droplab.destroy();
}
destroyDroplab() {
this.droplab.destroy();
}
}
window.gl = window.gl || {};
gl.FilteredSearchDropdownManager = FilteredSearchDropdownManager;
})();
window.gl = window.gl || {};
gl.FilteredSearchDropdownManager = FilteredSearchDropdownManager;
......@@ -6,489 +6,487 @@ import RecentSearchesStore from './stores/recent_searches_store';
import RecentSearchesService from './services/recent_searches_service';
import eventHub from './event_hub';
(() => {
class FilteredSearchManager {
constructor(page) {
this.container = FilteredSearchContainer.container;
this.filteredSearchInput = this.container.querySelector('.filtered-search');
this.filteredSearchInputForm = this.filteredSearchInput.form;
this.clearSearchButton = this.container.querySelector('.clear-search');
this.tokensContainer = this.container.querySelector('.tokens-container');
this.filteredSearchTokenKeys = gl.FilteredSearchTokenKeys;
this.recentSearchesStore = new RecentSearchesStore();
let recentSearchesKey = 'issue-recent-searches';
if (page === 'merge_requests') {
recentSearchesKey = 'merge-request-recent-searches';
}
this.recentSearchesService = new RecentSearchesService(recentSearchesKey);
// Fetch recent searches from localStorage
this.fetchingRecentSearchesPromise = this.recentSearchesService.fetch()
.catch(() => {
// eslint-disable-next-line no-new
new Flash('An error occured while parsing recent searches');
// Gracefully fail to empty array
return [];
})
.then((searches) => {
// Put any searches that may have come in before
// we fetched the saved searches ahead of the already saved ones
const resultantSearches = this.recentSearchesStore.setRecentSearches(
this.recentSearchesStore.state.recentSearches.concat(searches),
);
this.recentSearchesService.save(resultantSearches);
});
if (this.filteredSearchInput) {
this.tokenizer = gl.FilteredSearchTokenizer;
this.dropdownManager = new gl.FilteredSearchDropdownManager(this.filteredSearchInput.getAttribute('data-base-endpoint') || '', page);
this.recentSearchesRoot = new RecentSearchesRoot(
this.recentSearchesStore,
this.recentSearchesService,
document.querySelector('.js-filtered-search-history-dropdown'),
class FilteredSearchManager {
constructor(page) {
this.container = FilteredSearchContainer.container;
this.filteredSearchInput = this.container.querySelector('.filtered-search');
this.filteredSearchInputForm = this.filteredSearchInput.form;
this.clearSearchButton = this.container.querySelector('.clear-search');
this.tokensContainer = this.container.querySelector('.tokens-container');
this.filteredSearchTokenKeys = gl.FilteredSearchTokenKeys;
this.recentSearchesStore = new RecentSearchesStore();
let recentSearchesKey = 'issue-recent-searches';
if (page === 'merge_requests') {
recentSearchesKey = 'merge-request-recent-searches';
}
this.recentSearchesService = new RecentSearchesService(recentSearchesKey);
// Fetch recent searches from localStorage
this.fetchingRecentSearchesPromise = this.recentSearchesService.fetch()
.catch(() => {
// eslint-disable-next-line no-new
new Flash('An error occured while parsing recent searches');
// Gracefully fail to empty array
return [];
})
.then((searches) => {
// Put any searches that may have come in before
// we fetched the saved searches ahead of the already saved ones
const resultantSearches = this.recentSearchesStore.setRecentSearches(
this.recentSearchesStore.state.recentSearches.concat(searches),
);
this.recentSearchesRoot.init();
this.recentSearchesService.save(resultantSearches);
});
this.bindEvents();
this.loadSearchParamsFromURL();
this.dropdownManager.setDropdown();
if (this.filteredSearchInput) {
this.tokenizer = gl.FilteredSearchTokenizer;
this.dropdownManager = new gl.FilteredSearchDropdownManager(this.filteredSearchInput.getAttribute('data-base-endpoint') || '', page);
this.cleanupWrapper = this.cleanup.bind(this);
document.addEventListener('beforeunload', this.cleanupWrapper);
}
}
this.recentSearchesRoot = new RecentSearchesRoot(
this.recentSearchesStore,
this.recentSearchesService,
document.querySelector('.js-filtered-search-history-dropdown'),
);
this.recentSearchesRoot.init();
cleanup() {
this.unbindEvents();
document.removeEventListener('beforeunload', this.cleanupWrapper);
this.bindEvents();
this.loadSearchParamsFromURL();
this.dropdownManager.setDropdown();
if (this.recentSearchesRoot) {
this.recentSearchesRoot.destroy();
}
this.cleanupWrapper = this.cleanup.bind(this);
document.addEventListener('beforeunload', this.cleanupWrapper);
}
}
bindEvents() {
this.handleFormSubmit = this.handleFormSubmit.bind(this);
this.setDropdownWrapper = this.dropdownManager.setDropdown.bind(this.dropdownManager);
this.toggleClearSearchButtonWrapper = this.toggleClearSearchButton.bind(this);
this.handleInputPlaceholderWrapper = this.handleInputPlaceholder.bind(this);
this.handleInputVisualTokenWrapper = this.handleInputVisualToken.bind(this);
this.checkForEnterWrapper = this.checkForEnter.bind(this);
this.onClearSearchWrapper = this.onClearSearch.bind(this);
this.checkForBackspaceWrapper = this.checkForBackspace.bind(this);
this.removeSelectedTokenWrapper = this.removeSelectedToken.bind(this);
this.unselectEditTokensWrapper = this.unselectEditTokens.bind(this);
this.editTokenWrapper = this.editToken.bind(this);
this.tokenChange = this.tokenChange.bind(this);
this.addInputContainerFocusWrapper = this.addInputContainerFocus.bind(this);
this.removeInputContainerFocusWrapper = this.removeInputContainerFocus.bind(this);
this.onrecentSearchesItemSelectedWrapper = this.onrecentSearchesItemSelected.bind(this);
this.filteredSearchInputForm.addEventListener('submit', this.handleFormSubmit);
this.filteredSearchInput.addEventListener('input', this.setDropdownWrapper);
this.filteredSearchInput.addEventListener('input', this.toggleClearSearchButtonWrapper);
this.filteredSearchInput.addEventListener('input', this.handleInputPlaceholderWrapper);
this.filteredSearchInput.addEventListener('input', this.handleInputVisualTokenWrapper);
this.filteredSearchInput.addEventListener('keydown', this.checkForEnterWrapper);
this.filteredSearchInput.addEventListener('keyup', this.checkForBackspaceWrapper);
this.filteredSearchInput.addEventListener('click', this.tokenChange);
this.filteredSearchInput.addEventListener('keyup', this.tokenChange);
this.filteredSearchInput.addEventListener('focus', this.addInputContainerFocusWrapper);
this.tokensContainer.addEventListener('click', FilteredSearchManager.selectToken);
this.tokensContainer.addEventListener('dblclick', this.editTokenWrapper);
this.clearSearchButton.addEventListener('click', this.onClearSearchWrapper);
document.addEventListener('click', gl.FilteredSearchVisualTokens.unselectTokens);
document.addEventListener('click', this.unselectEditTokensWrapper);
document.addEventListener('click', this.removeInputContainerFocusWrapper);
document.addEventListener('keydown', this.removeSelectedTokenWrapper);
eventHub.$on('recentSearchesItemSelected', this.onrecentSearchesItemSelectedWrapper);
}
cleanup() {
this.unbindEvents();
document.removeEventListener('beforeunload', this.cleanupWrapper);
unbindEvents() {
this.filteredSearchInputForm.removeEventListener('submit', this.handleFormSubmit);
this.filteredSearchInput.removeEventListener('input', this.setDropdownWrapper);
this.filteredSearchInput.removeEventListener('input', this.toggleClearSearchButtonWrapper);
this.filteredSearchInput.removeEventListener('input', this.handleInputPlaceholderWrapper);
this.filteredSearchInput.removeEventListener('input', this.handleInputVisualTokenWrapper);
this.filteredSearchInput.removeEventListener('keydown', this.checkForEnterWrapper);
this.filteredSearchInput.removeEventListener('keyup', this.checkForBackspaceWrapper);
this.filteredSearchInput.removeEventListener('click', this.tokenChange);
this.filteredSearchInput.removeEventListener('keyup', this.tokenChange);
this.filteredSearchInput.removeEventListener('focus', this.addInputContainerFocusWrapper);
this.tokensContainer.removeEventListener('click', FilteredSearchManager.selectToken);
this.tokensContainer.removeEventListener('dblclick', this.editTokenWrapper);
this.clearSearchButton.removeEventListener('click', this.onClearSearchWrapper);
document.removeEventListener('click', gl.FilteredSearchVisualTokens.unselectTokens);
document.removeEventListener('click', this.unselectEditTokensWrapper);
document.removeEventListener('click', this.removeInputContainerFocusWrapper);
document.removeEventListener('keydown', this.removeSelectedTokenWrapper);
eventHub.$off('recentSearchesItemSelected', this.onrecentSearchesItemSelectedWrapper);
if (this.recentSearchesRoot) {
this.recentSearchesRoot.destroy();
}
}
checkForBackspace(e) {
// 8 = Backspace Key
// 46 = Delete Key
if (e.keyCode === 8 || e.keyCode === 46) {
const { lastVisualToken } = gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
bindEvents() {
this.handleFormSubmit = this.handleFormSubmit.bind(this);
this.setDropdownWrapper = this.dropdownManager.setDropdown.bind(this.dropdownManager);
this.toggleClearSearchButtonWrapper = this.toggleClearSearchButton.bind(this);
this.handleInputPlaceholderWrapper = this.handleInputPlaceholder.bind(this);
this.handleInputVisualTokenWrapper = this.handleInputVisualToken.bind(this);
this.checkForEnterWrapper = this.checkForEnter.bind(this);
this.onClearSearchWrapper = this.onClearSearch.bind(this);
this.checkForBackspaceWrapper = this.checkForBackspace.bind(this);
this.removeSelectedTokenWrapper = this.removeSelectedToken.bind(this);
this.unselectEditTokensWrapper = this.unselectEditTokens.bind(this);
this.editTokenWrapper = this.editToken.bind(this);
this.tokenChange = this.tokenChange.bind(this);
this.addInputContainerFocusWrapper = this.addInputContainerFocus.bind(this);
this.removeInputContainerFocusWrapper = this.removeInputContainerFocus.bind(this);
this.onrecentSearchesItemSelectedWrapper = this.onrecentSearchesItemSelected.bind(this);
this.filteredSearchInputForm.addEventListener('submit', this.handleFormSubmit);
this.filteredSearchInput.addEventListener('input', this.setDropdownWrapper);
this.filteredSearchInput.addEventListener('input', this.toggleClearSearchButtonWrapper);
this.filteredSearchInput.addEventListener('input', this.handleInputPlaceholderWrapper);
this.filteredSearchInput.addEventListener('input', this.handleInputVisualTokenWrapper);
this.filteredSearchInput.addEventListener('keydown', this.checkForEnterWrapper);
this.filteredSearchInput.addEventListener('keyup', this.checkForBackspaceWrapper);
this.filteredSearchInput.addEventListener('click', this.tokenChange);
this.filteredSearchInput.addEventListener('keyup', this.tokenChange);
this.filteredSearchInput.addEventListener('focus', this.addInputContainerFocusWrapper);
this.tokensContainer.addEventListener('click', FilteredSearchManager.selectToken);
this.tokensContainer.addEventListener('dblclick', this.editTokenWrapper);
this.clearSearchButton.addEventListener('click', this.onClearSearchWrapper);
document.addEventListener('click', gl.FilteredSearchVisualTokens.unselectTokens);
document.addEventListener('click', this.unselectEditTokensWrapper);
document.addEventListener('click', this.removeInputContainerFocusWrapper);
document.addEventListener('keydown', this.removeSelectedTokenWrapper);
eventHub.$on('recentSearchesItemSelected', this.onrecentSearchesItemSelectedWrapper);
}
if (this.filteredSearchInput.value === '' && lastVisualToken) {
this.filteredSearchInput.value = gl.FilteredSearchVisualTokens.getLastTokenPartial();
gl.FilteredSearchVisualTokens.removeLastTokenPartial();
}
unbindEvents() {
this.filteredSearchInputForm.removeEventListener('submit', this.handleFormSubmit);
this.filteredSearchInput.removeEventListener('input', this.setDropdownWrapper);
this.filteredSearchInput.removeEventListener('input', this.toggleClearSearchButtonWrapper);
this.filteredSearchInput.removeEventListener('input', this.handleInputPlaceholderWrapper);
this.filteredSearchInput.removeEventListener('input', this.handleInputVisualTokenWrapper);
this.filteredSearchInput.removeEventListener('keydown', this.checkForEnterWrapper);
this.filteredSearchInput.removeEventListener('keyup', this.checkForBackspaceWrapper);
this.filteredSearchInput.removeEventListener('click', this.tokenChange);
this.filteredSearchInput.removeEventListener('keyup', this.tokenChange);
this.filteredSearchInput.removeEventListener('focus', this.addInputContainerFocusWrapper);
this.tokensContainer.removeEventListener('click', FilteredSearchManager.selectToken);
this.tokensContainer.removeEventListener('dblclick', this.editTokenWrapper);
this.clearSearchButton.removeEventListener('click', this.onClearSearchWrapper);
document.removeEventListener('click', gl.FilteredSearchVisualTokens.unselectTokens);
document.removeEventListener('click', this.unselectEditTokensWrapper);
document.removeEventListener('click', this.removeInputContainerFocusWrapper);
document.removeEventListener('keydown', this.removeSelectedTokenWrapper);
eventHub.$off('recentSearchesItemSelected', this.onrecentSearchesItemSelectedWrapper);
}
checkForBackspace(e) {
// 8 = Backspace Key
// 46 = Delete Key
if (e.keyCode === 8 || e.keyCode === 46) {
const { lastVisualToken } = gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
// Reposition dropdown so that it is aligned with cursor
this.dropdownManager.updateCurrentDropdownOffset();
if (this.filteredSearchInput.value === '' && lastVisualToken) {
this.filteredSearchInput.value = gl.FilteredSearchVisualTokens.getLastTokenPartial();
gl.FilteredSearchVisualTokens.removeLastTokenPartial();
}
}
checkForEnter(e) {
if (e.keyCode === 38 || e.keyCode === 40) {
const selectionStart = this.filteredSearchInput.selectionStart;
// Reposition dropdown so that it is aligned with cursor
this.dropdownManager.updateCurrentDropdownOffset();
}
}
e.preventDefault();
this.filteredSearchInput.setSelectionRange(selectionStart, selectionStart);
}
checkForEnter(e) {
if (e.keyCode === 38 || e.keyCode === 40) {
const selectionStart = this.filteredSearchInput.selectionStart;
if (e.keyCode === 13) {
const dropdown = this.dropdownManager.mapping[this.dropdownManager.currentDropdown];
const dropdownEl = dropdown.element;
const activeElements = dropdownEl.querySelectorAll('.droplab-item-active');
e.preventDefault();
this.filteredSearchInput.setSelectionRange(selectionStart, selectionStart);
}
e.preventDefault();
if (e.keyCode === 13) {
const dropdown = this.dropdownManager.mapping[this.dropdownManager.currentDropdown];
const dropdownEl = dropdown.element;
const activeElements = dropdownEl.querySelectorAll('.droplab-item-active');
if (!activeElements.length) {
if (this.isHandledAsync) {
e.stopImmediatePropagation();
e.preventDefault();
this.filteredSearchInput.blur();
this.dropdownManager.resetDropdowns();
} else {
// Prevent droplab from opening dropdown
this.dropdownManager.destroyDroplab();
}
if (!activeElements.length) {
if (this.isHandledAsync) {
e.stopImmediatePropagation();
this.search();
this.filteredSearchInput.blur();
this.dropdownManager.resetDropdowns();
} else {
// Prevent droplab from opening dropdown
this.dropdownManager.destroyDroplab();
}
this.search();
}
}
}
addInputContainerFocus() {
const inputContainer = this.filteredSearchInput.closest('.filtered-search-box');
addInputContainerFocus() {
const inputContainer = this.filteredSearchInput.closest('.filtered-search-box');
if (inputContainer) {
inputContainer.classList.add('focus');
}
if (inputContainer) {
inputContainer.classList.add('focus');
}
}
removeInputContainerFocus(e) {
const inputContainer = this.filteredSearchInput.closest('.filtered-search-box');
const isElementInFilteredSearch = inputContainer && inputContainer.contains(e.target);
const isElementInDynamicFilterDropdown = e.target.closest('.filter-dropdown') !== null;
const isElementInStaticFilterDropdown = e.target.closest('ul[data-dropdown]') !== null;
removeInputContainerFocus(e) {
const inputContainer = this.filteredSearchInput.closest('.filtered-search-box');
const isElementInFilteredSearch = inputContainer && inputContainer.contains(e.target);
const isElementInDynamicFilterDropdown = e.target.closest('.filter-dropdown') !== null;
const isElementInStaticFilterDropdown = e.target.closest('ul[data-dropdown]') !== null;
if (!isElementInFilteredSearch && !isElementInDynamicFilterDropdown &&
!isElementInStaticFilterDropdown && inputContainer) {
inputContainer.classList.remove('focus');
}
if (!isElementInFilteredSearch && !isElementInDynamicFilterDropdown &&
!isElementInStaticFilterDropdown && inputContainer) {
inputContainer.classList.remove('focus');
}
}
static selectToken(e) {
const button = e.target.closest('.selectable');
static selectToken(e) {
const button = e.target.closest('.selectable');
if (button) {
e.preventDefault();
e.stopPropagation();
gl.FilteredSearchVisualTokens.selectToken(button);
}
if (button) {
e.preventDefault();
e.stopPropagation();
gl.FilteredSearchVisualTokens.selectToken(button);
}
}
unselectEditTokens(e) {
const inputContainer = this.container.querySelector('.filtered-search-box');
const isElementInFilteredSearch = inputContainer && inputContainer.contains(e.target);
const isElementInFilterDropdown = e.target.closest('.filter-dropdown') !== null;
const isElementTokensContainer = e.target.classList.contains('tokens-container');
unselectEditTokens(e) {
const inputContainer = this.container.querySelector('.filtered-search-box');
const isElementInFilteredSearch = inputContainer && inputContainer.contains(e.target);
const isElementInFilterDropdown = e.target.closest('.filter-dropdown') !== null;
const isElementTokensContainer = e.target.classList.contains('tokens-container');
if ((!isElementInFilteredSearch && !isElementInFilterDropdown) || isElementTokensContainer) {
gl.FilteredSearchVisualTokens.moveInputToTheRight();
this.dropdownManager.resetDropdowns();
}
if ((!isElementInFilteredSearch && !isElementInFilterDropdown) || isElementTokensContainer) {
gl.FilteredSearchVisualTokens.moveInputToTheRight();
this.dropdownManager.resetDropdowns();
}
}
editToken(e) {
const token = e.target.closest('.js-visual-token');
editToken(e) {
const token = e.target.closest('.js-visual-token');
if (token) {
gl.FilteredSearchVisualTokens.editToken(token);
this.tokenChange();
}
if (token) {
gl.FilteredSearchVisualTokens.editToken(token);
this.tokenChange();
}
}
toggleClearSearchButton() {
const query = gl.DropdownUtils.getSearchQuery();
const hidden = 'hidden';
const hasHidden = this.clearSearchButton.classList.contains(hidden);
toggleClearSearchButton() {
const query = gl.DropdownUtils.getSearchQuery();
const hidden = 'hidden';
const hasHidden = this.clearSearchButton.classList.contains(hidden);
if (query.length === 0 && !hasHidden) {
this.clearSearchButton.classList.add(hidden);
} else if (query.length && hasHidden) {
this.clearSearchButton.classList.remove(hidden);
}
if (query.length === 0 && !hasHidden) {
this.clearSearchButton.classList.add(hidden);
} else if (query.length && hasHidden) {
this.clearSearchButton.classList.remove(hidden);
}
}
handleInputPlaceholder() {
const query = gl.DropdownUtils.getSearchQuery();
const placeholder = 'Search or filter results...';
const currentPlaceholder = this.filteredSearchInput.placeholder;
handleInputPlaceholder() {
const query = gl.DropdownUtils.getSearchQuery();
const placeholder = 'Search or filter results...';
const currentPlaceholder = this.filteredSearchInput.placeholder;
if (query.length === 0 && currentPlaceholder !== placeholder) {
this.filteredSearchInput.placeholder = placeholder;
} else if (query.length > 0 && currentPlaceholder !== '') {
this.filteredSearchInput.placeholder = '';
}
if (query.length === 0 && currentPlaceholder !== placeholder) {
this.filteredSearchInput.placeholder = placeholder;
} else if (query.length > 0 && currentPlaceholder !== '') {
this.filteredSearchInput.placeholder = '';
}
}
removeSelectedToken(e) {
// 8 = Backspace Key
// 46 = Delete Key
if (e.keyCode === 8 || e.keyCode === 46) {
gl.FilteredSearchVisualTokens.removeSelectedToken();
this.handleInputPlaceholder();
this.toggleClearSearchButton();
}
removeSelectedToken(e) {
// 8 = Backspace Key
// 46 = Delete Key
if (e.keyCode === 8 || e.keyCode === 46) {
gl.FilteredSearchVisualTokens.removeSelectedToken();
this.handleInputPlaceholder();
this.toggleClearSearchButton();
}
}
onClearSearch(e) {
e.preventDefault();
this.clearSearch();
}
onClearSearch(e) {
e.preventDefault();
this.clearSearch();
}
clearSearch() {
this.filteredSearchInput.value = '';
clearSearch() {
this.filteredSearchInput.value = '';
const removeElements = [];
const removeElements = [];
[].forEach.call(this.tokensContainer.children, (t) => {
if (t.classList.contains('js-visual-token')) {
removeElements.push(t);
}
});
[].forEach.call(this.tokensContainer.children, (t) => {
if (t.classList.contains('js-visual-token')) {
removeElements.push(t);
}
});
removeElements.forEach((el) => {
el.parentElement.removeChild(el);
});
removeElements.forEach((el) => {
el.parentElement.removeChild(el);
});
this.clearSearchButton.classList.add('hidden');
this.handleInputPlaceholder();
this.clearSearchButton.classList.add('hidden');
this.handleInputPlaceholder();
this.dropdownManager.resetDropdowns();
this.dropdownManager.resetDropdowns();
if (this.isHandledAsync) {
this.search();
}
if (this.isHandledAsync) {
this.search();
}
}
handleInputVisualToken() {
const input = this.filteredSearchInput;
const { tokens, searchToken }
= gl.FilteredSearchTokenizer.processTokens(input.value);
const { isLastVisualTokenValid }
= gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
if (isLastVisualTokenValid) {
tokens.forEach((t) => {
input.value = input.value.replace(`${t.key}:${t.symbol}${t.value}`, '');
gl.FilteredSearchVisualTokens.addFilterVisualToken(t.key, `${t.symbol}${t.value}`);
});
const fragments = searchToken.split(':');
if (fragments.length > 1) {
const inputValues = fragments[0].split(' ');
const tokenKey = inputValues.last();
if (inputValues.length > 1) {
inputValues.pop();
const searchTerms = inputValues.join(' ');
input.value = input.value.replace(searchTerms, '');
gl.FilteredSearchVisualTokens.addSearchVisualToken(searchTerms);
}
handleInputVisualToken() {
const input = this.filteredSearchInput;
const { tokens, searchToken }
= gl.FilteredSearchTokenizer.processTokens(input.value);
const { isLastVisualTokenValid }
= gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
if (isLastVisualTokenValid) {
tokens.forEach((t) => {
input.value = input.value.replace(`${t.key}:${t.symbol}${t.value}`, '');
gl.FilteredSearchVisualTokens.addFilterVisualToken(t.key, `${t.symbol}${t.value}`);
});
gl.FilteredSearchVisualTokens.addFilterVisualToken(tokenKey);
input.value = input.value.replace(`${tokenKey}:`, '');
}
} else {
// Keep listening to token until we determine that the user is done typing the token value
const valueCompletedRegex = /([~%@]{0,1}".+")|([~%@]{0,1}'.+')|^((?![~%@]')(?![~%@]")(?!')(?!")).*/g;
const fragments = searchToken.split(':');
if (fragments.length > 1) {
const inputValues = fragments[0].split(' ');
const tokenKey = inputValues.last();
if (searchToken.match(valueCompletedRegex) && input.value[input.value.length - 1] === ' ') {
gl.FilteredSearchVisualTokens.addFilterVisualToken(searchToken);
if (inputValues.length > 1) {
inputValues.pop();
const searchTerms = inputValues.join(' ');
// Trim the last space as seen in the if statement above
input.value = input.value.replace(searchToken, '').trim();
input.value = input.value.replace(searchTerms, '');
gl.FilteredSearchVisualTokens.addSearchVisualToken(searchTerms);
}
gl.FilteredSearchVisualTokens.addFilterVisualToken(tokenKey);
input.value = input.value.replace(`${tokenKey}:`, '');
}
}
} else {
// Keep listening to token until we determine that the user is done typing the token value
const valueCompletedRegex = /([~%@]{0,1}".+")|([~%@]{0,1}'.+')|^((?![~%@]')(?![~%@]")(?!')(?!")).*/g;
handleFormSubmit(e) {
e.preventDefault();
this.search();
}
if (searchToken.match(valueCompletedRegex) && input.value[input.value.length - 1] === ' ') {
gl.FilteredSearchVisualTokens.addFilterVisualToken(searchToken);
saveCurrentSearchQuery() {
// Don't save before we have fetched the already saved searches
this.fetchingRecentSearchesPromise.then(() => {
const searchQuery = gl.DropdownUtils.getSearchQuery();
if (searchQuery.length > 0) {
const resultantSearches = this.recentSearchesStore.addRecentSearch(searchQuery);
this.recentSearchesService.save(resultantSearches);
}
});
// Trim the last space as seen in the if statement above
input.value = input.value.replace(searchToken, '').trim();
}
}
}
loadSearchParamsFromURL() {
const params = gl.utils.getUrlParamsArray();
const usernameParams = this.getUsernameParams();
let hasFilteredSearch = false;
handleFormSubmit(e) {
e.preventDefault();
this.search();
}
params.forEach((p) => {
const split = p.split('=');
const keyParam = decodeURIComponent(split[0]);
const value = split[1];
saveCurrentSearchQuery() {
// Don't save before we have fetched the already saved searches
this.fetchingRecentSearchesPromise.then(() => {
const searchQuery = gl.DropdownUtils.getSearchQuery();
if (searchQuery.length > 0) {
const resultantSearches = this.recentSearchesStore.addRecentSearch(searchQuery);
this.recentSearchesService.save(resultantSearches);
}
});
}
// Check if it matches edge conditions listed in this.filteredSearchTokenKeys
const condition = this.filteredSearchTokenKeys.searchByConditionUrl(p);
loadSearchParamsFromURL() {
const params = gl.utils.getUrlParamsArray();
const usernameParams = this.getUsernameParams();
let hasFilteredSearch = false;
if (condition) {
hasFilteredSearch = true;
gl.FilteredSearchVisualTokens.addFilterVisualToken(condition.tokenKey, condition.value);
} else {
// Sanitize value since URL converts spaces into +
// Replace before decode so that we know what was originally + versus the encoded +
const sanitizedValue = value ? decodeURIComponent(value.replace(/\+/g, ' ')) : value;
const match = this.filteredSearchTokenKeys.searchByKeyParam(keyParam);
if (match) {
const indexOf = keyParam.indexOf('_');
const sanitizedKey = indexOf !== -1 ? keyParam.slice(0, keyParam.indexOf('_')) : keyParam;
const symbol = match.symbol;
let quotationsToUse = '';
if (sanitizedValue.indexOf(' ') !== -1) {
// Prefer ", but use ' if required
quotationsToUse = sanitizedValue.indexOf('"') === -1 ? '"' : '\'';
}
params.forEach((p) => {
const split = p.split('=');
const keyParam = decodeURIComponent(split[0]);
const value = split[1];
// Check if it matches edge conditions listed in this.filteredSearchTokenKeys
const condition = this.filteredSearchTokenKeys.searchByConditionUrl(p);
if (condition) {
hasFilteredSearch = true;
gl.FilteredSearchVisualTokens.addFilterVisualToken(condition.tokenKey, condition.value);
} else {
// Sanitize value since URL converts spaces into +
// Replace before decode so that we know what was originally + versus the encoded +
const sanitizedValue = value ? decodeURIComponent(value.replace(/\+/g, ' ')) : value;
const match = this.filteredSearchTokenKeys.searchByKeyParam(keyParam);
if (match) {
const indexOf = keyParam.indexOf('_');
const sanitizedKey = indexOf !== -1 ? keyParam.slice(0, keyParam.indexOf('_')) : keyParam;
const symbol = match.symbol;
let quotationsToUse = '';
if (sanitizedValue.indexOf(' ') !== -1) {
// Prefer ", but use ' if required
quotationsToUse = sanitizedValue.indexOf('"') === -1 ? '"' : '\'';
}
hasFilteredSearch = true;
gl.FilteredSearchVisualTokens.addFilterVisualToken(sanitizedKey, `${symbol}${quotationsToUse}${sanitizedValue}${quotationsToUse}`);
} else if (!match && keyParam === 'assignee_id') {
const id = parseInt(value, 10);
if (usernameParams[id]) {
hasFilteredSearch = true;
gl.FilteredSearchVisualTokens.addFilterVisualToken(sanitizedKey, `${symbol}${quotationsToUse}${sanitizedValue}${quotationsToUse}`);
} else if (!match && keyParam === 'assignee_id') {
const id = parseInt(value, 10);
if (usernameParams[id]) {
hasFilteredSearch = true;
gl.FilteredSearchVisualTokens.addFilterVisualToken('assignee', `@${usernameParams[id]}`);
}
} else if (!match && keyParam === 'author_id') {
const id = parseInt(value, 10);
if (usernameParams[id]) {
hasFilteredSearch = true;
gl.FilteredSearchVisualTokens.addFilterVisualToken('author', `@${usernameParams[id]}`);
}
} else if (!match && keyParam === 'search') {
gl.FilteredSearchVisualTokens.addFilterVisualToken('assignee', `@${usernameParams[id]}`);
}
} else if (!match && keyParam === 'author_id') {
const id = parseInt(value, 10);
if (usernameParams[id]) {
hasFilteredSearch = true;
this.filteredSearchInput.value = sanitizedValue;
gl.FilteredSearchVisualTokens.addFilterVisualToken('author', `@${usernameParams[id]}`);
}
} else if (!match && keyParam === 'search') {
hasFilteredSearch = true;
this.filteredSearchInput.value = sanitizedValue;
}
});
}
});
this.saveCurrentSearchQuery();
this.saveCurrentSearchQuery();
if (hasFilteredSearch) {
this.clearSearchButton.classList.remove('hidden');
this.handleInputPlaceholder();
}
if (hasFilteredSearch) {
this.clearSearchButton.classList.remove('hidden');
this.handleInputPlaceholder();
}
}
search() {
const paths = [];
const searchQuery = gl.DropdownUtils.getSearchQuery();
this.saveCurrentSearchQuery();
search() {
const paths = [];
const searchQuery = gl.DropdownUtils.getSearchQuery();
const { tokens, searchToken }
= this.tokenizer.processTokens(searchQuery);
const currentState = gl.utils.getParameterByName('state') || 'opened';
paths.push(`state=${currentState}`);
this.saveCurrentSearchQuery();
tokens.forEach((token) => {
const condition = this.filteredSearchTokenKeys
.searchByConditionKeyValue(token.key, token.value.toLowerCase());
const { param } = this.filteredSearchTokenKeys.searchByKey(token.key) || {};
const keyParam = param ? `${token.key}_${param}` : token.key;
let tokenPath = '';
const { tokens, searchToken }
= this.tokenizer.processTokens(searchQuery);
const currentState = gl.utils.getParameterByName('state') || 'opened';
paths.push(`state=${currentState}`);
if (condition) {
tokenPath = condition.url;
} else {
let tokenValue = token.value;
tokens.forEach((token) => {
const condition = this.filteredSearchTokenKeys
.searchByConditionKeyValue(token.key, token.value.toLowerCase());
const { param } = this.filteredSearchTokenKeys.searchByKey(token.key) || {};
const keyParam = param ? `${token.key}_${param}` : token.key;
let tokenPath = '';
if ((tokenValue[0] === '\'' && tokenValue[tokenValue.length - 1] === '\'') ||
(tokenValue[0] === '"' && tokenValue[tokenValue.length - 1] === '"')) {
tokenValue = tokenValue.slice(1, tokenValue.length - 1);
}
if (condition) {
tokenPath = condition.url;
} else {
let tokenValue = token.value;
tokenPath = `${keyParam}=${encodeURIComponent(tokenValue)}`;
if ((tokenValue[0] === '\'' && tokenValue[tokenValue.length - 1] === '\'') ||
(tokenValue[0] === '"' && tokenValue[tokenValue.length - 1] === '"')) {
tokenValue = tokenValue.slice(1, tokenValue.length - 1);
}
paths.push(tokenPath);
});
if (searchToken) {
const sanitized = searchToken.split(' ').map(t => encodeURIComponent(t)).join('+');
paths.push(`search=${sanitized}`);
tokenPath = `${keyParam}=${encodeURIComponent(tokenValue)}`;
}
const parameterizedUrl = `?scope=all&utf8=%E2%9C%93&${paths.join('&')}`;
paths.push(tokenPath);
});
if (this.updateObject) {
this.updateObject(parameterizedUrl);
} else {
gl.utils.visitUrl(parameterizedUrl);
}
if (searchToken) {
const sanitized = searchToken.split(' ').map(t => encodeURIComponent(t)).join('+');
paths.push(`search=${sanitized}`);
}
getUsernameParams() {
const usernamesById = {};
try {
const attribute = this.filteredSearchInput.getAttribute('data-username-params');
JSON.parse(attribute).forEach((user) => {
usernamesById[user.id] = user.username;
});
} catch (e) {
// do nothing
}
return usernamesById;
const parameterizedUrl = `?scope=all&utf8=%E2%9C%93&${paths.join('&')}`;
if (this.updateObject) {
this.updateObject(parameterizedUrl);
} else {
gl.utils.visitUrl(parameterizedUrl);
}
}
tokenChange() {
const dropdown = this.dropdownManager.mapping[this.dropdownManager.currentDropdown];
getUsernameParams() {
const usernamesById = {};
try {
const attribute = this.filteredSearchInput.getAttribute('data-username-params');
JSON.parse(attribute).forEach((user) => {
usernamesById[user.id] = user.username;
});
} catch (e) {
// do nothing
}
return usernamesById;
}
if (dropdown) {
const currentDropdownRef = dropdown.reference;
tokenChange() {
const dropdown = this.dropdownManager.mapping[this.dropdownManager.currentDropdown];
this.setDropdownWrapper();
currentDropdownRef.dispatchInputEvent();
}
}
if (dropdown) {
const currentDropdownRef = dropdown.reference;
onrecentSearchesItemSelected(text) {
this.clearSearch();
this.filteredSearchInput.value = text;
this.filteredSearchInput.dispatchEvent(new CustomEvent('input'));
this.search();
this.setDropdownWrapper();
currentDropdownRef.dispatchInputEvent();
}
}
window.gl = window.gl || {};
gl.FilteredSearchManager = FilteredSearchManager;
})();
onrecentSearchesItemSelected(text) {
this.clearSearch();
this.filteredSearchInput.value = text;
this.filteredSearchInput.dispatchEvent(new CustomEvent('input'));
this.search();
}
}
window.gl = window.gl || {};
gl.FilteredSearchManager = FilteredSearchManager;
(() => {
const tokenKeys = [{
key: 'author',
type: 'string',
param: 'username',
symbol: '@',
}, {
key: 'assignee',
type: 'string',
param: 'username',
symbol: '@',
}, {
key: 'milestone',
type: 'string',
param: 'title',
symbol: '%',
}, {
key: 'label',
type: 'array',
param: 'name[]',
symbol: '~',
}];
const tokenKeys = [{
key: 'author',
type: 'string',
param: 'username',
symbol: '@',
}, {
key: 'assignee',
type: 'string',
param: 'username',
symbol: '@',
}, {
key: 'milestone',
type: 'string',
param: 'title',
symbol: '%',
}, {
key: 'label',
type: 'array',
param: 'name[]',
symbol: '~',
}];
const alternativeTokenKeys = [{
key: 'label',
type: 'string',
param: 'name',
symbol: '~',
}];
const alternativeTokenKeys = [{
key: 'label',
type: 'string',
param: 'name',
symbol: '~',
}];
const tokenKeysWithAlternative = tokenKeys.concat(alternativeTokenKeys);
const tokenKeysWithAlternative = tokenKeys.concat(alternativeTokenKeys);
const conditions = [{
url: 'assignee_id=0',
tokenKey: 'assignee',
value: 'none',
}, {
url: 'milestone_title=No+Milestone',
tokenKey: 'milestone',
value: 'none',
}, {
url: 'milestone_title=%23upcoming',
tokenKey: 'milestone',
value: 'upcoming',
}, {
url: 'milestone_title=%23started',
tokenKey: 'milestone',
value: 'started',
}, {
url: 'label_name[]=No+Label',
tokenKey: 'label',
value: 'none',
}];
const conditions = [{
url: 'assignee_id=0',
tokenKey: 'assignee',
value: 'none',
}, {
url: 'milestone_title=No+Milestone',
tokenKey: 'milestone',
value: 'none',
}, {
url: 'milestone_title=%23upcoming',
tokenKey: 'milestone',
value: 'upcoming',
}, {
url: 'milestone_title=%23started',
tokenKey: 'milestone',
value: 'started',
}, {
url: 'label_name[]=No+Label',
tokenKey: 'label',
value: 'none',
}];
class FilteredSearchTokenKeys {
static get() {
return tokenKeys;
}
class FilteredSearchTokenKeys {
static get() {
return tokenKeys;
}
static getAlternatives() {
return alternativeTokenKeys;
}
static getAlternatives() {
return alternativeTokenKeys;
}
static getConditions() {
return conditions;
}
static getConditions() {
return conditions;
}
static searchByKey(key) {
return tokenKeys.find(tokenKey => tokenKey.key === key) || null;
}
static searchByKey(key) {
return tokenKeys.find(tokenKey => tokenKey.key === key) || null;
}
static searchBySymbol(symbol) {
return tokenKeys.find(tokenKey => tokenKey.symbol === symbol) || null;
}
static searchBySymbol(symbol) {
return tokenKeys.find(tokenKey => tokenKey.symbol === symbol) || null;
}
static searchByKeyParam(keyParam) {
return tokenKeysWithAlternative.find((tokenKey) => {
let tokenKeyParam = tokenKey.key;
static searchByKeyParam(keyParam) {
return tokenKeysWithAlternative.find((tokenKey) => {
let tokenKeyParam = tokenKey.key;
if (tokenKey.param) {
tokenKeyParam += `_${tokenKey.param}`;
}
if (tokenKey.param) {
tokenKeyParam += `_${tokenKey.param}`;
}
return keyParam === tokenKeyParam;
}) || null;
}
return keyParam === tokenKeyParam;
}) || null;
}
static searchByConditionUrl(url) {
return conditions.find(condition => condition.url === url) || null;
}
static searchByConditionUrl(url) {
return conditions.find(condition => condition.url === url) || null;
}
static searchByConditionKeyValue(key, value) {
return conditions
.find(condition => condition.tokenKey === key && condition.value === value) || null;
}
static searchByConditionKeyValue(key, value) {
return conditions
.find(condition => condition.tokenKey === key && condition.value === value) || null;
}
}
window.gl = window.gl || {};
gl.FilteredSearchTokenKeys = FilteredSearchTokenKeys;
})();
window.gl = window.gl || {};
gl.FilteredSearchTokenKeys = FilteredSearchTokenKeys;
require('./filtered_search_token_keys');
(() => {
class FilteredSearchTokenizer {
static processTokens(input) {
const allowedKeys = gl.FilteredSearchTokenKeys.get().map(i => i.key);
// Regex extracts `(token):(symbol)(value)`
// Values that start with a double quote must end in a double quote (same for single)
const tokenRegex = new RegExp(`(${allowedKeys.join('|')}):([~%@]?)(?:('[^']*'{0,1})|("[^"]*"{0,1})|(\\S+))`, 'g');
const tokens = [];
const tokenIndexes = []; // stores key+value for simple search
let lastToken = null;
const searchToken = input.replace(tokenRegex, (match, key, symbol, v1, v2, v3) => {
let tokenValue = v1 || v2 || v3;
let tokenSymbol = symbol;
let tokenIndex = '';
if (tokenValue === '~' || tokenValue === '%' || tokenValue === '@') {
tokenSymbol = tokenValue;
tokenValue = '';
}
tokenIndex = `${key}:${tokenValue}`;
// Prevent adding duplicates
if (tokenIndexes.indexOf(tokenIndex) === -1) {
tokenIndexes.push(tokenIndex);
tokens.push({
key,
value: tokenValue || '',
symbol: tokenSymbol || '',
});
}
return '';
}).replace(/\s{2,}/g, ' ').trim() || '';
if (tokens.length > 0) {
const last = tokens[tokens.length - 1];
const lastString = `${last.key}:${last.symbol}${last.value}`;
lastToken = input.lastIndexOf(lastString) ===
input.length - lastString.length ? last : searchToken;
} else {
lastToken = searchToken;
class FilteredSearchTokenizer {
static processTokens(input) {
const allowedKeys = gl.FilteredSearchTokenKeys.get().map(i => i.key);
// Regex extracts `(token):(symbol)(value)`
// Values that start with a double quote must end in a double quote (same for single)
const tokenRegex = new RegExp(`(${allowedKeys.join('|')}):([~%@]?)(?:('[^']*'{0,1})|("[^"]*"{0,1})|(\\S+))`, 'g');
const tokens = [];
const tokenIndexes = []; // stores key+value for simple search
let lastToken = null;
const searchToken = input.replace(tokenRegex, (match, key, symbol, v1, v2, v3) => {
let tokenValue = v1 || v2 || v3;
let tokenSymbol = symbol;
let tokenIndex = '';
if (tokenValue === '~' || tokenValue === '%' || tokenValue === '@') {
tokenSymbol = tokenValue;
tokenValue = '';
}
return {
tokens,
lastToken,
searchToken,
};
tokenIndex = `${key}:${tokenValue}`;
// Prevent adding duplicates
if (tokenIndexes.indexOf(tokenIndex) === -1) {
tokenIndexes.push(tokenIndex);
tokens.push({
key,
value: tokenValue || '',
symbol: tokenSymbol || '',
});
}
return '';
}).replace(/\s{2,}/g, ' ').trim() || '';
if (tokens.length > 0) {
const last = tokens[tokens.length - 1];
const lastString = `${last.key}:${last.symbol}${last.value}`;
lastToken = input.lastIndexOf(lastString) ===
input.length - lastString.length ? last : searchToken;
} else {
lastToken = searchToken;
}
return {
tokens,
lastToken,
searchToken,
};
}
}
window.gl = window.gl || {};
gl.FilteredSearchTokenizer = FilteredSearchTokenizer;
})();
window.gl = window.gl || {};
gl.FilteredSearchTokenizer = FilteredSearchTokenizer;
......@@ -3,69 +3,67 @@ require('~/filtered_search/filtered_search_tokenizer');
require('~/filtered_search/filtered_search_dropdown');
require('~/filtered_search/dropdown_user');
(() => {
describe('Dropdown User', () => {
describe('getSearchInput', () => {
let dropdownUser;
describe('Dropdown User', () => {
describe('getSearchInput', () => {
let dropdownUser;
beforeEach(() => {
spyOn(gl.DropdownUser.prototype, 'bindEvents').and.callFake(() => {});
spyOn(gl.DropdownUser.prototype, 'getProjectId').and.callFake(() => {});
spyOn(gl.DropdownUtils, 'getSearchInput').and.callFake(() => {});
beforeEach(() => {
spyOn(gl.DropdownUser.prototype, 'bindEvents').and.callFake(() => {});
spyOn(gl.DropdownUser.prototype, 'getProjectId').and.callFake(() => {});
spyOn(gl.DropdownUtils, 'getSearchInput').and.callFake(() => {});
dropdownUser = new gl.DropdownUser();
});
it('should not return the double quote found in value', () => {
spyOn(gl.FilteredSearchTokenizer, 'processTokens').and.returnValue({
lastToken: '"johnny appleseed',
});
dropdownUser = new gl.DropdownUser();
});
expect(dropdownUser.getSearchInput()).toBe('johnny appleseed');
it('should not return the double quote found in value', () => {
spyOn(gl.FilteredSearchTokenizer, 'processTokens').and.returnValue({
lastToken: '"johnny appleseed',
});
it('should not return the single quote found in value', () => {
spyOn(gl.FilteredSearchTokenizer, 'processTokens').and.returnValue({
lastToken: '\'larry boy',
});
expect(dropdownUser.getSearchInput()).toBe('johnny appleseed');
});
expect(dropdownUser.getSearchInput()).toBe('larry boy');
it('should not return the single quote found in value', () => {
spyOn(gl.FilteredSearchTokenizer, 'processTokens').and.returnValue({
lastToken: '\'larry boy',
});
expect(dropdownUser.getSearchInput()).toBe('larry boy');
});
});
describe('config AjaxFilter\'s endpoint', () => {
beforeEach(() => {
spyOn(gl.DropdownUser.prototype, 'bindEvents').and.callFake(() => {});
spyOn(gl.DropdownUser.prototype, 'getProjectId').and.callFake(() => {});
});
describe('config AjaxFilter\'s endpoint', () => {
beforeEach(() => {
spyOn(gl.DropdownUser.prototype, 'bindEvents').and.callFake(() => {});
spyOn(gl.DropdownUser.prototype, 'getProjectId').and.callFake(() => {});
});
it('should return endpoint', () => {
window.gon = {
relative_url_root: '',
};
const dropdown = new gl.DropdownUser();
it('should return endpoint', () => {
window.gon = {
relative_url_root: '',
};
const dropdown = new gl.DropdownUser();
expect(dropdown.config.AjaxFilter.endpoint).toBe('/autocomplete/users.json');
});
expect(dropdown.config.AjaxFilter.endpoint).toBe('/autocomplete/users.json');
});
it('should return endpoint when relative_url_root is undefined', () => {
const dropdown = new gl.DropdownUser();
it('should return endpoint when relative_url_root is undefined', () => {
const dropdown = new gl.DropdownUser();
expect(dropdown.config.AjaxFilter.endpoint).toBe('/autocomplete/users.json');
});
expect(dropdown.config.AjaxFilter.endpoint).toBe('/autocomplete/users.json');
});
it('should return endpoint with relative url when available', () => {
window.gon = {
relative_url_root: '/gitlab_directory',
};
const dropdown = new gl.DropdownUser();
it('should return endpoint with relative url when available', () => {
window.gon = {
relative_url_root: '/gitlab_directory',
};
const dropdown = new gl.DropdownUser();
expect(dropdown.config.AjaxFilter.endpoint).toBe('/gitlab_directory/autocomplete/users.json');
});
expect(dropdown.config.AjaxFilter.endpoint).toBe('/gitlab_directory/autocomplete/users.json');
});
afterEach(() => {
window.gon = {};
});
afterEach(() => {
window.gon = {};
});
});
})();
});
......@@ -3,308 +3,306 @@ require('~/filtered_search/dropdown_utils');
require('~/filtered_search/filtered_search_tokenizer');
require('~/filtered_search/filtered_search_dropdown_manager');
(() => {
describe('Dropdown Utils', () => {
describe('getEscapedText', () => {
it('should return same word when it has no space', () => {
const escaped = gl.DropdownUtils.getEscapedText('textWithoutSpace');
expect(escaped).toBe('textWithoutSpace');
});
describe('Dropdown Utils', () => {
describe('getEscapedText', () => {
it('should return same word when it has no space', () => {
const escaped = gl.DropdownUtils.getEscapedText('textWithoutSpace');
expect(escaped).toBe('textWithoutSpace');
});
it('should escape with double quotes', () => {
let escaped = gl.DropdownUtils.getEscapedText('text with space');
expect(escaped).toBe('"text with space"');
it('should escape with double quotes', () => {
let escaped = gl.DropdownUtils.getEscapedText('text with space');
expect(escaped).toBe('"text with space"');
escaped = gl.DropdownUtils.getEscapedText('won\'t fix');
expect(escaped).toBe('"won\'t fix"');
});
escaped = gl.DropdownUtils.getEscapedText('won\'t fix');
expect(escaped).toBe('"won\'t fix"');
});
it('should escape with single quotes', () => {
const escaped = gl.DropdownUtils.getEscapedText('won"t fix');
expect(escaped).toBe('\'won"t fix\'');
});
it('should escape with single quotes', () => {
const escaped = gl.DropdownUtils.getEscapedText('won"t fix');
expect(escaped).toBe('\'won"t fix\'');
});
it('should escape with single quotes by default', () => {
const escaped = gl.DropdownUtils.getEscapedText('won"t\' fix');
expect(escaped).toBe('\'won"t\' fix\'');
});
it('should escape with single quotes by default', () => {
const escaped = gl.DropdownUtils.getEscapedText('won"t\' fix');
expect(escaped).toBe('\'won"t\' fix\'');
});
});
describe('filterWithSymbol', () => {
let input;
const item = {
title: '@root',
};
describe('filterWithSymbol', () => {
let input;
const item = {
title: '@root',
};
beforeEach(() => {
setFixtures(`
<input type="text" id="test" />
`);
beforeEach(() => {
setFixtures(`
<input type="text" id="test" />
`);
input = document.getElementById('test');
});
input = document.getElementById('test');
});
it('should filter without symbol', () => {
input.value = 'roo';
it('should filter without symbol', () => {
input.value = 'roo';
const updatedItem = gl.DropdownUtils.filterWithSymbol('@', input, item);
expect(updatedItem.droplab_hidden).toBe(false);
});
const updatedItem = gl.DropdownUtils.filterWithSymbol('@', input, item);
expect(updatedItem.droplab_hidden).toBe(false);
});
it('should filter with symbol', () => {
input.value = '@roo';
it('should filter with symbol', () => {
input.value = '@roo';
const updatedItem = gl.DropdownUtils.filterWithSymbol('@', input, item);
expect(updatedItem.droplab_hidden).toBe(false);
});
const updatedItem = gl.DropdownUtils.filterWithSymbol('@', input, item);
expect(updatedItem.droplab_hidden).toBe(false);
});
describe('filters multiple word title', () => {
const multipleWordItem = {
title: 'Community Contributions',
};
describe('filters multiple word title', () => {
const multipleWordItem = {
title: 'Community Contributions',
};
it('should filter with double quote', () => {
input.value = '"';
it('should filter with double quote', () => {
input.value = '"';
const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem);
expect(updatedItem.droplab_hidden).toBe(false);
});
const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem);
expect(updatedItem.droplab_hidden).toBe(false);
});
it('should filter with double quote and symbol', () => {
input.value = '~"';
it('should filter with double quote and symbol', () => {
input.value = '~"';
const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem);
expect(updatedItem.droplab_hidden).toBe(false);
});
const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem);
expect(updatedItem.droplab_hidden).toBe(false);
});
it('should filter with double quote and multiple words', () => {
input.value = '"community con';
it('should filter with double quote and multiple words', () => {
input.value = '"community con';
const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem);
expect(updatedItem.droplab_hidden).toBe(false);
});
const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem);
expect(updatedItem.droplab_hidden).toBe(false);
});
it('should filter with double quote, symbol and multiple words', () => {
input.value = '~"community con';
it('should filter with double quote, symbol and multiple words', () => {
input.value = '~"community con';
const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem);
expect(updatedItem.droplab_hidden).toBe(false);
});
const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem);
expect(updatedItem.droplab_hidden).toBe(false);
});
it('should filter with single quote', () => {
input.value = '\'';
it('should filter with single quote', () => {
input.value = '\'';
const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem);
expect(updatedItem.droplab_hidden).toBe(false);
});
const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem);
expect(updatedItem.droplab_hidden).toBe(false);
});
it('should filter with single quote and symbol', () => {
input.value = '~\'';
it('should filter with single quote and symbol', () => {
input.value = '~\'';
const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem);
expect(updatedItem.droplab_hidden).toBe(false);
});
const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem);
expect(updatedItem.droplab_hidden).toBe(false);
});
it('should filter with single quote and multiple words', () => {
input.value = '\'community con';
it('should filter with single quote and multiple words', () => {
input.value = '\'community con';
const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem);
expect(updatedItem.droplab_hidden).toBe(false);
});
const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem);
expect(updatedItem.droplab_hidden).toBe(false);
});
it('should filter with single quote, symbol and multiple words', () => {
input.value = '~\'community con';
it('should filter with single quote, symbol and multiple words', () => {
input.value = '~\'community con';
const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem);
expect(updatedItem.droplab_hidden).toBe(false);
});
const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem);
expect(updatedItem.droplab_hidden).toBe(false);
});
});
});
describe('filterHint', () => {
let input;
beforeEach(() => {
setFixtures(`
<ul class="tokens-container">
<li class="input-token">
<input class="filtered-search" type="text" id="test" />
</li>
</ul>
`);
input = document.getElementById('test');
});
describe('filterHint', () => {
let input;
it('should filter', () => {
input.value = 'l';
let updatedItem = gl.DropdownUtils.filterHint(input, {
hint: 'label',
});
expect(updatedItem.droplab_hidden).toBe(false);
beforeEach(() => {
setFixtures(`
<ul class="tokens-container">
<li class="input-token">
<input class="filtered-search" type="text" id="test" />
</li>
</ul>
`);
input.value = 'o';
updatedItem = gl.DropdownUtils.filterHint(input, {
hint: 'label',
});
expect(updatedItem.droplab_hidden).toBe(true);
});
input = document.getElementById('test');
});
it('should return droplab_hidden false when item has no hint', () => {
const updatedItem = gl.DropdownUtils.filterHint(input, {}, '');
expect(updatedItem.droplab_hidden).toBe(false);
it('should filter', () => {
input.value = 'l';
let updatedItem = gl.DropdownUtils.filterHint(input, {
hint: 'label',
});
expect(updatedItem.droplab_hidden).toBe(false);
it('should allow multiple if item.type is array', () => {
input.value = 'label:~first la';
const updatedItem = gl.DropdownUtils.filterHint(input, {
hint: 'label',
type: 'array',
});
expect(updatedItem.droplab_hidden).toBe(false);
input.value = 'o';
updatedItem = gl.DropdownUtils.filterHint(input, {
hint: 'label',
});
expect(updatedItem.droplab_hidden).toBe(true);
});
it('should prevent multiple if item.type is not array', () => {
input.value = 'milestone:~first mile';
let updatedItem = gl.DropdownUtils.filterHint(input, {
hint: 'milestone',
});
expect(updatedItem.droplab_hidden).toBe(true);
it('should return droplab_hidden false when item has no hint', () => {
const updatedItem = gl.DropdownUtils.filterHint(input, {}, '');
expect(updatedItem.droplab_hidden).toBe(false);
});
updatedItem = gl.DropdownUtils.filterHint(input, {
hint: 'milestone',
type: 'string',
});
expect(updatedItem.droplab_hidden).toBe(true);
it('should allow multiple if item.type is array', () => {
input.value = 'label:~first la';
const updatedItem = gl.DropdownUtils.filterHint(input, {
hint: 'label',
type: 'array',
});
expect(updatedItem.droplab_hidden).toBe(false);
});
describe('setDataValueIfSelected', () => {
beforeEach(() => {
spyOn(gl.FilteredSearchDropdownManager, 'addWordToInput')
.and.callFake(() => {});
it('should prevent multiple if item.type is not array', () => {
input.value = 'milestone:~first mile';
let updatedItem = gl.DropdownUtils.filterHint(input, {
hint: 'milestone',
});
expect(updatedItem.droplab_hidden).toBe(true);
it('calls addWordToInput when dataValue exists', () => {
const selected = {
getAttribute: () => 'value',
};
gl.DropdownUtils.setDataValueIfSelected(null, selected);
expect(gl.FilteredSearchDropdownManager.addWordToInput.calls.count()).toEqual(1);
updatedItem = gl.DropdownUtils.filterHint(input, {
hint: 'milestone',
type: 'string',
});
expect(updatedItem.droplab_hidden).toBe(true);
});
});
it('returns true when dataValue exists', () => {
const selected = {
getAttribute: () => 'value',
};
describe('setDataValueIfSelected', () => {
beforeEach(() => {
spyOn(gl.FilteredSearchDropdownManager, 'addWordToInput')
.and.callFake(() => {});
});
const result = gl.DropdownUtils.setDataValueIfSelected(null, selected);
expect(result).toBe(true);
});
it('calls addWordToInput when dataValue exists', () => {
const selected = {
getAttribute: () => 'value',
};
it('returns false when dataValue does not exist', () => {
const selected = {
getAttribute: () => null,
};
gl.DropdownUtils.setDataValueIfSelected(null, selected);
expect(gl.FilteredSearchDropdownManager.addWordToInput.calls.count()).toEqual(1);
});
const result = gl.DropdownUtils.setDataValueIfSelected(null, selected);
expect(result).toBe(false);
});
it('returns true when dataValue exists', () => {
const selected = {
getAttribute: () => 'value',
};
const result = gl.DropdownUtils.setDataValueIfSelected(null, selected);
expect(result).toBe(true);
});
describe('getInputSelectionPosition', () => {
describe('word with trailing spaces', () => {
const value = 'label:none ';
it('returns false when dataValue does not exist', () => {
const selected = {
getAttribute: () => null,
};
const result = gl.DropdownUtils.setDataValueIfSelected(null, selected);
expect(result).toBe(false);
});
});
it('should return selectionStart when cursor is at the trailing space', () => {
const { left, right } = gl.DropdownUtils.getInputSelectionPosition({
selectionStart: 11,
value,
});
describe('getInputSelectionPosition', () => {
describe('word with trailing spaces', () => {
const value = 'label:none ';
expect(left).toBe(11);
expect(right).toBe(11);
it('should return selectionStart when cursor is at the trailing space', () => {
const { left, right } = gl.DropdownUtils.getInputSelectionPosition({
selectionStart: 11,
value,
});
it('should return input when cursor is at the start of input', () => {
const { left, right } = gl.DropdownUtils.getInputSelectionPosition({
selectionStart: 0,
value,
});
expect(left).toBe(11);
expect(right).toBe(11);
});
expect(left).toBe(0);
expect(right).toBe(10);
it('should return input when cursor is at the start of input', () => {
const { left, right } = gl.DropdownUtils.getInputSelectionPosition({
selectionStart: 0,
value,
});
it('should return input when cursor is at the middle of input', () => {
const { left, right } = gl.DropdownUtils.getInputSelectionPosition({
selectionStart: 7,
value,
});
expect(left).toBe(0);
expect(right).toBe(10);
});
expect(left).toBe(0);
expect(right).toBe(10);
it('should return input when cursor is at the middle of input', () => {
const { left, right } = gl.DropdownUtils.getInputSelectionPosition({
selectionStart: 7,
value,
});
it('should return input when cursor is at the end of input', () => {
const { left, right } = gl.DropdownUtils.getInputSelectionPosition({
selectionStart: 10,
value,
});
expect(left).toBe(0);
expect(right).toBe(10);
});
expect(left).toBe(0);
expect(right).toBe(10);
it('should return input when cursor is at the end of input', () => {
const { left, right } = gl.DropdownUtils.getInputSelectionPosition({
selectionStart: 10,
value,
});
});
describe('multiple words', () => {
const value = 'label:~"Community Contribution"';
expect(left).toBe(0);
expect(right).toBe(10);
});
});
it('should return input when cursor is after the first word', () => {
const { left, right } = gl.DropdownUtils.getInputSelectionPosition({
selectionStart: 17,
value,
});
describe('multiple words', () => {
const value = 'label:~"Community Contribution"';
expect(left).toBe(0);
expect(right).toBe(31);
it('should return input when cursor is after the first word', () => {
const { left, right } = gl.DropdownUtils.getInputSelectionPosition({
selectionStart: 17,
value,
});
it('should return input when cursor is before the second word', () => {
const { left, right } = gl.DropdownUtils.getInputSelectionPosition({
selectionStart: 18,
value,
});
expect(left).toBe(0);
expect(right).toBe(31);
});
expect(left).toBe(0);
expect(right).toBe(31);
it('should return input when cursor is before the second word', () => {
const { left, right } = gl.DropdownUtils.getInputSelectionPosition({
selectionStart: 18,
value,
});
});
describe('incomplete multiple words', () => {
const value = 'label:~"Community Contribution';
expect(left).toBe(0);
expect(right).toBe(31);
});
});
it('should return entire input when cursor is at the start of input', () => {
const { left, right } = gl.DropdownUtils.getInputSelectionPosition({
selectionStart: 0,
value,
});
describe('incomplete multiple words', () => {
const value = 'label:~"Community Contribution';
expect(left).toBe(0);
expect(right).toBe(30);
it('should return entire input when cursor is at the start of input', () => {
const { left, right } = gl.DropdownUtils.getInputSelectionPosition({
selectionStart: 0,
value,
});
it('should return entire input when cursor is at the end of input', () => {
const { left, right } = gl.DropdownUtils.getInputSelectionPosition({
selectionStart: 30,
value,
});
expect(left).toBe(0);
expect(right).toBe(30);
});
expect(left).toBe(0);
expect(right).toBe(30);
it('should return entire input when cursor is at the end of input', () => {
const { left, right } = gl.DropdownUtils.getInputSelectionPosition({
selectionStart: 30,
value,
});
expect(left).toBe(0);
expect(right).toBe(30);
});
});
});
})();
});
......@@ -3,99 +3,97 @@ require('~/filtered_search/filtered_search_visual_tokens');
require('~/filtered_search/filtered_search_tokenizer');
require('~/filtered_search/filtered_search_dropdown_manager');
(() => {
describe('Filtered Search Dropdown Manager', () => {
describe('addWordToInput', () => {
function getInputValue() {
return document.querySelector('.filtered-search').value;
}
function setInputValue(value) {
document.querySelector('.filtered-search').value = value;
}
beforeEach(() => {
setFixtures(`
<ul class="tokens-container">
<li class="input-token">
<input class="filtered-search">
</li>
</ul>
`);
});
describe('Filtered Search Dropdown Manager', () => {
describe('addWordToInput', () => {
function getInputValue() {
return document.querySelector('.filtered-search').value;
}
function setInputValue(value) {
document.querySelector('.filtered-search').value = value;
}
beforeEach(() => {
setFixtures(`
<ul class="tokens-container">
<li class="input-token">
<input class="filtered-search">
</li>
</ul>
`);
});
describe('input has no existing value', () => {
it('should add just tokenName', () => {
gl.FilteredSearchDropdownManager.addWordToInput('milestone');
describe('input has no existing value', () => {
it('should add just tokenName', () => {
gl.FilteredSearchDropdownManager.addWordToInput('milestone');
const token = document.querySelector('.tokens-container .js-visual-token');
const token = document.querySelector('.tokens-container .js-visual-token');
expect(token.classList.contains('filtered-search-token')).toEqual(true);
expect(token.querySelector('.name').innerText).toBe('milestone');
expect(getInputValue()).toBe('');
});
expect(token.classList.contains('filtered-search-token')).toEqual(true);
expect(token.querySelector('.name').innerText).toBe('milestone');
expect(getInputValue()).toBe('');
});
it('should add tokenName and tokenValue', () => {
gl.FilteredSearchDropdownManager.addWordToInput('label');
it('should add tokenName and tokenValue', () => {
gl.FilteredSearchDropdownManager.addWordToInput('label');
let token = document.querySelector('.tokens-container .js-visual-token');
let token = document.querySelector('.tokens-container .js-visual-token');
expect(token.classList.contains('filtered-search-token')).toEqual(true);
expect(token.querySelector('.name').innerText).toBe('label');
expect(getInputValue()).toBe('');
expect(token.classList.contains('filtered-search-token')).toEqual(true);
expect(token.querySelector('.name').innerText).toBe('label');
expect(getInputValue()).toBe('');
gl.FilteredSearchDropdownManager.addWordToInput('label', 'none');
// We have to get that reference again
// Because gl.FilteredSearchDropdownManager deletes the previous token
token = document.querySelector('.tokens-container .js-visual-token');
gl.FilteredSearchDropdownManager.addWordToInput('label', 'none');
// We have to get that reference again
// Because gl.FilteredSearchDropdownManager deletes the previous token
token = document.querySelector('.tokens-container .js-visual-token');
expect(token.classList.contains('filtered-search-token')).toEqual(true);
expect(token.querySelector('.name').innerText).toBe('label');
expect(token.querySelector('.value').innerText).toBe('none');
expect(getInputValue()).toBe('');
});
expect(token.classList.contains('filtered-search-token')).toEqual(true);
expect(token.querySelector('.name').innerText).toBe('label');
expect(token.querySelector('.value').innerText).toBe('none');
expect(getInputValue()).toBe('');
});
});
describe('input has existing value', () => {
it('should be able to just add tokenName', () => {
setInputValue('a');
gl.FilteredSearchDropdownManager.addWordToInput('author');
describe('input has existing value', () => {
it('should be able to just add tokenName', () => {
setInputValue('a');
gl.FilteredSearchDropdownManager.addWordToInput('author');
const token = document.querySelector('.tokens-container .js-visual-token');
const token = document.querySelector('.tokens-container .js-visual-token');
expect(token.classList.contains('filtered-search-token')).toEqual(true);
expect(token.querySelector('.name').innerText).toBe('author');
expect(getInputValue()).toBe('');
});
expect(token.classList.contains('filtered-search-token')).toEqual(true);
expect(token.querySelector('.name').innerText).toBe('author');
expect(getInputValue()).toBe('');
});
it('should replace tokenValue', () => {
gl.FilteredSearchDropdownManager.addWordToInput('author');
it('should replace tokenValue', () => {
gl.FilteredSearchDropdownManager.addWordToInput('author');
setInputValue('roo');
gl.FilteredSearchDropdownManager.addWordToInput(null, '@root');
setInputValue('roo');
gl.FilteredSearchDropdownManager.addWordToInput(null, '@root');
const token = document.querySelector('.tokens-container .js-visual-token');
const token = document.querySelector('.tokens-container .js-visual-token');
expect(token.classList.contains('filtered-search-token')).toEqual(true);
expect(token.querySelector('.name').innerText).toBe('author');
expect(token.querySelector('.value').innerText).toBe('@root');
expect(getInputValue()).toBe('');
});
expect(token.classList.contains('filtered-search-token')).toEqual(true);
expect(token.querySelector('.name').innerText).toBe('author');
expect(token.querySelector('.value').innerText).toBe('@root');
expect(getInputValue()).toBe('');
});
it('should add tokenValues containing spaces', () => {
gl.FilteredSearchDropdownManager.addWordToInput('label');
it('should add tokenValues containing spaces', () => {
gl.FilteredSearchDropdownManager.addWordToInput('label');
setInputValue('"test ');
gl.FilteredSearchDropdownManager.addWordToInput('label', '~\'"test me"\'');
setInputValue('"test ');
gl.FilteredSearchDropdownManager.addWordToInput('label', '~\'"test me"\'');
const token = document.querySelector('.tokens-container .js-visual-token');
const token = document.querySelector('.tokens-container .js-visual-token');
expect(token.classList.contains('filtered-search-token')).toEqual(true);
expect(token.querySelector('.name').innerText).toBe('label');
expect(token.querySelector('.value').innerText).toBe('~\'"test me"\'');
expect(getInputValue()).toBe('');
});
expect(token.classList.contains('filtered-search-token')).toEqual(true);
expect(token.querySelector('.name').innerText).toBe('label');
expect(token.querySelector('.value').innerText).toBe('~\'"test me"\'');
expect(getInputValue()).toBe('');
});
});
});
})();
});
......@@ -6,271 +6,269 @@ require('~/filtered_search/filtered_search_dropdown_manager');
require('~/filtered_search/filtered_search_manager');
const FilteredSearchSpecHelper = require('../helpers/filtered_search_spec_helper');
(() => {
describe('Filtered Search Manager', () => {
let input;
let manager;
let tokensContainer;
const placeholder = 'Search or filter results...';
function dispatchBackspaceEvent(element, eventType) {
const backspaceKey = 8;
const event = new Event(eventType);
event.keyCode = backspaceKey;
element.dispatchEvent(event);
}
describe('Filtered Search Manager', () => {
let input;
let manager;
let tokensContainer;
const placeholder = 'Search or filter results...';
function dispatchBackspaceEvent(element, eventType) {
const backspaceKey = 8;
const event = new Event(eventType);
event.keyCode = backspaceKey;
element.dispatchEvent(event);
}
function dispatchDeleteEvent(element, eventType) {
const deleteKey = 46;
const event = new Event(eventType);
event.keyCode = deleteKey;
element.dispatchEvent(event);
}
beforeEach(() => {
setFixtures(`
<div class="filtered-search-box">
<form>
<ul class="tokens-container list-unstyled">
${FilteredSearchSpecHelper.createInputHTML(placeholder)}
</ul>
<button class="clear-search" type="button">
<i class="fa fa-times"></i>
</button>
</form>
</div>
`);
spyOn(gl.FilteredSearchManager.prototype, 'loadSearchParamsFromURL').and.callFake(() => {});
spyOn(gl.FilteredSearchManager.prototype, 'tokenChange').and.callFake(() => {});
spyOn(gl.FilteredSearchDropdownManager.prototype, 'setDropdown').and.callFake(() => {});
spyOn(gl.FilteredSearchDropdownManager.prototype, 'updateDropdownOffset').and.callFake(() => {});
spyOn(gl.utils, 'getParameterByName').and.returnValue(null);
spyOn(gl.FilteredSearchVisualTokens, 'unselectTokens').and.callThrough();
input = document.querySelector('.filtered-search');
tokensContainer = document.querySelector('.tokens-container');
manager = new gl.FilteredSearchManager();
});
function dispatchDeleteEvent(element, eventType) {
const deleteKey = 46;
const event = new Event(eventType);
event.keyCode = deleteKey;
element.dispatchEvent(event);
}
afterEach(() => {
manager.cleanup();
});
beforeEach(() => {
setFixtures(`
<div class="filtered-search-box">
<form>
<ul class="tokens-container list-unstyled">
${FilteredSearchSpecHelper.createInputHTML(placeholder)}
</ul>
<button class="clear-search" type="button">
<i class="fa fa-times"></i>
</button>
</form>
</div>
`);
describe('search', () => {
const defaultParams = '?scope=all&utf8=%E2%9C%93&state=opened';
spyOn(gl.FilteredSearchManager.prototype, 'loadSearchParamsFromURL').and.callFake(() => {});
spyOn(gl.FilteredSearchManager.prototype, 'tokenChange').and.callFake(() => {});
spyOn(gl.FilteredSearchDropdownManager.prototype, 'setDropdown').and.callFake(() => {});
spyOn(gl.FilteredSearchDropdownManager.prototype, 'updateDropdownOffset').and.callFake(() => {});
spyOn(gl.utils, 'getParameterByName').and.returnValue(null);
spyOn(gl.FilteredSearchVisualTokens, 'unselectTokens').and.callThrough();
it('should search with a single word', (done) => {
input.value = 'searchTerm';
input = document.querySelector('.filtered-search');
tokensContainer = document.querySelector('.tokens-container');
manager = new gl.FilteredSearchManager();
});
spyOn(gl.utils, 'visitUrl').and.callFake((url) => {
expect(url).toEqual(`${defaultParams}&search=searchTerm`);
done();
});
afterEach(() => {
manager.cleanup();
manager.search();
});
describe('search', () => {
const defaultParams = '?scope=all&utf8=%E2%9C%93&state=opened';
it('should search with a single word', (done) => {
input.value = 'searchTerm';
it('should search with multiple words', (done) => {
input.value = 'awesome search terms';
spyOn(gl.utils, 'visitUrl').and.callFake((url) => {
expect(url).toEqual(`${defaultParams}&search=searchTerm`);
done();
});
manager.search();
spyOn(gl.utils, 'visitUrl').and.callFake((url) => {
expect(url).toEqual(`${defaultParams}&search=awesome+search+terms`);
done();
});
it('should search with multiple words', (done) => {
input.value = 'awesome search terms';
manager.search();
});
spyOn(gl.utils, 'visitUrl').and.callFake((url) => {
expect(url).toEqual(`${defaultParams}&search=awesome+search+terms`);
done();
});
it('should search with special characters', (done) => {
input.value = '~!@#$%^&*()_+{}:<>,.?/';
manager.search();
spyOn(gl.utils, 'visitUrl').and.callFake((url) => {
expect(url).toEqual(`${defaultParams}&search=~!%40%23%24%25%5E%26*()_%2B%7B%7D%3A%3C%3E%2C.%3F%2F`);
done();
});
it('should search with special characters', (done) => {
input.value = '~!@#$%^&*()_+{}:<>,.?/';
manager.search();
});
spyOn(gl.utils, 'visitUrl').and.callFake((url) => {
expect(url).toEqual(`${defaultParams}&search=~!%40%23%24%25%5E%26*()_%2B%7B%7D%3A%3C%3E%2C.%3F%2F`);
done();
});
it('removes duplicated tokens', (done) => {
tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(`
${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~bug')}
${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~bug')}
`);
manager.search();
spyOn(gl.utils, 'visitUrl').and.callFake((url) => {
expect(url).toEqual(`${defaultParams}&label_name[]=bug`);
done();
});
it('removes duplicated tokens', (done) => {
tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(`
${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~bug')}
${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~bug')}
`);
spyOn(gl.utils, 'visitUrl').and.callFake((url) => {
expect(url).toEqual(`${defaultParams}&label_name[]=bug`);
done();
});
manager.search();
});
});
manager.search();
});
describe('handleInputPlaceholder', () => {
it('should render placeholder when there is no input', () => {
expect(input.placeholder).toEqual(placeholder);
});
describe('handleInputPlaceholder', () => {
it('should render placeholder when there is no input', () => {
expect(input.placeholder).toEqual(placeholder);
});
it('should not render placeholder when there is input', () => {
input.value = 'test words';
const event = new Event('input');
input.dispatchEvent(event);
it('should not render placeholder when there is input', () => {
input.value = 'test words';
expect(input.placeholder).toEqual('');
});
const event = new Event('input');
input.dispatchEvent(event);
it('should not render placeholder when there are tokens and no input', () => {
tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(
FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~bug'),
);
expect(input.placeholder).toEqual('');
});
const event = new Event('input');
input.dispatchEvent(event);
it('should not render placeholder when there are tokens and no input', () => {
expect(input.placeholder).toEqual('');
});
});
describe('checkForBackspace', () => {
describe('tokens and no input', () => {
beforeEach(() => {
tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(
FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~bug'),
);
const event = new Event('input');
input.dispatchEvent(event);
expect(input.placeholder).toEqual('');
});
});
describe('checkForBackspace', () => {
describe('tokens and no input', () => {
beforeEach(() => {
tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(
FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~bug'),
);
});
it('removes last token', () => {
spyOn(gl.FilteredSearchVisualTokens, 'removeLastTokenPartial').and.callThrough();
dispatchBackspaceEvent(input, 'keyup');
expect(gl.FilteredSearchVisualTokens.removeLastTokenPartial).toHaveBeenCalled();
});
it('sets the input', () => {
spyOn(gl.FilteredSearchVisualTokens, 'getLastTokenPartial').and.callThrough();
dispatchDeleteEvent(input, 'keyup');
it('removes last token', () => {
spyOn(gl.FilteredSearchVisualTokens, 'removeLastTokenPartial').and.callThrough();
dispatchBackspaceEvent(input, 'keyup');
expect(gl.FilteredSearchVisualTokens.getLastTokenPartial).toHaveBeenCalled();
expect(input.value).toEqual('~bug');
});
expect(gl.FilteredSearchVisualTokens.removeLastTokenPartial).toHaveBeenCalled();
});
it('does not remove token or change input when there is existing input', () => {
spyOn(gl.FilteredSearchVisualTokens, 'removeLastTokenPartial').and.callThrough();
it('sets the input', () => {
spyOn(gl.FilteredSearchVisualTokens, 'getLastTokenPartial').and.callThrough();
input.value = 'text';
dispatchDeleteEvent(input, 'keyup');
expect(gl.FilteredSearchVisualTokens.removeLastTokenPartial).not.toHaveBeenCalled();
expect(gl.FilteredSearchVisualTokens.getLastTokenPartial).not.toHaveBeenCalled();
expect(input.value).toEqual('text');
expect(gl.FilteredSearchVisualTokens.getLastTokenPartial).toHaveBeenCalled();
expect(input.value).toEqual('~bug');
});
});
describe('removeSelectedToken', () => {
function getVisualTokens() {
return tokensContainer.querySelectorAll('.js-visual-token');
}
it('does not remove token or change input when there is existing input', () => {
spyOn(gl.FilteredSearchVisualTokens, 'removeLastTokenPartial').and.callThrough();
spyOn(gl.FilteredSearchVisualTokens, 'getLastTokenPartial').and.callThrough();
beforeEach(() => {
tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(
FilteredSearchSpecHelper.createFilterVisualTokenHTML('milestone', 'none', true),
);
});
input.value = 'text';
dispatchDeleteEvent(input, 'keyup');
it('removes selected token when the backspace key is pressed', () => {
expect(getVisualTokens().length).toEqual(1);
expect(gl.FilteredSearchVisualTokens.removeLastTokenPartial).not.toHaveBeenCalled();
expect(gl.FilteredSearchVisualTokens.getLastTokenPartial).not.toHaveBeenCalled();
expect(input.value).toEqual('text');
});
});
dispatchBackspaceEvent(document, 'keydown');
describe('removeSelectedToken', () => {
function getVisualTokens() {
return tokensContainer.querySelectorAll('.js-visual-token');
}
expect(getVisualTokens().length).toEqual(0);
});
beforeEach(() => {
tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(
FilteredSearchSpecHelper.createFilterVisualTokenHTML('milestone', 'none', true),
);
});
it('removes selected token when the delete key is pressed', () => {
expect(getVisualTokens().length).toEqual(1);
it('removes selected token when the backspace key is pressed', () => {
expect(getVisualTokens().length).toEqual(1);
dispatchDeleteEvent(document, 'keydown');
dispatchBackspaceEvent(document, 'keydown');
expect(getVisualTokens().length).toEqual(0);
});
expect(getVisualTokens().length).toEqual(0);
});
it('updates the input placeholder after removal', () => {
manager.handleInputPlaceholder();
it('removes selected token when the delete key is pressed', () => {
expect(getVisualTokens().length).toEqual(1);
expect(input.placeholder).toEqual('');
expect(getVisualTokens().length).toEqual(1);
dispatchDeleteEvent(document, 'keydown');
dispatchBackspaceEvent(document, 'keydown');
expect(getVisualTokens().length).toEqual(0);
});
expect(input.placeholder).not.toEqual('');
expect(getVisualTokens().length).toEqual(0);
});
it('updates the input placeholder after removal', () => {
manager.handleInputPlaceholder();
it('updates the clear button after removal', () => {
manager.toggleClearSearchButton();
expect(input.placeholder).toEqual('');
expect(getVisualTokens().length).toEqual(1);
const clearButton = document.querySelector('.clear-search');
dispatchBackspaceEvent(document, 'keydown');
expect(clearButton.classList.contains('hidden')).toEqual(false);
expect(getVisualTokens().length).toEqual(1);
expect(input.placeholder).not.toEqual('');
expect(getVisualTokens().length).toEqual(0);
});
dispatchBackspaceEvent(document, 'keydown');
it('updates the clear button after removal', () => {
manager.toggleClearSearchButton();
expect(clearButton.classList.contains('hidden')).toEqual(true);
expect(getVisualTokens().length).toEqual(0);
});
const clearButton = document.querySelector('.clear-search');
expect(clearButton.classList.contains('hidden')).toEqual(false);
expect(getVisualTokens().length).toEqual(1);
dispatchBackspaceEvent(document, 'keydown');
expect(clearButton.classList.contains('hidden')).toEqual(true);
expect(getVisualTokens().length).toEqual(0);
});
});
describe('unselects token', () => {
beforeEach(() => {
tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(`
${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~bug', true)}
${FilteredSearchSpecHelper.createSearchVisualTokenHTML('search term')}
${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~awesome')}
`);
});
describe('unselects token', () => {
beforeEach(() => {
tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(`
${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~bug', true)}
${FilteredSearchSpecHelper.createSearchVisualTokenHTML('search term')}
${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~awesome')}
`);
});
it('unselects token when input is clicked', () => {
const selectedToken = tokensContainer.querySelector('.js-visual-token .selected');
it('unselects token when input is clicked', () => {
const selectedToken = tokensContainer.querySelector('.js-visual-token .selected');
expect(selectedToken.classList.contains('selected')).toEqual(true);
expect(gl.FilteredSearchVisualTokens.unselectTokens).not.toHaveBeenCalled();
expect(selectedToken.classList.contains('selected')).toEqual(true);
expect(gl.FilteredSearchVisualTokens.unselectTokens).not.toHaveBeenCalled();
// Click directly on input attached to document
// so that the click event will propagate properly
document.querySelector('.filtered-search').click();
// Click directly on input attached to document
// so that the click event will propagate properly
document.querySelector('.filtered-search').click();
expect(gl.FilteredSearchVisualTokens.unselectTokens).toHaveBeenCalled();
expect(selectedToken.classList.contains('selected')).toEqual(false);
});
expect(gl.FilteredSearchVisualTokens.unselectTokens).toHaveBeenCalled();
expect(selectedToken.classList.contains('selected')).toEqual(false);
});
it('unselects token when document.body is clicked', () => {
const selectedToken = tokensContainer.querySelector('.js-visual-token .selected');
it('unselects token when document.body is clicked', () => {
const selectedToken = tokensContainer.querySelector('.js-visual-token .selected');
expect(selectedToken.classList.contains('selected')).toEqual(true);
expect(gl.FilteredSearchVisualTokens.unselectTokens).not.toHaveBeenCalled();
expect(selectedToken.classList.contains('selected')).toEqual(true);
expect(gl.FilteredSearchVisualTokens.unselectTokens).not.toHaveBeenCalled();
document.body.click();
document.body.click();
expect(selectedToken.classList.contains('selected')).toEqual(false);
expect(gl.FilteredSearchVisualTokens.unselectTokens).toHaveBeenCalled();
});
expect(selectedToken.classList.contains('selected')).toEqual(false);
expect(gl.FilteredSearchVisualTokens.unselectTokens).toHaveBeenCalled();
});
});
describe('toggleInputContainerFocus', () => {
it('toggles on focus', () => {
input.focus();
expect(document.querySelector('.filtered-search-box').classList.contains('focus')).toEqual(true);
});
describe('toggleInputContainerFocus', () => {
it('toggles on focus', () => {
input.focus();
expect(document.querySelector('.filtered-search-box').classList.contains('focus')).toEqual(true);
});
it('toggles on blur', () => {
input.blur();
expect(document.querySelector('.filtered-search-box').classList.contains('focus')).toEqual(false);
});
it('toggles on blur', () => {
input.blur();
expect(document.querySelector('.filtered-search-box').classList.contains('focus')).toEqual(false);
});
});
})();
});
require('~/extensions/array');
require('~/filtered_search/filtered_search_token_keys');
(() => {
describe('Filtered Search Token Keys', () => {
describe('get', () => {
let tokenKeys;
beforeEach(() => {
tokenKeys = gl.FilteredSearchTokenKeys.get();
});
it('should return tokenKeys', () => {
expect(tokenKeys !== null).toBe(true);
});
it('should return tokenKeys as an array', () => {
expect(tokenKeys instanceof Array).toBe(true);
});
});
describe('getConditions', () => {
let conditions;
beforeEach(() => {
conditions = gl.FilteredSearchTokenKeys.getConditions();
});
it('should return conditions', () => {
expect(conditions !== null).toBe(true);
});
it('should return conditions as an array', () => {
expect(conditions instanceof Array).toBe(true);
});
});
describe('searchByKey', () => {
it('should return null when key not found', () => {
const tokenKey = gl.FilteredSearchTokenKeys.searchByKey('notakey');
expect(tokenKey === null).toBe(true);
});
it('should return tokenKey when found by key', () => {
const tokenKeys = gl.FilteredSearchTokenKeys.get();
const result = gl.FilteredSearchTokenKeys.searchByKey(tokenKeys[0].key);
expect(result).toEqual(tokenKeys[0]);
});
});
describe('searchBySymbol', () => {
it('should return null when symbol not found', () => {
const tokenKey = gl.FilteredSearchTokenKeys.searchBySymbol('notasymbol');
expect(tokenKey === null).toBe(true);
});
it('should return tokenKey when found by symbol', () => {
const tokenKeys = gl.FilteredSearchTokenKeys.get();
const result = gl.FilteredSearchTokenKeys.searchBySymbol(tokenKeys[0].symbol);
expect(result).toEqual(tokenKeys[0]);
});
});
describe('searchByKeyParam', () => {
it('should return null when key param not found', () => {
const tokenKey = gl.FilteredSearchTokenKeys.searchByKeyParam('notakeyparam');
expect(tokenKey === null).toBe(true);
});
it('should return tokenKey when found by key param', () => {
const tokenKeys = gl.FilteredSearchTokenKeys.get();
const result = gl.FilteredSearchTokenKeys.searchByKeyParam(`${tokenKeys[0].key}_${tokenKeys[0].param}`);
expect(result).toEqual(tokenKeys[0]);
});
it('should return alternative tokenKey when found by key param', () => {
const tokenKeys = gl.FilteredSearchTokenKeys.getAlternatives();
const result = gl.FilteredSearchTokenKeys.searchByKeyParam(`${tokenKeys[0].key}_${tokenKeys[0].param}`);
expect(result).toEqual(tokenKeys[0]);
});
});
describe('searchByConditionUrl', () => {
it('should return null when condition url not found', () => {
const condition = gl.FilteredSearchTokenKeys.searchByConditionUrl(null);
expect(condition === null).toBe(true);
});
it('should return condition when found by url', () => {
const conditions = gl.FilteredSearchTokenKeys.getConditions();
const result = gl.FilteredSearchTokenKeys.searchByConditionUrl(conditions[0].url);
expect(result).toBe(conditions[0]);
});
});
describe('searchByConditionKeyValue', () => {
it('should return null when condition tokenKey and value not found', () => {
const condition = gl.FilteredSearchTokenKeys.searchByConditionKeyValue(null, null);
expect(condition === null).toBe(true);
});
it('should return condition when found by tokenKey and value', () => {
const conditions = gl.FilteredSearchTokenKeys.getConditions();
const result = gl.FilteredSearchTokenKeys
.searchByConditionKeyValue(conditions[0].tokenKey, conditions[0].value);
expect(result).toEqual(conditions[0]);
});
describe('Filtered Search Token Keys', () => {
describe('get', () => {
let tokenKeys;
beforeEach(() => {
tokenKeys = gl.FilteredSearchTokenKeys.get();
});
it('should return tokenKeys', () => {
expect(tokenKeys !== null).toBe(true);
});
it('should return tokenKeys as an array', () => {
expect(tokenKeys instanceof Array).toBe(true);
});
});
describe('getConditions', () => {
let conditions;
beforeEach(() => {
conditions = gl.FilteredSearchTokenKeys.getConditions();
});
it('should return conditions', () => {
expect(conditions !== null).toBe(true);
});
it('should return conditions as an array', () => {
expect(conditions instanceof Array).toBe(true);
});
});
describe('searchByKey', () => {
it('should return null when key not found', () => {
const tokenKey = gl.FilteredSearchTokenKeys.searchByKey('notakey');
expect(tokenKey === null).toBe(true);
});
it('should return tokenKey when found by key', () => {
const tokenKeys = gl.FilteredSearchTokenKeys.get();
const result = gl.FilteredSearchTokenKeys.searchByKey(tokenKeys[0].key);
expect(result).toEqual(tokenKeys[0]);
});
});
describe('searchBySymbol', () => {
it('should return null when symbol not found', () => {
const tokenKey = gl.FilteredSearchTokenKeys.searchBySymbol('notasymbol');
expect(tokenKey === null).toBe(true);
});
it('should return tokenKey when found by symbol', () => {
const tokenKeys = gl.FilteredSearchTokenKeys.get();
const result = gl.FilteredSearchTokenKeys.searchBySymbol(tokenKeys[0].symbol);
expect(result).toEqual(tokenKeys[0]);
});
});
describe('searchByKeyParam', () => {
it('should return null when key param not found', () => {
const tokenKey = gl.FilteredSearchTokenKeys.searchByKeyParam('notakeyparam');
expect(tokenKey === null).toBe(true);
});
it('should return tokenKey when found by key param', () => {
const tokenKeys = gl.FilteredSearchTokenKeys.get();
const result = gl.FilteredSearchTokenKeys.searchByKeyParam(`${tokenKeys[0].key}_${tokenKeys[0].param}`);
expect(result).toEqual(tokenKeys[0]);
});
it('should return alternative tokenKey when found by key param', () => {
const tokenKeys = gl.FilteredSearchTokenKeys.getAlternatives();
const result = gl.FilteredSearchTokenKeys.searchByKeyParam(`${tokenKeys[0].key}_${tokenKeys[0].param}`);
expect(result).toEqual(tokenKeys[0]);
});
});
describe('searchByConditionUrl', () => {
it('should return null when condition url not found', () => {
const condition = gl.FilteredSearchTokenKeys.searchByConditionUrl(null);
expect(condition === null).toBe(true);
});
it('should return condition when found by url', () => {
const conditions = gl.FilteredSearchTokenKeys.getConditions();
const result = gl.FilteredSearchTokenKeys.searchByConditionUrl(conditions[0].url);
expect(result).toBe(conditions[0]);
});
});
describe('searchByConditionKeyValue', () => {
it('should return null when condition tokenKey and value not found', () => {
const condition = gl.FilteredSearchTokenKeys.searchByConditionKeyValue(null, null);
expect(condition === null).toBe(true);
});
it('should return condition when found by tokenKey and value', () => {
const conditions = gl.FilteredSearchTokenKeys.getConditions();
const result = gl.FilteredSearchTokenKeys
.searchByConditionKeyValue(conditions[0].tokenKey, conditions[0].value);
expect(result).toEqual(conditions[0]);
});
});
})();
});
......@@ -2,134 +2,132 @@ require('~/extensions/array');
require('~/filtered_search/filtered_search_token_keys');
require('~/filtered_search/filtered_search_tokenizer');
(() => {
describe('Filtered Search Tokenizer', () => {
describe('processTokens', () => {
it('returns for input containing only search value', () => {
const results = gl.FilteredSearchTokenizer.processTokens('searchTerm');
expect(results.searchToken).toBe('searchTerm');
expect(results.tokens.length).toBe(0);
expect(results.lastToken).toBe(results.searchToken);
});
it('returns for input containing only tokens', () => {
const results = gl.FilteredSearchTokenizer
.processTokens('author:@root label:~"Very Important" milestone:%v1.0 assignee:none');
expect(results.searchToken).toBe('');
expect(results.tokens.length).toBe(4);
expect(results.tokens[3]).toBe(results.lastToken);
expect(results.tokens[0].key).toBe('author');
expect(results.tokens[0].value).toBe('root');
expect(results.tokens[0].symbol).toBe('@');
expect(results.tokens[1].key).toBe('label');
expect(results.tokens[1].value).toBe('"Very Important"');
expect(results.tokens[1].symbol).toBe('~');
expect(results.tokens[2].key).toBe('milestone');
expect(results.tokens[2].value).toBe('v1.0');
expect(results.tokens[2].symbol).toBe('%');
expect(results.tokens[3].key).toBe('assignee');
expect(results.tokens[3].value).toBe('none');
expect(results.tokens[3].symbol).toBe('');
});
it('returns for input starting with search value and ending with tokens', () => {
const results = gl.FilteredSearchTokenizer
.processTokens('searchTerm anotherSearchTerm milestone:none');
expect(results.searchToken).toBe('searchTerm anotherSearchTerm');
expect(results.tokens.length).toBe(1);
expect(results.tokens[0]).toBe(results.lastToken);
expect(results.tokens[0].key).toBe('milestone');
expect(results.tokens[0].value).toBe('none');
expect(results.tokens[0].symbol).toBe('');
});
it('returns for input starting with tokens and ending with search value', () => {
const results = gl.FilteredSearchTokenizer
.processTokens('assignee:@user searchTerm');
expect(results.searchToken).toBe('searchTerm');
expect(results.tokens.length).toBe(1);
expect(results.tokens[0].key).toBe('assignee');
expect(results.tokens[0].value).toBe('user');
expect(results.tokens[0].symbol).toBe('@');
expect(results.lastToken).toBe(results.searchToken);
});
it('returns for input containing search value wrapped between tokens', () => {
const results = gl.FilteredSearchTokenizer
.processTokens('author:@root label:~"Won\'t fix" searchTerm anotherSearchTerm milestone:none');
expect(results.searchToken).toBe('searchTerm anotherSearchTerm');
expect(results.tokens.length).toBe(3);
expect(results.tokens[2]).toBe(results.lastToken);
expect(results.tokens[0].key).toBe('author');
expect(results.tokens[0].value).toBe('root');
expect(results.tokens[0].symbol).toBe('@');
expect(results.tokens[1].key).toBe('label');
expect(results.tokens[1].value).toBe('"Won\'t fix"');
expect(results.tokens[1].symbol).toBe('~');
expect(results.tokens[2].key).toBe('milestone');
expect(results.tokens[2].value).toBe('none');
expect(results.tokens[2].symbol).toBe('');
});
it('returns for input containing search value in between tokens', () => {
const results = gl.FilteredSearchTokenizer
.processTokens('author:@root searchTerm assignee:none anotherSearchTerm label:~Doing');
expect(results.searchToken).toBe('searchTerm anotherSearchTerm');
expect(results.tokens.length).toBe(3);
expect(results.tokens[2]).toBe(results.lastToken);
expect(results.tokens[0].key).toBe('author');
expect(results.tokens[0].value).toBe('root');
expect(results.tokens[0].symbol).toBe('@');
expect(results.tokens[1].key).toBe('assignee');
expect(results.tokens[1].value).toBe('none');
expect(results.tokens[1].symbol).toBe('');
expect(results.tokens[2].key).toBe('label');
expect(results.tokens[2].value).toBe('Doing');
expect(results.tokens[2].symbol).toBe('~');
});
it('returns search value for invalid tokens', () => {
const results = gl.FilteredSearchTokenizer.processTokens('fake:token');
expect(results.lastToken).toBe('fake:token');
expect(results.searchToken).toBe('fake:token');
expect(results.tokens.length).toEqual(0);
});
it('returns search value and token for mix of valid and invalid tokens', () => {
const results = gl.FilteredSearchTokenizer.processTokens('label:real fake:token');
expect(results.tokens.length).toEqual(1);
expect(results.tokens[0].key).toBe('label');
expect(results.tokens[0].value).toBe('real');
expect(results.tokens[0].symbol).toBe('');
expect(results.lastToken).toBe('fake:token');
expect(results.searchToken).toBe('fake:token');
});
it('returns search value for invalid symbols', () => {
const results = gl.FilteredSearchTokenizer.processTokens('std::includes');
expect(results.lastToken).toBe('std::includes');
expect(results.searchToken).toBe('std::includes');
});
it('removes duplicated values', () => {
const results = gl.FilteredSearchTokenizer.processTokens('label:~foo label:~foo');
expect(results.tokens.length).toBe(1);
expect(results.tokens[0].key).toBe('label');
expect(results.tokens[0].value).toBe('foo');
expect(results.tokens[0].symbol).toBe('~');
});
describe('Filtered Search Tokenizer', () => {
describe('processTokens', () => {
it('returns for input containing only search value', () => {
const results = gl.FilteredSearchTokenizer.processTokens('searchTerm');
expect(results.searchToken).toBe('searchTerm');
expect(results.tokens.length).toBe(0);
expect(results.lastToken).toBe(results.searchToken);
});
it('returns for input containing only tokens', () => {
const results = gl.FilteredSearchTokenizer
.processTokens('author:@root label:~"Very Important" milestone:%v1.0 assignee:none');
expect(results.searchToken).toBe('');
expect(results.tokens.length).toBe(4);
expect(results.tokens[3]).toBe(results.lastToken);
expect(results.tokens[0].key).toBe('author');
expect(results.tokens[0].value).toBe('root');
expect(results.tokens[0].symbol).toBe('@');
expect(results.tokens[1].key).toBe('label');
expect(results.tokens[1].value).toBe('"Very Important"');
expect(results.tokens[1].symbol).toBe('~');
expect(results.tokens[2].key).toBe('milestone');
expect(results.tokens[2].value).toBe('v1.0');
expect(results.tokens[2].symbol).toBe('%');
expect(results.tokens[3].key).toBe('assignee');
expect(results.tokens[3].value).toBe('none');
expect(results.tokens[3].symbol).toBe('');
});
it('returns for input starting with search value and ending with tokens', () => {
const results = gl.FilteredSearchTokenizer
.processTokens('searchTerm anotherSearchTerm milestone:none');
expect(results.searchToken).toBe('searchTerm anotherSearchTerm');
expect(results.tokens.length).toBe(1);
expect(results.tokens[0]).toBe(results.lastToken);
expect(results.tokens[0].key).toBe('milestone');
expect(results.tokens[0].value).toBe('none');
expect(results.tokens[0].symbol).toBe('');
});
it('returns for input starting with tokens and ending with search value', () => {
const results = gl.FilteredSearchTokenizer
.processTokens('assignee:@user searchTerm');
expect(results.searchToken).toBe('searchTerm');
expect(results.tokens.length).toBe(1);
expect(results.tokens[0].key).toBe('assignee');
expect(results.tokens[0].value).toBe('user');
expect(results.tokens[0].symbol).toBe('@');
expect(results.lastToken).toBe(results.searchToken);
});
it('returns for input containing search value wrapped between tokens', () => {
const results = gl.FilteredSearchTokenizer
.processTokens('author:@root label:~"Won\'t fix" searchTerm anotherSearchTerm milestone:none');
expect(results.searchToken).toBe('searchTerm anotherSearchTerm');
expect(results.tokens.length).toBe(3);
expect(results.tokens[2]).toBe(results.lastToken);
expect(results.tokens[0].key).toBe('author');
expect(results.tokens[0].value).toBe('root');
expect(results.tokens[0].symbol).toBe('@');
expect(results.tokens[1].key).toBe('label');
expect(results.tokens[1].value).toBe('"Won\'t fix"');
expect(results.tokens[1].symbol).toBe('~');
expect(results.tokens[2].key).toBe('milestone');
expect(results.tokens[2].value).toBe('none');
expect(results.tokens[2].symbol).toBe('');
});
it('returns for input containing search value in between tokens', () => {
const results = gl.FilteredSearchTokenizer
.processTokens('author:@root searchTerm assignee:none anotherSearchTerm label:~Doing');
expect(results.searchToken).toBe('searchTerm anotherSearchTerm');
expect(results.tokens.length).toBe(3);
expect(results.tokens[2]).toBe(results.lastToken);
expect(results.tokens[0].key).toBe('author');
expect(results.tokens[0].value).toBe('root');
expect(results.tokens[0].symbol).toBe('@');
expect(results.tokens[1].key).toBe('assignee');
expect(results.tokens[1].value).toBe('none');
expect(results.tokens[1].symbol).toBe('');
expect(results.tokens[2].key).toBe('label');
expect(results.tokens[2].value).toBe('Doing');
expect(results.tokens[2].symbol).toBe('~');
});
it('returns search value for invalid tokens', () => {
const results = gl.FilteredSearchTokenizer.processTokens('fake:token');
expect(results.lastToken).toBe('fake:token');
expect(results.searchToken).toBe('fake:token');
expect(results.tokens.length).toEqual(0);
});
it('returns search value and token for mix of valid and invalid tokens', () => {
const results = gl.FilteredSearchTokenizer.processTokens('label:real fake:token');
expect(results.tokens.length).toEqual(1);
expect(results.tokens[0].key).toBe('label');
expect(results.tokens[0].value).toBe('real');
expect(results.tokens[0].symbol).toBe('');
expect(results.lastToken).toBe('fake:token');
expect(results.searchToken).toBe('fake:token');
});
it('returns search value for invalid symbols', () => {
const results = gl.FilteredSearchTokenizer.processTokens('std::includes');
expect(results.lastToken).toBe('std::includes');
expect(results.searchToken).toBe('std::includes');
});
it('removes duplicated values', () => {
const results = gl.FilteredSearchTokenizer.processTokens('label:~foo label:~foo');
expect(results.tokens.length).toBe(1);
expect(results.tokens[0].key).toBe('label');
expect(results.tokens[0].value).toBe('foo');
expect(results.tokens[0].symbol).toBe('~');
});
});
})();
});
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