Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-04-20 13:00:54 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-04-20 13:00:54 +0300
commit3cccd102ba543e02725d247893729e5c73b38295 (patch)
treef36a04ec38517f5deaaacb5acc7d949688d1e187 /spec/frontend/jobs/components
parent205943281328046ef7b4528031b90fbda70c75ac (diff)
Add latest changes from gitlab-org/gitlab@14-10-stable-eev14.10.0-rc42
Diffstat (limited to 'spec/frontend/jobs/components')
-rw-r--r--spec/frontend/jobs/components/filtered_search/jobs_filtered_search_spec.js49
-rw-r--r--spec/frontend/jobs/components/filtered_search/tokens/job_status_token_spec.js57
-rw-r--r--spec/frontend/jobs/components/job_app_spec.js23
-rw-r--r--spec/frontend/jobs/components/table/graphql/cache_config_spec.js20
-rw-r--r--spec/frontend/jobs/components/table/job_table_app_spec.js105
-rw-r--r--spec/frontend/jobs/components/table/jobs_table_tabs_spec.js46
-rw-r--r--spec/frontend/jobs/components/trigger_block_spec.js7
7 files changed, 276 insertions, 31 deletions
diff --git a/spec/frontend/jobs/components/filtered_search/jobs_filtered_search_spec.js b/spec/frontend/jobs/components/filtered_search/jobs_filtered_search_spec.js
new file mode 100644
index 00000000000..322cfa3ba1f
--- /dev/null
+++ b/spec/frontend/jobs/components/filtered_search/jobs_filtered_search_spec.js
@@ -0,0 +1,49 @@
+import { GlFilteredSearch } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import { OPERATOR_IS_ONLY } from '~/vue_shared/components/filtered_search_bar/constants';
+import JobsFilteredSearch from '~/jobs/components/filtered_search/jobs_filtered_search.vue';
+import { mockFailedSearchToken } from '../../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 createComponent = () => {
+ wrapper = shallowMount(JobsFilteredSearch);
+ };
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('displays filtered search', () => {
+ expect(findFilteredSearch().exists()).toBe(true);
+ });
+
+ it('displays status token', () => {
+ expect(findStatusToken()).toMatchObject({
+ type: 'status',
+ icon: 'status',
+ title: 'Status',
+ unique: true,
+ operators: OPERATOR_IS_ONLY,
+ });
+ });
+
+ it('emits filter token to parent component', () => {
+ findFilteredSearch().vm.$emit('submit', mockFailedSearchToken);
+
+ expect(wrapper.emitted('filterJobsBySearch')).toEqual([[mockFailedSearchToken]]);
+ });
+});
diff --git a/spec/frontend/jobs/components/filtered_search/tokens/job_status_token_spec.js b/spec/frontend/jobs/components/filtered_search/tokens/job_status_token_spec.js
new file mode 100644
index 00000000000..ce8e482cc16
--- /dev/null
+++ b/spec/frontend/jobs/components/filtered_search/tokens/job_status_token_spec.js
@@ -0,0 +1,57 @@
+import { GlFilteredSearchToken, GlFilteredSearchSuggestion, GlIcon } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import { stubComponent } from 'helpers/stub_component';
+import JobStatusToken from '~/jobs/components/filtered_search/tokens/job_status_token.vue';
+
+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: 'status',
+ icon: 'status',
+ title: 'Status',
+ unique: true,
+ },
+ value: {
+ data: '',
+ },
+ };
+
+ const createComponent = () => {
+ wrapper = shallowMount(JobStatusToken, {
+ propsData: {
+ ...defaultProps,
+ },
+ stubs: {
+ GlFilteredSearchToken: stubComponent(GlFilteredSearchToken, {
+ template: `<div><slot name="suggestions"></slot></div>`,
+ }),
+ },
+ });
+ };
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ 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/jobs/components/job_app_spec.js b/spec/frontend/jobs/components/job_app_spec.js
index 06ebcd7f134..9abe66b4696 100644
--- a/spec/frontend/jobs/components/job_app_spec.js
+++ b/spec/frontend/jobs/components/job_app_spec.js
@@ -375,8 +375,8 @@ describe('Job App', () => {
});
describe('sidebar', () => {
- it('has no blank blocks', (done) => {
- setupAndMount({
+ it('has no blank blocks', async () => {
+ await setupAndMount({
jobData: {
duration: null,
finished_at: null,
@@ -387,17 +387,14 @@ describe('Job App', () => {
tags: [],
cancel_path: null,
},
- })
- .then(() => {
- const blocks = wrapper.findAll('.blocks-container > *').wrappers;
- expect(blocks.length).toBeGreaterThan(0);
-
- blocks.forEach((block) => {
- expect(block.text().trim()).not.toBe('');
- });
- })
- .then(done)
- .catch(done.fail);
+ });
+
+ const blocks = wrapper.findAll('.blocks-container > *').wrappers;
+ expect(blocks.length).toBeGreaterThan(0);
+
+ blocks.forEach((block) => {
+ expect(block.text().trim()).not.toBe('');
+ });
});
});
});
diff --git a/spec/frontend/jobs/components/table/graphql/cache_config_spec.js b/spec/frontend/jobs/components/table/graphql/cache_config_spec.js
index ac79186cb46..88c97285b85 100644
--- a/spec/frontend/jobs/components/table/graphql/cache_config_spec.js
+++ b/spec/frontend/jobs/components/table/graphql/cache_config_spec.js
@@ -33,6 +33,26 @@ describe('jobs/components/table/graphql/cache_config', () => {
);
});
+ it('should not add to existing cache if the incoming elements are the same', () => {
+ // simulate that this is the last page
+ const finalExistingCache = {
+ ...CIJobConnectionExistingCache,
+ pageInfo: {
+ hasNextPage: false,
+ },
+ };
+
+ const res = cacheConfig.typePolicies.CiJobConnection.merge(
+ CIJobConnectionExistingCache,
+ finalExistingCache,
+ {
+ args: firstLoadArgs,
+ },
+ );
+
+ expect(res.nodes).toHaveLength(CIJobConnectionExistingCache.nodes.length);
+ });
+
it('should contain the pageInfo key as part of the result', () => {
const res = cacheConfig.typePolicies.CiJobConnection.merge({}, CIJobConnectionIncomingCache, {
args: firstLoadArgs,
diff --git a/spec/frontend/jobs/components/table/job_table_app_spec.js b/spec/frontend/jobs/components/table/job_table_app_spec.js
index 4d51624dfff..986fba21fb9 100644
--- a/spec/frontend/jobs/components/table/job_table_app_spec.js
+++ b/spec/frontend/jobs/components/table/job_table_app_spec.js
@@ -1,30 +1,48 @@
-import { GlSkeletonLoader, GlAlert, GlEmptyState, GlIntersectionObserver } from '@gitlab/ui';
+import {
+ GlSkeletonLoader,
+ GlAlert,
+ GlEmptyState,
+ GlIntersectionObserver,
+ GlLoadingIcon,
+} from '@gitlab/ui';
import { mount, shallowMount } from '@vue/test-utils';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
+import { s__ } from '~/locale';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
+import createFlash from '~/flash';
import getJobsQuery from '~/jobs/components/table/graphql/queries/get_jobs.query.graphql';
import JobsTable from '~/jobs/components/table/jobs_table.vue';
import JobsTableApp from '~/jobs/components/table/jobs_table_app.vue';
import JobsTableTabs from '~/jobs/components/table/jobs_table_tabs.vue';
-import { mockJobsQueryResponse, mockJobsQueryEmptyResponse } from '../../mock_data';
+import JobsFilteredSearch from '~/jobs/components/filtered_search/jobs_filtered_search.vue';
+import {
+ mockJobsQueryResponse,
+ mockJobsQueryEmptyResponse,
+ mockFailedSearchToken,
+} from '../../mock_data';
const projectPath = 'gitlab-org/gitlab';
Vue.use(VueApollo);
+jest.mock('~/flash');
+
describe('Job table app', () => {
let wrapper;
+ let jobsTableVueSearch = true;
const successHandler = jest.fn().mockResolvedValue(mockJobsQueryResponse);
const failedHandler = jest.fn().mockRejectedValue(new Error('GraphQL error'));
const emptyHandler = jest.fn().mockResolvedValue(mockJobsQueryEmptyResponse);
const findSkeletonLoader = () => wrapper.findComponent(GlSkeletonLoader);
+ const findLoadingSpinner = () => wrapper.findComponent(GlLoadingIcon);
const findTable = () => wrapper.findComponent(JobsTable);
const findTabs = () => wrapper.findComponent(JobsTableTabs);
const findAlert = () => wrapper.findComponent(GlAlert);
const findEmptyState = () => wrapper.findComponent(GlEmptyState);
+ const findFilteredSearch = () => wrapper.findComponent(JobsFilteredSearch);
const triggerInfiniteScroll = () =>
wrapper.findComponent(GlIntersectionObserver).vm.$emit('appear');
@@ -48,6 +66,7 @@ describe('Job table app', () => {
},
provide: {
fullPath: projectPath,
+ glFeatures: { jobsTableVueSearch },
},
apolloProvider: createMockApolloProvider(handler),
});
@@ -58,11 +77,21 @@ describe('Job table app', () => {
});
describe('loading state', () => {
- it('should display skeleton loader when loading', () => {
+ beforeEach(() => {
createComponent();
+ });
+ it('should display skeleton loader when loading', () => {
expect(findSkeletonLoader().exists()).toBe(true);
expect(findTable().exists()).toBe(false);
+ expect(findLoadingSpinner().exists()).toBe(false);
+ });
+
+ it('when switching tabs only the skeleton loader should show', () => {
+ findTabs().vm.$emit('fetchJobsByStatus', null);
+
+ expect(findSkeletonLoader().exists()).toBe(true);
+ expect(findLoadingSpinner().exists()).toBe(false);
});
});
@@ -76,6 +105,7 @@ describe('Job table app', () => {
it('should display the jobs table with data', () => {
expect(findTable().exists()).toBe(true);
expect(findSkeletonLoader().exists()).toBe(false);
+ expect(findLoadingSpinner().exists()).toBe(false);
});
it('should refetch jobs query on fetchJobsByStatus event', async () => {
@@ -98,8 +128,12 @@ describe('Job table app', () => {
});
it('handles infinite scrolling by calling fetch more', async () => {
+ expect(findLoadingSpinner().exists()).toBe(true);
+
await waitForPromises();
+ expect(findLoadingSpinner().exists()).toBe(false);
+
expect(successHandler).toHaveBeenCalledWith({
after: 'eyJpZCI6IjIzMTcifQ',
fullPath: 'gitlab-org/gitlab',
@@ -137,4 +171,69 @@ describe('Job table app', () => {
expect(findTable().exists()).toBe(true);
});
});
+
+ describe('filtered search', () => {
+ it('should display filtered search', () => {
+ createComponent();
+
+ expect(findFilteredSearch().exists()).toBe(true);
+ });
+
+ // this test should be updated once BE supports tab and filtered search filtering
+ // https://gitlab.com/gitlab-org/gitlab/-/issues/356210
+ it.each`
+ scope | shouldDisplay
+ ${null} | ${true}
+ ${['FAILED', 'SUCCESS', 'CANCELED']} | ${false}
+ `(
+ 'with tab scope $scope the filtered search displays $shouldDisplay',
+ async ({ scope, shouldDisplay }) => {
+ createComponent();
+
+ await waitForPromises();
+
+ await findTabs().vm.$emit('fetchJobsByStatus', scope);
+
+ expect(findFilteredSearch().exists()).toBe(shouldDisplay);
+ },
+ );
+
+ it('refetches jobs query when filtering', async () => {
+ createComponent();
+
+ jest.spyOn(wrapper.vm.$apollo.queries.jobs, 'refetch').mockImplementation(jest.fn());
+
+ expect(wrapper.vm.$apollo.queries.jobs.refetch).toHaveBeenCalledTimes(0);
+
+ await findFilteredSearch().vm.$emit('filterJobsBySearch', [mockFailedSearchToken]);
+
+ expect(wrapper.vm.$apollo.queries.jobs.refetch).toHaveBeenCalledTimes(1);
+ });
+
+ it('shows raw text warning when user inputs raw text', async () => {
+ const expectedWarning = {
+ message: s__(
+ 'Jobs|Raw text search is not currently supported for the jobs filtered search feature. Please use the available search tokens.',
+ ),
+ type: 'warning',
+ };
+
+ createComponent();
+
+ jest.spyOn(wrapper.vm.$apollo.queries.jobs, 'refetch').mockImplementation(jest.fn());
+
+ await findFilteredSearch().vm.$emit('filterJobsBySearch', ['raw text']);
+
+ expect(createFlash).toHaveBeenCalledWith(expectedWarning);
+ expect(wrapper.vm.$apollo.queries.jobs.refetch).toHaveBeenCalledTimes(0);
+ });
+
+ it('should not display filtered search', () => {
+ jobsTableVueSearch = false;
+
+ createComponent();
+
+ expect(findFilteredSearch().exists()).toBe(false);
+ });
+ });
});
diff --git a/spec/frontend/jobs/components/table/jobs_table_tabs_spec.js b/spec/frontend/jobs/components/table/jobs_table_tabs_spec.js
index ac9b45be932..23632001060 100644
--- a/spec/frontend/jobs/components/table/jobs_table_tabs_spec.js
+++ b/spec/frontend/jobs/components/table/jobs_table_tabs_spec.js
@@ -1,3 +1,4 @@
+import { GlTab } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import { trimText } from 'helpers/text_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
@@ -7,16 +8,31 @@ describe('Jobs Table Tabs', () => {
let wrapper;
const defaultProps = {
- jobCounts: { all: 848, pending: 0, running: 0, finished: 704 },
+ allJobsCount: 286,
+ loading: false,
};
- const findTab = (testId) => wrapper.findByTestId(testId);
+ const statuses = {
+ success: 'SUCCESS',
+ failed: 'FAILED',
+ canceled: 'CANCELED',
+ };
+
+ const findAllTab = () => wrapper.findByTestId('jobs-all-tab');
+ const findFinishedTab = () => wrapper.findByTestId('jobs-finished-tab');
+
+ const triggerTabChange = (index) => wrapper.findAllComponents(GlTab).at(index).vm.$emit('click');
- const createComponent = () => {
+ const createComponent = (props = defaultProps) => {
wrapper = extendedWrapper(
mount(JobsTableTabs, {
provide: {
- ...defaultProps,
+ jobStatuses: {
+ ...statuses,
+ },
+ },
+ propsData: {
+ ...props,
},
}),
);
@@ -30,13 +46,21 @@ describe('Jobs Table Tabs', () => {
wrapper.destroy();
});
+ it('displays All tab with count', () => {
+ expect(trimText(findAllTab().text())).toBe(`All ${defaultProps.allJobsCount}`);
+ });
+
+ it('displays Finished tab with no count', () => {
+ expect(findFinishedTab().text()).toBe('Finished');
+ });
+
it.each`
- tabId | text | count
- ${'jobs-all-tab'} | ${'All'} | ${defaultProps.jobCounts.all}
- ${'jobs-pending-tab'} | ${'Pending'} | ${defaultProps.jobCounts.pending}
- ${'jobs-running-tab'} | ${'Running'} | ${defaultProps.jobCounts.running}
- ${'jobs-finished-tab'} | ${'Finished'} | ${defaultProps.jobCounts.finished}
- `('displays the right tab text and badge count', ({ tabId, text, count }) => {
- expect(trimText(findTab(tabId).text())).toBe(`${text} ${count}`);
+ tabIndex | expectedScope
+ ${0} | ${null}
+ ${1} | ${[statuses.success, statuses.failed, statuses.canceled]}
+ `('emits fetchJobsByStatus with $expectedScope on tab change', ({ tabIndex, expectedScope }) => {
+ triggerTabChange(tabIndex);
+
+ expect(wrapper.emitted()).toEqual({ fetchJobsByStatus: [[expectedScope]] });
});
});
diff --git a/spec/frontend/jobs/components/trigger_block_spec.js b/spec/frontend/jobs/components/trigger_block_spec.js
index e0eb873dc2f..78596612d23 100644
--- a/spec/frontend/jobs/components/trigger_block_spec.js
+++ b/spec/frontend/jobs/components/trigger_block_spec.js
@@ -1,12 +1,12 @@
-import { GlButton, GlTable } from '@gitlab/ui';
+import { GlButton, GlTableLite } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import TriggerBlock from '~/jobs/components/trigger_block.vue';
describe('Trigger block', () => {
let wrapper;
- const findRevealButton = () => wrapper.find(GlButton);
- const findVariableTable = () => wrapper.find(GlTable);
+ const findRevealButton = () => wrapper.findComponent(GlButton);
+ const findVariableTable = () => wrapper.findComponent(GlTableLite);
const findShortToken = () => wrapper.find('[data-testid="trigger-short-token"]');
const findVariableValue = (index) =>
wrapper.findAll('[data-testid="trigger-build-value"]').at(index);
@@ -22,7 +22,6 @@ describe('Trigger block', () => {
afterEach(() => {
wrapper.destroy();
- wrapper = null;
});
describe('with short token and no variables', () => {