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
1
Merge Requests
1
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
nexedi
gitlab-ce
Commits
08a09c6b
Commit
08a09c6b
authored
Apr 17, 2017
by
Alfredo Sumaran
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Remove IIFEs in filtered_search_bundle.js
parent
eeaeb275
Changes
15
Hide whitespace changes
Inline
Side-by-side
Showing
15 changed files
with
1825 additions
and
1855 deletions
+1825
-1855
app/assets/javascripts/filtered_search/dropdown_hint.js
app/assets/javascripts/filtered_search/dropdown_hint.js
+60
-62
app/assets/javascripts/filtered_search/dropdown_non_user.js
app/assets/javascripts/filtered_search/dropdown_non_user.js
+37
-39
app/assets/javascripts/filtered_search/dropdown_user.js
app/assets/javascripts/filtered_search/dropdown_user.js
+52
-54
app/assets/javascripts/filtered_search/dropdown_utils.js
app/assets/javascripts/filtered_search/dropdown_utils.js
+148
-150
app/assets/javascripts/filtered_search/filtered_search_dropdown.js
...s/javascripts/filtered_search/filtered_search_dropdown.js
+96
-98
app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
...ripts/filtered_search/filtered_search_dropdown_manager.js
+148
-150
app/assets/javascripts/filtered_search/filtered_search_manager.js
...ts/javascripts/filtered_search/filtered_search_manager.js
+387
-389
app/assets/javascripts/filtered_search/filtered_search_token_keys.js
...javascripts/filtered_search/filtered_search_token_keys.js
+83
-85
app/assets/javascripts/filtered_search/filtered_search_tokenizer.js
.../javascripts/filtered_search/filtered_search_tokenizer.js
+49
-51
spec/javascripts/filtered_search/dropdown_user_spec.js
spec/javascripts/filtered_search/dropdown_user_spec.js
+46
-48
spec/javascripts/filtered_search/dropdown_utils_spec.js
spec/javascripts/filtered_search/dropdown_utils_spec.js
+222
-224
spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js
.../filtered_search/filtered_search_dropdown_manager_spec.js
+71
-73
spec/javascripts/filtered_search/filtered_search_manager_spec.js
...vascripts/filtered_search/filtered_search_manager_spec.js
+196
-198
spec/javascripts/filtered_search/filtered_search_token_keys_spec.js
...cripts/filtered_search/filtered_search_token_keys_spec.js
+103
-105
spec/javascripts/filtered_search/filtered_search_tokenizer_spec.js
...scripts/filtered_search/filtered_search_tokenizer_spec.js
+127
-129
No files found.
app/assets/javascripts/filtered_search/dropdown_hint.js
View file @
08a09c6b
...
...
@@ -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
;
app/assets/javascripts/filtered_search/dropdown_non_user.js
View file @
08a09c6b
...
...
@@ -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
;
app/assets/javascripts/filtered_search/dropdown_user.js
View file @
08a09c6b
...
...
@@ -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
;
app/assets/javascripts/filtered_search/dropdown_utils.js
View file @
08a09c6b
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
;
app/assets/javascripts/filtered_search/filtered_search_dropdown.js
View file @
08a09c6b
(()
=>
{
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
;
app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
View file @
08a09c6b
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
;
app/assets/javascripts/filtered_search/filtered_search_manager.js
View file @
08a09c6b
...
...
@@ -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
;
app/assets/javascripts/filtered_search/filtered_search_token_keys.js
View file @
08a09c6b
(()
=>
{
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
;
app/assets/javascripts/filtered_search/filtered_search_tokenizer.js
View file @
08a09c6b
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
;
spec/javascripts/filtered_search/dropdown_user_spec.js
View file @
08a09c6b
...
...
@@ -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
=
{};
});
});
})
()
;
});
spec/javascripts/filtered_search/dropdown_utils_spec.js
View file @
08a09c6b
...
...
@@ -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
);
});
});
});
})
()
;
});
spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js
View file @
08a09c6b
...
...
@@ -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
(
''
);
});
});
});
})
()
;
});
spec/javascripts/filtered_search/filtered_search_manager_spec.js
View file @
08a09c6b
...
...
@@ -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 delet
e key is pressed
'
,
()
=>
{
expect
(
getVisualTokens
().
length
).
toEqual
(
1
);
it
(
'
removes selected token when the backspac
e key is pressed
'
,
()
=>
{
expect
(
getVisualTokens
().
length
).
toEqual
(
1
);
dispatchDelet
eEvent
(
document
,
'
keydown
'
);
dispatchBackspac
eEvent
(
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
);
});
});
})
()
;
});
spec/javascripts/filtered_search/filtered_search_token_keys_spec.js
View file @
08a09c6b
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
]);
});
});
})
()
;
});
spec/javascripts/filtered_search/filtered_search_tokenizer_spec.js
View file @
08a09c6b
...
...
@@ -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
(
'
~
'
);
});
});
})
()
;
});
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