diff options
Diffstat (limited to 'spec/frontend/pipelines')
-rw-r--r-- | spec/frontend/pipelines/mock_data.js | 423 | ||||
-rw-r--r-- | spec/frontend/pipelines/pipelines_spec.js | 659 |
2 files changed, 1082 insertions, 0 deletions
diff --git a/spec/frontend/pipelines/mock_data.js b/spec/frontend/pipelines/mock_data.js new file mode 100644 index 00000000000..f876987cd88 --- /dev/null +++ b/spec/frontend/pipelines/mock_data.js @@ -0,0 +1,423 @@ +export const pipelineWithStages = { + id: 20333396, + user: { + id: 128633, + name: 'Rémy Coutable', + username: 'rymai', + state: 'active', + avatar_url: + 'https://secure.gravatar.com/avatar/263da227929cc0035cb0eba512bcf81a?s=80\u0026d=identicon', + web_url: 'https://gitlab.com/rymai', + path: '/rymai', + }, + active: true, + coverage: '58.24', + source: 'push', + created_at: '2018-04-11T14:04:53.881Z', + updated_at: '2018-04-11T14:05:00.792Z', + path: '/gitlab-org/gitlab/pipelines/20333396', + flags: { + latest: true, + stuck: false, + auto_devops: false, + yaml_errors: false, + retryable: false, + cancelable: true, + failure_reason: false, + }, + details: { + status: { + icon: 'status_running', + text: 'running', + label: 'running', + group: 'running', + has_details: true, + details_path: '/gitlab-org/gitlab/pipelines/20333396', + favicon: + 'https://assets.gitlab-static.net/assets/ci_favicons/favicon_status_running-2eb56be2871937954b2ba6d6f4ee9fdf7e5e1c146ac45f7be98119ccaca1aca9.ico', + }, + duration: null, + finished_at: null, + stages: [ + { + name: 'build', + title: 'build: skipped', + status: { + icon: 'status_skipped', + text: 'skipped', + label: 'skipped', + group: 'skipped', + has_details: true, + details_path: '/gitlab-org/gitlab/pipelines/20333396#build', + favicon: + 'https://assets.gitlab-static.net/assets/ci_favicons/favicon_status_skipped-a2eee568a5bffdb494050c7b62dde241de9189280836288ac8923d369f16222d.ico', + }, + path: '/gitlab-org/gitlab/pipelines/20333396#build', + dropdown_path: '/gitlab-org/gitlab/pipelines/20333396/stage.json?stage=build', + }, + { + name: 'prepare', + title: 'prepare: passed', + status: { + icon: 'status_success', + text: 'passed', + label: 'passed', + group: 'success', + has_details: true, + details_path: '/gitlab-org/gitlab/pipelines/20333396#prepare', + favicon: + 'https://assets.gitlab-static.net/assets/ci_favicons/favicon_status_success-26f59841becbef8c6fe414e9e74471d8bfd6a91b5855c19fe7f5923a40a7da47.ico', + }, + path: '/gitlab-org/gitlab/pipelines/20333396#prepare', + dropdown_path: '/gitlab-org/gitlab/pipelines/20333396/stage.json?stage=prepare', + }, + { + name: 'test', + title: 'test: running', + status: { + icon: 'status_running', + text: 'running', + label: 'running', + group: 'running', + has_details: true, + details_path: '/gitlab-org/gitlab/pipelines/20333396#test', + favicon: + 'https://assets.gitlab-static.net/assets/ci_favicons/favicon_status_running-2eb56be2871937954b2ba6d6f4ee9fdf7e5e1c146ac45f7be98119ccaca1aca9.ico', + }, + path: '/gitlab-org/gitlab/pipelines/20333396#test', + dropdown_path: '/gitlab-org/gitlab/pipelines/20333396/stage.json?stage=test', + }, + { + name: 'post-test', + title: 'post-test: created', + status: { + icon: 'status_created', + text: 'created', + label: 'created', + group: 'created', + has_details: true, + details_path: '/gitlab-org/gitlab/pipelines/20333396#post-test', + favicon: + 'https://assets.gitlab-static.net/assets/ci_favicons/favicon_status_created-e997aa0b7db73165df8a9d6803932b18d7b7cc37d604d2d96e378fea2dba9c5f.ico', + }, + path: '/gitlab-org/gitlab/pipelines/20333396#post-test', + dropdown_path: '/gitlab-org/gitlab/pipelines/20333396/stage.json?stage=post-test', + }, + { + name: 'pages', + title: 'pages: created', + status: { + icon: 'status_created', + text: 'created', + label: 'created', + group: 'created', + has_details: true, + details_path: '/gitlab-org/gitlab/pipelines/20333396#pages', + favicon: + 'https://assets.gitlab-static.net/assets/ci_favicons/favicon_status_created-e997aa0b7db73165df8a9d6803932b18d7b7cc37d604d2d96e378fea2dba9c5f.ico', + }, + path: '/gitlab-org/gitlab/pipelines/20333396#pages', + dropdown_path: '/gitlab-org/gitlab/pipelines/20333396/stage.json?stage=pages', + }, + { + name: 'post-cleanup', + title: 'post-cleanup: created', + status: { + icon: 'status_created', + text: 'created', + label: 'created', + group: 'created', + has_details: true, + details_path: '/gitlab-org/gitlab/pipelines/20333396#post-cleanup', + favicon: + 'https://assets.gitlab-static.net/assets/ci_favicons/favicon_status_created-e997aa0b7db73165df8a9d6803932b18d7b7cc37d604d2d96e378fea2dba9c5f.ico', + }, + path: '/gitlab-org/gitlab/pipelines/20333396#post-cleanup', + dropdown_path: '/gitlab-org/gitlab/pipelines/20333396/stage.json?stage=post-cleanup', + }, + ], + artifacts: [ + { + name: 'gitlab:assets:compile', + expired: false, + expire_at: '2018-05-12T14:22:54.730Z', + path: '/gitlab-org/gitlab/-/jobs/62411438/artifacts/download', + keep_path: '/gitlab-org/gitlab/-/jobs/62411438/artifacts/keep', + browse_path: '/gitlab-org/gitlab/-/jobs/62411438/artifacts/browse', + }, + { + name: 'rspec-mysql 12 28', + expired: false, + expire_at: '2018-05-12T14:22:45.136Z', + path: '/gitlab-org/gitlab/-/jobs/62411397/artifacts/download', + keep_path: '/gitlab-org/gitlab/-/jobs/62411397/artifacts/keep', + browse_path: '/gitlab-org/gitlab/-/jobs/62411397/artifacts/browse', + }, + { + name: 'rspec-mysql 6 28', + expired: false, + expire_at: '2018-05-12T14:22:41.523Z', + path: '/gitlab-org/gitlab/-/jobs/62411391/artifacts/download', + keep_path: '/gitlab-org/gitlab/-/jobs/62411391/artifacts/keep', + browse_path: '/gitlab-org/gitlab/-/jobs/62411391/artifacts/browse', + }, + { + name: 'rspec-pg geo 0 1', + expired: false, + expire_at: '2018-05-12T14:22:13.287Z', + path: '/gitlab-org/gitlab/-/jobs/62411353/artifacts/download', + keep_path: '/gitlab-org/gitlab/-/jobs/62411353/artifacts/keep', + browse_path: '/gitlab-org/gitlab/-/jobs/62411353/artifacts/browse', + }, + { + name: 'rspec-mysql 0 28', + expired: false, + expire_at: '2018-05-12T14:22:06.834Z', + path: '/gitlab-org/gitlab/-/jobs/62411385/artifacts/download', + keep_path: '/gitlab-org/gitlab/-/jobs/62411385/artifacts/keep', + browse_path: '/gitlab-org/gitlab/-/jobs/62411385/artifacts/browse', + }, + { + name: 'spinach-mysql 0 2', + expired: false, + expire_at: '2018-05-12T14:21:51.409Z', + path: '/gitlab-org/gitlab/-/jobs/62411423/artifacts/download', + keep_path: '/gitlab-org/gitlab/-/jobs/62411423/artifacts/keep', + browse_path: '/gitlab-org/gitlab/-/jobs/62411423/artifacts/browse', + }, + { + name: 'karma', + expired: false, + expire_at: '2018-05-12T14:21:20.934Z', + path: '/gitlab-org/gitlab/-/jobs/62411440/artifacts/download', + keep_path: '/gitlab-org/gitlab/-/jobs/62411440/artifacts/keep', + browse_path: '/gitlab-org/gitlab/-/jobs/62411440/artifacts/browse', + }, + { + name: 'spinach-pg 0 2', + expired: false, + expire_at: '2018-05-12T14:20:01.028Z', + path: '/gitlab-org/gitlab/-/jobs/62411419/artifacts/download', + keep_path: '/gitlab-org/gitlab/-/jobs/62411419/artifacts/keep', + browse_path: '/gitlab-org/gitlab/-/jobs/62411419/artifacts/browse', + }, + { + name: 'spinach-pg 1 2', + expired: false, + expire_at: '2018-05-12T14:19:04.336Z', + path: '/gitlab-org/gitlab/-/jobs/62411421/artifacts/download', + keep_path: '/gitlab-org/gitlab/-/jobs/62411421/artifacts/keep', + browse_path: '/gitlab-org/gitlab/-/jobs/62411421/artifacts/browse', + }, + { + name: 'sast', + expired: null, + expire_at: null, + path: '/gitlab-org/gitlab/-/jobs/62411442/artifacts/download', + browse_path: '/gitlab-org/gitlab/-/jobs/62411442/artifacts/browse', + }, + { + name: 'code_quality', + expired: false, + expire_at: '2018-04-18T14:16:24.484Z', + path: '/gitlab-org/gitlab/-/jobs/62411441/artifacts/download', + keep_path: '/gitlab-org/gitlab/-/jobs/62411441/artifacts/keep', + browse_path: '/gitlab-org/gitlab/-/jobs/62411441/artifacts/browse', + }, + { + name: 'cache gems', + expired: null, + expire_at: null, + path: '/gitlab-org/gitlab/-/jobs/62411447/artifacts/download', + browse_path: '/gitlab-org/gitlab/-/jobs/62411447/artifacts/browse', + }, + { + name: 'dependency_scanning', + expired: null, + expire_at: null, + path: '/gitlab-org/gitlab/-/jobs/62411443/artifacts/download', + browse_path: '/gitlab-org/gitlab/-/jobs/62411443/artifacts/browse', + }, + { + name: 'compile-assets', + expired: false, + expire_at: '2018-04-18T14:12:07.638Z', + path: '/gitlab-org/gitlab/-/jobs/62411334/artifacts/download', + keep_path: '/gitlab-org/gitlab/-/jobs/62411334/artifacts/keep', + browse_path: '/gitlab-org/gitlab/-/jobs/62411334/artifacts/browse', + }, + { + name: 'setup-test-env', + expired: false, + expire_at: '2018-04-18T14:10:27.024Z', + path: '/gitlab-org/gitlab/-/jobs/62411336/artifacts/download', + keep_path: '/gitlab-org/gitlab/-/jobs/62411336/artifacts/keep', + browse_path: '/gitlab-org/gitlab/-/jobs/62411336/artifacts/browse', + }, + { + name: 'retrieve-tests-metadata', + expired: false, + expire_at: '2018-05-12T14:06:35.926Z', + path: '/gitlab-org/gitlab/-/jobs/62411333/artifacts/download', + keep_path: '/gitlab-org/gitlab/-/jobs/62411333/artifacts/keep', + browse_path: '/gitlab-org/gitlab/-/jobs/62411333/artifacts/browse', + }, + ], + manual_actions: [ + { + name: 'package-and-qa', + path: '/gitlab-org/gitlab/-/jobs/62411330/play', + playable: true, + }, + { + name: 'review-docs-deploy', + path: '/gitlab-org/gitlab/-/jobs/62411332/play', + playable: true, + }, + ], + }, + ref: { + name: 'master', + path: '/gitlab-org/gitlab/commits/master', + tag: false, + branch: true, + }, + commit: { + id: 'e6a2885c503825792cb8a84a8731295e361bd059', + short_id: 'e6a2885c', + title: "Merge branch 'ce-to-ee-2018-04-11' into 'master'", + created_at: '2018-04-11T14:04:39.000Z', + parent_ids: [ + '5d9b5118f6055f72cff1a82b88133609912f2c1d', + '6fdc6ee76a8062fe41b1a33f7c503334a6ebdc02', + ], + message: + "Merge branch 'ce-to-ee-2018-04-11' into 'master'\n\nCE upstream - 2018-04-11 12:26 UTC\n\nSee merge request gitlab-org/gitlab-ee!5326", + author_name: 'Rémy Coutable', + author_email: 'remy@rymai.me', + authored_date: '2018-04-11T14:04:39.000Z', + committer_name: 'Rémy Coutable', + committer_email: 'remy@rymai.me', + committed_date: '2018-04-11T14:04:39.000Z', + author: { + id: 128633, + name: 'Rémy Coutable', + username: 'rymai', + state: 'active', + avatar_url: + 'https://secure.gravatar.com/avatar/263da227929cc0035cb0eba512bcf81a?s=80\u0026d=identicon', + web_url: 'https://gitlab.com/rymai', + path: '/rymai', + }, + author_gravatar_url: + 'https://secure.gravatar.com/avatar/263da227929cc0035cb0eba512bcf81a?s=80\u0026d=identicon', + commit_url: + 'https://gitlab.com/gitlab-org/gitlab/commit/e6a2885c503825792cb8a84a8731295e361bd059', + commit_path: '/gitlab-org/gitlab/commit/e6a2885c503825792cb8a84a8731295e361bd059', + }, + cancel_path: '/gitlab-org/gitlab/pipelines/20333396/cancel', + triggered_by: null, + triggered: [], +}; + +export const stageReply = { + name: 'deploy', + title: 'deploy: running', + latest_statuses: [ + { + id: 928, + name: 'stop staging', + started: false, + build_path: '/twitter/flight/-/jobs/928', + cancel_path: '/twitter/flight/-/jobs/928/cancel', + playable: false, + created_at: '2018-04-04T20:02:02.728Z', + updated_at: '2018-04-04T20:02:02.766Z', + status: { + icon: 'status_pending', + text: 'pending', + label: 'pending', + group: 'pending', + tooltip: 'pending', + has_details: true, + details_path: '/twitter/flight/-/jobs/928', + favicon: + '/assets/ci_favicons/dev/favicon_status_pending-db32e1faf94b9f89530ac519790920d1f18ea8f6af6cd2e0a26cd6840cacf101.ico', + action: { + icon: 'cancel', + title: 'Cancel', + path: '/twitter/flight/-/jobs/928/cancel', + method: 'post', + }, + }, + }, + { + id: 926, + name: 'production', + started: false, + build_path: '/twitter/flight/-/jobs/926', + retry_path: '/twitter/flight/-/jobs/926/retry', + play_path: '/twitter/flight/-/jobs/926/play', + playable: true, + created_at: '2018-04-04T20:00:57.202Z', + updated_at: '2018-04-04T20:11:13.110Z', + status: { + icon: 'status_canceled', + text: 'canceled', + label: 'manual play action', + group: 'canceled', + tooltip: 'canceled', + has_details: true, + details_path: '/twitter/flight/-/jobs/926', + favicon: + '/assets/ci_favicons/dev/favicon_status_canceled-5491840b9b6feafba0bc599cbd49ee9580321dc809683856cf1b0d51532b1af6.ico', + action: { + icon: 'play', + title: 'Play', + path: '/twitter/flight/-/jobs/926/play', + method: 'post', + }, + }, + }, + { + id: 217, + name: 'staging', + started: '2018-03-07T08:41:46.234Z', + build_path: '/twitter/flight/-/jobs/217', + retry_path: '/twitter/flight/-/jobs/217/retry', + playable: false, + created_at: '2018-03-07T14:41:58.093Z', + updated_at: '2018-03-07T14:41:58.093Z', + status: { + icon: 'status_success', + text: 'passed', + label: 'passed', + group: 'success', + tooltip: 'passed', + has_details: true, + details_path: '/twitter/flight/-/jobs/217', + favicon: + '/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico', + action: { + icon: 'retry', + title: 'Retry', + path: '/twitter/flight/-/jobs/217/retry', + method: 'post', + }, + }, + }, + ], + status: { + icon: 'status_running', + text: 'running', + label: 'running', + group: 'running', + tooltip: 'running', + has_details: true, + details_path: '/twitter/flight/pipelines/13#deploy', + favicon: + '/assets/ci_favicons/dev/favicon_status_running-c3ad2fc53ea6079c174e5b6c1351ff349e99ec3af5a5622fb77b0fe53ea279c1.ico', + }, + path: '/twitter/flight/pipelines/13#deploy', + dropdown_path: '/twitter/flight/pipelines/13/stage.json?stage=deploy', +}; diff --git a/spec/frontend/pipelines/pipelines_spec.js b/spec/frontend/pipelines/pipelines_spec.js new file mode 100644 index 00000000000..40cd0ad9047 --- /dev/null +++ b/spec/frontend/pipelines/pipelines_spec.js @@ -0,0 +1,659 @@ +import { mount } from '@vue/test-utils'; +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; +import waitForPromises from 'helpers/wait_for_promises'; +import PipelinesComponent from '~/pipelines/components/pipelines.vue'; +import Store from '~/pipelines/stores/pipelines_store'; +import { pipelineWithStages, stageReply } from './mock_data'; + +describe('Pipelines', () => { + const jsonFixtureName = 'pipelines/pipelines.json'; + + preloadFixtures(jsonFixtureName); + + let pipelines; + let wrapper; + let mock; + + const paths = { + endpoint: 'twitter/flight/pipelines.json', + autoDevopsPath: '/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', + }; + + const noPermissions = { + endpoint: 'twitter/flight/pipelines.json', + autoDevopsPath: '/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', + }; + + const defaultProps = { + hasGitlabCi: true, + canCreatePipeline: true, + ...paths, + }; + + const createComponent = (props = defaultProps, methods) => { + wrapper = mount(PipelinesComponent, { + propsData: { + store: new Store(), + ...props, + }, + methods: { + ...methods, + }, + }); + }; + + beforeEach(() => { + mock = new MockAdapter(axios); + pipelines = getJSONFixture(jsonFixtureName); + }); + + afterEach(() => { + wrapper.destroy(); + mock.restore(); + }); + + describe('With permission', () => { + describe('With pipelines in main tab', () => { + beforeEach(() => { + mock.onGet('twitter/flight/pipelines.json').reply(200, pipelines); + createComponent(); + return waitForPromises(); + }); + + it('renders tabs', () => { + expect(wrapper.find('.js-pipelines-tab-all').text()).toContain('All'); + }); + + it('renders Run Pipeline link', () => { + expect(wrapper.find('.js-run-pipeline').attributes('href')).toBe(paths.newPipelinePath); + }); + + it('renders CI Lint link', () => { + expect(wrapper.find('.js-ci-lint').attributes('href')).toBe(paths.ciLintPath); + }); + + it('renders Clear Runner Cache button', () => { + expect(wrapper.find('.js-clear-cache').text()).toBe('Clear Runner Caches'); + }); + + it('renders pipelines table', () => { + expect(wrapper.findAll('.gl-responsive-table-row')).toHaveLength( + pipelines.pipelines.length + 1, + ); + }); + }); + + 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, + }, + }); + + createComponent(); + + return waitForPromises(); + }); + + it('renders tabs', () => { + expect(wrapper.find('.js-pipelines-tab-all').text()).toContain('All'); + }); + + it('renders Run Pipeline link', () => { + expect(wrapper.find('.js-run-pipeline').attributes('href')).toEqual(paths.newPipelinePath); + }); + + it('renders CI Lint link', () => { + expect(wrapper.find('.js-ci-lint').attributes('href')).toEqual(paths.ciLintPath); + }); + + it('renders Clear Runner Cache button', () => { + expect(wrapper.find('.js-clear-cache').text()).toEqual('Clear Runner Caches'); + }); + + it('renders tab empty state', () => { + expect(wrapper.find('.empty-state h4').text()).toEqual('There are currently no pipelines.'); + }); + }); + + 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 }); + + return waitForPromises(); + }); + + it('renders empty state', () => { + expect(wrapper.find('.js-empty-state h4').text()).toEqual('Build with confidence'); + + expect(wrapper.find('.js-get-started-pipelines').attributes('href')).toEqual( + paths.helpPagePath, + ); + }); + + it('does not render tabs nor buttons', () => { + expect(wrapper.find('.js-pipelines-tab-all').exists()).toBeFalsy(); + expect(wrapper.find('.js-run-pipeline').exists()).toBeFalsy(); + expect(wrapper.find('.js-ci-lint').exists()).toBeFalsy(); + expect(wrapper.find('.js-clear-cache').exists()).toBeFalsy(); + }); + }); + + describe('When API returns error', () => { + beforeEach(() => { + mock.onGet('twitter/flight/pipelines.json').reply(500, {}); + createComponent({ hasGitlabCi: false, canCreatePipeline: true, ...paths }); + + return waitForPromises(); + }); + + it('renders tabs', () => { + expect(wrapper.find('.js-pipelines-tab-all').text()).toContain('All'); + }); + + it('renders buttons', () => { + expect(wrapper.find('.js-run-pipeline').attributes('href')).toEqual(paths.newPipelinePath); + + expect(wrapper.find('.js-ci-lint').attributes('href')).toEqual(paths.ciLintPath); + expect(wrapper.find('.js-clear-cache').text()).toEqual('Clear Runner Caches'); + }); + + it('renders error state', () => { + expect(wrapper.find('.empty-state').text()).toContain( + 'There was an error fetching the pipelines.', + ); + }); + }); + }); + + describe('Without permission', () => { + describe('With pipelines in main tab', () => { + beforeEach(() => { + mock.onGet('twitter/flight/pipelines.json').reply(200, pipelines); + + createComponent({ hasGitlabCi: false, canCreatePipeline: false, ...noPermissions }); + + return waitForPromises(); + }); + + it('renders tabs', () => { + expect(wrapper.find('.js-pipelines-tab-all').text()).toContain('All'); + }); + + it('does not render buttons', () => { + expect(wrapper.find('.js-run-pipeline').exists()).toBeFalsy(); + expect(wrapper.find('.js-ci-lint').exists()).toBeFalsy(); + expect(wrapper.find('.js-clear-cache').exists()).toBeFalsy(); + }); + + it('renders pipelines table', () => { + expect(wrapper.findAll('.gl-responsive-table-row')).toHaveLength( + pipelines.pipelines.length + 1, + ); + }); + }); + + 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, + }, + }); + + createComponent({ hasGitlabCi: true, canCreatePipeline: false, ...noPermissions }); + + return waitForPromises(); + }); + + it('renders tabs', () => { + expect(wrapper.find('.js-pipelines-tab-all').text()).toContain('All'); + }); + + it('does not render buttons', () => { + expect(wrapper.find('.js-run-pipeline').exists()).toBeFalsy(); + expect(wrapper.find('.js-ci-lint').exists()).toBeFalsy(); + expect(wrapper.find('.js-clear-cache').exists()).toBeFalsy(); + }); + + it('renders tab empty state', () => { + expect(wrapper.find('.empty-state h4').text()).toEqual('There are currently no pipelines.'); + }); + }); + + 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: false, ...noPermissions }); + + return waitForPromises(); + }); + + it('renders empty state without button to set CI', () => { + expect(wrapper.find('.js-empty-state').text()).toEqual( + 'This project is not currently set up to run pipelines.', + ); + + expect(wrapper.find('.js-get-started-pipelines').exists()).toBeFalsy(); + }); + + it('does not render tabs or buttons', () => { + expect(wrapper.find('.js-pipelines-tab-all').exists()).toBeFalsy(); + expect(wrapper.find('.js-run-pipeline').exists()).toBeFalsy(); + expect(wrapper.find('.js-ci-lint').exists()).toBeFalsy(); + expect(wrapper.find('.js-clear-cache').exists()).toBeFalsy(); + }); + }); + + describe('When API returns error', () => { + beforeEach(() => { + mock.onGet('twitter/flight/pipelines.json').reply(500, {}); + + createComponent({ hasGitlabCi: false, canCreatePipeline: true, ...noPermissions }); + + return waitForPromises(); + }); + + it('renders tabs', () => { + expect(wrapper.find('.js-pipelines-tab-all').text()).toContain('All'); + }); + + it('does not renders buttons', () => { + expect(wrapper.find('.js-run-pipeline').exists()).toBeFalsy(); + expect(wrapper.find('.js-ci-lint').exists()).toBeFalsy(); + expect(wrapper.find('.js-clear-cache').exists()).toBeFalsy(); + }); + + it('renders error state', () => { + expect(wrapper.find('.empty-state').text()).toContain( + 'There was an error fetching the pipelines.', + ); + }); + }); + }); + + describe('successful request', () => { + describe('with pipelines', () => { + beforeEach(() => { + mock.onGet('twitter/flight/pipelines.json').reply(200, pipelines); + + createComponent(); + return waitForPromises(); + }); + + it('should render table', () => { + expect(wrapper.find('.table-holder').exists()).toBe(true); + expect(wrapper.findAll('.gl-responsive-table-row')).toHaveLength( + pipelines.pipelines.length + 1, + ); + }); + + it('should render navigation tabs', () => { + expect(wrapper.find('.js-pipelines-tab-pending').text()).toContain('Pending'); + + expect(wrapper.find('.js-pipelines-tab-all').text()).toContain('All'); + + expect(wrapper.find('.js-pipelines-tab-running').text()).toContain('Running'); + + expect(wrapper.find('.js-pipelines-tab-finished').text()).toContain('Finished'); + + expect(wrapper.find('.js-pipelines-tab-branches').text()).toContain('Branches'); + + expect(wrapper.find('.js-pipelines-tab-tags').text()).toContain('Tags'); + }); + + it('should make an API request when using tabs', () => { + const updateContentMock = jest.fn(() => {}); + createComponent( + { hasGitlabCi: true, canCreatePipeline: true, ...paths }, + { + updateContent: updateContentMock, + }, + ); + + return waitForPromises().then(() => { + wrapper.find('.js-pipelines-tab-finished').trigger('click'); + + expect(updateContentMock).toHaveBeenCalledWith({ scope: 'finished', page: '1' }); + }); + }); + + describe('with pagination', () => { + it('should make an API request when using pagination', () => { + const updateContentMock = jest.fn(() => {}); + createComponent( + { hasGitlabCi: true, canCreatePipeline: true, ...paths }, + { + updateContent: updateContentMock, + }, + ); + + return waitForPromises() + .then(() => { + // Mock pagination + wrapper.vm.store.state.pageInfo = { + page: 1, + total: 10, + perPage: 2, + nextPage: 2, + totalPages: 5, + }; + + return wrapper.vm.$nextTick(); + }) + .then(() => { + wrapper.find('.next-page-item').trigger('click'); + + expect(updateContentMock).toHaveBeenCalledWith({ scope: 'all', page: '2' }); + }); + }); + }); + }); + }); + + describe('methods', () => { + beforeEach(() => { + jest.spyOn(window.history, 'pushState').mockImplementation(() => null); + }); + + describe('onChangeTab', () => { + it('should set page to 1', () => { + const updateContentMock = jest.fn(() => {}); + createComponent( + { hasGitlabCi: true, canCreatePipeline: true, ...paths }, + { + updateContent: updateContentMock, + }, + ); + + wrapper.vm.onChangeTab('running'); + + expect(updateContentMock).toHaveBeenCalledWith({ scope: 'running', page: '1' }); + }); + }); + + describe('onChangePage', () => { + it('should update page and keep scope', () => { + const updateContentMock = jest.fn(() => {}); + createComponent( + { hasGitlabCi: true, canCreatePipeline: true, ...paths }, + { + updateContent: updateContentMock, + }, + ); + + wrapper.vm.onChangePage(4); + + expect(updateContentMock).toHaveBeenCalledWith({ scope: wrapper.vm.scope, page: '4' }); + }); + }); + }); + + describe('computed properties', () => { + beforeEach(() => { + createComponent(); + }); + + describe('tabs', () => { + it('returns default tabs', () => { + expect(wrapper.vm.tabs).toEqual([ + { name: 'All', scope: 'all', count: undefined, isActive: true }, + { name: 'Pending', scope: 'pending', count: undefined, isActive: false }, + { name: 'Running', scope: 'running', count: undefined, isActive: false }, + { name: 'Finished', scope: 'finished', count: undefined, isActive: false }, + { name: 'Branches', scope: 'branches', isActive: false }, + { name: 'Tags', scope: 'tags', isActive: false }, + ]); + }); + }); + + describe('emptyTabMessage', () => { + it('returns message with scope', () => { + wrapper.vm.scope = 'pending'; + + return wrapper.vm.$nextTick().then(() => { + expect(wrapper.vm.emptyTabMessage).toEqual('There are currently no pending pipelines.'); + }); + }); + + it('returns message without scope when scope is `all`', () => { + expect(wrapper.vm.emptyTabMessage).toEqual('There are currently no pipelines.'); + }); + }); + + describe('stateToRender', () => { + it('returns loading state when the app is loading', () => { + expect(wrapper.vm.stateToRender).toEqual('loading'); + }); + + it('returns error state when app has error', () => { + wrapper.vm.hasError = true; + wrapper.vm.isLoading = false; + + return wrapper.vm.$nextTick().then(() => { + expect(wrapper.vm.stateToRender).toEqual('error'); + }); + }); + + it('returns table list when app has pipelines', () => { + wrapper.vm.isLoading = false; + wrapper.vm.hasError = false; + wrapper.vm.state.pipelines = pipelines.pipelines; + + return wrapper.vm.$nextTick().then(() => { + expect(wrapper.vm.stateToRender).toEqual('tableList'); + }); + }); + + it('returns empty tab when app does not have pipelines but project has pipelines', () => { + wrapper.vm.state.count.all = 10; + wrapper.vm.isLoading = false; + + return wrapper.vm.$nextTick().then(() => { + expect(wrapper.vm.stateToRender).toEqual('emptyTab'); + }); + }); + + it('returns empty tab when project has CI', () => { + wrapper.vm.isLoading = false; + + return wrapper.vm.$nextTick().then(() => { + expect(wrapper.vm.stateToRender).toEqual('emptyTab'); + }); + }); + + it('returns empty state when project does not have pipelines nor CI', () => { + createComponent({ hasGitlabCi: false, canCreatePipeline: true, ...paths }); + + wrapper.vm.isLoading = false; + + return wrapper.vm.$nextTick().then(() => { + expect(wrapper.vm.stateToRender).toEqual('emptyState'); + }); + }); + }); + + describe('shouldRenderTabs', () => { + it('returns true when state is loading & has already made the first request', () => { + wrapper.vm.isLoading = true; + wrapper.vm.hasMadeRequest = true; + + return wrapper.vm.$nextTick().then(() => { + expect(wrapper.vm.shouldRenderTabs).toEqual(true); + }); + }); + + 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 wrapper.vm.$nextTick().then(() => { + expect(wrapper.vm.shouldRenderTabs).toEqual(true); + }); + }); + + 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; + + return wrapper.vm.$nextTick().then(() => { + expect(wrapper.vm.shouldRenderTabs).toEqual(true); + }); + }); + + 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; + + return wrapper.vm.$nextTick().then(() => { + expect(wrapper.vm.shouldRenderTabs).toEqual(true); + }); + }); + + it('returns false when has not made first request', () => { + wrapper.vm.hasMadeRequest = false; + + return wrapper.vm.$nextTick().then(() => { + expect(wrapper.vm.shouldRenderTabs).toEqual(false); + }); + }); + + it('returns false when state is empty state', () => { + createComponent({ hasGitlabCi: false, canCreatePipeline: true, ...paths }); + + wrapper.vm.isLoading = false; + wrapper.vm.hasMadeRequest = true; + + return wrapper.vm.$nextTick().then(() => { + expect(wrapper.vm.shouldRenderTabs).toEqual(false); + }); + }); + }); + + describe('shouldRenderButtons', () => { + it('returns true when it has paths & has made the first request', () => { + wrapper.vm.hasMadeRequest = true; + + return wrapper.vm.$nextTick().then(() => { + expect(wrapper.vm.shouldRenderButtons).toEqual(true); + }); + }); + + it('returns false when it has not made the first request', () => { + wrapper.vm.hasMadeRequest = false; + + return wrapper.vm.$nextTick().then(() => { + expect(wrapper.vm.shouldRenderButtons).toEqual(false); + }); + }); + }); + }); + + describe('updates results when a staged is clicked', () => { + beforeEach(() => { + const copyPipeline = Object.assign({}, 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); + + createComponent(); + }); + + 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; + wrapper.find('.js-builds-dropdown-button').trigger('click'); + }) + .then(() => { + expect(cancelMock).toHaveBeenCalled(); + expect(stopMock).toHaveBeenCalled(); + expect(restartMock).toHaveBeenCalled(); + }); + }); + }); + + 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); + + return waitForPromises() + .then(() => { + wrapper.find('.js-builds-dropdown-button').trigger('click'); + expect(stopMock).toHaveBeenCalled(); + }) + .then(() => { + expect(restartMock).toHaveBeenCalled(); + }); + }); + }); + }); +}); |