diff options
Diffstat (limited to 'spec/frontend/ci/common/private')
4 files changed, 288 insertions, 0 deletions
diff --git a/spec/frontend/ci/common/private/job_links_layer_spec.js b/spec/frontend/ci/common/private/job_links_layer_spec.js new file mode 100644 index 00000000000..c2defc8d770 --- /dev/null +++ b/spec/frontend/ci/common/private/job_links_layer_spec.js @@ -0,0 +1,85 @@ +import { shallowMount } from '@vue/test-utils'; +import mockPipelineResponse from 'test_fixtures/pipelines/pipeline_details.json'; +import LinksInner from '~/ci/pipeline_details/graph/components/links_inner.vue'; +import LinksLayer from '~/ci/common/private/job_links_layer.vue'; + +import { generateResponse } from 'jest/ci/pipeline_details/graph/mock_data'; + +describe('links layer component', () => { + let wrapper; + + const findLinksInner = () => wrapper.findComponent(LinksInner); + + const pipeline = generateResponse(mockPipelineResponse, 'root/fungi-xoxo'); + const containerId = `pipeline-links-container-${pipeline.id}`; + const slotContent = "<div>Ceci n'est pas un graphique</div>"; + + const defaultProps = { + containerId, + containerMeasurements: { width: 400, height: 400 }, + pipelineId: pipeline.id, + pipelineData: pipeline.stages, + showLinks: false, + }; + + const createComponent = ({ mountFn = shallowMount, props = {} } = {}) => { + wrapper = mountFn(LinksLayer, { + propsData: { + ...defaultProps, + ...props, + }, + slots: { + default: slotContent, + }, + stubs: { + 'links-inner': true, + }, + }); + }; + + describe('with show links off', () => { + beforeEach(() => { + createComponent(); + }); + + it('renders the default slot', () => { + expect(wrapper.html()).toContain(slotContent); + }); + + it('does not render inner links component', () => { + expect(findLinksInner().exists()).toBe(false); + }); + }); + + describe('with show links on', () => { + beforeEach(() => { + createComponent({ + props: { + showLinks: true, + }, + }); + }); + + it('renders the default slot', () => { + expect(wrapper.html()).toContain(slotContent); + }); + + it('renders the inner links component', () => { + expect(findLinksInner().exists()).toBe(true); + }); + }); + + describe('with width or height measurement at 0', () => { + beforeEach(() => { + createComponent({ props: { containerMeasurements: { width: 0, height: 100 } } }); + }); + + it('renders the default slot', () => { + expect(wrapper.html()).toContain(slotContent); + }); + + it('does not render the inner links component', () => { + expect(findLinksInner().exists()).toBe(false); + }); + }); +}); diff --git a/spec/frontend/ci/common/private/jobs_filtered_search/jobs_filtered_search_spec.js b/spec/frontend/ci/common/private/jobs_filtered_search/jobs_filtered_search_spec.js new file mode 100644 index 00000000000..079738557a4 --- /dev/null +++ b/spec/frontend/ci/common/private/jobs_filtered_search/jobs_filtered_search_spec.js @@ -0,0 +1,123 @@ +import { GlFilteredSearch } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; +import { + OPERATORS_IS, + TOKEN_TITLE_STATUS, + TOKEN_TYPE_STATUS, + TOKEN_TYPE_JOBS_RUNNER_TYPE, + TOKEN_TITLE_JOBS_RUNNER_TYPE, +} from '~/vue_shared/components/filtered_search_bar/constants'; +import JobsFilteredSearch from '~/ci/common/private/jobs_filtered_search/app.vue'; +import { mockFailedSearchToken } from 'jest/ci/jobs_mock_data'; + +describe('Jobs filtered search', () => { + let wrapper; + + const findFilteredSearch = () => wrapper.findComponent(GlFilteredSearch); + const getSearchToken = (type) => + findFilteredSearch() + .props('availableTokens') + .find((token) => token.type === type); + + const findStatusToken = () => getSearchToken('status'); + const findRunnerTypeToken = () => getSearchToken('jobs-runner-type'); + + const createComponent = (props, provideOptions = {}) => { + wrapper = shallowMount(JobsFilteredSearch, { + propsData: { + ...props, + }, + provide: { + glFeatures: { adminJobsFilterRunnerType: true }, + ...provideOptions, + }, + }); + }; + + it('displays filtered search', () => { + createComponent(); + + expect(findFilteredSearch().exists()).toBe(true); + }); + + it('displays status token', () => { + createComponent(); + + expect(findStatusToken()).toMatchObject({ + type: TOKEN_TYPE_STATUS, + icon: 'status', + title: TOKEN_TITLE_STATUS, + unique: true, + operators: OPERATORS_IS, + }); + }); + + it('displays token for runner type', () => { + createComponent(); + + expect(findRunnerTypeToken()).toMatchObject({ + type: TOKEN_TYPE_JOBS_RUNNER_TYPE, + title: TOKEN_TITLE_JOBS_RUNNER_TYPE, + operators: OPERATORS_IS, + }); + }); + + it('emits filter token to parent component', () => { + createComponent(); + + findFilteredSearch().vm.$emit('submit', mockFailedSearchToken); + + expect(wrapper.emitted('filterJobsBySearch')).toEqual([[mockFailedSearchToken]]); + }); + + it('filtered search value is empty array when no query string is passed', () => { + createComponent(); + + expect(findFilteredSearch().props('value')).toEqual([]); + }); + + describe('with query string passed', () => { + it('filtered search returns correct data shape', () => { + const tokenStatusesValue = 'SUCCESS'; + const tokenRunnerTypesValue = 'INSTANCE_VALUE'; + + createComponent({ + queryString: { statuses: tokenStatusesValue, runnerTypes: tokenRunnerTypesValue }, + }); + + expect(findFilteredSearch().props('value')).toEqual([ + { type: TOKEN_TYPE_STATUS, value: { data: tokenStatusesValue, operator: '=' } }, + { + type: TOKEN_TYPE_JOBS_RUNNER_TYPE, + value: { data: tokenRunnerTypesValue, operator: '=' }, + }, + ]); + }); + }); + + describe('when feature flag `adminJobsFilterRunnerType` is disabled', () => { + const provideOptions = { glFeatures: { adminJobsFilterRunnerType: false } }; + + it('does not display token for runner type', () => { + createComponent(null, provideOptions); + + expect(findRunnerTypeToken()).toBeUndefined(); + }); + + describe('with query string passed', () => { + it('filtered search returns only data shape for search token `status` and not for search token `jobs runner type`', () => { + const tokenStatusesValue = 'SUCCESS'; + const tokenRunnerTypesValue = 'INSTANCE_VALUE'; + + createComponent( + { queryString: { statuses: tokenStatusesValue, runnerTypes: tokenRunnerTypesValue } }, + provideOptions, + ); + + expect(findFilteredSearch().props('value')).toEqual([ + { type: TOKEN_TYPE_STATUS, value: { data: tokenStatusesValue, operator: '=' } }, + ]); + }); + }); + }); +}); diff --git a/spec/frontend/ci/common/private/jobs_filtered_search/tokens/job_status_token_spec.js b/spec/frontend/ci/common/private/jobs_filtered_search/tokens/job_status_token_spec.js new file mode 100644 index 00000000000..78a1963d939 --- /dev/null +++ b/spec/frontend/ci/common/private/jobs_filtered_search/tokens/job_status_token_spec.js @@ -0,0 +1,58 @@ +import { GlFilteredSearchToken, GlFilteredSearchSuggestion, GlIcon } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; +import { stubComponent } from 'helpers/stub_component'; +import JobStatusToken from '~/ci/common/private/jobs_filtered_search/tokens/job_status_token.vue'; +import { + TOKEN_TITLE_STATUS, + TOKEN_TYPE_STATUS, +} from '~/vue_shared/components/filtered_search_bar/constants'; + +describe('Job Status Token', () => { + let wrapper; + + const findFilteredSearchToken = () => wrapper.findComponent(GlFilteredSearchToken); + const findAllFilteredSearchSuggestions = () => + wrapper.findAllComponents(GlFilteredSearchSuggestion); + const findAllGlIcons = () => wrapper.findAllComponents(GlIcon); + + const defaultProps = { + config: { + type: TOKEN_TYPE_STATUS, + icon: 'status', + title: TOKEN_TITLE_STATUS, + unique: true, + }, + value: { + data: '', + }, + cursorPosition: 'start', + }; + + const createComponent = () => { + wrapper = shallowMount(JobStatusToken, { + propsData: { + ...defaultProps, + }, + stubs: { + GlFilteredSearchToken: stubComponent(GlFilteredSearchToken, { + template: `<div><slot name="suggestions"></slot></div>`, + }), + }, + }); + }; + + beforeEach(() => { + createComponent(); + }); + + it('passes config correctly', () => { + expect(findFilteredSearchToken().props('config')).toEqual(defaultProps.config); + }); + + it('renders all job statuses available', () => { + const expectedLength = 11; + + expect(findAllFilteredSearchSuggestions()).toHaveLength(expectedLength); + expect(findAllGlIcons()).toHaveLength(expectedLength); + }); +}); diff --git a/spec/frontend/ci/common/private/jobs_filtered_search/utils_spec.js b/spec/frontend/ci/common/private/jobs_filtered_search/utils_spec.js new file mode 100644 index 00000000000..8f6d2368bf4 --- /dev/null +++ b/spec/frontend/ci/common/private/jobs_filtered_search/utils_spec.js @@ -0,0 +1,22 @@ +import { validateQueryString } from '~/ci/common/private/jobs_filtered_search/utils'; + +describe('Filtered search utils', () => { + describe('validateQueryString', () => { + it.each` + queryStringObject | expected + ${{ statuses: 'SUCCESS' }} | ${{ statuses: 'SUCCESS' }} + ${{ statuses: 'failed' }} | ${{ statuses: 'FAILED' }} + ${{ runnerTypes: 'instance_type' }} | ${{ runnerTypes: 'INSTANCE_TYPE' }} + ${{ runnerTypes: 'wrong_runner_type' }} | ${null} + ${{ statuses: 'SUCCESS', runnerTypes: 'instance_type' }} | ${{ statuses: 'SUCCESS', runnerTypes: 'INSTANCE_TYPE' }} + ${{ wrong: 'SUCCESS' }} | ${null} + ${{ statuses: 'wrong' }} | ${null} + ${{ wrong: 'wrong' }} | ${null} + `( + 'when provided $queryStringObject, the expected result is $expected', + ({ queryStringObject, expected }) => { + expect(validateQueryString(queryStringObject)).toEqual(expected); + }, + ); + }); +}); |