Commit e7be625f authored by pburdette's avatar pburdette

Filter by branch name

Add pipeline branch name
token. Make necessary code
changes to update query params
with tokens. Search with debounce
after first 20 authors / branches
are rendered.
parent f6c40a9e
...@@ -249,15 +249,22 @@ export default { ...@@ -249,15 +249,22 @@ export default {
createFlash(s__('Pipelines|Something went wrong while cleaning runners cache.')); createFlash(s__('Pipelines|Something went wrong while cleaning runners cache.'));
}); });
}, },
resetRequestData() {
this.requestData = { page: this.page, scope: this.scope };
},
filterPipelines(filters) { filterPipelines(filters) {
this.resetRequestData();
filters.forEach(filter => { filters.forEach(filter => {
this.requestData[filter.type] = filter.value.data; // do not add Any for username query param, so we
// can fetch all trigger authors
if (filter.value.data !== ANY_TRIGGER_AUTHOR) {
this.requestData[filter.type] = filter.value.data;
}
}); });
// set query params back to default if filtering by Any author if (filters.length === 0) {
// or input is cleared on submit this.resetRequestData();
if (this.requestData.username === ANY_TRIGGER_AUTHOR || filters.length === 0) {
this.requestData = { page: this.page, scope: this.scope };
} }
this.updateContent(this.requestData); this.updateContent(this.requestData);
......
...@@ -2,8 +2,10 @@ ...@@ -2,8 +2,10 @@
import { GlFilteredSearch } from '@gitlab/ui'; import { GlFilteredSearch } from '@gitlab/ui';
import { __, s__ } from '~/locale'; import { __, s__ } from '~/locale';
import PipelineTriggerAuthorToken from './tokens/pipeline_trigger_author_token.vue'; import PipelineTriggerAuthorToken from './tokens/pipeline_trigger_author_token.vue';
import PipelineBranchNameToken from './tokens/pipeline_branch_name_token.vue';
import Api from '~/api'; import Api from '~/api';
import createFlash from '~/flash'; import createFlash from '~/flash';
import { FETCH_AUTHOR_ERROR_MESSAGE, FETCH_BRANCH_ERROR_MESSAGE } from '../constants';
export default { export default {
components: { components: {
...@@ -22,6 +24,7 @@ export default { ...@@ -22,6 +24,7 @@ export default {
data() { data() {
return { return {
projectUsers: null, projectUsers: null,
projectBranches: null,
}; };
}, },
computed: { computed: {
...@@ -31,11 +34,21 @@ export default { ...@@ -31,11 +34,21 @@ export default {
type: 'username', type: 'username',
icon: 'user', icon: 'user',
title: s__('Pipeline|Trigger author'), title: s__('Pipeline|Trigger author'),
dataType: 'username',
unique: true, unique: true,
token: PipelineTriggerAuthorToken, token: PipelineTriggerAuthorToken,
operators: [{ value: '=', description: __('is'), default: 'true' }], operators: [{ value: '=', description: __('is'), default: 'true' }],
triggerAuthors: this.projectUsers, triggerAuthors: this.projectUsers,
projectId: this.projectId,
},
{
type: 'ref',
icon: 'branch',
title: s__('Pipeline|Branch name'),
unique: true,
token: PipelineBranchNameToken,
operators: [{ value: '=', description: __('is'), default: 'true' }],
branches: this.projectBranches,
projectId: this.projectId,
}, },
]; ];
}, },
...@@ -46,7 +59,16 @@ export default { ...@@ -46,7 +59,16 @@ export default {
this.projectUsers = users; this.projectUsers = users;
}) })
.catch(err => { .catch(err => {
createFlash(__('There was a problem fetching project users.')); createFlash(FETCH_AUTHOR_ERROR_MESSAGE);
throw err;
});
Api.branches(this.projectId)
.then(({ data }) => {
this.projectBranches = data.map(branch => branch.name);
})
.catch(err => {
createFlash(FETCH_BRANCH_ERROR_MESSAGE);
throw err; throw err;
}); });
}, },
......
<script>
import { GlFilteredSearchToken, GlFilteredSearchSuggestion, GlLoadingIcon } from '@gitlab/ui';
import Api from '~/api';
import { FETCH_BRANCH_ERROR_MESSAGE, FILTER_PIPELINES_SEARCH_DELAY } from '../../constants';
import createFlash from '~/flash';
import { debounce } from 'lodash';
export default {
components: {
GlFilteredSearchToken,
GlFilteredSearchSuggestion,
GlLoadingIcon,
},
props: {
config: {
type: Object,
required: true,
},
value: {
type: Object,
required: true,
},
},
data() {
return {
branches: this.config.branches,
loading: true,
};
},
methods: {
fetchBranchBySearchTerm(searchTerm) {
Api.branches(this.config.projectId, searchTerm)
.then(res => {
this.branches = res.data.map(branch => branch.name);
this.loading = false;
})
.catch(err => {
createFlash(FETCH_BRANCH_ERROR_MESSAGE);
this.loading = false;
throw err;
});
},
searchBranches: debounce(function debounceSearch({ data }) {
this.fetchBranchBySearchTerm(data);
}, FILTER_PIPELINES_SEARCH_DELAY),
},
};
</script>
<template>
<gl-filtered-search-token
:config="config"
v-bind="{ ...$props, ...$attrs }"
v-on="$listeners"
@input="searchBranches"
>
<template #suggestions>
<gl-loading-icon v-if="loading" />
<template v-else>
<gl-filtered-search-suggestion
v-for="(branch, index) in branches"
:key="index"
:value="branch"
>
{{ branch }}
</gl-filtered-search-suggestion>
</template>
</template>
</gl-filtered-search-token>
</template>
...@@ -4,8 +4,16 @@ import { ...@@ -4,8 +4,16 @@ import {
GlAvatar, GlAvatar,
GlFilteredSearchSuggestion, GlFilteredSearchSuggestion,
GlDropdownDivider, GlDropdownDivider,
GlLoadingIcon,
} from '@gitlab/ui'; } from '@gitlab/ui';
import { ANY_TRIGGER_AUTHOR } from '../../constants'; import Api from '~/api';
import createFlash from '~/flash';
import { debounce } from 'lodash';
import {
ANY_TRIGGER_AUTHOR,
FETCH_AUTHOR_ERROR_MESSAGE,
FILTER_PIPELINES_SEARCH_DELAY,
} from '../../constants';
export default { export default {
anyTriggerAuthor: ANY_TRIGGER_AUTHOR, anyTriggerAuthor: ANY_TRIGGER_AUTHOR,
...@@ -14,6 +22,7 @@ export default { ...@@ -14,6 +22,7 @@ export default {
GlAvatar, GlAvatar,
GlFilteredSearchSuggestion, GlFilteredSearchSuggestion,
GlDropdownDivider, GlDropdownDivider,
GlLoadingIcon,
}, },
props: { props: {
config: { config: {
...@@ -25,26 +34,49 @@ export default { ...@@ -25,26 +34,49 @@ export default {
required: true, required: true,
}, },
}, },
data() {
return {
users: this.config.triggerAuthors,
loading: true,
};
},
computed: { computed: {
currentValue() { currentValue() {
return this.value.data.toLowerCase(); return this.value.data.toLowerCase();
}, },
filteredTriggerAuthors() {
return this.config.triggerAuthors.filter(user => {
return user.username.toLowerCase().includes(this.currentValue);
});
},
activeUser() { activeUser() {
return this.config.triggerAuthors.find(user => { return this.users.find(user => {
return user.username.toLowerCase() === this.currentValue; return user.username.toLowerCase() === this.currentValue;
}); });
}, },
}, },
methods: {
fetchAuthorBySearchTerm(searchTerm) {
Api.projectUsers(this.config.projectId, searchTerm)
.then(res => {
this.users = res;
this.loading = false;
})
.catch(err => {
createFlash(FETCH_AUTHOR_ERROR_MESSAGE);
this.loading = false;
throw err;
});
},
searchAuthors: debounce(function debounceSearch({ data }) {
this.fetchAuthorBySearchTerm(data);
}, FILTER_PIPELINES_SEARCH_DELAY),
},
}; };
</script> </script>
<template> <template>
<gl-filtered-search-token :config="config" v-bind="{ ...$props, ...$attrs }" v-on="$listeners"> <gl-filtered-search-token
:config="config"
v-bind="{ ...$props, ...$attrs }"
v-on="$listeners"
@input="searchAuthors"
>
<template #view="{inputValue}"> <template #view="{inputValue}">
<gl-avatar <gl-avatar
v-if="activeUser" v-if="activeUser"
...@@ -60,19 +92,23 @@ export default { ...@@ -60,19 +92,23 @@ export default {
$options.anyTriggerAuthor $options.anyTriggerAuthor
}}</gl-filtered-search-suggestion> }}</gl-filtered-search-suggestion>
<gl-dropdown-divider /> <gl-dropdown-divider />
<gl-filtered-search-suggestion
v-for="user in filteredTriggerAuthors" <gl-loading-icon v-if="loading" />
:key="user.username" <template v-else>
:value="user.username" <gl-filtered-search-suggestion
> v-for="user in users"
<div class="d-flex"> :key="user.username"
<gl-avatar :size="32" :src="user.avatar_url" /> :value="user.username"
<div> >
<div>{{ user.name }}</div> <div class="d-flex">
<div>@{{ user.username }}</div> <gl-avatar :size="32" :src="user.avatar_url" />
<div>
<div>{{ user.name }}</div>
<div>@{{ user.username }}</div>
</div>
</div> </div>
</div> </gl-filtered-search-suggestion>
</gl-filtered-search-suggestion> </template>
</template> </template>
</gl-filtered-search-token> </gl-filtered-search-token>
</template> </template>
import { __ } from '~/locale';
export const CANCEL_REQUEST = 'CANCEL_REQUEST'; export const CANCEL_REQUEST = 'CANCEL_REQUEST';
export const PIPELINES_TABLE = 'PIPELINES_TABLE'; export const PIPELINES_TABLE = 'PIPELINES_TABLE';
export const LAYOUT_CHANGE_DELAY = 300; export const LAYOUT_CHANGE_DELAY = 300;
export const FILTER_PIPELINES_SEARCH_DELAY = 200;
export const ANY_TRIGGER_AUTHOR = 'Any'; export const ANY_TRIGGER_AUTHOR = 'Any';
export const TestStatus = { export const TestStatus = {
...@@ -8,3 +11,6 @@ export const TestStatus = { ...@@ -8,3 +11,6 @@ export const TestStatus = {
SKIPPED: 'skipped', SKIPPED: 'skipped',
SUCCESS: 'success', SUCCESS: 'success',
}; };
export const FETCH_AUTHOR_ERROR_MESSAGE = __('There was a problem fetching project users.');
export const FETCH_BRANCH_ERROR_MESSAGE = __('There was a problem fetching project branches.');
...@@ -19,7 +19,7 @@ export default class PipelinesService { ...@@ -19,7 +19,7 @@ export default class PipelinesService {
} }
getPipelines(data = {}) { getPipelines(data = {}) {
const { scope, page, username } = data; const { scope, page, username, ref } = data;
const { CancelToken } = axios; const { CancelToken } = axios;
const queryParams = { scope, page }; const queryParams = { scope, page };
...@@ -28,6 +28,10 @@ export default class PipelinesService { ...@@ -28,6 +28,10 @@ export default class PipelinesService {
queryParams.username = username; queryParams.username = username;
} }
if (ref) {
queryParams.ref = ref;
}
this.cancelationSource = CancelToken.source(); this.cancelationSource = CancelToken.source();
return axios.get(this.endpoint, { return axios.get(this.endpoint, {
......
...@@ -35,13 +35,18 @@ export default { ...@@ -35,13 +35,18 @@ export default {
}, },
onChangeWithFilter(params) { onChangeWithFilter(params) {
const { username } = this.requestData; const { username, ref } = this.requestData;
const paramsData = params;
if (username) { if (username) {
return { ...params, username }; paramsData.username = username;
} }
return params; if (ref) {
paramsData.ref = ref;
}
return paramsData;
}, },
updateInternalState(parameters) { updateInternalState(parameters) {
......
...@@ -12,7 +12,7 @@ class Projects::PipelinesController < Projects::ApplicationController ...@@ -12,7 +12,7 @@ class Projects::PipelinesController < Projects::ApplicationController
before_action :authorize_update_pipeline!, only: [:retry, :cancel] before_action :authorize_update_pipeline!, only: [:retry, :cancel]
before_action do before_action do
push_frontend_feature_flag(:junit_pipeline_view) push_frontend_feature_flag(:junit_pipeline_view)
push_frontend_feature_flag(:filter_pipelines_search) push_frontend_feature_flag(:filter_pipelines_search, default_enabled: true)
push_frontend_feature_flag(:dag_pipeline_tab) push_frontend_feature_flag(:dag_pipeline_tab)
end end
before_action :ensure_pipeline, only: [:show] before_action :ensure_pipeline, only: [:show]
...@@ -269,7 +269,7 @@ class Projects::PipelinesController < Projects::ApplicationController ...@@ -269,7 +269,7 @@ class Projects::PipelinesController < Projects::ApplicationController
end end
def index_params def index_params
params.permit(:scope, :username) params.permit(:scope, :username, :ref)
end end
end end
......
---
title: Filter pipelines by trigger author and branch name
merge_request: 31386
author:
type: added
...@@ -15178,6 +15178,9 @@ msgstr "" ...@@ -15178,6 +15178,9 @@ msgstr ""
msgid "Pipelines|parent" msgid "Pipelines|parent"
msgstr "" msgstr ""
msgid "Pipeline|Branch name"
msgstr ""
msgid "Pipeline|Commit" msgid "Pipeline|Commit"
msgstr "" msgstr ""
...@@ -21191,6 +21194,9 @@ msgstr "" ...@@ -21191,6 +21194,9 @@ msgstr ""
msgid "There was a problem communicating with your device." msgid "There was a problem communicating with your device."
msgstr "" msgstr ""
msgid "There was a problem fetching project branches."
msgstr ""
msgid "There was a problem fetching project users." msgid "There was a problem fetching project users."
msgstr "" msgstr ""
......
...@@ -195,6 +195,26 @@ describe Projects::PipelinesController do ...@@ -195,6 +195,26 @@ describe Projects::PipelinesController do
end end
end end
context 'filter by ref' do
let!(:pipeline) { create(:ci_pipeline, :running, project: project, ref: 'branch-1') }
context 'when pipelines with the ref exists' do
it 'returns matched pipelines' do
get_pipelines_index_json(ref: 'branch-1')
check_pipeline_response(returned: 1, all: 1, running: 1, pending: 0, finished: 0)
end
end
context 'when no pipeline with the ref exists' do
it 'returns empty list' do
get_pipelines_index_json(ref: 'invalid-ref')
check_pipeline_response(returned: 0, all: 0, running: 0, pending: 0, finished: 0)
end
end
end
def get_pipelines_index_json(params = {}) def get_pipelines_index_json(params = {})
get :index, params: { get :index, params: {
namespace_id: project.namespace, namespace_id: project.namespace,
......
...@@ -3,7 +3,13 @@ import { mount } from '@vue/test-utils'; ...@@ -3,7 +3,13 @@ import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter'; import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import PipelinesFilteredSearch from '~/pipelines/components/pipelines_filtered_search.vue'; import PipelinesFilteredSearch from '~/pipelines/components/pipelines_filtered_search.vue';
import { users, mockSearch, pipelineWithStages } from '../mock_data'; import {
users,
mockSearch,
pipelineWithStages,
branches,
mockBranchesAfterMap,
} from '../mock_data';
import { GlFilteredSearch } from '@gitlab/ui'; import { GlFilteredSearch } from '@gitlab/ui';
describe('Pipelines filtered search', () => { describe('Pipelines filtered search', () => {
...@@ -30,6 +36,7 @@ describe('Pipelines filtered search', () => { ...@@ -30,6 +36,7 @@ describe('Pipelines filtered search', () => {
mock = new MockAdapter(axios); mock = new MockAdapter(axios);
jest.spyOn(Api, 'projectUsers').mockResolvedValue(users); jest.spyOn(Api, 'projectUsers').mockResolvedValue(users);
jest.spyOn(Api, 'branches').mockResolvedValue({ data: branches });
createComponent(); createComponent();
}); });
...@@ -52,9 +59,19 @@ describe('Pipelines filtered search', () => { ...@@ -52,9 +59,19 @@ describe('Pipelines filtered search', () => {
type: 'username', type: 'username',
icon: 'user', icon: 'user',
title: 'Trigger author', title: 'Trigger author',
dataType: 'username',
unique: true, unique: true,
triggerAuthors: users, triggerAuthors: users,
projectId: '21',
operators: [expect.objectContaining({ value: '=' })],
});
expect(getSearchToken('ref')).toMatchObject({
type: 'ref',
icon: 'branch',
title: 'Branch name',
unique: true,
branches: mockBranchesAfterMap,
projectId: '21',
operators: [expect.objectContaining({ value: '=' })], operators: [expect.objectContaining({ value: '=' })],
}); });
}); });
...@@ -65,6 +82,12 @@ describe('Pipelines filtered search', () => { ...@@ -65,6 +82,12 @@ describe('Pipelines filtered search', () => {
expect(wrapper.vm.projectUsers).toEqual(users); expect(wrapper.vm.projectUsers).toEqual(users);
}); });
it('fetches and sets branches', () => {
expect(Api.branches).toHaveBeenCalled();
expect(wrapper.vm.projectBranches).toEqual(mockBranchesAfterMap);
});
it('emits filterPipelines on submit with correct filter', () => { it('emits filterPipelines on submit with correct filter', () => {
findFilteredSearch().vm.$emit('submit', mockSearch); findFilteredSearch().vm.$emit('submit', mockSearch);
......
...@@ -479,4 +479,90 @@ export const users = [ ...@@ -479,4 +479,90 @@ export const users = [
}, },
]; ];
export const mockSearch = { type: 'username', value: { data: 'root', operator: '=' } }; export const branches = [
{
name: 'branch-1',
commit: {
id: '21fb056cc47dcf706670e6de635b1b326490ebdc',
short_id: '21fb056c',
created_at: '2020-05-07T10:58:28.000-04:00',
parent_ids: null,
title: 'Add new file',
message: 'Add new file',
author_name: 'Administrator',
author_email: 'admin@example.com',
authored_date: '2020-05-07T10:58:28.000-04:00',
committer_name: 'Administrator',
committer_email: 'admin@example.com',
committed_date: '2020-05-07T10:58:28.000-04:00',
web_url:
'http://192.168.1.22:3000/root/dag-pipeline/-/commit/21fb056cc47dcf706670e6de635b1b326490ebdc',
},
merged: false,
protected: false,
developers_can_push: false,
developers_can_merge: false,
can_push: true,
default: false,
web_url: 'http://192.168.1.22:3000/root/dag-pipeline/-/tree/branch-1',
},
{
name: 'branch-10',
commit: {
id: '66673b07efef254dab7d537f0433a40e61cf84fe',
short_id: '66673b07',
created_at: '2020-03-16T11:04:46.000-04:00',
parent_ids: null,
title: 'Update .gitlab-ci.yml',
message: 'Update .gitlab-ci.yml',
author_name: 'Administrator',
author_email: 'admin@example.com',
authored_date: '2020-03-16T11:04:46.000-04:00',
committer_name: 'Administrator',
committer_email: 'admin@example.com',
committed_date: '2020-03-16T11:04:46.000-04:00',
web_url:
'http://192.168.1.22:3000/root/dag-pipeline/-/commit/66673b07efef254dab7d537f0433a40e61cf84fe',
},
merged: false,
protected: false,
developers_can_push: false,
developers_can_merge: false,
can_push: true,
default: false,
web_url: 'http://192.168.1.22:3000/root/dag-pipeline/-/tree/branch-10',
},
{
name: 'branch-11',
commit: {
id: '66673b07efef254dab7d537f0433a40e61cf84fe',
short_id: '66673b07',
created_at: '2020-03-16T11:04:46.000-04:00',
parent_ids: null,
title: 'Update .gitlab-ci.yml',
message: 'Update .gitlab-ci.yml',
author_name: 'Administrator',
author_email: 'admin@example.com',
authored_date: '2020-03-16T11:04:46.000-04:00',
committer_name: 'Administrator',
committer_email: 'admin@example.com',
committed_date: '2020-03-16T11:04:46.000-04:00',
web_url:
'http://192.168.1.22:3000/root/dag-pipeline/-/commit/66673b07efef254dab7d537f0433a40e61cf84fe',
},
merged: false,
protected: false,
developers_can_push: false,
developers_can_merge: false,
can_push: true,
default: false,
web_url: 'http://192.168.1.22:3000/root/dag-pipeline/-/tree/branch-11',
},
];
export const mockSearch = [
{ type: 'username', value: { data: 'root', operator: '=' } },
{ type: 'ref', value: { data: 'master', operator: '=' } },
];
export const mockBranchesAfterMap = ['branch-1', 'branch-10', 'branch-11'];
...@@ -5,7 +5,7 @@ import axios from '~/lib/utils/axios_utils'; ...@@ -5,7 +5,7 @@ import axios from '~/lib/utils/axios_utils';
import waitForPromises from 'helpers/wait_for_promises'; import waitForPromises from 'helpers/wait_for_promises';
import PipelinesComponent from '~/pipelines/components/pipelines.vue'; import PipelinesComponent from '~/pipelines/components/pipelines.vue';
import Store from '~/pipelines/stores/pipelines_store'; import Store from '~/pipelines/stores/pipelines_store';
import { pipelineWithStages, stageReply, users, mockSearch } from './mock_data'; import { pipelineWithStages, stageReply, users, mockSearch, branches } from './mock_data';
import { GlFilteredSearch } from '@gitlab/ui'; import { GlFilteredSearch } from '@gitlab/ui';
describe('Pipelines', () => { describe('Pipelines', () => {
...@@ -63,7 +63,9 @@ describe('Pipelines', () => { ...@@ -63,7 +63,9 @@ describe('Pipelines', () => {
beforeEach(() => { beforeEach(() => {
mock = new MockAdapter(axios); mock = new MockAdapter(axios);
pipelines = getJSONFixture(jsonFixtureName); pipelines = getJSONFixture(jsonFixtureName);
jest.spyOn(Api, 'projectUsers').mockResolvedValue(users); jest.spyOn(Api, 'projectUsers').mockResolvedValue(users);
jest.spyOn(Api, 'branches').mockResolvedValue({ data: branches });
}); });
afterEach(() => { afterEach(() => {
...@@ -674,9 +676,9 @@ describe('Pipelines', () => { ...@@ -674,9 +676,9 @@ describe('Pipelines', () => {
it('updates request data and query params on filter submit', () => { it('updates request data and query params on filter submit', () => {
const updateContentMock = jest.spyOn(wrapper.vm, 'updateContent'); const updateContentMock = jest.spyOn(wrapper.vm, 'updateContent');
const expectedQueryParams = { page: '1', scope: 'all', username: 'root' }; const expectedQueryParams = { page: '1', scope: 'all', username: 'root', ref: 'master' };
findFilteredSearch().vm.$emit('submit', [mockSearch]); findFilteredSearch().vm.$emit('submit', mockSearch);
expect(wrapper.vm.requestData).toEqual(expectedQueryParams); expect(wrapper.vm.requestData).toEqual(expectedQueryParams);
expect(updateContentMock).toHaveBeenCalledWith(expectedQueryParams); expect(updateContentMock).toHaveBeenCalledWith(expectedQueryParams);
......
import { GlFilteredSearchToken, GlFilteredSearchSuggestion, GlLoadingIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import PipelineBranchNameToken from '~/pipelines/components/tokens/pipeline_branch_name_token.vue';
import { branches } from '../mock_data';
describe('Pipeline Branch Name Token', () => {
let wrapper;
const findFilteredSearchToken = () => wrapper.find(GlFilteredSearchToken);
const findAllFilteredSearchSuggestions = () => wrapper.findAll(GlFilteredSearchSuggestion);
const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
const stubs = {
GlFilteredSearchToken: {
template: `<div><slot name="suggestions"></slot></div>`,
},
};
const defaultProps = {
config: {
type: 'ref',
icon: 'branch',
title: 'Branch name',
dataType: 'ref',
unique: true,
branches,
projectId: '21',
},
value: {
data: '',
},
};
const createComponent = (options, data) => {
wrapper = shallowMount(PipelineBranchNameToken, {
propsData: {
...defaultProps,
},
data() {
return {
...data,
};
},
...options,
});
};
beforeEach(() => {
createComponent();
});
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
it('passes config correctly', () => {
expect(findFilteredSearchToken().props('config')).toEqual(defaultProps.config);
});
describe('displays loading icon correctly', () => {
it('shows loading icon', () => {
createComponent({ stubs }, { loading: true });
expect(findLoadingIcon().exists()).toBe(true);
});
it('does not show loading icon', () => {
createComponent({ stubs }, { loading: false });
expect(findLoadingIcon().exists()).toBe(false);
});
});
describe('shows branches correctly', () => {
it('renders all trigger authors', () => {
createComponent({ stubs }, { branches, loading: false });
expect(findAllFilteredSearchSuggestions()).toHaveLength(branches.length);
});
it('renders only the branch searched for', () => {
const mockBranches = ['master'];
createComponent({ stubs }, { branches: mockBranches, loading: false });
expect(findAllFilteredSearchSuggestions()).toHaveLength(mockBranches.length);
});
});
});
import { GlFilteredSearchToken, GlFilteredSearchSuggestion } from '@gitlab/ui'; import { GlFilteredSearchToken, GlFilteredSearchSuggestion, GlLoadingIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import PipelineTriggerAuthorToken from '~/pipelines/components/tokens/pipeline_trigger_author_token.vue'; import PipelineTriggerAuthorToken from '~/pipelines/components/tokens/pipeline_trigger_author_token.vue';
import { users } from '../mock_data'; import { users } from '../mock_data';
...@@ -8,6 +8,7 @@ describe('Pipeline Trigger Author Token', () => { ...@@ -8,6 +8,7 @@ describe('Pipeline Trigger Author Token', () => {
const findFilteredSearchToken = () => wrapper.find(GlFilteredSearchToken); const findFilteredSearchToken = () => wrapper.find(GlFilteredSearchToken);
const findAllFilteredSearchSuggestions = () => wrapper.findAll(GlFilteredSearchSuggestion); const findAllFilteredSearchSuggestions = () => wrapper.findAll(GlFilteredSearchSuggestion);
const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
const stubs = { const stubs = {
GlFilteredSearchToken: { GlFilteredSearchToken: {
...@@ -24,20 +25,27 @@ describe('Pipeline Trigger Author Token', () => { ...@@ -24,20 +25,27 @@ describe('Pipeline Trigger Author Token', () => {
unique: true, unique: true,
triggerAuthors: users, triggerAuthors: users,
}, },
value: {
data: '',
},
}; };
const createComponent = (props = {}, options) => { const createComponent = (options, data) => {
wrapper = shallowMount(PipelineTriggerAuthorToken, { wrapper = shallowMount(PipelineTriggerAuthorToken, {
propsData: { propsData: {
...props,
...defaultProps, ...defaultProps,
}, },
data() {
return {
...data,
};
},
...options, ...options,
}); });
}; };
beforeEach(() => { beforeEach(() => {
createComponent({ value: { data: '' } }); createComponent();
}); });
afterEach(() => { afterEach(() => {
...@@ -49,14 +57,41 @@ describe('Pipeline Trigger Author Token', () => { ...@@ -49,14 +57,41 @@ describe('Pipeline Trigger Author Token', () => {
expect(findFilteredSearchToken().props('config')).toEqual(defaultProps.config); expect(findFilteredSearchToken().props('config')).toEqual(defaultProps.config);
}); });
describe('displays loading icon correctly', () => {
it('shows loading icon', () => {
createComponent({ stubs }, { loading: true });
expect(findLoadingIcon().exists()).toBe(true);
});
it('does not show loading icon', () => {
createComponent({ stubs }, { loading: false });
expect(findLoadingIcon().exists()).toBe(false);
});
});
describe('shows trigger authors correctly', () => { describe('shows trigger authors correctly', () => {
beforeEach(() => {});
it('renders all trigger authors', () => { it('renders all trigger authors', () => {
createComponent({ value: { data: '' } }, { stubs }); createComponent({ stubs }, { users, loading: false });
expect(findAllFilteredSearchSuggestions()).toHaveLength(7);
// should have length of all users plus the static 'Any' option
expect(findAllFilteredSearchSuggestions()).toHaveLength(users.length + 1);
}); });
it('renders only the trigger author searched for', () => { it('renders only the trigger author searched for', () => {
createComponent({ value: { data: 'root' } }, { stubs }); createComponent(
{ stubs },
{
users: [
{ name: 'Arnold', username: 'admin', state: 'active', avatar_url: 'avatar-link' },
],
loading: false,
},
);
expect(findAllFilteredSearchSuggestions()).toHaveLength(2); expect(findAllFilteredSearchSuggestions()).toHaveLength(2);
}); });
}); });
......
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