summaryrefslogtreecommitdiff
path: root/spec/frontend/vue_shared/components/filtered_search_bar
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-04-20 23:50:22 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2021-04-20 23:50:22 +0000
commit9dc93a4519d9d5d7be48ff274127136236a3adb3 (patch)
tree70467ae3692a0e35e5ea56bcb803eb512a10bedb /spec/frontend/vue_shared/components/filtered_search_bar
parent4b0f34b6d759d6299322b3a54453e930c6121ff0 (diff)
downloadgitlab-ce-9dc93a4519d9d5d7be48ff274127136236a3adb3.tar.gz
Add latest changes from gitlab-org/gitlab@13-11-stable-eev13.11.0-rc43
Diffstat (limited to 'spec/frontend/vue_shared/components/filtered_search_bar')
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/mock_data.js47
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/tokens/emoji_token_spec.js217
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/tokens/epic_token_spec.js180
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/tokens/label_token_spec.js16
4 files changed, 460 insertions, 0 deletions
diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/mock_data.js b/spec/frontend/vue_shared/components/filtered_search_bar/mock_data.js
index 7606b3bd91c..c24528ba4d2 100644
--- a/spec/frontend/vue_shared/components/filtered_search_bar/mock_data.js
+++ b/spec/frontend/vue_shared/components/filtered_search_bar/mock_data.js
@@ -3,6 +3,8 @@ import { mockLabels } from 'jest/vue_shared/components/sidebar/labels_select_vue
import Api from '~/api';
import AuthorToken from '~/vue_shared/components/filtered_search_bar/tokens/author_token.vue';
import BranchToken from '~/vue_shared/components/filtered_search_bar/tokens/branch_token.vue';
+import EmojiToken from '~/vue_shared/components/filtered_search_bar/tokens/emoji_token.vue';
+import EpicToken from '~/vue_shared/components/filtered_search_bar/tokens/epic_token.vue';
import LabelToken from '~/vue_shared/components/filtered_search_bar/tokens/label_token.vue';
import MilestoneToken from '~/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue';
@@ -59,6 +61,21 @@ export const mockMilestones = [
mockEscapedMilestone,
];
+export const mockEpics = [
+ { iid: 1, id: 1, title: 'Foo' },
+ { iid: 2, id: 2, title: 'Bar' },
+];
+
+export const mockEmoji1 = {
+ name: 'thumbsup',
+};
+
+export const mockEmoji2 = {
+ name: 'star',
+};
+
+export const mockEmojis = [mockEmoji1, mockEmoji2];
+
export const mockBranchToken = {
type: 'source_branch',
icon: 'branch',
@@ -103,6 +120,28 @@ export const mockMilestoneToken = {
fetchMilestones: () => Promise.resolve({ data: mockMilestones }),
};
+export const mockEpicToken = {
+ type: 'epic_iid',
+ icon: 'clock',
+ title: 'Epic',
+ unique: true,
+ symbol: '&',
+ token: EpicToken,
+ operators: [{ value: '=', description: 'is', default: 'true' }],
+ fetchEpics: () => Promise.resolve({ data: mockEpics }),
+ fetchSingleEpic: () => Promise.resolve({ data: mockEpics[0] }),
+};
+
+export const mockReactionEmojiToken = {
+ type: 'my_reaction_emoji',
+ icon: 'thumb-up',
+ title: 'My-Reaction',
+ unique: true,
+ token: EmojiToken,
+ operators: [{ value: '=', description: 'is', default: 'true' }],
+ fetchEmojis: () => Promise.resolve(mockEmojis),
+};
+
export const mockMembershipToken = {
type: 'with_inherited_permissions',
icon: 'group',
@@ -168,6 +207,14 @@ export const tokenValuePlain = {
value: { data: 'foo' },
};
+export const tokenValueEpic = {
+ type: 'epic_iid',
+ value: {
+ operator: '=',
+ data: '"foo"::&42',
+ },
+};
+
export const mockHistoryItems = [
[tokenValueAuthor, tokenValueLabel, tokenValueMilestone, 'duo'],
[tokenValueAuthor, 'si'],
diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/emoji_token_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/emoji_token_spec.js
new file mode 100644
index 00000000000..231f2f01428
--- /dev/null
+++ b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/emoji_token_spec.js
@@ -0,0 +1,217 @@
+import {
+ GlFilteredSearchToken,
+ GlFilteredSearchSuggestion,
+ GlFilteredSearchTokenSegment,
+ GlDropdownDivider,
+} from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
+import MockAdapter from 'axios-mock-adapter';
+import waitForPromises from 'helpers/wait_for_promises';
+import { deprecatedCreateFlash as createFlash } from '~/flash';
+import axios from '~/lib/utils/axios_utils';
+
+import {
+ DEFAULT_LABEL_NONE,
+ DEFAULT_LABEL_ANY,
+} from '~/vue_shared/components/filtered_search_bar/constants';
+import EmojiToken from '~/vue_shared/components/filtered_search_bar/tokens/emoji_token.vue';
+
+import { mockReactionEmojiToken, mockEmojis } from '../mock_data';
+
+jest.mock('~/flash');
+const GlEmoji = { template: '<img/>' };
+const defaultStubs = {
+ Portal: true,
+ GlFilteredSearchSuggestionList: {
+ template: '<div></div>',
+ methods: {
+ getValue: () => '=',
+ },
+ },
+ GlEmoji,
+};
+
+function createComponent(options = {}) {
+ const {
+ config = mockReactionEmojiToken,
+ value = { data: '' },
+ active = false,
+ stubs = defaultStubs,
+ } = options;
+ return mount(EmojiToken, {
+ propsData: {
+ config,
+ value,
+ active,
+ },
+ provide: {
+ portalName: 'fake target',
+ alignSuggestions: function fakeAlignSuggestions() {},
+ suggestionsListClass: 'custom-class',
+ },
+ stubs,
+ });
+}
+
+describe('EmojiToken', () => {
+ let mock;
+ let wrapper;
+
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ });
+
+ afterEach(() => {
+ mock.restore();
+ wrapper.destroy();
+ });
+
+ describe('computed', () => {
+ beforeEach(async () => {
+ wrapper = createComponent({ value: { data: mockEmojis[0].name } });
+
+ wrapper.setData({
+ emojis: mockEmojis,
+ });
+
+ await wrapper.vm.$nextTick();
+ });
+
+ describe('currentValue', () => {
+ it('returns lowercase string for `value.data`', () => {
+ expect(wrapper.vm.currentValue).toBe(mockEmojis[0].name);
+ });
+ });
+
+ describe('activeEmoji', () => {
+ it('returns object for currently present `value.data`', () => {
+ expect(wrapper.vm.activeEmoji).toEqual(mockEmojis[0]);
+ });
+ });
+ });
+
+ describe('methods', () => {
+ beforeEach(() => {
+ wrapper = createComponent();
+ });
+
+ describe('fetchEmojiBySearchTerm', () => {
+ it('calls `config.fetchEmojis` with provided searchTerm param', () => {
+ jest.spyOn(wrapper.vm.config, 'fetchEmojis');
+
+ wrapper.vm.fetchEmojiBySearchTerm('foo');
+
+ expect(wrapper.vm.config.fetchEmojis).toHaveBeenCalledWith('foo');
+ });
+
+ it('sets response to `emojis` when request is successful', () => {
+ jest.spyOn(wrapper.vm.config, 'fetchEmojis').mockResolvedValue(mockEmojis);
+
+ wrapper.vm.fetchEmojiBySearchTerm('foo');
+
+ return waitForPromises().then(() => {
+ expect(wrapper.vm.emojis).toEqual(mockEmojis);
+ });
+ });
+
+ it('calls `createFlash` with flash error message when request fails', () => {
+ jest.spyOn(wrapper.vm.config, 'fetchEmojis').mockRejectedValue({});
+
+ wrapper.vm.fetchEmojiBySearchTerm('foo');
+
+ return waitForPromises().then(() => {
+ expect(createFlash).toHaveBeenCalledWith('There was a problem fetching emojis.');
+ });
+ });
+
+ it('sets `loading` to false when request completes', () => {
+ jest.spyOn(wrapper.vm.config, 'fetchEmojis').mockRejectedValue({});
+
+ wrapper.vm.fetchEmojiBySearchTerm('foo');
+
+ return waitForPromises().then(() => {
+ expect(wrapper.vm.loading).toBe(false);
+ });
+ });
+ });
+ });
+
+ describe('template', () => {
+ const defaultEmojis = [DEFAULT_LABEL_NONE, DEFAULT_LABEL_ANY];
+
+ beforeEach(async () => {
+ wrapper = createComponent({
+ value: { data: `"${mockEmojis[0].name}"` },
+ });
+
+ wrapper.setData({
+ emojis: mockEmojis,
+ });
+
+ await wrapper.vm.$nextTick();
+ });
+
+ it('renders gl-filtered-search-token component', () => {
+ expect(wrapper.find(GlFilteredSearchToken).exists()).toBe(true);
+ });
+
+ it('renders token item when value is selected', () => {
+ const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment);
+
+ expect(tokenSegments).toHaveLength(3); // My Reaction, =, "thumbsup"
+ expect(tokenSegments.at(2).find(GlEmoji).attributes('data-name')).toEqual('thumbsup');
+ });
+
+ it('renders provided defaultEmojis as suggestions', async () => {
+ wrapper = createComponent({
+ active: true,
+ config: { ...mockReactionEmojiToken, defaultEmojis },
+ stubs: { Portal: true, GlEmoji },
+ });
+ const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment);
+ const suggestionsSegment = tokenSegments.at(2);
+ suggestionsSegment.vm.$emit('activate');
+ await wrapper.vm.$nextTick();
+
+ const suggestions = wrapper.findAll(GlFilteredSearchSuggestion);
+
+ expect(suggestions).toHaveLength(defaultEmojis.length);
+ defaultEmojis.forEach((emoji, index) => {
+ expect(suggestions.at(index).text()).toBe(emoji.text);
+ });
+ });
+
+ it('does not render divider when no defaultEmojis', async () => {
+ wrapper = createComponent({
+ active: true,
+ config: { ...mockReactionEmojiToken, defaultEmojis: [] },
+ stubs: { Portal: true, GlEmoji },
+ });
+ const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment);
+ const suggestionsSegment = tokenSegments.at(2);
+ suggestionsSegment.vm.$emit('activate');
+ await wrapper.vm.$nextTick();
+
+ expect(wrapper.find(GlFilteredSearchSuggestion).exists()).toBe(false);
+ expect(wrapper.find(GlDropdownDivider).exists()).toBe(false);
+ });
+
+ it('renders `DEFAULT_LABEL_NONE` and `DEFAULT_LABEL_ANY` as default suggestions', async () => {
+ wrapper = createComponent({
+ active: true,
+ config: { ...mockReactionEmojiToken },
+ stubs: { Portal: true, GlEmoji },
+ });
+ const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment);
+ const suggestionsSegment = tokenSegments.at(2);
+ suggestionsSegment.vm.$emit('activate');
+ await wrapper.vm.$nextTick();
+
+ const suggestions = wrapper.findAll(GlFilteredSearchSuggestion);
+
+ expect(suggestions).toHaveLength(2);
+ expect(suggestions.at(0).text()).toBe(DEFAULT_LABEL_NONE.text);
+ expect(suggestions.at(1).text()).toBe(DEFAULT_LABEL_ANY.text);
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/epic_token_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/epic_token_spec.js
new file mode 100644
index 00000000000..0c3f9e1363f
--- /dev/null
+++ b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/epic_token_spec.js
@@ -0,0 +1,180 @@
+import { GlFilteredSearchToken, GlFilteredSearchTokenSegment } from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
+import MockAdapter from 'axios-mock-adapter';
+import waitForPromises from 'helpers/wait_for_promises';
+import createFlash from '~/flash';
+import axios from '~/lib/utils/axios_utils';
+
+import EpicToken from '~/vue_shared/components/filtered_search_bar/tokens/epic_token.vue';
+
+import { mockEpicToken, mockEpics } from '../mock_data';
+
+jest.mock('~/flash');
+
+const defaultStubs = {
+ Portal: true,
+ GlFilteredSearchSuggestionList: {
+ template: '<div></div>',
+ methods: {
+ getValue: () => '=',
+ },
+ },
+};
+
+function createComponent(options = {}) {
+ const {
+ config = mockEpicToken,
+ value = { data: '' },
+ active = false,
+ stubs = defaultStubs,
+ } = options;
+ return mount(EpicToken, {
+ propsData: {
+ config,
+ value,
+ active,
+ },
+ provide: {
+ portalName: 'fake target',
+ alignSuggestions: function fakeAlignSuggestions() {},
+ suggestionsListClass: 'custom-class',
+ },
+ stubs,
+ });
+}
+
+describe('EpicToken', () => {
+ let mock;
+ let wrapper;
+
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ wrapper = createComponent();
+ });
+
+ afterEach(() => {
+ mock.restore();
+ wrapper.destroy();
+ });
+
+ describe('computed', () => {
+ beforeEach(async () => {
+ wrapper = createComponent({
+ data: {
+ epics: mockEpics,
+ },
+ });
+
+ await wrapper.vm.$nextTick();
+ });
+
+ describe('currentValue', () => {
+ it.each`
+ data | id
+ ${`${mockEpics[0].title}::&${mockEpics[0].iid}`} | ${mockEpics[0].iid}
+ ${mockEpics[0].iid} | ${mockEpics[0].iid}
+ ${'foobar'} | ${'foobar'}
+ `('$data returns $id', async ({ data, id }) => {
+ wrapper.setProps({ value: { data } });
+
+ await wrapper.vm.$nextTick();
+
+ expect(wrapper.vm.currentValue).toBe(id);
+ });
+ });
+
+ describe('activeEpic', () => {
+ it('returns object for currently present `value.data`', async () => {
+ wrapper.setProps({
+ value: { data: `${mockEpics[0].iid}` },
+ });
+
+ await wrapper.vm.$nextTick();
+
+ expect(wrapper.vm.activeEpic).toEqual(mockEpics[0]);
+ });
+ });
+ });
+
+ describe('methods', () => {
+ describe('fetchEpicsBySearchTerm', () => {
+ it('calls `config.fetchEpics` with provided searchTerm param', () => {
+ jest.spyOn(wrapper.vm.config, 'fetchEpics');
+
+ wrapper.vm.fetchEpicsBySearchTerm('foo');
+
+ expect(wrapper.vm.config.fetchEpics).toHaveBeenCalledWith('foo');
+ });
+
+ it('sets response to `epics` when request is successful', async () => {
+ jest.spyOn(wrapper.vm.config, 'fetchEpics').mockResolvedValue({
+ data: mockEpics,
+ });
+
+ wrapper.vm.fetchEpicsBySearchTerm();
+
+ await waitForPromises();
+
+ expect(wrapper.vm.epics).toEqual(mockEpics);
+ });
+
+ it('calls `createFlash` with flash error message when request fails', async () => {
+ jest.spyOn(wrapper.vm.config, 'fetchEpics').mockRejectedValue({});
+
+ wrapper.vm.fetchEpicsBySearchTerm('foo');
+
+ await waitForPromises();
+
+ expect(createFlash).toHaveBeenCalledWith({
+ message: 'There was a problem fetching epics.',
+ });
+ });
+
+ it('sets `loading` to false when request completes', async () => {
+ jest.spyOn(wrapper.vm.config, 'fetchEpics').mockRejectedValue({});
+
+ wrapper.vm.fetchEpicsBySearchTerm('foo');
+
+ await waitForPromises();
+
+ expect(wrapper.vm.loading).toBe(false);
+ });
+ });
+
+ describe('fetchSingleEpic', () => {
+ it('calls `config.fetchSingleEpic` with provided iid param', async () => {
+ jest.spyOn(wrapper.vm.config, 'fetchSingleEpic');
+
+ wrapper.vm.fetchSingleEpic(1);
+
+ expect(wrapper.vm.config.fetchSingleEpic).toHaveBeenCalledWith(1);
+
+ await waitForPromises();
+
+ expect(wrapper.vm.epics).toEqual([mockEpics[0]]);
+ });
+ });
+ });
+
+ describe('template', () => {
+ beforeEach(async () => {
+ wrapper = createComponent({
+ value: { data: `${mockEpics[0].iid}` },
+ data: { epics: mockEpics },
+ });
+
+ await wrapper.vm.$nextTick();
+ });
+
+ it('renders gl-filtered-search-token component', () => {
+ expect(wrapper.find(GlFilteredSearchToken).exists()).toBe(true);
+ });
+
+ it('renders token item when value is selected', () => {
+ const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment);
+
+ expect(tokenSegments).toHaveLength(3);
+ expect(tokenSegments.at(2).text()).toBe(`${mockEpics[0].title}::&${mockEpics[0].iid}`);
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/label_token_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/label_token_spec.js
index 7676ce10ce0..8528c062426 100644
--- a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/label_token_spec.js
+++ b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/label_token_spec.js
@@ -118,6 +118,22 @@ describe('LabelToken', () => {
wrapper = createComponent();
});
+ describe('getLabelName', () => {
+ it('returns value of `name` or `title` property present in provided label param', () => {
+ let mockLabel = {
+ title: 'foo',
+ };
+
+ expect(wrapper.vm.getLabelName(mockLabel)).toBe(mockLabel.title);
+
+ mockLabel = {
+ name: 'foo',
+ };
+
+ expect(wrapper.vm.getLabelName(mockLabel)).toBe(mockLabel.name);
+ });
+ });
+
describe('fetchLabelBySearchTerm', () => {
it('calls `config.fetchLabels` with provided searchTerm param', () => {
jest.spyOn(wrapper.vm.config, 'fetchLabels');