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:
Diffstat (limited to 'spec/frontend/pipelines/pipelines_spec.js')
-rw-r--r--spec/frontend/pipelines/pipelines_spec.js990
1 files changed, 497 insertions, 493 deletions
diff --git a/spec/frontend/pipelines/pipelines_spec.js b/spec/frontend/pipelines/pipelines_spec.js
index 5d82669b0b8..811303a5624 100644
--- a/spec/frontend/pipelines/pipelines_spec.js
+++ b/spec/frontend/pipelines/pipelines_spec.js
@@ -1,49 +1,50 @@
-import { nextTick } from 'vue';
+import { GlFilteredSearch, GlButton, GlLoadingIcon, GlPagination } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
+import { chunk } from 'lodash';
+import { nextTick } from 'vue';
+import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
-import { GlFilteredSearch, GlButton, GlLoadingIcon } from '@gitlab/ui';
import Api from '~/api';
+import { deprecatedCreateFlash as createFlash } from '~/flash';
import axios from '~/lib/utils/axios_utils';
-import NavigationTabs from '~/vue_shared/components/navigation_tabs.vue';
-import TablePagination from '~/vue_shared/components/pagination/table_pagination.vue';
-
-import NavigationControls from '~/pipelines/components/pipelines_list/nav_controls.vue';
-import EmptyState from '~/pipelines/components/pipelines_list/empty_state.vue';
import BlankState from '~/pipelines/components/pipelines_list/blank_state.vue';
-import PipelinesTableComponent from '~/pipelines/components/pipelines_list/pipelines_table.vue';
-
+import EmptyState from '~/pipelines/components/pipelines_list/empty_state.vue';
+import NavigationControls from '~/pipelines/components/pipelines_list/nav_controls.vue';
import PipelinesComponent from '~/pipelines/components/pipelines_list/pipelines.vue';
+import PipelinesTableComponent from '~/pipelines/components/pipelines_list/pipelines_table.vue';
+import { RAW_TEXT_WARNING } from '~/pipelines/constants';
import Store from '~/pipelines/stores/pipelines_store';
+import NavigationTabs from '~/vue_shared/components/navigation_tabs.vue';
+import TablePagination from '~/vue_shared/components/pagination/table_pagination.vue';
+
import { pipelineWithStages, stageReply, users, mockSearch, branches } from './mock_data';
-import { RAW_TEXT_WARNING } from '~/pipelines/constants';
-import { deprecatedCreateFlash as createFlash } from '~/flash';
jest.mock('~/flash');
-describe('Pipelines', () => {
- const jsonFixtureName = 'pipelines/pipelines.json';
+const mockProjectPath = 'twitter/flight';
+const mockProjectId = '21';
+const mockPipelinesEndpoint = `/${mockProjectPath}/pipelines.json`;
+const mockPipelinesResponse = getJSONFixture('pipelines/pipelines.json');
+const mockPipelinesIds = mockPipelinesResponse.pipelines.map(({ id }) => id);
- preloadFixtures(jsonFixtureName);
-
- let pipelines;
+describe('Pipelines', () => {
let wrapper;
let mock;
+ let origWindowLocation;
const paths = {
- endpoint: 'twitter/flight/pipelines.json',
autoDevopsHelpPath: '/help/topics/autodevops/index.md',
helpPagePath: '/help/ci/quick_start/README',
emptyStateSvgPath: '/assets/illustrations/pipelines_empty.svg',
errorStateSvgPath: '/assets/illustrations/pipelines_failed.svg',
noPipelinesSvgPath: '/assets/illustrations/pipelines_pending.svg',
ciLintPath: '/ci/lint',
- resetCachePath: '/twitter/flight/settings/ci_cd/reset_cache',
- newPipelinePath: '/twitter/flight/pipelines/new',
+ resetCachePath: `${mockProjectPath}/settings/ci_cd/reset_cache`,
+ newPipelinePath: `${mockProjectPath}/pipelines/new`,
};
const noPermissions = {
- endpoint: 'twitter/flight/pipelines.json',
autoDevopsHelpPath: '/help/topics/autodevops/index.md',
helpPagePath: '/help/ci/quick_start/README',
emptyStateSvgPath: '/assets/illustrations/pipelines_empty.svg',
@@ -57,101 +58,140 @@ describe('Pipelines', () => {
...paths,
};
- const findFilteredSearch = () => wrapper.find(GlFilteredSearch);
- const findByTestId = (id) => wrapper.find(`[data-testid="${id}"]`);
- const findNavigationTabs = () => wrapper.find(NavigationTabs);
- const findNavigationControls = () => wrapper.find(NavigationControls);
- const findTab = (tab) => findByTestId(`pipelines-tab-${tab}`);
-
- const findRunPipelineButton = () => findByTestId('run-pipeline-button');
- const findCiLintButton = () => findByTestId('ci-lint-button');
- const findCleanCacheButton = () => findByTestId('clear-cache-button');
-
- const findEmptyState = () => wrapper.find(EmptyState);
- const findBlankState = () => wrapper.find(BlankState);
- const findStagesDropdown = () => wrapper.find('.js-builds-dropdown-button');
-
- const findTablePagination = () => wrapper.find(TablePagination);
+ const findFilteredSearch = () => wrapper.findComponent(GlFilteredSearch);
+ const findNavigationTabs = () => wrapper.findComponent(NavigationTabs);
+ const findNavigationControls = () => wrapper.findComponent(NavigationControls);
+ const findPipelinesTable = () => wrapper.findComponent(PipelinesTableComponent);
+ const findEmptyState = () => wrapper.findComponent(EmptyState);
+ const findBlankState = () => wrapper.findComponent(BlankState);
+ const findTablePagination = () => wrapper.findComponent(TablePagination);
+
+ const findTab = (tab) => wrapper.findByTestId(`pipelines-tab-${tab}`);
+ const findRunPipelineButton = () => wrapper.findByTestId('run-pipeline-button');
+ const findCiLintButton = () => wrapper.findByTestId('ci-lint-button');
+ const findCleanCacheButton = () => wrapper.findByTestId('clear-cache-button');
+ const findStagesDropdown = () => wrapper.findByTestId('mini-pipeline-graph-dropdown-toggle');
+ const findPipelineUrlLinks = () => wrapper.findAll('[data-testid="pipeline-url-link"]');
const createComponent = (props = defaultProps) => {
- wrapper = mount(PipelinesComponent, {
- propsData: {
- store: new Store(),
- projectId: '21',
- params: {},
- ...props,
- },
- });
+ wrapper = extendedWrapper(
+ mount(PipelinesComponent, {
+ propsData: {
+ store: new Store(),
+ projectId: mockProjectId,
+ endpoint: mockPipelinesEndpoint,
+ params: {},
+ ...props,
+ },
+ }),
+ );
};
- beforeEach(() => {
+ beforeAll(() => {
+ origWindowLocation = window.location;
delete window.location;
+ window.location = { search: '' };
+ });
+
+ afterAll(() => {
+ window.location = origWindowLocation;
});
beforeEach(() => {
- window.location = { search: '' };
mock = new MockAdapter(axios);
- pipelines = getJSONFixture(jsonFixtureName);
+ jest.spyOn(window.history, 'pushState');
jest.spyOn(Api, 'projectUsers').mockResolvedValue(users);
jest.spyOn(Api, 'branches').mockResolvedValue({ data: branches });
});
afterEach(() => {
wrapper.destroy();
- mock.restore();
+ mock.reset();
+ window.history.pushState.mockReset();
});
- describe('With permission', () => {
- describe('With pipelines in main tab', () => {
- beforeEach(() => {
- mock.onGet('twitter/flight/pipelines.json').reply(200, pipelines);
- createComponent();
- return waitForPromises();
- });
+ describe('when pipelines are not yet loaded', () => {
+ beforeEach(async () => {
+ createComponent();
+ await nextTick();
+ });
- it('renders tabs', () => {
- expect(findTab('all').text()).toContain('All');
- });
+ it('shows loading state when the app is loading', () => {
+ expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
+ });
- it('renders Run Pipeline link', () => {
- expect(findRunPipelineButton().attributes('href')).toBe(paths.newPipelinePath);
+ it('does not display tabs when the first request has not yet been made', () => {
+ expect(findNavigationTabs().exists()).toBe(false);
+ });
+
+ it('does not display buttons', () => {
+ expect(findNavigationControls().exists()).toBe(false);
+ });
+ });
+
+ describe('when there are pipelines in the project', () => {
+ beforeEach(() => {
+ mock
+ .onGet(mockPipelinesEndpoint, { params: { scope: 'all', page: '1' } })
+ .reply(200, mockPipelinesResponse);
+ });
+
+ describe('when user has no permissions', () => {
+ beforeEach(async () => {
+ createComponent({ hasGitlabCi: true, canCreatePipeline: false, ...noPermissions });
+ await waitForPromises();
});
- it('renders CI Lint link', () => {
- expect(findCiLintButton().attributes('href')).toBe(paths.ciLintPath);
+ it('renders "All" tab with count different from "0"', () => {
+ expect(findTab('all').text()).toMatchInterpolatedText('All 3');
});
- it('renders Clear Runner Cache button', () => {
- expect(findCleanCacheButton().text()).toBe('Clear Runner Caches');
+ it('does not render buttons', () => {
+ expect(findNavigationControls().exists()).toBe(false);
+
+ expect(findRunPipelineButton().exists()).toBe(false);
+ expect(findCiLintButton().exists()).toBe(false);
+ expect(findCleanCacheButton().exists()).toBe(false);
});
- it('renders pipelines table', () => {
- expect(wrapper.findAll('.gl-responsive-table-row')).toHaveLength(
- pipelines.pipelines.length + 1,
- );
+ it('renders pipelines in a table', () => {
+ expect(findPipelinesTable().exists()).toBe(true);
+
+ expect(findPipelineUrlLinks()).toHaveLength(mockPipelinesIds.length);
+ expect(findPipelineUrlLinks().at(0).text()).toBe(`#${mockPipelinesIds[0]}`);
+ expect(findPipelineUrlLinks().at(1).text()).toBe(`#${mockPipelinesIds[1]}`);
+ expect(findPipelineUrlLinks().at(2).text()).toBe(`#${mockPipelinesIds[2]}`);
});
});
- describe('Without pipelines on main tab with CI', () => {
- beforeEach(() => {
- mock.onGet('twitter/flight/pipelines.json').reply(200, {
- pipelines: [],
- count: {
- all: 0,
- pending: 0,
- running: 0,
- finished: 0,
- },
- });
-
+ describe('when user has permissions', () => {
+ beforeEach(async () => {
createComponent();
+ await waitForPromises();
+ });
- return waitForPromises();
+ it('should set up navigation tabs', () => {
+ expect(findNavigationTabs().props('tabs')).toEqual([
+ { name: 'All', scope: 'all', count: '3', isActive: true },
+ { name: 'Finished', scope: 'finished', count: undefined, isActive: false },
+ { name: 'Branches', scope: 'branches', isActive: false },
+ { name: 'Tags', scope: 'tags', isActive: false },
+ ]);
});
- it('renders tabs', () => {
- expect(findTab('all').text()).toContain('All');
+ it('renders "All" tab with count different from "0"', () => {
+ expect(findTab('all').text()).toMatchInterpolatedText('All 3');
+ });
+
+ it('should render other navigation tabs', () => {
+ expect(findTab('finished').text()).toBe('Finished');
+ expect(findTab('branches').text()).toBe('Branches');
+ expect(findTab('tags').text()).toBe('Tags');
+ });
+
+ it('shows navigation controls', () => {
+ expect(findNavigationControls().exists()).toBe(true);
});
it('renders Run Pipeline link', () => {
@@ -166,549 +206,513 @@ describe('Pipelines', () => {
expect(findCleanCacheButton().text()).toBe('Clear Runner Caches');
});
- it('renders tab empty state', () => {
- expect(findBlankState().text()).toBe('There are currently no pipelines.');
- });
-
- it('renders tab empty state finished scope', () => {
- wrapper.vm.scope = 'finished';
+ it('renders pipelines in a table', () => {
+ expect(findPipelinesTable().exists()).toBe(true);
- return nextTick().then(() => {
- expect(findBlankState().text()).toBe('There are currently no finished pipelines.');
- });
+ expect(findPipelineUrlLinks()).toHaveLength(mockPipelinesIds.length);
+ expect(findPipelineUrlLinks().at(0).text()).toBe(`#${mockPipelinesIds[0]}`);
+ expect(findPipelineUrlLinks().at(1).text()).toBe(`#${mockPipelinesIds[1]}`);
+ expect(findPipelineUrlLinks().at(2).text()).toBe(`#${mockPipelinesIds[2]}`);
});
- });
-
- describe('Without pipelines nor CI', () => {
- beforeEach(() => {
- mock.onGet('twitter/flight/pipelines.json').reply(200, {
- pipelines: [],
- count: {
- all: 0,
- pending: 0,
- running: 0,
- finished: 0,
- },
- });
- createComponent({ hasGitlabCi: false, canCreatePipeline: true, ...paths });
+ describe('when user goes to a tab', () => {
+ const goToTab = (tab) => {
+ findNavigationTabs().vm.$emit('onChangeTab', tab);
+ };
- return waitForPromises();
- });
+ describe('when the scope in the tab has pipelines', () => {
+ const mockFinishedPipeline = mockPipelinesResponse.pipelines[0];
- it('renders empty state', () => {
- expect(findEmptyState().find('h4').text()).toBe('Build with confidence');
- expect(findEmptyState().find(GlButton).attributes('href')).toBe(paths.helpPagePath);
- });
+ beforeEach(async () => {
+ mock
+ .onGet(mockPipelinesEndpoint, { params: { scope: 'finished', page: '1' } })
+ .reply(200, {
+ pipelines: [mockFinishedPipeline],
+ count: mockPipelinesResponse.count,
+ });
- it('does not render tabs nor buttons', () => {
- expect(findTab('all').exists()).toBe(false);
- expect(findRunPipelineButton().exists()).toBeFalsy();
- expect(findCiLintButton().exists()).toBeFalsy();
- expect(findCleanCacheButton().exists()).toBeFalsy();
- });
- });
+ goToTab('finished');
- describe('When API returns error', () => {
- beforeEach(() => {
- mock.onGet('twitter/flight/pipelines.json').reply(500, {});
- createComponent({ hasGitlabCi: false, canCreatePipeline: true, ...paths });
+ await waitForPromises();
+ });
- return waitForPromises();
- });
+ it('should filter pipelines', async () => {
+ expect(findPipelinesTable().exists()).toBe(true);
- it('renders tabs', () => {
- expect(findTab('all').text()).toContain('All');
- });
+ expect(findPipelineUrlLinks()).toHaveLength(1);
+ expect(findPipelineUrlLinks().at(0).text()).toBe(`#${mockFinishedPipeline.id}`);
+ });
- it('renders buttons', () => {
- expect(findRunPipelineButton().attributes('href')).toBe(paths.newPipelinePath);
+ it('should update browser bar', () => {
+ expect(window.history.pushState).toHaveBeenCalledTimes(1);
+ expect(window.history.pushState).toHaveBeenCalledWith(
+ expect.anything(),
+ expect.anything(),
+ `${window.location.pathname}?scope=finished&page=1`,
+ );
+ });
+ });
- expect(findCiLintButton().attributes('href')).toBe(paths.ciLintPath);
- expect(findCleanCacheButton().text()).toBe('Clear Runner Caches');
- });
+ describe('when the scope in the tab is empty', () => {
+ beforeEach(async () => {
+ mock
+ .onGet(mockPipelinesEndpoint, { params: { scope: 'branches', page: '1' } })
+ .reply(200, {
+ pipelines: [],
+ count: mockPipelinesResponse.count,
+ });
- it('renders error state', () => {
- expect(findBlankState().text()).toContain('There was an error fetching the pipelines.');
- });
- });
- });
+ goToTab('branches');
- describe('Without permission', () => {
- describe('With pipelines in main tab', () => {
- beforeEach(() => {
- mock.onGet('twitter/flight/pipelines.json').reply(200, pipelines);
+ await waitForPromises();
+ });
- createComponent({ hasGitlabCi: false, canCreatePipeline: false, ...noPermissions });
+ it('should filter pipelines', async () => {
+ expect(findBlankState().text()).toBe('There are currently no pipelines.');
+ });
- return waitForPromises();
+ it('should update browser bar', () => {
+ expect(window.history.pushState).toHaveBeenCalledTimes(1);
+ expect(window.history.pushState).toHaveBeenCalledWith(
+ expect.anything(),
+ expect.anything(),
+ `${window.location.pathname}?scope=branches&page=1`,
+ );
+ });
+ });
});
- it('renders tabs', () => {
- expect(findTab('all').text()).toContain('All');
- });
+ describe('when user triggers a filtered search', () => {
+ const mockFilteredPipeline = mockPipelinesResponse.pipelines[1];
- it('does not render buttons', () => {
- expect(findRunPipelineButton().exists()).toBeFalsy();
- expect(findCiLintButton().exists()).toBeFalsy();
- expect(findCleanCacheButton().exists()).toBeFalsy();
- });
+ let expectedParams;
- it('renders pipelines table', () => {
- expect(wrapper.findAll('.gl-responsive-table-row')).toHaveLength(
- pipelines.pipelines.length + 1,
- );
- });
- });
+ beforeEach(async () => {
+ expectedParams = {
+ page: '1',
+ scope: 'all',
+ username: 'root',
+ ref: 'master',
+ status: 'pending',
+ };
+
+ mock
+ .onGet(mockPipelinesEndpoint, {
+ params: expectedParams,
+ })
+ .replyOnce(200, {
+ pipelines: [mockFilteredPipeline],
+ count: mockPipelinesResponse.count,
+ });
- describe('Without pipelines on main tab with CI', () => {
- beforeEach(() => {
- mock.onGet('twitter/flight/pipelines.json').reply(200, {
- pipelines: [],
- count: {
- all: 0,
- pending: 0,
- running: 0,
- finished: 0,
- },
+ findFilteredSearch().vm.$emit('submit', mockSearch);
+
+ await waitForPromises();
});
- createComponent({ hasGitlabCi: true, canCreatePipeline: false, ...noPermissions });
+ it('requests data with query params on filter submit', async () => {
+ expect(mock.history.get[1].params).toEqual(expectedParams);
+ });
- return waitForPromises();
- });
+ it('renders filtered pipelines', async () => {
+ expect(findPipelineUrlLinks()).toHaveLength(1);
+ expect(findPipelineUrlLinks().at(0).text()).toBe(`#${mockFilteredPipeline.id}`);
+ });
- it('renders tabs', () => {
- expect(findTab('all').text()).toContain('All');
+ it('should update browser bar', () => {
+ expect(window.history.pushState).toHaveBeenCalledTimes(1);
+ expect(window.history.pushState).toHaveBeenCalledWith(
+ expect.anything(),
+ expect.anything(),
+ `${window.location.pathname}?page=1&scope=all&username=root&ref=master&status=pending`,
+ );
+ });
});
- it('does not render buttons', () => {
- expect(findRunPipelineButton().exists()).toBeFalsy();
- expect(findCiLintButton().exists()).toBeFalsy();
- expect(findCleanCacheButton().exists()).toBeFalsy();
- });
+ describe('when user triggers a filtered search with raw text', () => {
+ beforeEach(async () => {
+ findFilteredSearch().vm.$emit('submit', ['rawText']);
- it('renders tab empty state', () => {
- expect(wrapper.find('.empty-state h4').text()).toBe('There are currently no pipelines.');
- });
- });
+ await waitForPromises();
+ });
- describe('Without pipelines nor CI', () => {
- beforeEach(() => {
- mock.onGet('twitter/flight/pipelines.json').reply(200, {
- pipelines: [],
- count: {
- all: 0,
- pending: 0,
- running: 0,
- finished: 0,
- },
+ it('requests data with query params on filter submit', async () => {
+ expect(mock.history.get[1].params).toEqual({ page: '1', scope: 'all' });
});
- createComponent({ hasGitlabCi: false, canCreatePipeline: false, ...noPermissions });
+ it('displays a warning message if raw text search is used', () => {
+ expect(createFlash).toHaveBeenCalledTimes(1);
+ expect(createFlash).toHaveBeenCalledWith(RAW_TEXT_WARNING, 'warning');
+ });
- return waitForPromises();
+ it('should update browser bar', () => {
+ expect(window.history.pushState).toHaveBeenCalledTimes(1);
+ expect(window.history.pushState).toHaveBeenCalledWith(
+ expect.anything(),
+ expect.anything(),
+ `${window.location.pathname}?page=1&scope=all`,
+ );
+ });
});
+ });
+ });
- it('renders empty state without button to set CI', () => {
- expect(findEmptyState().text()).toBe(
- 'This project is not currently set up to run pipelines.',
- );
+ describe('when there are multiple pages of pipelines', () => {
+ const mockPageSize = 2;
+ const mockPageHeaders = ({ page = 1 } = {}) => {
+ return {
+ 'X-PER-PAGE': `${mockPageSize}`,
+ 'X-PREV-PAGE': `${page - 1}`,
+ 'X-PAGE': `${page}`,
+ 'X-NEXT-PAGE': `${page + 1}`,
+ };
+ };
+ const [firstPage, secondPage] = chunk(mockPipelinesResponse.pipelines, mockPageSize);
+
+ const goToPage = (page) => {
+ findTablePagination().find(GlPagination).vm.$emit('input', page);
+ };
+
+ beforeEach(async () => {
+ mock.onGet(mockPipelinesEndpoint, { params: { scope: 'all', page: '1' } }).reply(
+ 200,
+ {
+ pipelines: firstPage,
+ count: mockPipelinesResponse.count,
+ },
+ mockPageHeaders({ page: 1 }),
+ );
+ mock.onGet(mockPipelinesEndpoint, { params: { scope: 'all', page: '2' } }).reply(
+ 200,
+ {
+ pipelines: secondPage,
+ count: mockPipelinesResponse.count,
+ },
+ mockPageHeaders({ page: 2 }),
+ );
- expect(findEmptyState().find(GlButton).exists()).toBeFalsy();
- });
+ createComponent();
- it('does not render tabs or buttons', () => {
- expect(findTab('all').exists()).toBe(false);
- expect(findRunPipelineButton().exists()).toBeFalsy();
- expect(findCiLintButton().exists()).toBeFalsy();
- expect(findCleanCacheButton().exists()).toBeFalsy();
- });
+ await waitForPromises();
});
- describe('When API returns error', () => {
- beforeEach(() => {
- mock.onGet('twitter/flight/pipelines.json').reply(500, {});
-
- createComponent({ hasGitlabCi: false, canCreatePipeline: true, ...noPermissions });
+ it('shows the first page of pipelines', () => {
+ expect(findPipelineUrlLinks()).toHaveLength(firstPage.length);
+ expect(findPipelineUrlLinks().at(0).text()).toBe(`#${firstPage[0].id}`);
+ expect(findPipelineUrlLinks().at(1).text()).toBe(`#${firstPage[1].id}`);
+ });
- return waitForPromises();
- });
+ it('should not update browser bar', () => {
+ expect(window.history.pushState).not.toHaveBeenCalled();
+ });
- it('renders tabs', () => {
- expect(findTab('all').text()).toContain('All');
+ describe('when user goes to next page', () => {
+ beforeEach(async () => {
+ goToPage(2);
+ await waitForPromises();
});
- it('does not renders buttons', () => {
- expect(findRunPipelineButton().exists()).toBeFalsy();
- expect(findCiLintButton().exists()).toBeFalsy();
- expect(findCleanCacheButton().exists()).toBeFalsy();
+ it('should update page and keep scope the same scope', () => {
+ expect(findPipelineUrlLinks()).toHaveLength(secondPage.length);
+ expect(findPipelineUrlLinks().at(0).text()).toBe(`#${secondPage[0].id}`);
});
- it('renders error state', () => {
- expect(wrapper.find('.empty-state').text()).toContain(
- 'There was an error fetching the pipelines.',
+ it('should update browser bar', () => {
+ expect(window.history.pushState).toHaveBeenCalledTimes(1);
+ expect(window.history.pushState).toHaveBeenCalledWith(
+ expect.anything(),
+ expect.anything(),
+ `${window.location.pathname}?page=2&scope=all`,
);
});
});
});
- describe('successful request', () => {
- describe('with pipelines', () => {
- beforeEach(() => {
- mock.onGet('twitter/flight/pipelines.json').reply(200, pipelines);
+ describe('when pipelines can be polled', () => {
+ beforeEach(() => {
+ const emptyResponse = {
+ pipelines: [],
+ count: { all: '0' },
+ };
+ // Mock no pipelines in the first attempt
+ mock
+ .onGet(mockPipelinesEndpoint, { params: { scope: 'all', page: '1' } })
+ .replyOnce(200, emptyResponse, {
+ 'POLL-INTERVAL': 100,
+ });
+ // Mock pipelines in the next attempt
+ mock
+ .onGet(mockPipelinesEndpoint, { params: { scope: 'all', page: '1' } })
+ .reply(200, mockPipelinesResponse, {
+ 'POLL-INTERVAL': 100,
+ });
+ });
+
+ describe('data is loaded for the first time', () => {
+ beforeEach(async () => {
createComponent();
- return waitForPromises();
+ await waitForPromises();
});
- it('should render table', () => {
- expect(wrapper.findAll('.gl-responsive-table-row')).toHaveLength(
- pipelines.pipelines.length + 1,
- );
+ it('shows tabs', () => {
+ expect(findNavigationTabs().exists()).toBe(true);
});
- it('should set up navigation tabs', () => {
- expect(findNavigationTabs().props('tabs')).toEqual([
- { name: 'All', scope: 'all', count: '3', isActive: true },
- { name: 'Finished', scope: 'finished', count: undefined, isActive: false },
- { name: 'Branches', scope: 'branches', isActive: false },
- { name: 'Tags', scope: 'tags', isActive: false },
- ]);
+ it('should update page and keep scope the same scope', () => {
+ expect(findPipelineUrlLinks()).toHaveLength(0);
});
- it('should render navigation tabs', () => {
- expect(findTab('all').html()).toContain('All');
- expect(findTab('finished').text()).toContain('Finished');
- expect(findTab('branches').text()).toContain('Branches');
- expect(findTab('tags').text()).toContain('Tags');
- });
-
- it('should make an API request when using tabs', () => {
- createComponent({ hasGitlabCi: true, canCreatePipeline: true, ...paths });
- jest.spyOn(wrapper.vm.service, 'getPipelines');
-
- return waitForPromises().then(() => {
- findTab('finished').trigger('click');
-
- expect(wrapper.vm.service.getPipelines).toHaveBeenCalledWith({
- scope: 'finished',
- page: '1',
- });
+ describe('data is loaded for a second time', () => {
+ beforeEach(async () => {
+ jest.runOnlyPendingTimers();
+ await waitForPromises();
});
- });
-
- describe('with pagination', () => {
- it('should make an API request when using pagination', () => {
- createComponent({ hasGitlabCi: true, canCreatePipeline: true, ...paths });
- jest.spyOn(wrapper.vm.service, 'getPipelines');
- return waitForPromises()
- .then(() => {
- // Mock pagination
- wrapper.vm.store.state.pageInfo = {
- page: 1,
- total: 10,
- perPage: 2,
- nextPage: 2,
- totalPages: 5,
- };
+ it('shows tabs', () => {
+ expect(findNavigationTabs().exists()).toBe(true);
+ });
- return nextTick();
- })
- .then(() => {
- wrapper.find('.next-page-item').trigger('click');
- expect(wrapper.vm.service.getPipelines).toHaveBeenCalledWith({
- scope: 'all',
- page: '2',
- });
- });
+ it('is loading after a time', async () => {
+ expect(findPipelineUrlLinks()).toHaveLength(mockPipelinesIds.length);
+ expect(findPipelineUrlLinks().at(0).text()).toBe(`#${mockPipelinesIds[0]}`);
+ expect(findPipelineUrlLinks().at(1).text()).toBe(`#${mockPipelinesIds[1]}`);
+ expect(findPipelineUrlLinks().at(2).text()).toBe(`#${mockPipelinesIds[2]}`);
});
});
});
});
- describe('User Interaction', () => {
- let updateContentMock;
-
+ describe('when no pipelines exist', () => {
beforeEach(() => {
- jest.spyOn(window.history, 'pushState').mockImplementation(() => null);
- });
-
- beforeEach(() => {
- mock.onGet(paths.endpoint).reply(200, pipelines);
- createComponent();
-
- updateContentMock = jest.spyOn(wrapper.vm, 'updateContent');
-
- return waitForPromises();
+ mock.onGet(mockPipelinesEndpoint, { params: { scope: 'all', page: '1' } }).reply(200, {
+ pipelines: [],
+ count: { all: '0' },
+ });
});
- describe('when user changes tabs', () => {
- it('should set page to 1', () => {
- findNavigationTabs().vm.$emit('onChangeTab', 'running');
+ describe('when CI is enabled and user has permissions', () => {
+ beforeEach(async () => {
+ createComponent();
+ await waitForPromises();
+ });
- expect(updateContentMock).toHaveBeenCalledWith({ scope: 'running', page: '1' });
+ it('renders tab with count of "0"', () => {
+ expect(findNavigationTabs().exists()).toBe(true);
+ expect(findTab('all').text()).toMatchInterpolatedText('All 0');
});
- });
- describe('when user changes page', () => {
- it('should update page and keep scope', () => {
- findTablePagination().vm.change(4);
+ it('renders Run Pipeline link', () => {
+ expect(findRunPipelineButton().attributes('href')).toBe(paths.newPipelinePath);
+ });
- expect(updateContentMock).toHaveBeenCalledWith({ scope: wrapper.vm.scope, page: '4' });
+ it('renders CI Lint link', () => {
+ expect(findCiLintButton().attributes('href')).toBe(paths.ciLintPath);
});
- });
- describe('updates results when a staged is clicked', () => {
- beforeEach(() => {
- const copyPipeline = { ...pipelineWithStages };
- copyPipeline.id += 1;
- mock
- .onGet('twitter/flight/pipelines.json')
- .reply(
- 200,
- {
- pipelines: [pipelineWithStages],
- count: {
- all: 1,
- finished: 1,
- pending: 0,
- running: 0,
- },
- },
- {
- 'POLL-INTERVAL': 100,
- },
- )
- .onGet(pipelineWithStages.details.stages[0].dropdown_path)
- .reply(200, stageReply);
+ it('renders Clear Runner Cache button', () => {
+ expect(findCleanCacheButton().text()).toBe('Clear Runner Caches');
+ });
- createComponent();
+ it('renders empty state', () => {
+ expect(findBlankState().text()).toBe('There are currently no pipelines.');
});
- describe('when a request is being made', () => {
- it('stops polling, cancels the request, & restarts polling', () => {
- const stopMock = jest.spyOn(wrapper.vm.poll, 'stop');
- const restartMock = jest.spyOn(wrapper.vm.poll, 'restart');
- const cancelMock = jest.spyOn(wrapper.vm.service.cancelationSource, 'cancel');
- mock.onGet('twitter/flight/pipelines.json').reply(200, pipelines);
-
- return waitForPromises()
- .then(() => {
- wrapper.vm.isMakingRequest = true;
- findStagesDropdown().trigger('click');
- })
- .then(() => {
- expect(cancelMock).toHaveBeenCalled();
- expect(stopMock).toHaveBeenCalled();
- expect(restartMock).toHaveBeenCalled();
- });
+ it('renders tab empty state finished scope', async () => {
+ mock.onGet(mockPipelinesEndpoint, { params: { scope: 'finished', page: '1' } }).reply(200, {
+ pipelines: [],
+ count: { all: '0' },
});
- });
- describe('when no request is being made', () => {
- it('stops polling & restarts polling', () => {
- const stopMock = jest.spyOn(wrapper.vm.poll, 'stop');
- const restartMock = jest.spyOn(wrapper.vm.poll, 'restart');
- mock.onGet('twitter/flight/pipelines.json').reply(200, pipelines);
+ findNavigationTabs().vm.$emit('onChangeTab', 'finished');
- return waitForPromises()
- .then(() => {
- findStagesDropdown().trigger('click');
- expect(stopMock).toHaveBeenCalled();
- })
- .then(() => {
- expect(restartMock).toHaveBeenCalled();
- });
- });
- });
- });
- });
+ await waitForPromises();
- describe('Rendered content', () => {
- beforeEach(() => {
- createComponent();
+ expect(findBlankState().text()).toBe('There are currently no finished pipelines.');
+ });
});
- describe('displays different content', () => {
- it('shows loading state when the app is loading', () => {
- expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
+ describe('when CI is not enabled and user has permissions', () => {
+ beforeEach(async () => {
+ createComponent({ hasGitlabCi: false, canCreatePipeline: true, ...paths });
+ await waitForPromises();
});
- it('shows error state when app has error', () => {
- wrapper.vm.hasError = true;
- wrapper.vm.isLoading = false;
-
- return nextTick().then(() => {
- expect(findBlankState().props('message')).toBe(
- 'There was an error fetching the pipelines. Try again in a few moments or contact your support team.',
- );
- });
+ it('renders empty state', () => {
+ expect(findEmptyState().find('[data-testid="header-text"]').text()).toBe(
+ 'Build with confidence',
+ );
+ expect(findEmptyState().find('[data-testid="info-text"]').text()).toContain(
+ 'GitLab CI/CD can automatically build, test, and deploy your code.',
+ );
+ expect(findEmptyState().find(GlButton).text()).toBe('Get started with CI/CD');
+ expect(findEmptyState().find(GlButton).attributes('href')).toBe(paths.helpPagePath);
});
- it('shows table list when app has pipelines', () => {
- wrapper.vm.isLoading = false;
- wrapper.vm.hasError = false;
- wrapper.vm.state.pipelines = pipelines.pipelines;
-
- return nextTick().then(() => {
- expect(wrapper.find(PipelinesTableComponent).exists()).toBe(true);
- });
+ it('does not render tabs nor buttons', () => {
+ expect(findNavigationTabs().exists()).toBe(false);
+ expect(findTab('all').exists()).toBe(false);
+ expect(findRunPipelineButton().exists()).toBe(false);
+ expect(findCiLintButton().exists()).toBe(false);
+ expect(findCleanCacheButton().exists()).toBe(false);
});
+ });
- it('shows empty tab when app does not have pipelines but project has pipelines', () => {
- wrapper.vm.state.count.all = 10;
- wrapper.vm.isLoading = false;
-
- return nextTick().then(() => {
- expect(findBlankState().exists()).toBe(true);
- expect(findBlankState().props('message')).toBe('There are currently no pipelines.');
- });
+ describe('when CI is not enabled and user has no permissions', () => {
+ beforeEach(async () => {
+ createComponent({ hasGitlabCi: false, canCreatePipeline: false, ...noPermissions });
+ await waitForPromises();
});
- it('shows empty tab when project has CI', () => {
- wrapper.vm.isLoading = false;
+ it('renders empty state without button to set CI', () => {
+ expect(findEmptyState().text()).toBe(
+ 'This project is not currently set up to run pipelines.',
+ );
- return nextTick().then(() => {
- expect(findBlankState().exists()).toBe(true);
- expect(findBlankState().props('message')).toBe('There are currently no pipelines.');
- });
+ expect(findEmptyState().find(GlButton).exists()).toBe(false);
});
- it('shows empty state when project does not have pipelines nor CI', () => {
- createComponent({ hasGitlabCi: false, canCreatePipeline: true, ...paths });
-
- wrapper.vm.isLoading = false;
-
- return nextTick().then(() => {
- expect(wrapper.find(EmptyState).exists()).toBe(true);
- });
+ it('does not render tabs or buttons', () => {
+ expect(findTab('all').exists()).toBe(false);
+ expect(findRunPipelineButton().exists()).toBe(false);
+ expect(findCiLintButton().exists()).toBe(false);
+ expect(findCleanCacheButton().exists()).toBe(false);
});
});
- describe('displays tabs', () => {
- it('returns true when state is loading & has already made the first request', () => {
- wrapper.vm.isLoading = true;
- wrapper.vm.hasMadeRequest = true;
+ describe('when CI is enabled and user has no permissions', () => {
+ beforeEach(() => {
+ createComponent({ hasGitlabCi: true, canCreatePipeline: false, ...noPermissions });
- return nextTick().then(() => {
- expect(findNavigationTabs().exists()).toBe(true);
- });
+ return waitForPromises();
});
- it('returns true when state is tableList & has already made the first request', () => {
- wrapper.vm.isLoading = false;
- wrapper.vm.state.pipelines = pipelines.pipelines;
- wrapper.vm.hasMadeRequest = true;
-
- return nextTick().then(() => {
- expect(findNavigationTabs().exists()).toBe(true);
- });
+ it('renders tab with count of "0"', () => {
+ expect(findTab('all').text()).toMatchInterpolatedText('All 0');
});
- it('returns true when state is error & has already made the first request', () => {
- wrapper.vm.isLoading = false;
- wrapper.vm.hasError = true;
- wrapper.vm.hasMadeRequest = true;
+ it('does not render buttons', () => {
+ expect(findRunPipelineButton().exists()).toBe(false);
+ expect(findCiLintButton().exists()).toBe(false);
+ expect(findCleanCacheButton().exists()).toBe(false);
+ });
- return nextTick().then(() => {
- expect(findNavigationTabs().exists()).toBe(true);
- });
+ it('renders empty state', () => {
+ expect(findBlankState().text()).toBe('There are currently no pipelines.');
});
+ });
+ });
- it('returns true when state is empty tab & has already made the first request', () => {
- wrapper.vm.isLoading = false;
- wrapper.vm.state.count.all = 10;
- wrapper.vm.hasMadeRequest = true;
+ describe('when a pipeline with stages exists', () => {
+ describe('updates results when a staged is clicked', () => {
+ let stopMock;
+ let restartMock;
+ let cancelMock;
- return nextTick().then(() => {
- expect(findNavigationTabs().exists()).toBe(true);
- });
- });
+ beforeEach(() => {
+ mock.onGet(mockPipelinesEndpoint, { scope: 'all', page: '1' }).reply(
+ 200,
+ {
+ pipelines: [pipelineWithStages],
+ count: { all: '1' },
+ },
+ {
+ 'POLL-INTERVAL': 100,
+ },
+ );
+ mock.onGet(pipelineWithStages.details.stages[0].dropdown_path).reply(200, stageReply);
- it('returns false when has not made first request', () => {
- wrapper.vm.hasMadeRequest = false;
+ createComponent();
- return nextTick().then(() => {
- expect(findNavigationTabs().exists()).toBe(false);
- });
+ stopMock = jest.spyOn(wrapper.vm.poll, 'stop');
+ restartMock = jest.spyOn(wrapper.vm.poll, 'restart');
+ cancelMock = jest.spyOn(wrapper.vm.service.cancelationSource, 'cancel');
});
- it('returns false when state is empty state', () => {
- createComponent({ hasGitlabCi: false, canCreatePipeline: true, ...paths });
-
- wrapper.vm.isLoading = false;
- wrapper.vm.hasMadeRequest = true;
+ describe('when a request is being made', () => {
+ beforeEach(async () => {
+ mock.onGet(mockPipelinesEndpoint).reply(200, mockPipelinesResponse);
- return nextTick().then(() => {
- expect(findNavigationTabs().exists()).toBe(false);
+ await waitForPromises();
});
- });
- });
- describe('displays buttons', () => {
- it('returns true when it has paths & has made the first request', () => {
- wrapper.vm.hasMadeRequest = true;
+ it('stops polling, cancels the request, & restarts polling', async () => {
+ // Mock init a polling cycle
+ wrapper.vm.poll.options.notificationCallback(true);
+
+ findStagesDropdown().trigger('click');
- return nextTick().then(() => {
- expect(findNavigationControls().exists()).toBe(true);
+ await waitForPromises();
+
+ expect(cancelMock).toHaveBeenCalled();
+ expect(stopMock).toHaveBeenCalled();
+ expect(restartMock).toHaveBeenCalled();
});
- });
- it('returns false when it has not made the first request', () => {
- wrapper.vm.hasMadeRequest = false;
+ it('stops polling & restarts polling', async () => {
+ findStagesDropdown().trigger('click');
- return nextTick().then(() => {
- expect(findNavigationControls().exists()).toBe(false);
+ expect(cancelMock).not.toHaveBeenCalled();
+ expect(stopMock).toHaveBeenCalled();
+ expect(restartMock).toHaveBeenCalled();
});
});
});
});
- describe('Pipeline filters', () => {
- let updateContentMock;
-
- beforeEach(() => {
- mock.onGet(paths.endpoint).reply(200, pipelines);
- createComponent();
+ describe('when pipelines cannot be loaded', () => {
+ beforeEach(async () => {
+ mock.onGet(mockPipelinesEndpoint).reply(500, {});
+ });
- updateContentMock = jest.spyOn(wrapper.vm, 'updateContent');
+ describe('when user has no permissions', () => {
+ beforeEach(async () => {
+ createComponent({ hasGitlabCi: false, canCreatePipeline: true, ...noPermissions });
- return waitForPromises();
- });
+ await waitForPromises();
+ });
- it('updates request data and query params on filter submit', async () => {
- const expectedQueryParams = {
- page: '1',
- scope: 'all',
- username: 'root',
- ref: 'master',
- status: 'pending',
- };
+ it('renders tabs', () => {
+ expect(findNavigationTabs().exists()).toBe(true);
+ expect(findTab('all').text()).toBe('All');
+ });
- findFilteredSearch().vm.$emit('submit', mockSearch);
- await nextTick();
+ it('does not render buttons', () => {
+ expect(findRunPipelineButton().exists()).toBe(false);
+ expect(findCiLintButton().exists()).toBe(false);
+ expect(findCleanCacheButton().exists()).toBe(false);
+ });
- expect(wrapper.vm.requestData).toEqual(expectedQueryParams);
- expect(updateContentMock).toHaveBeenCalledWith(expectedQueryParams);
+ it('shows error state', () => {
+ expect(findBlankState().text()).toBe(
+ 'There was an error fetching the pipelines. Try again in a few moments or contact your support team.',
+ );
+ });
});
- it('does not add query params if raw text search is used', async () => {
- const expectedQueryParams = { page: '1', scope: 'all' };
+ describe('when user has permissions', () => {
+ beforeEach(async () => {
+ createComponent();
- findFilteredSearch().vm.$emit('submit', ['rawText']);
- await nextTick();
+ await waitForPromises();
+ });
- expect(wrapper.vm.requestData).toEqual(expectedQueryParams);
- expect(updateContentMock).toHaveBeenCalledWith(expectedQueryParams);
- });
+ it('renders tabs', () => {
+ expect(findTab('all').text()).toBe('All');
+ });
+
+ it('renders buttons', () => {
+ expect(findRunPipelineButton().attributes('href')).toBe(paths.newPipelinePath);
- it('displays a warning message if raw text search is used', () => {
- findFilteredSearch().vm.$emit('submit', ['rawText']);
+ expect(findCiLintButton().attributes('href')).toBe(paths.ciLintPath);
+ expect(findCleanCacheButton().text()).toBe('Clear Runner Caches');
+ });
- expect(createFlash).toHaveBeenCalledTimes(1);
- expect(createFlash).toHaveBeenCalledWith(RAW_TEXT_WARNING, 'warning');
+ it('shows error state', () => {
+ expect(findBlankState().text()).toBe(
+ 'There was an error fetching the pipelines. Try again in a few moments or contact your support team.',
+ );
+ });
});
});
});