Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Jérome Perrin
gitlab-ce
Commits
e197f27f
Commit
e197f27f
authored
Dec 17, 2016
by
Clement Ho
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Refactor and use regex for string processing
parent
0e40c952
Changes
10
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
189 additions
and
536 deletions
+189
-536
app/assets/javascripts/filtered_search/dropdown_hint.js.es6
app/assets/javascripts/filtered_search/dropdown_hint.js.es6
+21
-37
app/assets/javascripts/filtered_search/dropdown_user.js.es6
app/assets/javascripts/filtered_search/dropdown_user.js.es6
+3
-6
app/assets/javascripts/filtered_search/dropdown_utils.js.es6
app/assets/javascripts/filtered_search/dropdown_utils.js.es6
+16
-14
app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6
...vascripts/filtered_search/filtered_search_dropdown.js.es6
+1
-1
app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6
...s/filtered_search/filtered_search_dropdown_manager.js.es6
+20
-14
app/assets/javascripts/filtered_search/filtered_search_manager.js.es6
...avascripts/filtered_search/filtered_search_manager.js.es6
+3
-11
app/assets/javascripts/filtered_search/filtered_search_tokenizer.js.es6
...ascripts/filtered_search/filtered_search_tokenizer.js.es6
+26
-152
spec/javascripts/filtered_search/dropdown_utils_spec.js.es6
spec/javascripts/filtered_search/dropdown_utils_spec.js.es6
+5
-20
spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js.es6
...tered_search/filtered_search_dropdown_manager_spec.js.es6
+1
-21
spec/javascripts/filtered_search/filtered_search_tokenizer_spec.js.es6
...pts/filtered_search/filtered_search_tokenizer_spec.js.es6
+93
-260
No files found.
app/assets/javascripts/filtered_search/dropdown_hint.js.es6
View file @
e197f27f
...
@@ -3,31 +3,13 @@
...
@@ -3,31 +3,13 @@
/* global droplabFilter */
/* global droplabFilter */
(() => {
(() => {
const dropdownData = [{
icon: 'fa-pencil',
hint: 'author:',
tag: '<author>',
}, {
icon: 'fa-user',
hint: 'assignee:',
tag: '<assignee>',
}, {
icon: 'fa-clock-o',
hint: 'milestone:',
tag: '<milestone>',
}, {
icon: 'fa-tag',
hint: 'label:',
tag: '<label>',
}];
class DropdownHint extends gl.FilteredSearchDropdown {
class DropdownHint extends gl.FilteredSearchDropdown {
constructor(droplab, dropdown, input) {
constructor(droplab, dropdown, input) {
super(droplab, dropdown, input);
super(droplab, dropdown, input);
this.config = {
this.config = {
droplabFilter: {
droplabFilter: {
template: 'hint',
template: 'hint',
filterFunction: gl.DropdownUtils.filter
Method
,
filterFunction: gl.DropdownUtils.filter
Hint
,
},
},
};
};
}
}
...
@@ -43,8 +25,7 @@
...
@@ -43,8 +25,7 @@
const tag = selected.querySelector('.js-filter-tag').innerText.trim();
const tag = selected.querySelector('.js-filter-tag').innerText.trim();
if (tag.length) {
if (tag.length) {
gl.FilteredSearchDropdownManager
gl.FilteredSearchDropdownManager.addWordToInput(token);
.addWordToInput(this.getSelectedTextWithoutEscaping(token));
}
}
this.dismissDropdown();
this.dismissDropdown();
this.dispatchInputEvent();
this.dispatchInputEvent();
...
@@ -52,24 +33,27 @@
...
@@ -52,24 +33,27 @@
}
}
}
}
getSelectedTextWithoutEscaping(selectedToken) {
const lastWord = this.input.value.split(' ').last();
const lastWordIndex = selectedToken.indexOf(lastWord);
return lastWordIndex === -1 ? selectedToken : selectedToken.slice(lastWord.length);
}
renderContent() {
renderContent() {
this.droplab.changeHookList(this.hookId, this.dropdown, [droplabFilter], this.config);
const dropdownData = [{
icon: 'fa-pencil',
// Clone dropdownData to prevent it from being
hint: 'author:',
// changed due to pass by reference
tag: '<author>',
const data = [];
}, {
dropdownData.forEach((item) => {
icon: 'fa-user',
data.push(Object.assign({}, item));
hint: 'assignee:',
});
tag: '<assignee>',
}, {
icon: 'fa-clock-o',
hint: 'milestone:',
tag: '<milestone>',
}, {
icon: 'fa-tag',
hint: 'label:',
tag: '<label>',
}];
this.droplab.setData(this.hookId, data);
this.droplab.changeHookList(this.hookId, this.dropdown, [droplabFilter], this.config);
this.droplab.setData(this.hookId, dropdownData);
}
}
init() {
init() {
...
...
app/assets/javascripts/filtered_search/dropdown_user.js.es6
View file @
e197f27f
...
@@ -37,13 +37,10 @@
...
@@ -37,13 +37,10 @@
}
}
getSearchInput() {
getSearchInput() {
const query = this.input.value;
const query = this.input.value.trim();
const { value } = gl.FilteredSearchTokenizer.getLastTokenObject(query);
const { lastToken } = gl.FilteredSearchTokenizer.processTokens(query);
const valueWithoutColon = value.slice(1);
const hasPrefix = valueWithoutColon[0] === '@';
const valueWithoutPrefix = valueWithoutColon.slice(1);
return
hasPrefix ? valueWithoutPrefix : valueWithoutColon
;
return
lastToken.value || ''
;
}
}
init() {
init() {
...
...
app/assets/javascripts/filtered_search/dropdown_utils.js.es6
View file @
e197f27f
...
@@ -22,30 +22,32 @@
...
@@ -22,30 +22,32 @@
static filterWithSymbol(filterSymbol, item, query) {
static filterWithSymbol(filterSymbol, item, query) {
const updatedItem = item;
const updatedItem = item;
const { value } = gl.FilteredSearchTokenizer.getLastTokenObject(query);
const { lastToken, searchToken } = gl.FilteredSearchTokenizer.processTokens(query);
const valueWithoutColon = value.slice(1).toLowerCase();
const prefix = valueWithoutColon[0];
const valueWithoutPrefix = valueWithoutColon.slice(1);
const title = updatedItem.title.toLowerCase();
if (lastToken !== searchToken) {
const value = lastToken.value.toLowerCase();
const title = updatedItem.title.toLowerCase();
// Eg. filterSymbol = ~ for labels
// Eg. filterSymbol = ~ for labels
const matchWithoutPrefix =
const matchWithoutSymbol = lastToken.symbol === filterSymbol && title.indexOf(value) !== -1;
prefix === filterSymbol && title.indexOf(valueWithoutPrefix) !== -1;
const match = title.indexOf(`${lastToken.symbol}${value}`) !== -1;
const match = title.indexOf(valueWithoutColon) !== -1;
updatedItem.droplab_hidden = !match && !matchWithoutSymbol;
} else {
updatedItem.droplab_hidden = false;
}
updatedItem.droplab_hidden = !match && !matchWithoutPrefix;
return updatedItem;
return updatedItem;
}
}
static filter
Method
(item, query) {
static filter
Hint
(item, query) {
const updatedItem = item;
const updatedItem = item;
const {
value } = gl.FilteredSearchTokenizer.getLastTokenObject
(query);
const {
lastToken } = gl.FilteredSearchTokenizer.processTokens
(query);
if (
value === ''
) {
if (
!lastToken
) {
updatedItem.droplab_hidden = false;
updatedItem.droplab_hidden = false;
} else {
} else {
updatedItem.droplab_hidden = updatedItem.hint.indexOf(
value
) === -1;
updatedItem.droplab_hidden = updatedItem.hint.indexOf(
lastToken.toLowerCase()
) === -1;
}
}
return updatedItem;
return updatedItem;
...
...
app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6
View file @
e197f27f
...
@@ -29,7 +29,7 @@
...
@@ -29,7 +29,7 @@
itemClicked(e, getValueFunction) {
itemClicked(e, getValueFunction) {
const { selected } = e.detail;
const { selected } = e.detail;
if (selected.tagName === 'LI') {
if (selected.tagName === 'LI'
&& selected.innerHTML
) {
const dataValueSet = gl.DropdownUtils.setDataValueIfSelected(selected);
const dataValueSet = gl.DropdownUtils.setDataValueIfSelected(selected);
if (!dataValueSet) {
if (!dataValueSet) {
...
...
app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6
View file @
e197f27f
...
@@ -57,17 +57,25 @@
...
@@ -57,17 +57,25 @@
static addWordToInput(word, addSpace = false) {
static addWordToInput(word, addSpace = false) {
const input = document.querySelector('.filtered-search');
const input = document.querySelector('.filtered-search');
input.value = input.value.trim();
const value = input.value;
const value = input.value;
const hasExistingValue = value.length !== 0;
const hasExistingValue = value.length !== 0;
const { lastToken } = gl.FilteredSearchTokenizer.processTokens(value);
const { lastToken, searchToken } = gl.FilteredSearchTokenizer.processTokens(value);
// Find out what part of the token value the user has typed
// and remove it from input before appending the selected token value
if (lastToken !== searchToken) {
const lastTokenString = `${lastToken.symbol}${lastToken.value}`;
if ({}.hasOwnProperty.call(lastToken, 'key')) {
// Spaces inside the token means that the token value will be escaped by quotes
// Spaces inside the token means that the token value will be escaped by quotes
const hasQuotes = lastToken
.value
.indexOf(' ') !== -1;
const hasQuotes = lastToken
String
.indexOf(' ') !== -1;
// Add 2 length to account for the length of the front and back quotes
// Add 2 length to account for the length of the front and back quotes
const lengthToRemove = hasQuotes ? lastToken
.value.length + 2 : lastToken.value
.length;
const lengthToRemove = hasQuotes ? lastToken
String.length + 2 : lastTokenString
.length;
input.value = value.slice(0, -1 * (lengthToRemove));
input.value = value.slice(0, -1 * (lengthToRemove));
} else if (searchToken !== '' && word.indexOf(searchToken) !== -1) {
input.value = value.slice(0, -1 * searchToken.length);
}
}
input.value += hasExistingValue && addSpace ? ` ${word}` : word;
input.value += hasExistingValue && addSpace ? ` ${word}` : word;
...
@@ -129,27 +137,25 @@
...
@@ -129,27 +137,25 @@
const match = gl.FilteredSearchTokenKeys.searchByKey(dropdownName.toLowerCase());
const match = gl.FilteredSearchTokenKeys.searchByKey(dropdownName.toLowerCase());
const shouldOpenFilterDropdown = match && this.currentDropdown !== match.key
const shouldOpenFilterDropdown = match && this.currentDropdown !== match.key
&&
{}.hasOwnProperty.call(this.mapping, match.key)
;
&&
this.mapping[match.key]
;
const shouldOpenHintDropdown = !match && this.currentDropdown !== 'hint';
const shouldOpenHintDropdown = !match && this.currentDropdown !== 'hint';
if (shouldOpenFilterDropdown || shouldOpenHintDropdown) {
if (shouldOpenFilterDropdown || shouldOpenHintDropdown) {
// `hint` is not listed as a tokenKey (since it is not a real `filter`)
const key = match && match.key ? match.key : 'hint';
const key = match && {}.hasOwnProperty.call(match, 'key') ? match.key : 'hint';
this.load(key, firstLoad);
this.load(key, firstLoad);
}
}
gl.droplab = this.droplab;
}
}
setDropdown() {
setDropdown() {
const { lastToken } = this.tokenizer.processTokens(this.filteredSearchInput.value);
const { lastToken, searchToken } = this.tokenizer
.processTokens(this.filteredSearchInput.value);
if (
typeof lastToken === 'string'
) {
if (
lastToken === searchToken
) {
// Token is not fully initialized yet because it has no value
// Token is not fully initialized yet because it has no value
// Eg. token = 'label:'
// Eg. token = 'label:'
const
{ tokenKey } = this.tokenizer.parseToken(lastToken
);
const
split = lastToken.split(':'
);
this.loadDropdown(
tokenKey
);
this.loadDropdown(
split.length > 1 ? split[0] : ''
);
} else if (
{}.hasOwnProperty.call(lastToken, 'key')
) {
} else if (
lastToken
) {
// Token has been initialized into an object because it has a value
// Token has been initialized into an object because it has a value
this.loadDropdown(lastToken.key);
this.loadDropdown(lastToken.key);
} else {
} else {
...
...
app/assets/javascripts/filtered_search/filtered_search_manager.js.es6
View file @
e197f27f
...
@@ -136,21 +136,13 @@
...
@@ -136,21 +136,13 @@
const condition = gl.FilteredSearchTokenKeys
const condition = gl.FilteredSearchTokenKeys
.searchByConditionKeyValue(token.key, token.value.toLowerCase());
.searchByConditionKeyValue(token.key, token.value.toLowerCase());
const { param } = gl.FilteredSearchTokenKeys.searchByKey(token.key);
const { param } = gl.FilteredSearchTokenKeys.searchByKey(token.key);
const keyParam = param ? `${token.key}_${param}` : token.key;
let tokenPath = '';
let tokenPath = '';
let keyParam = token.key;
if (condition) {
if (param) {
keyParam += `_${param}`;
}
if (token.wildcard && condition) {
tokenPath = condition.url;
tokenPath = condition.url;
} else if (token.wildcard) {
// wildcard means that the token does not have a symbol
tokenPath = `${keyParam}=${encodeURIComponent(token.value)}`;
} else {
} else {
// Remove the token symbol
tokenPath = `${keyParam}=${encodeURIComponent(token.value)}`;
tokenPath = `${keyParam}=${encodeURIComponent(token.value.slice(1))}`;
}
}
paths.push(tokenPath);
paths.push(tokenPath);
...
...
app/assets/javascripts/filtered_search/filtered_search_tokenizer.js.es6
View file @
e197f27f
(() => {
(() => {
class FilteredSearchTokenizer {
class FilteredSearchTokenizer {
static parseToken(input) {
const colonIndex = input.indexOf(':');
let tokenKey;
let tokenValue;
let tokenSymbol;
if (colonIndex !== -1) {
tokenKey = input.slice(0, colonIndex).toLowerCase();
tokenValue = input.slice(colonIndex + 1);
tokenSymbol = tokenValue[0];
}
return {
tokenKey,
tokenValue,
tokenSymbol,
};
}
static getLastTokenObject(input) {
const token = FilteredSearchTokenizer.getLastToken(input);
const colonIndex = token.indexOf(':');
const key = colonIndex !== -1 ? token.slice(0, colonIndex) : '';
const value = colonIndex !== -1 ? token.slice(colonIndex) : token;
return {
key,
value,
};
}
static getLastToken(input) {
let completeToken = false;
let completeQuotation = true;
let lastQuotation = '';
let i = input.length;
const doubleQuote = '"';
const singleQuote = '\'';
while (!completeToken && i >= 0) {
const isDoubleQuote = input[i] === doubleQuote;
const isSingleQuote = input[i] === singleQuote;
// If the second quotation is found
if ((lastQuotation === doubleQuote && isDoubleQuote) ||
(lastQuotation === singleQuote && isSingleQuote)) {
completeQuotation = true;
}
// Save the first quotation
if ((isDoubleQuote && lastQuotation === '') ||
(isSingleQuote && lastQuotation === '')) {
lastQuotation = input[i];
completeQuotation = false;
}
if (completeQuotation && input[i] === ' ') {
completeToken = true;
} else {
i -= 1;
}
}
// Adjust by 1 because of empty space
return input.slice(i + 1);
}
static processTokens(input) {
static processTokens(input) {
const tokenRegex = /(\w+):([~%@]?)(?:"(.*?)"|'(.*?)'|(\S+))/g;
const tokens = [];
const tokens = [];
let searchToken = '';
let lastToken = null;
let lastToken = '';
const searchToken = input.replace(tokenRegex, (match, key, symbol, v1, v2, v3) => {
let tokenValue = v1 || v2 || v3;
const inputs = input.split(' ');
let tokenSymbol = symbol;
let searchTerms = '';
let lastQuotation = '';
if (tokenValue === '~' || tokenValue === '%' || tokenValue === '@') {
let incompleteToken = false;
tokenSymbol = tokenValue;
tokenValue = '';
// Iterate through each word (broken up by spaces)
inputs.forEach((i) => {
if (incompleteToken) {
// Continue previous token as it had an escaped
// quote in the beginning
const prevToken = tokens.last();
prevToken.value += ` ${i}`;
// Remove last quotation from the value
const lastQuotationRegex = new RegExp(lastQuotation, 'g');
prevToken.value = prevToken.value.replace(lastQuotationRegex, '');
tokens[tokens.length - 1] = prevToken;
// Check to see if this quotation completes the token value
if (i.indexOf(lastQuotation) !== -1) {
lastToken = tokens.last();
incompleteToken = !incompleteToken;
}
return;
}
const colonIndex = i.indexOf(':');
if (colonIndex !== -1) {
const { tokenKey, tokenValue, tokenSymbol } = gl.FilteredSearchTokenizer.parseToken(i);
const keyMatch = gl.FilteredSearchTokenKeys.searchByKey(tokenKey);
const symbolMatch = gl.FilteredSearchTokenKeys.searchBySymbol(tokenSymbol);
const doubleQuoteOccurrences = tokenValue.split('"').length - 1;
const singleQuoteOccurrences = tokenValue.split('\'').length - 1;
const doubleQuoteIndex = tokenValue.indexOf('"');
const singleQuoteIndex = tokenValue.indexOf('\'');
const doubleQuoteExist = doubleQuoteIndex !== -1;
const singleQuoteExist = singleQuoteIndex !== -1;
const doubleQuoteExistOnly = doubleQuoteExist && !singleQuoteExist;
const doubleQuoteIsBeforeSingleQuote =
doubleQuoteExist && singleQuoteExist && doubleQuoteIndex < singleQuoteIndex;
const singleQuoteExistOnly = singleQuoteExist && !doubleQuoteExist;
const singleQuoteIsBeforeDoubleQuote =
doubleQuoteExist && singleQuoteExist && singleQuoteIndex < doubleQuoteIndex;
if ((doubleQuoteExistOnly || doubleQuoteIsBeforeSingleQuote)
&& doubleQuoteOccurrences % 2 !== 0) {
// " is found and is in front of ' (if any)
lastQuotation = '"';
incompleteToken = true;
} else if ((singleQuoteExistOnly || singleQuoteIsBeforeDoubleQuote)
&& singleQuoteOccurrences % 2 !== 0) {
// ' is found and is in front of " (if any)
lastQuotation = '\'';
incompleteToken = true;
}
if (keyMatch && tokenValue.length > 0) {
tokens.push({
key: keyMatch.key,
value: tokenValue,
wildcard: !symbolMatch,
});
lastToken = tokens.last();
return;
}
}
}
// Add space for next term
tokens.push({
searchTerms += `${i} `;
key,
lastToken = i;
value: tokenValue || '',
}, this);
symbol: tokenSymbol || '',
});
searchToken = searchTerms.trim();
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 {
return {
tokens,
tokens,
searchToken,
lastToken,
lastToken,
searchToken,
};
};
}
}
}
}
...
...
spec/javascripts/filtered_search/dropdown_utils_spec.js.es6
View file @
e197f27f
...
@@ -34,11 +34,6 @@
...
@@ -34,11 +34,6 @@
title: '@root',
title: '@root',
};
};
beforeEach(() => {
spyOn(gl.FilteredSearchTokenizer, 'getLastTokenObject')
.and.callFake(query => ({ value: query }));
});
it('should filter without symbol', () => {
it('should filter without symbol', () => {
const updatedItem = gl.DropdownUtils.filterWithSymbol('@', item, ':roo');
const updatedItem = gl.DropdownUtils.filterWithSymbol('@', item, ':roo');
expect(updatedItem.droplab_hidden).toBe(false);
expect(updatedItem.droplab_hidden).toBe(false);
...
@@ -49,37 +44,27 @@
...
@@ -49,37 +44,27 @@
expect(updatedItem.droplab_hidden).toBe(false);
expect(updatedItem.droplab_hidden).toBe(false);
});
});
it('should filter with invalid symbol', () => {
const updatedItem = gl.DropdownUtils.filterWithSymbol('@', item, ':#');
expect(updatedItem.droplab_hidden).toBe(true);
});
it('should filter with colon', () => {
it('should filter with colon', () => {
const updatedItem = gl.DropdownUtils.filterWithSymbol('@', item, ':');
const updatedItem = gl.DropdownUtils.filterWithSymbol('@', item, ':');
expect(updatedItem.droplab_hidden).toBe(false);
expect(updatedItem.droplab_hidden).toBe(false);
});
});
});
});
describe('filterMethod', () => {
describe('filterHint', () => {
beforeEach(() => {
it('should filter', () => {
spyOn(gl.FilteredSearchTokenizer, 'getLastTokenObject')
let updatedItem = gl.DropdownUtils.filterHint({
.and.callFake(query => ({ value: query }));
});
it('should filter by hint', () => {
let updatedItem = gl.DropdownUtils.filterMethod({
hint: 'label',
hint: 'label',
}, 'l');
}, 'l');
expect(updatedItem.droplab_hidden).toBe(false);
expect(updatedItem.droplab_hidden).toBe(false);
updatedItem = gl.DropdownUtils.filter
Method
({
updatedItem = gl.DropdownUtils.filter
Hint
({
hint: 'label',
hint: 'label',
}, 'o');
}, 'o');
expect(updatedItem.droplab_hidden).toBe(true);
expect(updatedItem.droplab_hidden).toBe(true);
});
});
it('should return droplab_hidden false when item has no hint', () => {
it('should return droplab_hidden false when item has no hint', () => {
const updatedItem = gl.DropdownUtils.filter
Method
({}, '');
const updatedItem = gl.DropdownUtils.filter
Hint
({}, '');
expect(updatedItem.droplab_hidden).toBe(false);
expect(updatedItem.droplab_hidden).toBe(false);
});
});
});
});
...
...
spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js.es6
View file @
e197f27f
...
@@ -21,13 +21,6 @@
...
@@ -21,13 +21,6 @@
});
});
describe('input has no existing value', () => {
describe('input has no existing value', () => {
beforeEach(() => {
spyOn(gl.FilteredSearchTokenizer, 'processTokens')
.and.callFake(() => ({
lastToken: {},
}));
});
it('should add word', () => {
it('should add word', () => {
gl.FilteredSearchDropdownManager.addWordToInput('firstWord');
gl.FilteredSearchDropdownManager.addWordToInput('firstWord');
expect(getInputValue()).toBe('firstWord');
expect(getInputValue()).toBe('firstWord');
...
@@ -61,26 +54,13 @@
...
@@ -61,26 +54,13 @@
value: 'roo',
value: 'roo',
};
};
spyOn(gl.FilteredSearchTokenizer, 'processTokens').and.callFake(() => ({
lastToken,
}));
document.querySelector('.filtered-search').value = `${lastToken.key}:${lastToken.value}`;
document.querySelector('.filtered-search').value = `${lastToken.key}:${lastToken.value}`;
gl.FilteredSearchDropdownManager.addWordToInput('root');
gl.FilteredSearchDropdownManager.addWordToInput('root');
expect(getInputValue()).toBe('author:root');
expect(getInputValue()).toBe('author:root');
});
});
it('should only add the remaining characters of the word (contains space)', () => {
it('should only add the remaining characters of the word (contains space)', () => {
const lastToken = {
document.querySelector('.filtered-search').value = 'label:~"test';
key: 'label',
value: 'test me',
};
spyOn(gl.FilteredSearchTokenizer, 'processTokens').and.callFake(() => ({
lastToken,
}));
document.querySelector('.filtered-search').value = `${lastToken.key}:"${lastToken.value}"`;
gl.FilteredSearchDropdownManager.addWordToInput('~\'"test me"\'');
gl.FilteredSearchDropdownManager.addWordToInput('~\'"test me"\'');
expect(getInputValue()).toBe('label:~\'"test me"\'');
expect(getInputValue()).toBe('label:~\'"test me"\'');
});
});
...
...
spec/javascripts/filtered_search/filtered_search_tokenizer_spec.js.es6
View file @
e197f27f
...
@@ -4,267 +4,100 @@
...
@@ -4,267 +4,100 @@
(() => {
(() => {
describe('Filtered Search Tokenizer', () => {
describe('Filtered Search Tokenizer', () => {
describe('parseToken', () => {
it('should return key, value and symbol', () => {
const { tokenKey, tokenValue, tokenSymbol } = gl.FilteredSearchTokenizer
.parseToken('author:@user');
expect(tokenKey).toBe('author');
expect(tokenValue).toBe('@user');
expect(tokenSymbol).toBe('@');
});
it('should return value with spaces', () => {
const { tokenKey, tokenValue, tokenSymbol } = gl.FilteredSearchTokenizer
.parseToken('label:~"test me"');
expect(tokenKey).toBe('label');
expect(tokenValue).toBe('~"test me"');
expect(tokenSymbol).toBe('~');
});
});
describe('getLastTokenObject', () => {
beforeEach(() => {
spyOn(gl.FilteredSearchTokenizer, 'getLastToken').and.callFake(input => input);
});
it('should return key and value', () => {
const { key, value } = gl.FilteredSearchTokenizer.getLastTokenObject('author:@root');
expect(key).toBe('author');
expect(value).toBe(':@root');
});
describe('string without colon', () => {
let lastTokenObject;
beforeEach(() => {
lastTokenObject = gl.FilteredSearchTokenizer.getLastTokenObject('author');
});
it('should return key as an empty string', () => {
expect(lastTokenObject.key).toBe('');
});
it('should return input as value', () => {
expect(lastTokenObject.value).toBe('author');
});
});
});
describe('getLastToken', () => {
it('returns entire string when there is only one word', () => {
const lastToken = gl.FilteredSearchTokenizer.getLastToken('input');
expect(lastToken).toBe('input');
});
it('returns last word when there are multiple words', () => {
const lastToken = gl.FilteredSearchTokenizer.getLastToken('this is a few words');
expect(lastToken).toBe('words');
});
it('returns last token when there are multiple tokens', () => {
const lastToken = gl.FilteredSearchTokenizer
.getLastToken('label:fun author:root milestone:2.0');
expect(lastToken).toBe('milestone:2.0');
});
it('returns last token containing spaces escaped by double quotes', () => {
const lastToken = gl.FilteredSearchTokenizer
.getLastToken('label:fun author:root milestone:2.0 label:~"Feature Proposal"');
expect(lastToken).toBe('label:~"Feature Proposal"');
});
it('returns last token containing spaces escaped by single quotes', () => {
const lastToken = gl.FilteredSearchTokenizer
.getLastToken('label:fun author:root milestone:2.0 label:~\'Feature Proposal\'');
expect(lastToken).toBe('label:~\'Feature Proposal\'');
});
it('returns last token containing special characters', () => {
const lastToken = gl.FilteredSearchTokenizer
.getLastToken('label:fun author:root milestone:2.0 label:~!@#$%^&*()');
expect(lastToken).toBe('label:~!@#$%^&*()');
});
});
describe('processTokens', () => {
describe('processTokens', () => {
describe('input does not contain any tokens', () => {
it('returns for input containing only search value', () => {
let results;
const results = gl.FilteredSearchTokenizer.processTokens('searchTerm');
beforeEach(() => {
expect(results.searchToken).toBe('searchTerm');
results = gl.FilteredSearchTokenizer.processTokens('searchTerm');
expect(results.tokens.length).toBe(0);
});
expect(results.lastToken).toBe(results.searchToken);
});
it('returns input as searchToken', () => {
expect(results.searchToken).toBe('searchTerm');
it('returns for input containing only tokens', () => {
});
const results = gl.FilteredSearchTokenizer
.processTokens('author:@root label:~"Very Important" milestone:%v1.0 assignee:none');
it('returns tokens as an empty array', () => {
expect(results.searchToken).toBe('');
expect(results.tokens.length).toBe(0);
expect(results.tokens.length).toBe(4);
});
expect(results.tokens[3]).toBe(results.lastToken);
it('returns lastToken equal to searchToken', () => {
expect(results.tokens[0].key).toBe('author');
expect(results.lastToken).toBe(results.searchToken);
expect(results.tokens[0].value).toBe('root');
});
expect(results.tokens[0].symbol).toBe('@');
});
expect(results.tokens[1].key).toBe('label');
describe('input contains only tokens', () => {
expect(results.tokens[1].value).toBe('Very Important');
let results;
expect(results.tokens[1].symbol).toBe('~');
beforeEach(() => {
results = gl.FilteredSearchTokenizer
expect(results.tokens[2].key).toBe('milestone');
.processTokens('author:@root label:~"Very Important" milestone:%v1.0 assignee:none');
expect(results.tokens[2].value).toBe('v1.0');
});
expect(results.tokens[2].symbol).toBe('%');
it('returns searchToken as an empty string', () => {
expect(results.tokens[3].key).toBe('assignee');
expect(results.searchToken).toBe('');
expect(results.tokens[3].value).toBe('none');
});
expect(results.tokens[3].symbol).toBe('');
});
it('returns tokens array of size equal to the number of tokens in input', () => {
expect(results.tokens.length).toBe(4);
it('returns for input starting with search value and ending with tokens', () => {
});
const results = gl.FilteredSearchTokenizer
.processTokens('searchTerm anotherSearchTerm milestone:none');
it('returns tokens array that matches the tokens found in input', () => {
expect(results.searchToken).toBe('searchTerm anotherSearchTerm');
expect(results.tokens[0].key).toBe('author');
expect(results.tokens.length).toBe(1);
expect(results.tokens[0].value).toBe('@root');
expect(results.tokens[0]).toBe(results.lastToken);
expect(results.tokens[0].wildcard).toBe(false);
expect(results.tokens[0].key).toBe('milestone');
expect(results.tokens[0].value).toBe('none');
expect(results.tokens[1].key).toBe('label');
expect(results.tokens[0].symbol).toBe('');
expect(results.tokens[1].value).toBe('~Very Important');
});
expect(results.tokens[1].wildcard).toBe(false);
it('returns for input starting with tokens and ending with search value', () => {
expect(results.tokens[2].key).toBe('milestone');
const results = gl.FilteredSearchTokenizer
expect(results.tokens[2].value).toBe('%v1.0');
.processTokens('assignee:@user searchTerm');
expect(results.tokens[2].wildcard).toBe(false);
expect(results.searchToken).toBe('searchTerm');
expect(results.tokens[3].key).toBe('assignee');
expect(results.tokens.length).toBe(1);
expect(results.tokens[3].value).toBe('none');
expect(results.tokens[0].key).toBe('assignee');
expect(results.tokens[3].wildcard).toBe(true);
expect(results.tokens[0].value).toBe('user');
});
expect(results.tokens[0].symbol).toBe('@');
expect(results.lastToken).toBe(results.searchToken);
it('returns lastToken equal to the last object in the tokens array', () => {
});
expect(results.tokens[3]).toBe(results.lastToken);
});
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');
describe('input starts with search value and ends with tokens', () => {
let results;
expect(results.searchToken).toBe('searchTerm anotherSearchTerm');
beforeEach(() => {
expect(results.tokens.length).toBe(3);
results = gl.FilteredSearchTokenizer
expect(results.tokens[2]).toBe(results.lastToken);
.processTokens('searchTerm anotherSearchTerm milestone:none');
});
expect(results.tokens[0].key).toBe('author');
expect(results.tokens[0].value).toBe('root');
it('returns searchToken', () => {
expect(results.tokens[0].symbol).toBe('@');
expect(results.searchToken).toBe('searchTerm anotherSearchTerm');
});
expect(results.tokens[1].key).toBe('label');
expect(results.tokens[1].value).toBe('Won\'t fix');
it('returns correct number of tokens', () => {
expect(results.tokens[1].symbol).toBe('~');
expect(results.tokens.length).toBe(1);
});
expect(results.tokens[2].key).toBe('milestone');
expect(results.tokens[2].value).toBe('none');
it('returns correct tokens', () => {
expect(results.tokens[2].symbol).toBe('');
expect(results.tokens[0].key).toBe('milestone');
});
expect(results.tokens[0].value).toBe('none');
expect(results.tokens[0].wildcard).toBe(true);
it('returns for input containing search value in between tokens', () => {
});
const results = gl.FilteredSearchTokenizer
.processTokens('author:@root searchTerm assignee:none anotherSearchTerm label:~Doing');
it('returns lastToken', () => {
expect(results.searchToken).toBe('searchTerm anotherSearchTerm');
expect(results.tokens[0]).toBe(results.lastToken);
expect(results.tokens.length).toBe(3);
});
expect(results.tokens[2]).toBe(results.lastToken);
});
expect(results.tokens[0].key).toBe('author');
describe('input starts with token and ends with search value', () => {
expect(results.tokens[0].value).toBe('root');
let results;
expect(results.tokens[0].symbol).toBe('@');
beforeEach(() => {
results = gl.FilteredSearchTokenizer
expect(results.tokens[1].key).toBe('assignee');
.processTokens('assignee:@user searchTerm');
expect(results.tokens[1].value).toBe('none');
});
expect(results.tokens[1].symbol).toBe('');
it('returns searchToken', () => {
expect(results.tokens[2].key).toBe('label');
expect(results.searchToken).toBe('searchTerm');
expect(results.tokens[2].value).toBe('Doing');
});
expect(results.tokens[2].symbol).toBe('~');
it('returns correct number of tokens', () => {
expect(results.tokens.length).toBe(1);
});
it('returns correct tokens', () => {
expect(results.tokens[0].key).toBe('assignee');
expect(results.tokens[0].value).toBe('@user');
expect(results.tokens[0].wildcard).toBe(false);
});
it('returns lastToken as the searchTerm', () => {
expect(results.lastToken).toBe(results.searchToken);
});
});
describe('input contains search value wrapped between tokens', () => {
let results;
beforeEach(() => {
results = gl.FilteredSearchTokenizer
.processTokens('author:@root label:~"Won\'t fix" searchTerm anotherSearchTerm milestone:none');
});
it('returns searchToken', () => {
expect(results.searchToken).toBe('searchTerm anotherSearchTerm');
});
it('returns correct number of tokens', () => {
expect(results.tokens.length).toBe(3);
});
it('returns tokens array in the order it was processed', () => {
expect(results.tokens[0].key).toBe('author');
expect(results.tokens[0].value).toBe('@root');
expect(results.tokens[0].wildcard).toBe(false);
expect(results.tokens[1].key).toBe('label');
expect(results.tokens[1].value).toBe('~Won\'t fix');
expect(results.tokens[1].wildcard).toBe(false);
expect(results.tokens[2].key).toBe('milestone');
expect(results.tokens[2].value).toBe('none');
expect(results.tokens[2].wildcard).toBe(true);
});
it('returns lastToken', () => {
expect(results.tokens[2]).toBe(results.lastToken);
});
});
describe('input search value is spaced in between tokens', () => {
let results;
beforeEach(() => {
results = gl.FilteredSearchTokenizer
.processTokens('author:@root searchTerm assignee:none anotherSearchTerm label:~Doing');
});
it('returns searchToken', () => {
expect(results.searchToken).toBe('searchTerm anotherSearchTerm');
});
it('returns correct number of tokens', () => {
expect(results.tokens.length).toBe(3);
});
it('returns tokens array in the order it was processed', () => {
expect(results.tokens[0].key).toBe('author');
expect(results.tokens[0].value).toBe('@root');
expect(results.tokens[0].wildcard).toBe(false);
expect(results.tokens[1].key).toBe('assignee');
expect(results.tokens[1].value).toBe('none');
expect(results.tokens[1].wildcard).toBe(true);
expect(results.tokens[2].key).toBe('label');
expect(results.tokens[2].value).toBe('~Doing');
expect(results.tokens[2].wildcard).toBe(false);
});
it('returns lastToken', () => {
expect(results.tokens[2]).toBe(results.lastToken);
});
});
});
});
});
});
});
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment