Commit 5ef79794 authored by Illya Klymov's avatar Illya Klymov

Merge branch 'mrincon-rename-token-values-to-suggestions' into 'master'

Refactor to rename "token values" to "suggestions" in base token and filtered search

See merge request gitlab-org/gitlab!64294
parents 888354d3 43f2004e
......@@ -215,35 +215,35 @@ export function urlQueryToFilter(query = '', options = {}) {
/**
* Returns array of token values from localStorage
* based on provided recentTokenValuesStorageKey
* based on provided recentSuggestionsStorageKey
*
* @param {String} recentTokenValuesStorageKey
* @param {String} recentSuggestionsStorageKey
* @returns
*/
export function getRecentlyUsedTokenValues(recentTokenValuesStorageKey) {
let recentlyUsedTokenValues = [];
export function getRecentlyUsedSuggestions(recentSuggestionsStorageKey) {
let recentlyUsedSuggestions = [];
if (AccessorUtilities.isLocalStorageAccessSafe()) {
recentlyUsedTokenValues = JSON.parse(localStorage.getItem(recentTokenValuesStorageKey)) || [];
recentlyUsedSuggestions = JSON.parse(localStorage.getItem(recentSuggestionsStorageKey)) || [];
}
return recentlyUsedTokenValues;
return recentlyUsedSuggestions;
}
/**
* Sets provided token value to recently used array
* within localStorage for provided recentTokenValuesStorageKey
* within localStorage for provided recentSuggestionsStorageKey
*
* @param {String} recentTokenValuesStorageKey
* @param {String} recentSuggestionsStorageKey
* @param {Object} tokenValue
*/
export function setTokenValueToRecentlyUsed(recentTokenValuesStorageKey, tokenValue) {
const recentlyUsedTokenValues = getRecentlyUsedTokenValues(recentTokenValuesStorageKey);
export function setTokenValueToRecentlyUsed(recentSuggestionsStorageKey, tokenValue) {
const recentlyUsedSuggestions = getRecentlyUsedSuggestions(recentSuggestionsStorageKey);
recentlyUsedTokenValues.splice(0, 0, { ...tokenValue });
recentlyUsedSuggestions.splice(0, 0, { ...tokenValue });
if (AccessorUtilities.isLocalStorageAccessSafe()) {
localStorage.setItem(
recentTokenValuesStorageKey,
JSON.stringify(uniqWith(recentlyUsedTokenValues, isEqual).slice(0, MAX_RECENT_TOKENS_SIZE)),
recentSuggestionsStorageKey,
JSON.stringify(uniqWith(recentlyUsedSuggestions, isEqual).slice(0, MAX_RECENT_TOKENS_SIZE)),
);
}
}
......@@ -74,13 +74,13 @@ export default {
:config="config"
:value="value"
:active="active"
:tokens-list-loading="loading"
:token-values="authors"
:suggestions-loading="loading"
:suggestions="authors"
:fn-active-token-value="getActiveAuthor"
:default-token-values="defaultAuthors"
:preloaded-token-values="preloadedAuthors"
:recent-token-values-storage-key="config.recentTokenValuesStorageKey"
@fetch-token-values="fetchAuthorBySearchTerm"
:default-suggestions="defaultAuthors"
:preloaded-suggestions="preloadedAuthors"
:recent-suggestions-storage-key="config.recentSuggestionsStorageKey"
@fetch-suggestions="fetchAuthorBySearchTerm"
v-on="$listeners"
>
<template #view="{ viewTokenProps: { inputValue, activeTokenValue } }">
......@@ -93,9 +93,9 @@ export default {
/>
<span>{{ activeTokenValue ? activeTokenValue.name : inputValue }}</span>
</template>
<template #token-values-list="{ tokenValues }">
<template #suggestions-list="{ suggestions }">
<gl-filtered-search-suggestion
v-for="author in tokenValues"
v-for="author in suggestions"
:key="author.username"
:value="author.username"
>
......
......@@ -8,7 +8,7 @@ import {
} from '@gitlab/ui';
import { DEBOUNCE_DELAY } from '../constants';
import { getRecentlyUsedTokenValues, setTokenValueToRecentlyUsed } from '../filtered_search_utils';
import { getRecentlyUsedSuggestions, setTokenValueToRecentlyUsed } from '../filtered_search_utils';
export default {
components: {
......@@ -31,12 +31,12 @@ export default {
type: Boolean,
required: true,
},
tokensListLoading: {
suggestionsLoading: {
type: Boolean,
required: false,
default: false,
},
tokenValues: {
suggestions: {
type: Array,
required: false,
default: () => [],
......@@ -44,21 +44,21 @@ export default {
fnActiveTokenValue: {
type: Function,
required: false,
default: (tokenValues, currentTokenValue) => {
return tokenValues.find(({ value }) => value === currentTokenValue);
default: (suggestions, currentTokenValue) => {
return suggestions.find(({ value }) => value === currentTokenValue);
},
},
defaultTokenValues: {
defaultSuggestions: {
type: Array,
required: false,
default: () => [],
},
preloadedTokenValues: {
preloadedSuggestions: {
type: Array,
required: false,
default: () => [],
},
recentTokenValuesStorageKey: {
recentSuggestionsStorageKey: {
type: String,
required: false,
default: '',
......@@ -77,21 +77,21 @@ export default {
data() {
return {
searchKey: '',
recentTokenValues: this.recentTokenValuesStorageKey
? getRecentlyUsedTokenValues(this.recentTokenValuesStorageKey)
recentSuggestions: this.recentSuggestionsStorageKey
? getRecentlyUsedSuggestions(this.recentSuggestionsStorageKey)
: [],
loading: false,
};
},
computed: {
isRecentTokenValuesEnabled() {
return Boolean(this.recentTokenValuesStorageKey);
isRecentSuggestionsEnabled() {
return Boolean(this.recentSuggestionsStorageKey);
},
recentTokenIds() {
return this.recentTokenValues.map((tokenValue) => tokenValue[this.valueIdentifier]);
return this.recentSuggestions.map((tokenValue) => tokenValue[this.valueIdentifier]);
},
preloadedTokenIds() {
return this.preloadedTokenValues.map((tokenValue) => tokenValue[this.valueIdentifier]);
return this.preloadedSuggestions.map((tokenValue) => tokenValue[this.valueIdentifier]);
},
currentTokenValue() {
if (this.fnCurrentTokenValue) {
......@@ -100,17 +100,17 @@ export default {
return this.value.data.toLowerCase();
},
activeTokenValue() {
return this.fnActiveTokenValue(this.tokenValues, this.currentTokenValue);
return this.fnActiveTokenValue(this.suggestions, this.currentTokenValue);
},
/**
* Return all the tokenValues when searchKey is present
* otherwise return only the tokenValues which aren't
* Return all the suggestions when searchKey is present
* otherwise return only the suggestions which aren't
* present in "Recently used"
*/
availableTokenValues() {
availableSuggestions() {
return this.searchKey
? this.tokenValues
: this.tokenValues.filter(
? this.suggestions
: this.suggestions.filter(
(tokenValue) =>
!this.recentTokenIds.includes(tokenValue[this.valueIdentifier]) &&
!this.preloadedTokenIds.includes(tokenValue[this.valueIdentifier]),
......@@ -121,8 +121,8 @@ export default {
active: {
immediate: true,
handler(newValue) {
if (!newValue && !this.tokenValues.length) {
this.$emit('fetch-token-values', this.value.data);
if (!newValue && !this.suggestions.length) {
this.$emit('fetch-suggestions', this.value.data);
}
},
},
......@@ -131,7 +131,7 @@ export default {
handleInput({ data }) {
this.searchKey = data;
setTimeout(() => {
if (!this.tokensListLoading) this.$emit('fetch-token-values', data);
if (!this.suggestionsLoading) this.$emit('fetch-suggestions', data);
}, DEBOUNCE_DELAY);
},
handleTokenValueSelected(activeTokenValue) {
......@@ -140,11 +140,11 @@ export default {
// 2. User has actually selected a value
// 3. Selected value is not part of preloaded list.
if (
this.isRecentTokenValuesEnabled &&
this.isRecentSuggestionsEnabled &&
activeTokenValue &&
!this.preloadedTokenIds.includes(activeTokenValue[this.valueIdentifier])
) {
setTokenValueToRecentlyUsed(this.recentTokenValuesStorageKey, activeTokenValue);
setTokenValueToRecentlyUsed(this.recentSuggestionsStorageKey, activeTokenValue);
}
},
},
......@@ -168,9 +168,9 @@ export default {
<slot name="view" :view-token-props="{ ...viewTokenProps, activeTokenValue }"></slot>
</template>
<template #suggestions>
<template v-if="defaultTokenValues.length">
<template v-if="defaultSuggestions.length">
<gl-filtered-search-suggestion
v-for="token in defaultTokenValues"
v-for="token in defaultSuggestions"
:key="token.value"
:value="token.value"
>
......@@ -178,19 +178,19 @@ export default {
</gl-filtered-search-suggestion>
<gl-dropdown-divider />
</template>
<template v-if="isRecentTokenValuesEnabled && recentTokenValues.length && !searchKey">
<template v-if="isRecentSuggestionsEnabled && recentSuggestions.length && !searchKey">
<gl-dropdown-section-header>{{ __('Recently used') }}</gl-dropdown-section-header>
<slot name="token-values-list" :token-values="recentTokenValues"></slot>
<slot name="suggestions-list" :suggestions="recentSuggestions"></slot>
<gl-dropdown-divider />
</template>
<slot
v-if="preloadedTokenValues.length && !searchKey"
name="token-values-list"
:token-values="preloadedTokenValues"
v-if="preloadedSuggestions.length && !searchKey"
name="suggestions-list"
:suggestions="preloadedSuggestions"
></slot>
<gl-loading-icon v-if="tokensListLoading" />
<gl-loading-icon v-if="suggestionsLoading" />
<template v-else>
<slot name="token-values-list" :token-values="availableTokenValues"></slot>
<slot name="suggestions-list" :suggestions="availableSuggestions"></slot>
</template>
</template>
</gl-filtered-search-token>
......
......@@ -96,12 +96,12 @@ export default {
:config="config"
:value="value"
:active="active"
:tokens-list-loading="loading"
:token-values="labels"
:suggestions-loading="loading"
:suggestions="labels"
:fn-active-token-value="getActiveLabel"
:default-token-values="defaultLabels"
:recent-token-values-storage-key="config.recentTokenValuesStorageKey"
@fetch-token-values="fetchLabelBySearchTerm"
:default-suggestions="defaultLabels"
:recent-suggestions-storage-key="config.recentSuggestionsStorageKey"
@fetch-suggestions="fetchLabelBySearchTerm"
v-on="$listeners"
>
<template
......@@ -115,9 +115,9 @@ export default {
>~{{ activeTokenValue ? getLabelName(activeTokenValue) : inputValue }}</gl-token
>
</template>
<template #token-values-list="{ tokenValues }">
<template #suggestions-list="{ suggestions }">
<gl-filtered-search-suggestion
v-for="label in tokenValues"
v-for="label in suggestions"
:key="label.id"
:value="getLabelName(label)"
>
......
......@@ -65,7 +65,7 @@ export default {
symbol: '@',
token: AuthorToken,
operators: OPERATOR_IS_ONLY,
recentTokenValuesStorageKey: `${this.groupFullPath}-epics-recent-tokens-author_username`,
recentSuggestionsStorageKey: `${this.groupFullPath}-epics-recent-tokens-author_username`,
fetchAuthors: Api.users.bind(Api),
preloadedAuthors,
},
......@@ -77,7 +77,7 @@ export default {
symbol: '~',
token: LabelToken,
operators: OPERATOR_IS_ONLY,
recentTokenValuesStorageKey: `${this.groupFullPath}-epics-recent-tokens-label_name`,
recentSuggestionsStorageKey: `${this.groupFullPath}-epics-recent-tokens-label_name`,
fetchLabels: (search = '') => {
const params = {
only_group_labels: true,
......
......@@ -775,7 +775,7 @@ export const mockAuthorTokenConfig = {
symbol: '@',
token: AuthorToken,
operators: OPERATOR_IS_ONLY,
recentTokenValuesStorageKey: 'gitlab-org-epics-recent-tokens-author_username',
recentSuggestionsStorageKey: 'gitlab-org-epics-recent-tokens-author_username',
fetchAuthors: expect.any(Function),
preloadedAuthors: [],
};
......@@ -788,7 +788,7 @@ export const mockLabelTokenConfig = {
symbol: '~',
token: LabelToken,
operators: OPERATOR_IS_ONLY,
recentTokenValuesStorageKey: 'gitlab-org-epics-recent-tokens-label_name',
recentSuggestionsStorageKey: 'gitlab-org-epics-recent-tokens-label_name',
fetchLabels: expect.any(Function),
};
......
......@@ -11,7 +11,7 @@ import {
processFilters,
filterToQueryObject,
urlQueryToFilter,
getRecentlyUsedTokenValues,
getRecentlyUsedSuggestions,
setTokenValueToRecentlyUsed,
} from '~/vue_shared/components/filtered_search_bar/filtered_search_utils';
......@@ -328,32 +328,32 @@ describe('urlQueryToFilter', () => {
);
});
describe('getRecentlyUsedTokenValues', () => {
describe('getRecentlyUsedSuggestions', () => {
useLocalStorageSpy();
beforeEach(() => {
localStorage.removeItem(mockStorageKey);
});
it('returns array containing recently used token values from provided recentTokenValuesStorageKey', () => {
it('returns array containing recently used token values from provided recentSuggestionsStorageKey', () => {
setLocalStorageAvailability(true);
const mockExpectedArray = [{ foo: 'bar' }];
localStorage.setItem(mockStorageKey, JSON.stringify(mockExpectedArray));
expect(getRecentlyUsedTokenValues(mockStorageKey)).toEqual(mockExpectedArray);
expect(getRecentlyUsedSuggestions(mockStorageKey)).toEqual(mockExpectedArray);
});
it('returns empty array when provided recentTokenValuesStorageKey does not have anything in localStorage', () => {
it('returns empty array when provided recentSuggestionsStorageKey does not have anything in localStorage', () => {
setLocalStorageAvailability(true);
expect(getRecentlyUsedTokenValues(mockStorageKey)).toEqual([]);
expect(getRecentlyUsedSuggestions(mockStorageKey)).toEqual([]);
});
it('returns empty array when when access to localStorage is not available', () => {
setLocalStorageAvailability(false);
expect(getRecentlyUsedTokenValues(mockStorageKey)).toEqual([]);
expect(getRecentlyUsedSuggestions(mockStorageKey)).toEqual([]);
});
});
......@@ -366,7 +366,7 @@ describe('setTokenValueToRecentlyUsed', () => {
localStorage.removeItem(mockStorageKey);
});
it('adds provided tokenValue to localStorage for recentTokenValuesStorageKey', () => {
it('adds provided tokenValue to localStorage for recentSuggestionsStorageKey', () => {
setLocalStorageAvailability(true);
setTokenValueToRecentlyUsed(mockStorageKey, mockTokenValue1);
......
......@@ -94,7 +94,7 @@ describe('AuthorToken', () => {
it('calls `config.fetchAuthors` with provided searchTerm param', () => {
jest.spyOn(wrapper.vm.config, 'fetchAuthors');
getBaseToken().vm.$emit('fetch-token-values', mockAuthors[0].username);
getBaseToken().vm.$emit('fetch-suggestions', mockAuthors[0].username);
expect(wrapper.vm.config.fetchAuthors).toHaveBeenCalledWith(
mockAuthorToken.fetchPath,
......@@ -105,17 +105,17 @@ describe('AuthorToken', () => {
it('sets response to `authors` when request is succesful', () => {
jest.spyOn(wrapper.vm.config, 'fetchAuthors').mockResolvedValue(mockAuthors);
getBaseToken().vm.$emit('fetch-token-values', 'root');
getBaseToken().vm.$emit('fetch-suggestions', 'root');
return waitForPromises().then(() => {
expect(getBaseToken().props('tokenValues')).toEqual(mockAuthors);
expect(getBaseToken().props('suggestions')).toEqual(mockAuthors);
});
});
it('calls `createFlash` with flash error message when request fails', () => {
jest.spyOn(wrapper.vm.config, 'fetchAuthors').mockRejectedValue({});
getBaseToken().vm.$emit('fetch-token-values', 'root');
getBaseToken().vm.$emit('fetch-suggestions', 'root');
return waitForPromises().then(() => {
expect(createFlash).toHaveBeenCalledWith({
......@@ -127,17 +127,17 @@ describe('AuthorToken', () => {
it('sets `loading` to false when request completes', async () => {
jest.spyOn(wrapper.vm.config, 'fetchAuthors').mockRejectedValue({});
getBaseToken().vm.$emit('fetch-token-values', 'root');
getBaseToken().vm.$emit('fetch-suggestions', 'root');
await waitForPromises();
expect(getBaseToken().props('tokensListLoading')).toBe(false);
expect(getBaseToken().props('suggestionsLoading')).toBe(false);
});
});
});
describe('template', () => {
const activateTokenValuesList = async () => {
const activateSuggestionsList = async () => {
const tokenSegments = wrapper.findAllComponents(GlFilteredSearchTokenSegment);
const suggestionsSegment = tokenSegments.at(2);
suggestionsSegment.vm.$emit('activate');
......@@ -154,7 +154,7 @@ describe('AuthorToken', () => {
expect(baseTokenEl.exists()).toBe(true);
expect(baseTokenEl.props()).toMatchObject({
tokenValues: mockAuthors,
suggestions: mockAuthors,
fnActiveTokenValue: wrapper.vm.getActiveAuthor,
});
});
......@@ -221,7 +221,7 @@ describe('AuthorToken', () => {
stubs: { Portal: true },
});
await activateTokenValuesList();
await activateSuggestionsList();
const suggestions = wrapper.findAll(GlFilteredSearchSuggestion);
......@@ -252,7 +252,7 @@ describe('AuthorToken', () => {
stubs: { Portal: true },
});
await activateTokenValuesList();
await activateSuggestionsList();
const suggestions = wrapper.findAll(GlFilteredSearchSuggestion);
......
......@@ -7,7 +7,7 @@ import {
import { DEFAULT_LABELS } from '~/vue_shared/components/filtered_search_bar/constants';
import {
getRecentlyUsedTokenValues,
getRecentlyUsedSuggestions,
setTokenValueToRecentlyUsed,
} from '~/vue_shared/components/filtered_search_bar/filtered_search_utils';
import BaseToken from '~/vue_shared/components/filtered_search_bar/tokens/base_token.vue';
......@@ -49,10 +49,10 @@ const mockProps = {
config: mockLabelToken,
value: { data: '' },
active: false,
tokenValues: [],
tokensListLoading: false,
defaultTokenValues: DEFAULT_LABELS,
recentTokenValuesStorageKey: mockStorageKey,
suggestions: [],
suggestionsLoading: false,
defaultSuggestions: DEFAULT_LABELS,
recentSuggestionsStorageKey: mockStorageKey,
fnCurrentTokenValue: jest.fn(),
};
......@@ -83,7 +83,7 @@ describe('BaseToken', () => {
props: {
...mockProps,
value: { data: `"${mockRegularLabel.title}"` },
tokenValues: mockLabels,
suggestions: mockLabels,
},
});
});
......@@ -93,8 +93,8 @@ describe('BaseToken', () => {
});
describe('data', () => {
it('calls `getRecentlyUsedTokenValues` to populate `recentTokenValues` when `recentTokenValuesStorageKey` is defined', () => {
expect(getRecentlyUsedTokenValues).toHaveBeenCalledWith(mockStorageKey);
it('calls `getRecentlyUsedSuggestions` to populate `recentSuggestions` when `recentSuggestionsStorageKey` is defined', () => {
expect(getRecentlyUsedSuggestions).toHaveBeenCalledWith(mockStorageKey);
});
});
......@@ -147,15 +147,15 @@ describe('BaseToken', () => {
wrapperWithTokenActive.destroy();
});
it('emits `fetch-token-values` event on the component when value of this prop is changed to false and `tokenValues` array is empty', async () => {
it('emits `fetch-suggestions` event on the component when value of this prop is changed to false and `suggestions` array is empty', async () => {
wrapperWithTokenActive.setProps({
active: false,
});
await wrapperWithTokenActive.vm.$nextTick();
expect(wrapperWithTokenActive.emitted('fetch-token-values')).toBeTruthy();
expect(wrapperWithTokenActive.emitted('fetch-token-values')).toEqual([
expect(wrapperWithTokenActive.emitted('fetch-suggestions')).toBeTruthy();
expect(wrapperWithTokenActive.emitted('fetch-suggestions')).toEqual([
[`"${mockRegularLabel.title}"`],
]);
});
......@@ -164,7 +164,7 @@ describe('BaseToken', () => {
describe('methods', () => {
describe('handleTokenValueSelected', () => {
it('calls `setTokenValueToRecentlyUsed` when `recentTokenValuesStorageKey` is defined', () => {
it('calls `setTokenValueToRecentlyUsed` when `recentSuggestionsStorageKey` is defined', () => {
const mockTokenValue = {
id: 1,
title: 'Foo',
......@@ -175,14 +175,14 @@ describe('BaseToken', () => {
expect(setTokenValueToRecentlyUsed).toHaveBeenCalledWith(mockStorageKey, mockTokenValue);
});
it('does not add token from preloadedTokenValues', async () => {
it('does not add token from preloadedSuggestions', async () => {
const mockTokenValue = {
id: 1,
title: 'Foo',
};
wrapper.setProps({
preloadedTokenValues: [mockTokenValue],
preloadedSuggestions: [mockTokenValue],
});
await wrapper.vm.$nextTick();
......@@ -228,7 +228,7 @@ describe('BaseToken', () => {
wrapperWithNoStubs.destroy();
});
it('emits `fetch-token-values` event on component after a delay when component emits `input` event', async () => {
it('emits `fetch-suggestions` event on component after a delay when component emits `input` event', async () => {
jest.useFakeTimers();
wrapperWithNoStubs.find(GlFilteredSearchToken).vm.$emit('input', { data: 'foo' });
......@@ -236,8 +236,8 @@ describe('BaseToken', () => {
jest.runAllTimers();
expect(wrapperWithNoStubs.emitted('fetch-token-values')).toBeTruthy();
expect(wrapperWithNoStubs.emitted('fetch-token-values')[2]).toEqual(['foo']);
expect(wrapperWithNoStubs.emitted('fetch-suggestions')).toBeTruthy();
expect(wrapperWithNoStubs.emitted('fetch-suggestions')[2]).toEqual(['foo']);
});
});
});
......
......@@ -159,7 +159,7 @@ describe('LabelToken', () => {
expect(baseTokenEl.exists()).toBe(true);
expect(baseTokenEl.props()).toMatchObject({
tokenValues: mockLabels,
suggestions: mockLabels,
fnActiveTokenValue: wrapper.vm.getActiveLabel,
});
});
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment