diff options
Diffstat (limited to 'spec/frontend/jobs')
47 files changed, 0 insertions, 8633 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 deleted file mode 100644 index 5ecddc7efd6..00000000000 --- a/spec/frontend/jobs/components/filtered_search/jobs_filtered_search_spec.js +++ /dev/null @@ -1,71 +0,0 @@ -import { GlFilteredSearch } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; -import { - OPERATORS_IS, - TOKEN_TITLE_STATUS, - TOKEN_TYPE_STATUS, -} 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 = (props) => { - wrapper = shallowMount(JobsFilteredSearch, { - propsData: { - ...props, - }, - }); - }; - - 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('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([]); - }); - - it('filtered search returns correct data shape when passed query string', () => { - const value = 'SUCCESS'; - - createComponent({ queryString: { statuses: value } }); - - expect(findFilteredSearch().props('value')).toEqual([ - { type: TOKEN_TYPE_STATUS, value: { data: value, operator: '=' } }, - ]); - }); -}); 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 deleted file mode 100644 index 6755b854f01..00000000000 --- a/spec/frontend/jobs/components/filtered_search/tokens/job_status_token_spec.js +++ /dev/null @@ -1,58 +0,0 @@ -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'; -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/jobs/components/filtered_search/utils_spec.js b/spec/frontend/jobs/components/filtered_search/utils_spec.js deleted file mode 100644 index 8440ab42b86..00000000000 --- a/spec/frontend/jobs/components/filtered_search/utils_spec.js +++ /dev/null @@ -1,19 +0,0 @@ -import { validateQueryString } from '~/jobs/components/filtered_search/utils'; - -describe('Filtered search utils', () => { - describe('validateQueryString', () => { - it.each` - queryStringObject | expected - ${{ statuses: 'SUCCESS' }} | ${{ statuses: 'SUCCESS' }} - ${{ statuses: 'failed' }} | ${{ statuses: 'FAILED' }} - ${{ wrong: 'SUCCESS' }} | ${null} - ${{ statuses: 'wrong' }} | ${null} - ${{ wrong: 'wrong' }} | ${null} - `( - 'when provided $queryStringObject, the expected result is $expected', - ({ queryStringObject, expected }) => { - expect(validateQueryString(queryStringObject)).toEqual(expected); - }, - ); - }); -}); diff --git a/spec/frontend/jobs/components/job/artifacts_block_spec.js b/spec/frontend/jobs/components/job/artifacts_block_spec.js deleted file mode 100644 index f9e52a5ae43..00000000000 --- a/spec/frontend/jobs/components/job/artifacts_block_spec.js +++ /dev/null @@ -1,193 +0,0 @@ -import { GlPopover } from '@gitlab/ui'; -import { mountExtended } from 'helpers/vue_test_utils_helper'; -import { trimText } from 'helpers/text_helper'; -import ArtifactsBlock from '~/jobs/components/job/sidebar/artifacts_block.vue'; -import { getTimeago } from '~/lib/utils/datetime_utility'; - -describe('Artifacts block', () => { - let wrapper; - - const createWrapper = (propsData) => - mountExtended(ArtifactsBlock, { - propsData: { - helpUrl: 'help-url', - ...propsData, - }, - }); - - const findArtifactRemoveElt = () => wrapper.findByTestId('artifacts-remove-timeline'); - const findJobLockedElt = () => wrapper.findByTestId('job-locked-message'); - const findKeepBtn = () => wrapper.findByTestId('keep-artifacts'); - const findDownloadBtn = () => wrapper.findByTestId('download-artifacts'); - const findBrowseBtn = () => wrapper.findByTestId('browse-artifacts'); - const findArtifactsHelpLink = () => wrapper.findByTestId('artifacts-help-link'); - const findPopover = () => wrapper.findComponent(GlPopover); - - const expireAt = '2018-08-14T09:38:49.157Z'; - const timeago = getTimeago(); - const formattedDate = timeago.format(expireAt); - const lockedText = - 'These artifacts are the latest. They will not be deleted (even if expired) until newer artifacts are available.'; - - const expiredArtifact = { - expire_at: expireAt, - expired: true, - locked: false, - }; - - const nonExpiredArtifact = { - download_path: '/gitlab-org/gitlab-foss/-/jobs/98314558/artifacts/download', - browse_path: '/gitlab-org/gitlab-foss/-/jobs/98314558/artifacts/browse', - keep_path: '/gitlab-org/gitlab-foss/-/jobs/98314558/artifacts/keep', - expire_at: expireAt, - expired: false, - locked: false, - }; - - const lockedExpiredArtifact = { - ...expiredArtifact, - download_path: '/gitlab-org/gitlab-foss/-/jobs/98314558/artifacts/download', - browse_path: '/gitlab-org/gitlab-foss/-/jobs/98314558/artifacts/browse', - expired: true, - locked: true, - }; - - const lockedNonExpiredArtifact = { - ...nonExpiredArtifact, - keep_path: undefined, - locked: true, - }; - - describe('with expired artifacts that are not locked', () => { - beforeEach(() => { - wrapper = createWrapper({ - artifact: expiredArtifact, - }); - }); - - it('renders expired artifact date and info', () => { - expect(trimText(findArtifactRemoveElt().text())).toBe( - `The artifacts were removed ${formattedDate}`, - ); - - expect( - findArtifactRemoveElt() - .find('[data-testid="artifact-expired-help-link"]') - .attributes('href'), - ).toBe('help-url'); - }); - - it('does not show the keep button', () => { - expect(findKeepBtn().exists()).toBe(false); - }); - - it('does not show the download button', () => { - expect(findDownloadBtn().exists()).toBe(false); - }); - - it('does not show the browse button', () => { - expect(findBrowseBtn().exists()).toBe(false); - }); - }); - - describe('with artifacts that will expire', () => { - beforeEach(() => { - wrapper = createWrapper({ - artifact: nonExpiredArtifact, - }); - }); - - it('renders will expire artifact date and info', () => { - expect(trimText(findArtifactRemoveElt().text())).toBe( - `The artifacts will be removed ${formattedDate}`, - ); - - expect( - findArtifactRemoveElt() - .find('[data-testid="artifact-expired-help-link"]') - .attributes('href'), - ).toBe('help-url'); - }); - - it('renders the keep button', () => { - expect(findKeepBtn().exists()).toBe(true); - }); - - it('renders the download button', () => { - expect(findDownloadBtn().exists()).toBe(true); - }); - - it('renders the browse button', () => { - expect(findBrowseBtn().exists()).toBe(true); - }); - }); - - describe('with expired locked artifacts', () => { - beforeEach(() => { - wrapper = createWrapper({ - artifact: lockedExpiredArtifact, - }); - }); - - it('renders the information that the artefacts are locked', () => { - expect(findArtifactRemoveElt().exists()).toBe(false); - expect(trimText(findJobLockedElt().text())).toBe(lockedText); - }); - - it('does not render the keep button', () => { - expect(findKeepBtn().exists()).toBe(false); - }); - - it('renders the download button', () => { - expect(findDownloadBtn().exists()).toBe(true); - }); - - it('renders the browse button', () => { - expect(findBrowseBtn().exists()).toBe(true); - }); - }); - - describe('with non expired locked artifacts', () => { - beforeEach(() => { - wrapper = createWrapper({ - artifact: lockedNonExpiredArtifact, - }); - }); - - it('renders the information that the artefacts are locked', () => { - expect(findArtifactRemoveElt().exists()).toBe(false); - expect(trimText(findJobLockedElt().text())).toBe(lockedText); - }); - - it('does not render the keep button', () => { - expect(findKeepBtn().exists()).toBe(false); - }); - - it('renders the download button', () => { - expect(findDownloadBtn().exists()).toBe(true); - }); - - it('renders the browse button', () => { - expect(findBrowseBtn().exists()).toBe(true); - }); - }); - - describe('artifacts help text', () => { - beforeEach(() => { - wrapper = createWrapper({ - artifact: lockedNonExpiredArtifact, - }); - }); - - it('displays help text', () => { - const expectedHelpText = - 'Job artifacts are files that are configured to be uploaded when a job finishes execution. Artifacts could be compiled files, unit tests or scanning reports, or any other files generated by a job.'; - - expect(findPopover().text()).toBe(expectedHelpText); - }); - - it('links to artifacts help page', () => { - expect(findArtifactsHelpLink().attributes('href')).toBe('/help/ci/jobs/job_artifacts'); - }); - }); -}); diff --git a/spec/frontend/jobs/components/job/commit_block_spec.js b/spec/frontend/jobs/components/job/commit_block_spec.js deleted file mode 100644 index 1c28b5079d7..00000000000 --- a/spec/frontend/jobs/components/job/commit_block_spec.js +++ /dev/null @@ -1,66 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import { extendedWrapper } from 'helpers/vue_test_utils_helper'; -import CommitBlock from '~/jobs/components/job/sidebar/commit_block.vue'; -import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; - -describe('Commit block', () => { - let wrapper; - - const commit = { - short_id: '1f0fb84f', - id: '1f0fb84fb6770d74d97eee58118fd3909cd4f48c', - commit_path: 'commit/1f0fb84fb6770d74d97eee58118fd3909cd4f48c', - title: 'Update README.md', - }; - - const mergeRequest = { - iid: '!21244', - path: 'merge_requests/21244', - }; - - const findCommitSha = () => wrapper.findByTestId('commit-sha'); - const findLinkSha = () => wrapper.findByTestId('link-commit'); - - const mountComponent = (props) => { - wrapper = extendedWrapper( - shallowMount(CommitBlock, { - propsData: { - commit, - ...props, - }, - }), - ); - }; - - describe('without merge request', () => { - beforeEach(() => { - mountComponent(); - }); - - it('renders pipeline short sha link', () => { - expect(findCommitSha().attributes('href')).toBe(commit.commit_path); - expect(findCommitSha().text()).toBe(commit.short_id); - }); - - it('renders clipboard button', () => { - expect(wrapper.findComponent(ClipboardButton).attributes('text')).toBe(commit.id); - }); - - it('renders git commit title', () => { - expect(wrapper.text()).toContain(commit.title); - }); - - it('does not render merge request', () => { - expect(findLinkSha().exists()).toBe(false); - }); - }); - - describe('with merge request', () => { - it('renders merge request link and reference', () => { - mountComponent({ mergeRequest }); - - expect(findLinkSha().attributes('href')).toBe(mergeRequest.path); - expect(findLinkSha().text()).toBe(`!${mergeRequest.iid}`); - }); - }); -}); diff --git a/spec/frontend/jobs/components/job/empty_state_spec.js b/spec/frontend/jobs/components/job/empty_state_spec.js deleted file mode 100644 index 970c2591795..00000000000 --- a/spec/frontend/jobs/components/job/empty_state_spec.js +++ /dev/null @@ -1,140 +0,0 @@ -import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; -import EmptyState from '~/jobs/components/job/empty_state.vue'; -import ManualVariablesForm from '~/jobs/components/job/manual_variables_form.vue'; -import { mockFullPath, mockId } from './mock_data'; - -describe('Empty State', () => { - let wrapper; - - const defaultProps = { - illustrationPath: 'illustrations/pending_job_empty.svg', - illustrationSizeClass: 'svg-430', - jobId: mockId, - title: 'This job has not started yet', - playable: false, - isRetryable: true, - }; - - const createWrapper = (props) => { - wrapper = shallowMountExtended(EmptyState, { - propsData: { - ...defaultProps, - ...props, - }, - provide: { - projectPath: mockFullPath, - }, - }); - }; - - const content = 'This job is in pending state and is waiting to be picked by a runner'; - - const findEmptyStateImage = () => wrapper.find('img'); - const findTitle = () => wrapper.findByTestId('job-empty-state-title'); - const findContent = () => wrapper.findByTestId('job-empty-state-content'); - const findAction = () => wrapper.findByTestId('job-empty-state-action'); - const findManualVarsForm = () => wrapper.findComponent(ManualVariablesForm); - - describe('renders image and title', () => { - beforeEach(() => { - createWrapper(); - }); - - it('renders empty state image', () => { - expect(findEmptyStateImage().exists()).toBe(true); - }); - - it('renders provided title', () => { - expect(findTitle().text().trim()).toBe(defaultProps.title); - }); - }); - - describe('with content', () => { - beforeEach(() => { - createWrapper({ content }); - }); - - it('renders content', () => { - expect(findContent().text().trim()).toBe(content); - }); - }); - - describe('without content', () => { - beforeEach(() => { - createWrapper(); - }); - - it('does not render content', () => { - expect(findContent().exists()).toBe(false); - }); - }); - - describe('with action', () => { - beforeEach(() => { - createWrapper({ - action: { - path: 'runner', - button_title: 'Check runner', - method: 'post', - }, - }); - }); - - it('renders action', () => { - expect(findAction().attributes('href')).toBe('runner'); - }); - }); - - describe('without action', () => { - beforeEach(() => { - createWrapper({ - action: null, - }); - }); - - it('does not render action', () => { - expect(findAction().exists()).toBe(false); - }); - - it('does not render manual variables form', () => { - expect(findManualVarsForm().exists()).toBe(false); - }); - }); - - describe('with playable action and not scheduled job', () => { - beforeEach(() => { - createWrapper({ - content, - playable: true, - scheduled: false, - action: { - path: 'runner', - button_title: 'Check runner', - method: 'post', - }, - }); - }); - - it('renders manual variables form', () => { - expect(findManualVarsForm().exists()).toBe(true); - }); - - it('does not render the empty state action', () => { - expect(findAction().exists()).toBe(false); - }); - }); - - describe('with playable action and scheduled job', () => { - beforeEach(() => { - createWrapper({ - playable: true, - scheduled: true, - content, - }); - }); - - it('does not render manual variables form', () => { - expect(findManualVarsForm().exists()).toBe(false); - }); - }); -}); diff --git a/spec/frontend/jobs/components/job/environments_block_spec.js b/spec/frontend/jobs/components/job/environments_block_spec.js deleted file mode 100644 index ab36f79ea5e..00000000000 --- a/spec/frontend/jobs/components/job/environments_block_spec.js +++ /dev/null @@ -1,260 +0,0 @@ -import { mount } from '@vue/test-utils'; -import EnvironmentsBlock from '~/jobs/components/job/environments_block.vue'; - -const TEST_CLUSTER_NAME = 'test_cluster'; -const TEST_CLUSTER_PATH = 'path/to/test_cluster'; -const TEST_KUBERNETES_NAMESPACE = 'this-is-a-kubernetes-namespace'; - -describe('Environments block', () => { - let wrapper; - - const status = { - group: 'success', - icon: 'status_success', - label: 'passed', - text: 'passed', - tooltip: 'passed', - }; - - const environment = { - environment_path: '/environment', - name: 'environment', - }; - - const lastDeployment = { iid: 'deployment', deployable: { build_path: 'bar' } }; - - const createEnvironmentWithLastDeployment = () => ({ - ...environment, - last_deployment: { ...lastDeployment }, - }); - - const createDeploymentWithCluster = () => ({ name: TEST_CLUSTER_NAME, path: TEST_CLUSTER_PATH }); - - const createDeploymentWithClusterAndKubernetesNamespace = () => ({ - name: TEST_CLUSTER_NAME, - path: TEST_CLUSTER_PATH, - kubernetes_namespace: TEST_KUBERNETES_NAMESPACE, - }); - - const createComponent = (deploymentStatus = {}, deploymentCluster = {}) => { - wrapper = mount(EnvironmentsBlock, { - propsData: { - deploymentStatus, - deploymentCluster, - iconStatus: status, - }, - }); - }; - - const findText = () => wrapper.findComponent(EnvironmentsBlock).text(); - const findJobDeploymentLink = () => wrapper.find('[data-testid="job-deployment-link"]'); - const findEnvironmentLink = () => wrapper.find('[data-testid="job-environment-link"]'); - const findClusterLink = () => wrapper.find('[data-testid="job-cluster-link"]'); - - describe('with last deployment', () => { - it('renders info for most recent deployment', () => { - createComponent({ - status: 'last', - environment, - }); - - expect(findText()).toBe('This job is deployed to environment.'); - }); - - describe('when there is a cluster', () => { - it('renders info with cluster', () => { - createComponent( - { - status: 'last', - environment: createEnvironmentWithLastDeployment(), - }, - createDeploymentWithCluster(), - ); - - expect(findText()).toBe( - `This job is deployed to environment using cluster ${TEST_CLUSTER_NAME}.`, - ); - }); - - describe('when there is a kubernetes namespace', () => { - it('renders info with cluster', () => { - createComponent( - { - status: 'last', - environment: createEnvironmentWithLastDeployment(), - }, - createDeploymentWithClusterAndKubernetesNamespace(), - ); - - expect(findText()).toBe( - `This job is deployed to environment using cluster ${TEST_CLUSTER_NAME} and namespace ${TEST_KUBERNETES_NAMESPACE}.`, - ); - }); - }); - }); - }); - - describe('with out of date deployment', () => { - describe('with last deployment', () => { - it('renders info for out date and most recent', () => { - createComponent({ - status: 'out_of_date', - environment: createEnvironmentWithLastDeployment(), - }); - - expect(findText()).toBe( - 'This job is an out-of-date deployment to environment. View the most recent deployment.', - ); - - expect(findJobDeploymentLink().attributes('href')).toBe('bar'); - }); - - describe('when there is a cluster', () => { - it('renders info with cluster', () => { - createComponent( - { - status: 'out_of_date', - environment: createEnvironmentWithLastDeployment(), - }, - createDeploymentWithCluster(), - ); - - expect(findText()).toBe( - `This job is an out-of-date deployment to environment using cluster ${TEST_CLUSTER_NAME}. View the most recent deployment.`, - ); - }); - - describe('when there is a kubernetes namespace', () => { - it('renders info with cluster', () => { - createComponent( - { - status: 'out_of_date', - environment: createEnvironmentWithLastDeployment(), - }, - createDeploymentWithClusterAndKubernetesNamespace(), - ); - - expect(findText()).toBe( - `This job is an out-of-date deployment to environment using cluster ${TEST_CLUSTER_NAME} and namespace ${TEST_KUBERNETES_NAMESPACE}. View the most recent deployment.`, - ); - }); - }); - }); - }); - - describe('without last deployment', () => { - it('renders info about out of date deployment', () => { - createComponent({ - status: 'out_of_date', - environment, - }); - - expect(findText()).toBe('This job is an out-of-date deployment to environment.'); - }); - }); - }); - - describe('with failed deployment', () => { - it('renders info about failed deployment', () => { - createComponent({ - status: 'failed', - environment, - }); - - expect(findText()).toBe('The deployment of this job to environment did not succeed.'); - }); - }); - - describe('creating deployment', () => { - describe('with last deployment', () => { - it('renders info about creating deployment and overriding latest deployment', () => { - createComponent({ - status: 'creating', - environment: createEnvironmentWithLastDeployment(), - }); - - expect(findText()).toBe( - 'This job is creating a deployment to environment. This will overwrite the latest deployment.', - ); - - expect(findEnvironmentLink().attributes('href')).toBe(environment.environment_path); - - expect(findJobDeploymentLink().attributes('href')).toBe('bar'); - - expect(findClusterLink().exists()).toBe(false); - }); - }); - - describe('without last deployment', () => { - it('renders info about deployment being created', () => { - createComponent({ - status: 'creating', - environment, - }); - - expect(findText()).toBe('This job is creating a deployment to environment.'); - }); - - describe('when there is a cluster', () => { - it('inclues information about the cluster', () => { - createComponent( - { - status: 'creating', - environment, - }, - createDeploymentWithCluster(), - ); - - expect(findText()).toBe( - `This job is creating a deployment to environment using cluster ${TEST_CLUSTER_NAME}.`, - ); - }); - }); - }); - - describe('without environment', () => { - it('does not render environment link', () => { - createComponent({ - status: 'creating', - environment: null, - }); - - expect(findEnvironmentLink().exists()).toBe(false); - }); - }); - }); - - describe('with a cluster', () => { - it('renders the cluster link', () => { - createComponent( - { - status: 'last', - environment: createEnvironmentWithLastDeployment(), - }, - createDeploymentWithCluster(), - ); - - expect(findText()).toBe( - `This job is deployed to environment using cluster ${TEST_CLUSTER_NAME}.`, - ); - - expect(findClusterLink().attributes('href')).toBe(TEST_CLUSTER_PATH); - }); - - describe('when the cluster is missing the path', () => { - it('renders the name without a link', () => { - createComponent( - { - status: 'last', - environment: createEnvironmentWithLastDeployment(), - }, - { name: 'the-cluster' }, - ); - - expect(findText()).toContain('using cluster the-cluster.'); - - expect(findClusterLink().exists()).toBe(false); - }); - }); - }); -}); diff --git a/spec/frontend/jobs/components/job/erased_block_spec.js b/spec/frontend/jobs/components/job/erased_block_spec.js deleted file mode 100644 index aeab676fc7e..00000000000 --- a/spec/frontend/jobs/components/job/erased_block_spec.js +++ /dev/null @@ -1,59 +0,0 @@ -import { GlLink } from '@gitlab/ui'; -import { mount } from '@vue/test-utils'; -import ErasedBlock from '~/jobs/components/job/erased_block.vue'; -import { getTimeago } from '~/lib/utils/datetime_utility'; - -describe('Erased block', () => { - let wrapper; - - const erasedAt = '2016-11-07T11:11:16.525Z'; - const timeago = getTimeago(); - const formattedDate = timeago.format(erasedAt); - - const findLink = () => wrapper.findComponent(GlLink); - - const createComponent = (props) => { - wrapper = mount(ErasedBlock, { - propsData: props, - }); - }; - - describe('with job erased by user', () => { - beforeEach(() => { - createComponent({ - user: { - username: 'root', - web_url: 'gitlab.com/root', - }, - erasedAt, - }); - }); - - it('renders username and link', () => { - expect(findLink().attributes('href')).toEqual('gitlab.com/root'); - - expect(wrapper.text().trim()).toContain('Job has been erased by'); - expect(wrapper.text().trim()).toContain('root'); - }); - - it('renders erasedAt', () => { - expect(wrapper.text().trim()).toContain(formattedDate); - }); - }); - - describe('with erased job', () => { - beforeEach(() => { - createComponent({ - erasedAt, - }); - }); - - it('renders username and link', () => { - expect(wrapper.text().trim()).toContain('Job has been erased'); - }); - - it('renders erasedAt', () => { - expect(wrapper.text().trim()).toContain(formattedDate); - }); - }); -}); diff --git a/spec/frontend/jobs/components/job/job_app_spec.js b/spec/frontend/jobs/components/job/job_app_spec.js deleted file mode 100644 index 8f5700ee22d..00000000000 --- a/spec/frontend/jobs/components/job/job_app_spec.js +++ /dev/null @@ -1,343 +0,0 @@ -import Vue, { nextTick } from 'vue'; -// eslint-disable-next-line no-restricted-imports -import Vuex from 'vuex'; -import { GlLoadingIcon } from '@gitlab/ui'; -import MockAdapter from 'axios-mock-adapter'; -import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; -import { TEST_HOST } from 'helpers/test_constants'; -import EmptyState from '~/jobs/components/job/empty_state.vue'; -import EnvironmentsBlock from '~/jobs/components/job/environments_block.vue'; -import ErasedBlock from '~/jobs/components/job/erased_block.vue'; -import JobApp from '~/jobs/components/job/job_app.vue'; -import JobLog from '~/jobs/components/log/log.vue'; -import JobLogTopBar from 'ee_else_ce/jobs/components/job/job_log_controllers.vue'; -import Sidebar from '~/jobs/components/job/sidebar/sidebar.vue'; -import StuckBlock from '~/jobs/components/job/stuck_block.vue'; -import UnmetPrerequisitesBlock from '~/jobs/components/job/unmet_prerequisites_block.vue'; -import createStore from '~/jobs/store'; -import axios from '~/lib/utils/axios_utils'; -import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; -import { MANUAL_STATUS } from '~/jobs/constants'; -import job from '../../mock_data'; -import { mockPendingJobData } from './mock_data'; - -describe('Job App', () => { - Vue.use(Vuex); - - let store; - let wrapper; - let mock; - - const initSettings = { - endpoint: `${TEST_HOST}jobs/123.json`, - pagePath: `${TEST_HOST}jobs/123`, - logState: - 'eyJvZmZzZXQiOjE3NDUxLCJuX29wZW5fdGFncyI6MCwiZmdfY29sb3IiOm51bGwsImJnX2NvbG9yIjpudWxsLCJzdHlsZV9tYXNrIjowfQ%3D%3D', - }; - - const props = { - artifactHelpUrl: 'help/artifact', - deploymentHelpUrl: 'help/deployment', - runnerSettingsUrl: 'settings/ci-cd/runners', - terminalPath: 'jobs/123/terminal', - projectPath: 'user-name/project-name', - subscriptionsMoreMinutesUrl: 'https://customers.gitlab.com/buy_pipeline_minutes', - }; - - const createComponent = () => { - wrapper = shallowMountExtended(JobApp, { - propsData: { ...props }, - store, - }); - }; - - const setupAndMount = async ({ jobData = {}, jobLogData = {} } = {}) => { - mock.onGet(initSettings.endpoint).replyOnce(HTTP_STATUS_OK, { ...job, ...jobData }); - mock.onGet(`${initSettings.pagePath}/trace.json`).reply(HTTP_STATUS_OK, jobLogData); - - const asyncInit = store.dispatch('init', initSettings); - - createComponent(); - - await asyncInit; - jest.runOnlyPendingTimers(); - await axios.waitForAll(); - await nextTick(); - }; - - const findLoadingComponent = () => wrapper.findComponent(GlLoadingIcon); - const findSidebar = () => wrapper.findComponent(Sidebar); - const findStuckBlockComponent = () => wrapper.findComponent(StuckBlock); - const findFailedJobComponent = () => wrapper.findComponent(UnmetPrerequisitesBlock); - const findEnvironmentsBlockComponent = () => wrapper.findComponent(EnvironmentsBlock); - const findErasedBlock = () => wrapper.findComponent(ErasedBlock); - const findEmptyState = () => wrapper.findComponent(EmptyState); - const findJobLog = () => wrapper.findComponent(JobLog); - const findJobLogTopBar = () => wrapper.findComponent(JobLogTopBar); - - const findJobContent = () => wrapper.findByTestId('job-content'); - const findArchivedJob = () => wrapper.findByTestId('archived-job'); - - beforeEach(() => { - mock = new MockAdapter(axios); - store = createStore(); - }); - - afterEach(() => { - mock.restore(); - // eslint-disable-next-line @gitlab/vtu-no-explicit-wrapper-destroy - wrapper.destroy(); - }); - - describe('while loading', () => { - beforeEach(() => { - store.state.isLoading = true; - createComponent(); - }); - - it('renders loading icon', () => { - expect(findLoadingComponent().exists()).toBe(true); - expect(findSidebar().exists()).toBe(false); - expect(findJobContent().exists()).toBe(false); - }); - }); - - describe('with successful request', () => { - describe('Header section', () => { - describe('job callout message', () => { - it('should not render the reason when reason is absent', () => - setupAndMount().then(() => { - expect(wrapper.vm.shouldRenderCalloutMessage).toBe(false); - })); - - it('should render the reason when reason is present', () => - setupAndMount({ - jobData: { - callout_message: 'There is an unkown failure, please try again', - }, - }).then(() => { - expect(wrapper.vm.shouldRenderCalloutMessage).toBe(true); - })); - }); - }); - - describe('stuck block', () => { - describe('without active runners available', () => { - it('renders stuck block when there are no runners', () => - setupAndMount({ - jobData: { - status: { - group: 'pending', - icon: 'status_pending', - label: 'pending', - text: 'pending', - details_path: 'path', - }, - stuck: true, - runners: { - available: false, - online: false, - }, - tags: [], - }, - }).then(() => { - expect(findStuckBlockComponent().exists()).toBe(true); - })); - }); - - it('does not render stuck block when there are runners', () => - setupAndMount({ - jobData: { - runners: { available: true }, - }, - }).then(() => { - expect(findStuckBlockComponent().exists()).toBe(false); - })); - }); - - describe('unmet prerequisites block', () => { - it('renders unmet prerequisites block when there is an unmet prerequisites failure', () => - setupAndMount({ - jobData: { - status: { - group: 'failed', - icon: 'status_failed', - label: 'failed', - text: 'failed', - details_path: 'path', - illustration: { - content: 'Retry this job in order to create the necessary resources.', - image: 'path', - size: 'svg-430', - title: 'Failed to create resources', - }, - }, - failure_reason: 'unmet_prerequisites', - has_trace: false, - runners: { - available: true, - }, - tags: [], - }, - }).then(() => { - expect(findFailedJobComponent().exists()).toBe(true); - })); - }); - - describe('environments block', () => { - it('renders environment block when job has environment', () => - setupAndMount({ - jobData: { - deployment_status: { - environment: { - environment_path: '/path', - name: 'foo', - }, - }, - }, - }).then(() => { - expect(findEnvironmentsBlockComponent().exists()).toBe(true); - })); - - it('does not render environment block when job has environment', () => - setupAndMount().then(() => { - expect(findEnvironmentsBlockComponent().exists()).toBe(false); - })); - }); - - describe('erased block', () => { - it('renders erased block when `erased` is true', () => - setupAndMount({ - jobData: { - erased_by: { - username: 'root', - web_url: 'gitlab.com/root', - }, - erased_at: '2016-11-07T11:11:16.525Z', - }, - }).then(() => { - expect(findErasedBlock().exists()).toBe(true); - })); - - it('does not render erased block when `erased` is false', () => - setupAndMount({ - jobData: { - erased_at: null, - }, - }).then(() => { - expect(findErasedBlock().exists()).toBe(false); - })); - }); - - describe('empty states block', () => { - it('renders empty state when job does not have log and is not running', () => - setupAndMount({ - jobData: { - has_trace: false, - status: { - group: 'pending', - icon: 'status_pending', - label: 'pending', - text: 'pending', - details_path: 'path', - illustration: { - image: 'path', - size: '340', - title: 'Empty State', - content: 'This is an empty state', - }, - action: { - button_title: 'Retry job', - method: 'post', - path: '/path', - }, - }, - }, - }).then(() => { - expect(findEmptyState().exists()).toBe(true); - })); - - it('does not render empty state when job does not have log but it is running', () => - setupAndMount({ - jobData: { - has_trace: false, - status: { - group: 'running', - icon: 'status_running', - label: 'running', - text: 'running', - details_path: 'path', - }, - }, - }).then(() => { - expect(findEmptyState().exists()).toBe(false); - })); - - it('does not render empty state when job has log but it is not running', () => - setupAndMount({ jobData: { has_trace: true } }).then(() => { - expect(findEmptyState().exists()).toBe(false); - })); - }); - - describe('sidebar', () => { - it('renders sidebar', async () => { - await setupAndMount(); - - expect(findSidebar().exists()).toBe(true); - }); - }); - }); - - describe('archived job', () => { - beforeEach(() => setupAndMount({ jobData: { archived: true } })); - - it('renders warning about job being archived', () => { - expect(findArchivedJob().exists()).toBe(true); - }); - }); - - describe('non-archived job', () => { - beforeEach(() => setupAndMount()); - - it('does not warning about job being archived', () => { - expect(findArchivedJob().exists()).toBe(false); - }); - }); - - describe('job log', () => { - beforeEach(() => setupAndMount()); - - it('should render job log header', () => { - expect(findJobLogTopBar().exists()).toBe(true); - }); - - it('should render job log', () => { - expect(findJobLog().exists()).toBe(true); - }); - }); - - describe('job log polling', () => { - beforeEach(() => { - jest.spyOn(store, 'dispatch'); - }); - - it('should poll job log by default', async () => { - await setupAndMount({ - jobData: mockPendingJobData, - }); - - expect(store.dispatch).toHaveBeenCalledWith('fetchJobLog'); - }); - - it('should NOT poll job log for manual variables form empty state', async () => { - const manualPendingJobData = mockPendingJobData; - manualPendingJobData.status.group = MANUAL_STATUS; - - await setupAndMount({ - jobData: manualPendingJobData, - }); - - expect(store.dispatch).not.toHaveBeenCalledWith('fetchJobLog'); - }); - }); -}); diff --git a/spec/frontend/jobs/components/job/job_container_item_spec.js b/spec/frontend/jobs/components/job/job_container_item_spec.js deleted file mode 100644 index 39782130d38..00000000000 --- a/spec/frontend/jobs/components/job/job_container_item_spec.js +++ /dev/null @@ -1,87 +0,0 @@ -import { GlIcon, GlLink } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; -import { nextTick } from 'vue'; -import delayedJobFixture from 'test_fixtures/jobs/delayed.json'; -import JobContainerItem from '~/jobs/components/job/sidebar/job_container_item.vue'; -import CiIcon from '~/vue_shared/components/ci_icon.vue'; -import job from '../../mock_data'; - -describe('JobContainerItem', () => { - let wrapper; - - const findCiIcon = () => wrapper.findComponent(CiIcon); - const findGlIcon = () => wrapper.findComponent(GlIcon); - - function createComponent(jobData = {}, props = { isActive: false, retried: false }) { - wrapper = shallowMount(JobContainerItem, { - propsData: { - job: { - ...jobData, - retried: props.retried, - }, - isActive: props.isActive, - }, - }); - } - - describe('when a job is not active and not retried', () => { - beforeEach(() => { - createComponent(job); - }); - - it('displays a status icon', () => { - expect(findCiIcon().props('status')).toBe(job.status); - }); - - it('displays the job name', () => { - expect(wrapper.text()).toContain(job.name); - }); - - it('displays a link to the job', () => { - const link = wrapper.findComponent(GlLink); - - expect(link.attributes('href')).toBe(job.status.details_path); - }); - }); - - describe('when a job is active', () => { - beforeEach(() => { - createComponent(job, { isActive: true }); - }); - - it('displays an arrow sprite icon', () => { - expect(findGlIcon().props('name')).toBe('arrow-right'); - }); - }); - - describe('when a job is retried', () => { - beforeEach(() => { - createComponent(job, { isActive: false, retried: true }); - }); - - it('displays a retry icon', () => { - expect(findGlIcon().props('name')).toBe('retry'); - }); - }); - - describe('for a delayed job', () => { - beforeEach(() => { - const remainingMilliseconds = 1337000; - jest - .spyOn(Date, 'now') - .mockImplementation( - () => new Date(delayedJobFixture.scheduled_at).getTime() - remainingMilliseconds, - ); - - createComponent(delayedJobFixture); - }); - - it('displays remaining time in tooltip', async () => { - await nextTick(); - - const link = wrapper.findComponent(GlLink); - - expect(link.attributes('title')).toMatch('delayed job - delayed manual action (00:22:17)'); - }); - }); -}); diff --git a/spec/frontend/jobs/components/job/job_log_controllers_spec.js b/spec/frontend/jobs/components/job/job_log_controllers_spec.js deleted file mode 100644 index 7b6d58f63d1..00000000000 --- a/spec/frontend/jobs/components/job/job_log_controllers_spec.js +++ /dev/null @@ -1,323 +0,0 @@ -import { GlSearchBoxByClick } from '@gitlab/ui'; -import { mount } from '@vue/test-utils'; -import JobLogControllers from '~/jobs/components/job/job_log_controllers.vue'; -import HelpPopover from '~/vue_shared/components/help_popover.vue'; -import { backoffMockImplementation } from 'helpers/backoff_helper'; -import * as commonUtils from '~/lib/utils/common_utils'; -import { mockJobLog } from '../../mock_data'; - -const mockToastShow = jest.fn(); - -describe('Job log controllers', () => { - let wrapper; - - beforeEach(() => { - jest.spyOn(commonUtils, 'backOff').mockImplementation(backoffMockImplementation); - }); - - afterEach(() => { - commonUtils.backOff.mockReset(); - }); - - const defaultProps = { - rawPath: '/raw', - size: 511952, - isScrollTopDisabled: false, - isScrollBottomDisabled: false, - isScrollingDown: true, - isJobLogSizeVisible: true, - isComplete: true, - jobLog: mockJobLog, - }; - - const createWrapper = (props, { jobLogJumpToFailures = false } = {}) => { - wrapper = mount(JobLogControllers, { - propsData: { - ...defaultProps, - ...props, - }, - provide: { - glFeatures: { - jobLogJumpToFailures, - }, - }, - data() { - return { - searchTerm: '82', - }; - }, - mocks: { - $toast: { - show: mockToastShow, - }, - }, - }); - }; - - const findTruncatedInfo = () => wrapper.find('[data-testid="log-truncated-info"]'); - const findRawLink = () => wrapper.find('[data-testid="raw-link"]'); - const findRawLinkController = () => wrapper.find('[data-testid="job-raw-link-controller"]'); - const findScrollTop = () => wrapper.find('[data-testid="job-controller-scroll-top"]'); - const findScrollBottom = () => wrapper.find('[data-testid="job-controller-scroll-bottom"]'); - const findJobLogSearch = () => wrapper.findComponent(GlSearchBoxByClick); - const findSearchHelp = () => wrapper.findComponent(HelpPopover); - const findScrollFailure = () => wrapper.find('[data-testid="job-controller-scroll-to-failure"]'); - - describe('Truncate information', () => { - describe('with isJobLogSizeVisible', () => { - beforeEach(() => { - createWrapper(); - }); - - it('renders size information', () => { - expect(findTruncatedInfo().text()).toMatch('499.95 KiB'); - }); - - it('renders link to raw job log', () => { - expect(findRawLink().attributes('href')).toBe(defaultProps.rawPath); - }); - }); - }); - - describe('links section', () => { - describe('with raw job log path', () => { - beforeEach(() => { - createWrapper(); - }); - - it('renders raw job log link', () => { - expect(findRawLinkController().attributes('href')).toBe(defaultProps.rawPath); - }); - }); - - describe('without raw job log path', () => { - beforeEach(() => { - createWrapper({ - rawPath: null, - }); - }); - - it('does not render raw job log link', () => { - expect(findRawLinkController().exists()).toBe(false); - }); - }); - }); - - describe('scroll buttons', () => { - describe('scroll top button', () => { - describe('when user can scroll top', () => { - beforeEach(() => { - createWrapper({ - isScrollTopDisabled: false, - }); - }); - - it('emits scrollJobLogTop event on click', async () => { - await findScrollTop().trigger('click'); - - expect(wrapper.emitted().scrollJobLogTop).toHaveLength(1); - }); - }); - - describe('when user can not scroll top', () => { - beforeEach(() => { - createWrapper({ - isScrollTopDisabled: true, - isScrollBottomDisabled: false, - isScrollingDown: false, - }); - }); - - it('renders disabled scroll top button', () => { - expect(findScrollTop().attributes('disabled')).toBeDefined(); - }); - - it('does not emit scrollJobLogTop event on click', async () => { - await findScrollTop().trigger('click'); - - expect(wrapper.emitted().scrollJobLogTop).toBeUndefined(); - }); - }); - }); - - describe('scroll bottom button', () => { - describe('when user can scroll bottom', () => { - beforeEach(() => { - createWrapper(); - }); - - it('emits scrollJobLogBottom event on click', async () => { - await findScrollBottom().trigger('click'); - - expect(wrapper.emitted().scrollJobLogBottom).toHaveLength(1); - }); - }); - - describe('when user can not scroll bottom', () => { - beforeEach(() => { - createWrapper({ - isScrollTopDisabled: false, - isScrollBottomDisabled: true, - isScrollingDown: false, - }); - }); - - it('renders disabled scroll bottom button', () => { - expect(findScrollBottom().attributes('disabled')).toEqual('disabled'); - }); - - it('does not emit scrollJobLogBottom event on click', async () => { - await findScrollBottom().trigger('click'); - - expect(wrapper.emitted().scrollJobLogBottom).toBeUndefined(); - }); - }); - - describe('while isScrollingDown is true', () => { - beforeEach(() => { - createWrapper(); - }); - - it('renders animate class for the scroll down button', () => { - expect(findScrollBottom().classes()).toContain('animate'); - }); - }); - - describe('while isScrollingDown is false', () => { - beforeEach(() => { - createWrapper({ - isScrollTopDisabled: true, - isScrollBottomDisabled: false, - isScrollingDown: false, - }); - }); - - it('does not render animate class for the scroll down button', () => { - expect(findScrollBottom().classes()).not.toContain('animate'); - }); - }); - }); - - describe('scroll to failure button', () => { - describe('with feature flag disabled', () => { - it('does not display button', () => { - createWrapper(); - - expect(findScrollFailure().exists()).toBe(false); - }); - }); - - describe('with red text failures on the page', () => { - let firstFailure; - let secondFailure; - - beforeEach(() => { - jest.spyOn(document, 'querySelectorAll').mockReturnValueOnce(['mock-element']); - - createWrapper({}, { jobLogJumpToFailures: true }); - - firstFailure = document.createElement('div'); - firstFailure.className = 'term-fg-l-red'; - document.body.appendChild(firstFailure); - - secondFailure = document.createElement('div'); - secondFailure.className = 'term-fg-l-red'; - document.body.appendChild(secondFailure); - }); - - afterEach(() => { - if (firstFailure) { - firstFailure.remove(); - firstFailure = null; - } - - if (secondFailure) { - secondFailure.remove(); - secondFailure = null; - } - }); - - it('is enabled', () => { - expect(findScrollFailure().props('disabled')).toBe(false); - }); - - it('scrolls to each failure', async () => { - jest.spyOn(firstFailure, 'scrollIntoView'); - - await findScrollFailure().trigger('click'); - - expect(firstFailure.scrollIntoView).toHaveBeenCalled(); - - await findScrollFailure().trigger('click'); - - expect(secondFailure.scrollIntoView).toHaveBeenCalled(); - - await findScrollFailure().trigger('click'); - - expect(firstFailure.scrollIntoView).toHaveBeenCalled(); - }); - }); - - describe('with no red text failures on the page', () => { - beforeEach(() => { - jest.spyOn(document, 'querySelectorAll').mockReturnValueOnce([]); - - createWrapper({}, { jobLogJumpToFailures: true }); - }); - - it('is disabled', () => { - expect(findScrollFailure().props('disabled')).toBe(true); - }); - }); - - describe('when the job log is not complete', () => { - beforeEach(() => { - jest.spyOn(document, 'querySelectorAll').mockReturnValueOnce(['mock-element']); - - createWrapper({ isComplete: false }, { jobLogJumpToFailures: true }); - }); - - it('is enabled', () => { - expect(findScrollFailure().props('disabled')).toBe(false); - }); - }); - - describe('on error', () => { - beforeEach(() => { - jest.spyOn(commonUtils, 'backOff').mockRejectedValueOnce(); - - createWrapper({}, { jobLogJumpToFailures: true }); - }); - - it('stays disabled', () => { - expect(findScrollFailure().props('disabled')).toBe(true); - }); - }); - }); - }); - - describe('Job log search', () => { - beforeEach(() => { - createWrapper(); - }); - - it('displays job log search', () => { - expect(findJobLogSearch().exists()).toBe(true); - expect(findSearchHelp().exists()).toBe(true); - }); - - it('emits search results', () => { - const expectedSearchResults = [[[mockJobLog[6].lines[1], mockJobLog[6].lines[2]]]]; - - findJobLogSearch().vm.$emit('submit'); - - expect(wrapper.emitted('searchResults')).toEqual(expectedSearchResults); - }); - - it('clears search results', () => { - findJobLogSearch().vm.$emit('clear'); - - expect(wrapper.emitted('searchResults')).toEqual([[[]]]); - }); - }); -}); diff --git a/spec/frontend/jobs/components/job/job_retry_forward_deployment_modal_spec.js b/spec/frontend/jobs/components/job/job_retry_forward_deployment_modal_spec.js deleted file mode 100644 index a44a13259aa..00000000000 --- a/spec/frontend/jobs/components/job/job_retry_forward_deployment_modal_spec.js +++ /dev/null @@ -1,67 +0,0 @@ -import { GlLink, GlModal } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; -import JobRetryForwardDeploymentModal from '~/jobs/components/job/sidebar/job_retry_forward_deployment_modal.vue'; -import { JOB_RETRY_FORWARD_DEPLOYMENT_MODAL } from '~/jobs/constants'; -import createStore from '~/jobs/store'; -import job from '../../mock_data'; - -describe('Job Retry Forward Deployment Modal', () => { - let store; - let wrapper; - - const retryOutdatedJobDocsUrl = 'url-to-docs'; - const findLink = () => wrapper.findComponent(GlLink); - const findModal = () => wrapper.findComponent(GlModal); - - const createWrapper = ({ props = {}, provide = {}, stubs = {} } = {}) => { - store = createStore(); - wrapper = shallowMount(JobRetryForwardDeploymentModal, { - propsData: { - modalId: 'modal-id', - href: job.retry_path, - ...props, - }, - provide, - store, - stubs, - }); - }; - - beforeEach(createWrapper); - - describe('Modal configuration', () => { - it('should display the correct messages', () => { - const modal = findModal(); - expect(modal.attributes('title')).toMatch(JOB_RETRY_FORWARD_DEPLOYMENT_MODAL.title); - expect(modal.text()).toMatch(JOB_RETRY_FORWARD_DEPLOYMENT_MODAL.info); - expect(modal.text()).toMatch(JOB_RETRY_FORWARD_DEPLOYMENT_MODAL.areYouSure); - }); - }); - - describe('Modal docs help link', () => { - it('should not display an info link when none is provided', () => { - createWrapper(); - - expect(findLink().exists()).toBe(false); - }); - - it('should display an info link when one is provided', () => { - createWrapper({ provide: { retryOutdatedJobDocsUrl } }); - - expect(findLink().attributes('href')).toBe(retryOutdatedJobDocsUrl); - expect(findLink().text()).toMatch(JOB_RETRY_FORWARD_DEPLOYMENT_MODAL.moreInfo); - }); - }); - - describe('Modal actions', () => { - beforeEach(createWrapper); - - it('should correctly configure the primary action', () => { - expect(findModal().props('actionPrimary').attributes).toMatchObject({ - 'data-method': 'post', - href: job.retry_path, - variant: 'danger', - }); - }); - }); -}); diff --git a/spec/frontend/jobs/components/job/job_sidebar_details_container_spec.js b/spec/frontend/jobs/components/job/job_sidebar_details_container_spec.js deleted file mode 100644 index c1028f3929d..00000000000 --- a/spec/frontend/jobs/components/job/job_sidebar_details_container_spec.js +++ /dev/null @@ -1,133 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import { extendedWrapper } from 'helpers/vue_test_utils_helper'; -import DetailRow from '~/jobs/components/job/sidebar/sidebar_detail_row.vue'; -import SidebarJobDetailsContainer from '~/jobs/components/job/sidebar/sidebar_job_details_container.vue'; -import createStore from '~/jobs/store'; -import job from '../../mock_data'; - -describe('Job Sidebar Details Container', () => { - let store; - let wrapper; - - const findJobTimeout = () => wrapper.findByTestId('job-timeout'); - const findJobTags = () => wrapper.findByTestId('job-tags'); - const findAllDetailsRow = () => wrapper.findAllComponents(DetailRow); - - const createWrapper = ({ props = {} } = {}) => { - store = createStore(); - wrapper = extendedWrapper( - shallowMount(SidebarJobDetailsContainer, { - propsData: props, - store, - stubs: { - DetailRow, - }, - }), - ); - }; - - describe('when no details are available', () => { - beforeEach(() => { - createWrapper(); - }); - - it('should render an empty container', () => { - expect(wrapper.html()).toBe(''); - }); - - it.each(['duration', 'erased_at', 'finished_at', 'queued_at', 'runner', 'coverage'])( - 'should not render %s details when missing', - async (detail) => { - await store.dispatch('receiveJobSuccess', { [detail]: undefined }); - - expect(findAllDetailsRow()).toHaveLength(0); - }, - ); - }); - - describe('when some of the details are available', () => { - beforeEach(createWrapper); - - it.each([ - ['duration', 'Elapsed time: 6 seconds'], - ['erased_at', 'Erased: 3 weeks ago'], - ['finished_at', 'Finished: 3 weeks ago'], - ['queued_duration', 'Queued: 9 seconds'], - ['runner', 'Runner: #1 (ABCDEFGH) local ci runner'], - ['coverage', 'Coverage: 20%'], - ])('uses %s to render job-%s', async (detail, value) => { - await store.dispatch('receiveJobSuccess', { [detail]: job[detail] }); - const detailsRow = findAllDetailsRow(); - - expect(detailsRow).toHaveLength(1); - expect(detailsRow.at(0).text()).toBe(value); - }); - - it('only renders tags', async () => { - const { tags } = job; - await store.dispatch('receiveJobSuccess', { tags }); - const tagsComponent = findJobTags(); - - expect(tagsComponent.text()).toBe('Tags: tag'); - }); - }); - - describe('when all the info are available', () => { - it('renders all the details components', async () => { - createWrapper(); - await store.dispatch('receiveJobSuccess', job); - - expect(findAllDetailsRow()).toHaveLength(7); - }); - - describe('duration row', () => { - it('renders all the details components', async () => { - createWrapper(); - await store.dispatch('receiveJobSuccess', job); - - expect(findAllDetailsRow().at(0).text()).toBe('Duration: 6 seconds'); - }); - }); - }); - - describe('timeout', () => { - const { - metadata: { timeout_human_readable, timeout_source }, - } = job; - - beforeEach(createWrapper); - - it('does not render if metadata is empty', async () => { - const metadata = {}; - await store.dispatch('receiveJobSuccess', { metadata }); - const detailsRow = findAllDetailsRow(); - - expect(wrapper.html()).toBe(''); - expect(detailsRow.exists()).toBe(false); - }); - - it('uses metadata to render timeout', async () => { - const metadata = { timeout_human_readable }; - await store.dispatch('receiveJobSuccess', { metadata }); - const detailsRow = findAllDetailsRow(); - - expect(detailsRow).toHaveLength(1); - expect(detailsRow.at(0).text()).toBe('Timeout: 1m 40s'); - }); - - it('uses metadata to render timeout and the source', async () => { - const metadata = { timeout_human_readable, timeout_source }; - await store.dispatch('receiveJobSuccess', { metadata }); - const detailsRow = findAllDetailsRow(); - - expect(detailsRow.at(0).text()).toBe('Timeout: 1m 40s (from runner)'); - }); - - it('should not render when no time is provided', async () => { - const metadata = { timeout_source }; - await store.dispatch('receiveJobSuccess', { metadata }); - - expect(findJobTimeout().exists()).toBe(false); - }); - }); -}); diff --git a/spec/frontend/jobs/components/job/job_sidebar_retry_button_spec.js b/spec/frontend/jobs/components/job/job_sidebar_retry_button_spec.js deleted file mode 100644 index 8a63bfdc3d6..00000000000 --- a/spec/frontend/jobs/components/job/job_sidebar_retry_button_spec.js +++ /dev/null @@ -1,64 +0,0 @@ -import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; -import JobsSidebarRetryButton from '~/jobs/components/job/sidebar/job_sidebar_retry_button.vue'; -import createStore from '~/jobs/store'; -import job from '../../mock_data'; - -describe('Job Sidebar Retry Button', () => { - let store; - let wrapper; - - const forwardDeploymentFailure = 'forward_deployment_failure'; - const findRetryButton = () => wrapper.findByTestId('retry-job-button'); - const findRetryLink = () => wrapper.findByTestId('retry-job-link'); - - const createWrapper = ({ props = {} } = {}) => { - store = createStore(); - wrapper = shallowMountExtended(JobsSidebarRetryButton, { - propsData: { - href: job.retry_path, - isManualJob: false, - modalId: 'modal-id', - ...props, - }, - store, - }); - }; - - beforeEach(createWrapper); - - it.each([ - [null, false, true], - ['unmet_prerequisites', false, true], - [forwardDeploymentFailure, true, false], - ])( - 'when error is: %s, should render button: %s | should render link: %s', - async (failureReason, buttonExists, linkExists) => { - await store.dispatch('receiveJobSuccess', { ...job, failure_reason: failureReason }); - - expect(findRetryButton().exists()).toBe(buttonExists); - expect(findRetryLink().exists()).toBe(linkExists); - }, - ); - - describe('Button', () => { - it('should have the correct configuration', async () => { - await store.dispatch('receiveJobSuccess', { failure_reason: forwardDeploymentFailure }); - - expect(findRetryButton().attributes()).toMatchObject({ - category: 'primary', - variant: 'confirm', - icon: 'retry', - }); - }); - }); - - describe('Link', () => { - it('should have the correct configuration', () => { - expect(findRetryLink().attributes()).toMatchObject({ - 'data-method': 'post', - href: job.retry_path, - icon: 'retry', - }); - }); - }); -}); diff --git a/spec/frontend/jobs/components/job/jobs_container_spec.js b/spec/frontend/jobs/components/job/jobs_container_spec.js deleted file mode 100644 index 05660880751..00000000000 --- a/spec/frontend/jobs/components/job/jobs_container_spec.js +++ /dev/null @@ -1,143 +0,0 @@ -import { GlLink } from '@gitlab/ui'; -import { mount } from '@vue/test-utils'; -import { extendedWrapper } from 'helpers/vue_test_utils_helper'; -import JobsContainer from '~/jobs/components/job/sidebar/jobs_container.vue'; - -describe('Jobs List block', () => { - let wrapper; - - const retried = { - status: { - details_path: '/gitlab-org/gitlab-foss/pipelines/28029444', - group: 'success', - has_details: true, - icon: 'status_success', - label: 'passed', - text: 'passed', - tooltip: 'passed', - }, - id: 233432756, - tooltip: 'build - passed', - retried: true, - }; - - const active = { - name: 'test', - status: { - details_path: '/gitlab-org/gitlab-foss/pipelines/28029444', - group: 'success', - has_details: true, - icon: 'status_success', - label: 'passed', - text: 'passed', - tooltip: 'passed', - }, - id: 2322756, - tooltip: 'build - passed', - active: true, - }; - - const job = { - name: 'build', - status: { - details_path: '/gitlab-org/gitlab-foss/pipelines/28029444', - group: 'success', - has_details: true, - icon: 'status_success', - label: 'passed', - text: 'passed', - tooltip: 'passed', - }, - id: 232153, - tooltip: 'build - passed', - }; - - const findAllJobs = () => wrapper.findAllComponents(GlLink); - const findJob = () => findAllJobs().at(0); - - const findArrowIcon = () => wrapper.findByTestId('arrow-right-icon'); - const findRetryIcon = () => wrapper.findByTestId('retry-icon'); - - const createComponent = (props) => { - wrapper = extendedWrapper( - mount(JobsContainer, { - propsData: { - ...props, - }, - }), - ); - }; - - it('renders a list of jobs', () => { - createComponent({ - jobs: [job, retried, active], - jobId: 12313, - }); - - expect(findAllJobs()).toHaveLength(3); - }); - - it('renders the arrow right icon when job id matches `jobId`', () => { - createComponent({ - jobs: [active], - jobId: active.id, - }); - - expect(findArrowIcon().exists()).toBe(true); - }); - - it('does not render the arrow right icon when the job is not active', () => { - createComponent({ - jobs: [job], - jobId: active.id, - }); - - expect(findArrowIcon().exists()).toBe(false); - }); - - it('renders the job name when present', () => { - createComponent({ - jobs: [job], - jobId: active.id, - }); - - expect(findJob().text()).toBe(job.name); - expect(findJob().text()).not.toContain(job.id.toString()); - }); - - it('renders job id when job name is not available', () => { - createComponent({ - jobs: [retried], - jobId: active.id, - }); - - expect(findJob().text()).toBe(retried.id.toString()); - }); - - it('links to the job page', () => { - createComponent({ - jobs: [job], - jobId: active.id, - }); - - expect(findJob().attributes('href')).toBe(job.status.details_path); - }); - - it('renders retry icon when job was retried', () => { - createComponent({ - jobs: [retried], - jobId: active.id, - }); - - expect(findRetryIcon().exists()).toBe(true); - }); - - it('does not render retry icon when job was not retried', () => { - createComponent({ - jobs: [job], - jobId: active.id, - }); - - expect(findRetryIcon().exists()).toBe(false); - }); -}); diff --git a/spec/frontend/jobs/components/job/manual_variables_form_spec.js b/spec/frontend/jobs/components/job/manual_variables_form_spec.js deleted file mode 100644 index 989fe5c11e9..00000000000 --- a/spec/frontend/jobs/components/job/manual_variables_form_spec.js +++ /dev/null @@ -1,364 +0,0 @@ -import { GlSprintf, GlLink } from '@gitlab/ui'; -import { createLocalVue } from '@vue/test-utils'; -import VueApollo from 'vue-apollo'; -import { nextTick } from 'vue'; -import { createAlert } from '~/alert'; -import { mountExtended } from 'helpers/vue_test_utils_helper'; -import createMockApollo from 'helpers/mock_apollo_helper'; -import { TYPENAME_CI_BUILD } from '~/graphql_shared/constants'; -import { JOB_GRAPHQL_ERRORS } from '~/jobs/constants'; -import { convertToGraphQLId } from '~/graphql_shared/utils'; -import waitForPromises from 'helpers/wait_for_promises'; -import { redirectTo } from '~/lib/utils/url_utility'; // eslint-disable-line import/no-deprecated -import ManualVariablesForm from '~/jobs/components/job/manual_variables_form.vue'; -import getJobQuery from '~/jobs/components/job/graphql/queries/get_job.query.graphql'; -import playJobMutation from '~/jobs/components/job/graphql/mutations/job_play_with_variables.mutation.graphql'; -import retryJobMutation from '~/jobs/components/job/graphql/mutations/job_retry_with_variables.mutation.graphql'; - -import { - mockFullPath, - mockId, - mockJobResponse, - mockJobWithVariablesResponse, - mockJobPlayMutationData, - mockJobRetryMutationData, -} from './mock_data'; - -const localVue = createLocalVue(); -jest.mock('~/alert'); -localVue.use(VueApollo); - -jest.mock('~/lib/utils/url_utility', () => ({ - ...jest.requireActual('~/lib/utils/url_utility'), - redirectTo: jest.fn(), -})); - -const defaultProvide = { - projectPath: mockFullPath, -}; - -describe('Manual Variables Form', () => { - let wrapper; - let mockApollo; - let requestHandlers; - - const getJobQueryResponseHandlerWithVariables = jest.fn().mockResolvedValue(mockJobResponse); - const playJobMutationHandler = jest.fn().mockResolvedValue({}); - const retryJobMutationHandler = jest.fn().mockResolvedValue({}); - - const defaultHandlers = { - getJobQueryResponseHandlerWithVariables, - playJobMutationHandler, - retryJobMutationHandler, - }; - - const createComponent = ({ props = {}, handlers = defaultHandlers } = {}) => { - requestHandlers = handlers; - - mockApollo = createMockApollo([ - [getJobQuery, handlers.getJobQueryResponseHandlerWithVariables], - [playJobMutation, handlers.playJobMutationHandler], - [retryJobMutation, handlers.retryJobMutationHandler], - ]); - - const options = { - localVue, - apolloProvider: mockApollo, - }; - - wrapper = mountExtended(ManualVariablesForm, { - propsData: { - jobId: mockId, - isRetryable: false, - ...props, - }, - provide: { - ...defaultProvide, - }, - ...options, - }); - - return waitForPromises(); - }; - - const findHelpText = () => wrapper.findComponent(GlSprintf); - const findHelpLink = () => wrapper.findComponent(GlLink); - const findCancelBtn = () => wrapper.findByTestId('cancel-btn'); - const findRunBtn = () => wrapper.findByTestId('run-manual-job-btn'); - const findDeleteVarBtn = () => wrapper.findByTestId('delete-variable-btn'); - const findAllDeleteVarBtns = () => wrapper.findAllByTestId('delete-variable-btn'); - const findDeleteVarBtnPlaceholder = () => wrapper.findByTestId('delete-variable-btn-placeholder'); - const findCiVariableKey = () => wrapper.findByTestId('ci-variable-key'); - const findAllCiVariableKeys = () => wrapper.findAllByTestId('ci-variable-key'); - const findCiVariableValue = () => wrapper.findByTestId('ci-variable-value'); - const findAllVariables = () => wrapper.findAllByTestId('ci-variable-row'); - - const setCiVariableKey = () => { - findCiVariableKey().setValue('new key'); - findCiVariableKey().vm.$emit('change'); - nextTick(); - }; - - const setCiVariableKeyByPosition = (position, value) => { - findAllCiVariableKeys().at(position).setValue(value); - findAllCiVariableKeys().at(position).vm.$emit('change'); - nextTick(); - }; - - afterEach(() => { - createAlert.mockClear(); - }); - - describe('when page renders', () => { - beforeEach(async () => { - await createComponent(); - }); - - it('renders help text with provided link', () => { - expect(findHelpText().exists()).toBe(true); - expect(findHelpLink().attributes('href')).toBe( - '/help/ci/variables/index#add-a-cicd-variable-to-a-project', - ); - }); - }); - - describe('when query is unsuccessful', () => { - beforeEach(async () => { - await createComponent({ - handlers: { - getJobQueryResponseHandlerWithVariables: jest.fn().mockRejectedValue({}), - }, - }); - }); - - it('shows an alert with error', () => { - expect(createAlert).toHaveBeenCalledWith({ - message: JOB_GRAPHQL_ERRORS.jobQueryErrorText, - }); - }); - }); - - describe('when job has not been retried', () => { - beforeEach(async () => { - await createComponent({ - handlers: { - getJobQueryResponseHandlerWithVariables: jest - .fn() - .mockResolvedValue(mockJobWithVariablesResponse), - }, - }); - }); - - it('does not render the cancel button', () => { - expect(findCancelBtn().exists()).toBe(false); - expect(findRunBtn().exists()).toBe(true); - }); - }); - - describe('when job has variables', () => { - beforeEach(async () => { - await createComponent({ - handlers: { - getJobQueryResponseHandlerWithVariables: jest - .fn() - .mockResolvedValue(mockJobWithVariablesResponse), - }, - }); - }); - - it('sets manual job variables', () => { - const queryKey = mockJobWithVariablesResponse.data.project.job.manualVariables.nodes[0].key; - const queryValue = - mockJobWithVariablesResponse.data.project.job.manualVariables.nodes[0].value; - - expect(findCiVariableKey().element.value).toBe(queryKey); - expect(findCiVariableValue().element.value).toBe(queryValue); - }); - }); - - describe('when play mutation fires', () => { - beforeEach(async () => { - await createComponent({ - handlers: { - playJobMutationHandler: jest.fn().mockResolvedValue(mockJobPlayMutationData), - }, - }); - }); - - it('passes variables in correct format', async () => { - await setCiVariableKey(); - - await findCiVariableValue().setValue('new value'); - - await findRunBtn().vm.$emit('click'); - - expect(requestHandlers.playJobMutationHandler).toHaveBeenCalledTimes(1); - expect(requestHandlers.playJobMutationHandler).toHaveBeenCalledWith({ - id: convertToGraphQLId(TYPENAME_CI_BUILD, mockId), - variables: [ - { - key: 'new key', - value: 'new value', - }, - ], - }); - }); - - it('redirects to job properly after job is run', async () => { - findRunBtn().vm.$emit('click'); - await waitForPromises(); - - expect(requestHandlers.playJobMutationHandler).toHaveBeenCalledTimes(1); - expect(redirectTo).toHaveBeenCalledWith(mockJobPlayMutationData.data.jobPlay.job.webPath); // eslint-disable-line import/no-deprecated - }); - }); - - describe('when play mutation is unsuccessful', () => { - beforeEach(async () => { - await createComponent({ - handlers: { - playJobMutationHandler: jest.fn().mockRejectedValue({}), - }, - }); - }); - - it('shows an alert with error', async () => { - findRunBtn().vm.$emit('click'); - await waitForPromises(); - - expect(createAlert).toHaveBeenCalledWith({ - message: JOB_GRAPHQL_ERRORS.jobMutationErrorText, - }); - }); - }); - - describe('when job is retryable', () => { - beforeEach(async () => { - await createComponent({ - props: { isRetryable: true }, - handlers: { - retryJobMutationHandler: jest.fn().mockResolvedValue(mockJobRetryMutationData), - }, - }); - }); - - it('renders cancel button', () => { - expect(findCancelBtn().exists()).toBe(true); - }); - - it('redirects to job properly after rerun', async () => { - findRunBtn().vm.$emit('click'); - await waitForPromises(); - - expect(requestHandlers.retryJobMutationHandler).toHaveBeenCalledTimes(1); - expect(redirectTo).toHaveBeenCalledWith(mockJobRetryMutationData.data.jobRetry.job.webPath); // eslint-disable-line import/no-deprecated - }); - }); - - describe('when retry mutation is unsuccessful', () => { - beforeEach(async () => { - await createComponent({ - props: { isRetryable: true }, - handlers: { - retryJobMutationHandler: jest.fn().mockRejectedValue({}), - }, - }); - }); - - it('shows an alert with error', async () => { - findRunBtn().vm.$emit('click'); - await waitForPromises(); - - expect(createAlert).toHaveBeenCalledWith({ - message: JOB_GRAPHQL_ERRORS.jobMutationErrorText, - }); - }); - }); - - describe('updating variables in UI', () => { - beforeEach(async () => { - await createComponent({ - handlers: { - getJobQueryResponseHandlerWithVariables: jest.fn().mockResolvedValue(mockJobResponse), - }, - }); - }); - - it('creates a new variable when user enters a new key value', async () => { - expect(findAllVariables()).toHaveLength(1); - - await setCiVariableKey(); - - expect(findAllVariables()).toHaveLength(2); - }); - - it('does not create extra empty variables', async () => { - expect(findAllVariables()).toHaveLength(1); - - await setCiVariableKey(); - - expect(findAllVariables()).toHaveLength(2); - - await setCiVariableKey(); - - expect(findAllVariables()).toHaveLength(2); - }); - - it('removes the correct variable row', async () => { - const variableKeyNameOne = 'key-one'; - const variableKeyNameThree = 'key-three'; - - await setCiVariableKeyByPosition(0, variableKeyNameOne); - - await setCiVariableKeyByPosition(1, 'key-two'); - - await setCiVariableKeyByPosition(2, variableKeyNameThree); - - expect(findAllVariables()).toHaveLength(4); - - await findAllDeleteVarBtns().at(1).trigger('click'); - - expect(findAllVariables()).toHaveLength(3); - - expect(findAllCiVariableKeys().at(0).element.value).toBe(variableKeyNameOne); - expect(findAllCiVariableKeys().at(1).element.value).toBe(variableKeyNameThree); - expect(findAllCiVariableKeys().at(2).element.value).toBe(''); - }); - - it('delete variable button should only show when there is more than one variable', async () => { - expect(findDeleteVarBtn().exists()).toBe(false); - - await setCiVariableKey(); - - expect(findDeleteVarBtn().exists()).toBe(true); - }); - }); - - describe('variable delete button placeholder', () => { - beforeEach(async () => { - await createComponent({ - handlers: { - getJobQueryResponseHandlerWithVariables: jest.fn().mockResolvedValue(mockJobResponse), - }, - }); - }); - - it('delete variable button placeholder should only exist when a user cannot remove', () => { - expect(findDeleteVarBtnPlaceholder().exists()).toBe(true); - }); - - it('does not show the placeholder button', () => { - expect(findDeleteVarBtnPlaceholder().classes('gl-opacity-0')).toBe(true); - }); - - it('placeholder button will not delete the row on click', async () => { - expect(findAllCiVariableKeys()).toHaveLength(1); - expect(findDeleteVarBtnPlaceholder().exists()).toBe(true); - - await findDeleteVarBtnPlaceholder().trigger('click'); - - expect(findAllCiVariableKeys()).toHaveLength(1); - expect(findDeleteVarBtnPlaceholder().exists()).toBe(true); - }); - }); -}); diff --git a/spec/frontend/jobs/components/job/mock_data.js b/spec/frontend/jobs/components/job/mock_data.js deleted file mode 100644 index fb3a361c9c9..00000000000 --- a/spec/frontend/jobs/components/job/mock_data.js +++ /dev/null @@ -1,123 +0,0 @@ -export const mockFullPath = 'Commit451/lab-coat'; -export const mockId = 401; - -export const mockJobResponse = { - data: { - project: { - id: 'gid://gitlab/Project/4', - job: { - id: 'gid://gitlab/Ci::Build/401', - manualJob: true, - manualVariables: { - nodes: [], - __typename: 'CiManualVariableConnection', - }, - name: 'manual_job', - retryable: true, - status: 'SUCCESS', - __typename: 'CiJob', - }, - __typename: 'Project', - }, - }, -}; - -export const mockJobWithVariablesResponse = { - data: { - project: { - id: 'gid://gitlab/Project/4', - job: { - id: 'gid://gitlab/Ci::Build/401', - manualJob: true, - manualVariables: { - nodes: [ - { - id: 'gid://gitlab/Ci::JobVariable/150', - key: 'new key', - value: 'new value', - __typename: 'CiManualVariable', - }, - ], - __typename: 'CiManualVariableConnection', - }, - name: 'manual_job', - retryable: true, - status: 'SUCCESS', - __typename: 'CiJob', - }, - __typename: 'Project', - }, - }, -}; - -export const mockJobPlayMutationData = { - data: { - jobPlay: { - job: { - id: 'gid://gitlab/Ci::Build/401', - manualVariables: { - nodes: [ - { - id: 'gid://gitlab/Ci::JobVariable/151', - key: 'new key', - value: 'new value', - __typename: 'CiManualVariable', - }, - ], - __typename: 'CiManualVariableConnection', - }, - webPath: '/Commit451/lab-coat/-/jobs/401', - __typename: 'CiJob', - }, - errors: [], - __typename: 'JobPlayPayload', - }, - }, -}; - -export const mockJobRetryMutationData = { - data: { - jobRetry: { - job: { - id: 'gid://gitlab/Ci::Build/401', - manualVariables: { - nodes: [ - { - id: 'gid://gitlab/Ci::JobVariable/151', - key: 'new key', - value: 'new value', - __typename: 'CiManualVariable', - }, - ], - __typename: 'CiManualVariableConnection', - }, - webPath: '/Commit451/lab-coat/-/jobs/401', - __typename: 'CiJob', - }, - errors: [], - __typename: 'JobRetryPayload', - }, - }, -}; - -export const mockPendingJobData = { - has_trace: false, - status: { - group: 'pending', - icon: 'status_pending', - label: 'pending', - text: 'pending', - details_path: 'path', - illustration: { - image: 'path', - size: '340', - title: '', - content: '', - }, - action: { - button_title: 'Retry job', - method: 'post', - path: '/path', - }, - }, -}; diff --git a/spec/frontend/jobs/components/job/sidebar_detail_row_spec.js b/spec/frontend/jobs/components/job/sidebar_detail_row_spec.js deleted file mode 100644 index 546f5392caf..00000000000 --- a/spec/frontend/jobs/components/job/sidebar_detail_row_spec.js +++ /dev/null @@ -1,68 +0,0 @@ -import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; -import SidebarDetailRow from '~/jobs/components/job/sidebar/sidebar_detail_row.vue'; -import { DOCS_URL } from 'jh_else_ce/lib/utils/url_utility'; - -describe('Sidebar detail row', () => { - let wrapper; - - const title = 'this is the title'; - const value = 'this is the value'; - const helpUrl = `${DOCS_URL}/runner/register/index.html`; - const path = 'path/to/value'; - - const findHelpLink = () => wrapper.findByTestId('job-sidebar-help-link'); - const findValueLink = () => wrapper.findByTestId('job-sidebar-value-link'); - - const createComponent = (props) => { - wrapper = shallowMountExtended(SidebarDetailRow, { - propsData: { - ...props, - }, - }); - }; - - describe('with title/value and without helpUrl/path', () => { - beforeEach(() => { - createComponent({ title, value }); - }); - - it('should render the provided title and value', () => { - expect(wrapper.text()).toBe(`${title}: ${value}`); - }); - - it('should not render the help link', () => { - expect(findHelpLink().exists()).toBe(false); - }); - - it('should not render the value link', () => { - expect(findValueLink().exists()).toBe(false); - }); - }); - - describe('when helpUrl provided', () => { - beforeEach(() => { - createComponent({ - helpUrl, - title, - value, - }); - }); - - it('should render the help link', () => { - expect(findHelpLink().exists()).toBe(true); - expect(findHelpLink().attributes('href')).toBe(helpUrl); - }); - }); - - describe('when path is provided', () => { - it('should render link to value', () => { - createComponent({ - path, - title, - value, - }); - - expect(findValueLink().attributes('href')).toBe(path); - }); - }); -}); diff --git a/spec/frontend/jobs/components/job/sidebar_header_spec.js b/spec/frontend/jobs/components/job/sidebar_header_spec.js deleted file mode 100644 index cf182330578..00000000000 --- a/spec/frontend/jobs/components/job/sidebar_header_spec.js +++ /dev/null @@ -1,87 +0,0 @@ -import Vue from 'vue'; -import VueApollo from 'vue-apollo'; -import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; -import createMockApollo from 'helpers/mock_apollo_helper'; -import waitForPromises from 'helpers/wait_for_promises'; -import SidebarHeader from '~/jobs/components/job/sidebar/sidebar_header.vue'; -import JobRetryButton from '~/jobs/components/job/sidebar/job_sidebar_retry_button.vue'; -import getJobQuery from '~/jobs/components/job/graphql/queries/get_job.query.graphql'; -import { mockFullPath, mockId, mockJobResponse } from './mock_data'; - -Vue.use(VueApollo); - -const defaultProvide = { - projectPath: mockFullPath, -}; - -describe('Sidebar Header', () => { - let wrapper; - - const createComponent = ({ options = {}, props = {}, restJob = {} } = {}) => { - wrapper = shallowMountExtended(SidebarHeader, { - propsData: { - ...props, - jobId: mockId, - restJob, - }, - provide: { - ...defaultProvide, - }, - ...options, - }); - }; - - const createComponentWithApollo = ({ props = {}, restJob = {} } = {}) => { - const getJobQueryResponse = jest.fn().mockResolvedValue(mockJobResponse); - - const requestHandlers = [[getJobQuery, getJobQueryResponse]]; - - const apolloProvider = createMockApollo(requestHandlers); - - const options = { - apolloProvider, - }; - - createComponent({ - props, - restJob, - options, - }); - - return waitForPromises(); - }; - - const findCancelButton = () => wrapper.findByTestId('cancel-button'); - const findEraseButton = () => wrapper.findByTestId('job-log-erase-link'); - const findJobName = () => wrapper.findByTestId('job-name'); - const findRetryButton = () => wrapper.findComponent(JobRetryButton); - - describe('when rendering contents', () => { - it('renders the correct job name', async () => { - await createComponentWithApollo(); - expect(findJobName().text()).toBe(mockJobResponse.data.project.job.name); - }); - - it('does not render buttons with no paths', async () => { - await createComponentWithApollo(); - expect(findCancelButton().exists()).toBe(false); - expect(findEraseButton().exists()).toBe(false); - expect(findRetryButton().exists()).toBe(false); - }); - - it('renders a retry button with a path', async () => { - await createComponentWithApollo({ restJob: { retry_path: 'retry/path' } }); - expect(findRetryButton().exists()).toBe(true); - }); - - it('renders a cancel button with a path', async () => { - await createComponentWithApollo({ restJob: { cancel_path: 'cancel/path' } }); - expect(findCancelButton().exists()).toBe(true); - }); - - it('renders an erase button with a path', async () => { - await createComponentWithApollo({ restJob: { erase_path: 'erase/path' } }); - expect(findEraseButton().exists()).toBe(true); - }); - }); -}); diff --git a/spec/frontend/jobs/components/job/sidebar_spec.js b/spec/frontend/jobs/components/job/sidebar_spec.js deleted file mode 100644 index fbff64b4d78..00000000000 --- a/spec/frontend/jobs/components/job/sidebar_spec.js +++ /dev/null @@ -1,216 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import { nextTick } from 'vue'; -import MockAdapter from 'axios-mock-adapter'; -import { extendedWrapper } from 'helpers/vue_test_utils_helper'; -import axios from '~/lib/utils/axios_utils'; -import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; -import ArtifactsBlock from '~/jobs/components/job/sidebar/artifacts_block.vue'; -import JobRetryForwardDeploymentModal from '~/jobs/components/job/sidebar/job_retry_forward_deployment_modal.vue'; -import JobsContainer from '~/jobs/components/job/sidebar/jobs_container.vue'; -import Sidebar from '~/jobs/components/job/sidebar/sidebar.vue'; -import StagesDropdown from '~/jobs/components/job/sidebar/stages_dropdown.vue'; -import createStore from '~/jobs/store'; -import job, { jobsInStage } from '../../mock_data'; - -describe('Sidebar details block', () => { - let mock; - let store; - let wrapper; - - const forwardDeploymentFailure = 'forward_deployment_failure'; - const findModal = () => wrapper.findComponent(JobRetryForwardDeploymentModal); - const findArtifactsBlock = () => wrapper.findComponent(ArtifactsBlock); - const findNewIssueButton = () => wrapper.findByTestId('job-new-issue'); - const findTerminalLink = () => wrapper.findByTestId('terminal-link'); - const findJobStagesDropdown = () => wrapper.findComponent(StagesDropdown); - const findJobsContainer = () => wrapper.findComponent(JobsContainer); - - const createWrapper = (props) => { - store = createStore(); - - store.state.job = job; - - wrapper = extendedWrapper( - shallowMount(Sidebar, { - propsData: { - ...props, - }, - - store, - }), - ); - }; - - beforeEach(() => { - mock = new MockAdapter(axios); - mock.onGet().reply(HTTP_STATUS_OK, { - name: job.stage, - }); - }); - - describe('without terminal path', () => { - it('does not render terminal link', async () => { - createWrapper(); - await store.dispatch('receiveJobSuccess', job); - - expect(findTerminalLink().exists()).toBe(false); - }); - }); - - describe('with terminal path', () => { - it('renders terminal link', async () => { - createWrapper(); - await store.dispatch('receiveJobSuccess', { ...job, terminal_path: 'job/43123/terminal' }); - - expect(findTerminalLink().exists()).toBe(true); - }); - }); - - describe('actions', () => { - beforeEach(() => { - createWrapper(); - return store.dispatch('receiveJobSuccess', job); - }); - - it('should render link to new issue', () => { - expect(findNewIssueButton().attributes('href')).toBe(job.new_issue_path); - expect(findNewIssueButton().text()).toBe('New issue'); - }); - }); - - describe('forward deployment failure', () => { - describe('when the relevant data is missing', () => { - it.each` - retryPath | failureReason - ${null} | ${null} - ${''} | ${''} - ${job.retry_path} | ${''} - ${''} | ${forwardDeploymentFailure} - ${job.retry_path} | ${'unmet_prerequisites'} - `( - 'should not render the modal when path and failure are $retryPath, $failureReason', - async ({ retryPath, failureReason }) => { - createWrapper(); - await store.dispatch('receiveJobSuccess', { - ...job, - failure_reason: failureReason, - retry_path: retryPath, - }); - expect(findModal().exists()).toBe(false); - }, - ); - }); - - describe('when there is the relevant error', () => { - beforeEach(() => { - createWrapper(); - return store.dispatch('receiveJobSuccess', { - ...job, - failure_reason: forwardDeploymentFailure, - }); - }); - - it('should render the modal', () => { - expect(findModal().exists()).toBe(true); - }); - }); - }); - - describe('stages dropdown', () => { - beforeEach(() => { - createWrapper(); - return store.dispatch('receiveJobSuccess', job); - }); - - describe('with stages', () => { - it('renders value provided as selectedStage as selected', () => { - expect(findJobStagesDropdown().props('selectedStage')).toBe(job.stage); - }); - }); - - describe('without jobs for stages', () => { - it('does not render jobs container', () => { - expect(findJobsContainer().exists()).toBe(false); - }); - }); - - describe('with jobs for stages', () => { - beforeEach(() => { - return store.dispatch('receiveJobsForStageSuccess', jobsInStage.latest_statuses); - }); - - it('renders list of jobs', () => { - expect(findJobsContainer().exists()).toBe(true); - }); - }); - - describe('when job data changes', () => { - const stageArg = job.pipeline.details.stages.find((stage) => stage.name === job.stage); - - beforeEach(() => { - jest.spyOn(store, 'dispatch'); - }); - - describe('and the job stage is currently selected', () => { - describe('when the status changed', () => { - it('refetch the jobs list for the stage', async () => { - await store.dispatch('receiveJobSuccess', { ...job, status: 'new' }); - - expect(store.dispatch).toHaveBeenNthCalledWith(2, 'fetchJobsForStage', { ...stageArg }); - }); - }); - - describe('when the status did not change', () => { - it('does not refetch the jobs list for the stage', async () => { - await store.dispatch('receiveJobSuccess', { ...job }); - - expect(store.dispatch).toHaveBeenCalledTimes(1); - expect(store.dispatch).toHaveBeenNthCalledWith(1, 'receiveJobSuccess', { - ...job, - }); - }); - }); - }); - - describe('and the job stage is not currently selected', () => { - it('does not refetch the jobs list for the stage', async () => { - // Setting stage to `random` on the job means that we are looking - // at `build` stage currently, but the job we are seeing in the logs - // belong to `random`, so we shouldn't have to refetch - await store.dispatch('receiveJobSuccess', { ...job, stage: 'random' }); - - expect(store.dispatch).toHaveBeenCalledTimes(1); - expect(store.dispatch).toHaveBeenNthCalledWith(1, 'receiveJobSuccess', { - ...job, - stage: 'random', - }); - }); - }); - }); - }); - - describe('artifacts', () => { - beforeEach(() => { - createWrapper(); - }); - - it('artifacts are not shown if there are no properties other than locked', () => { - expect(findArtifactsBlock().exists()).toBe(false); - }); - - it('artifacts are shown if present', async () => { - store.state.job.artifact = { - download_path: '/root/ci-project/-/jobs/1960/artifacts/download', - browse_path: '/root/ci-project/-/jobs/1960/artifacts/browse', - keep_path: '/root/ci-project/-/jobs/1960/artifacts/keep', - expire_at: '2021-03-23T17:57:11.211Z', - expired: false, - locked: false, - }; - - await nextTick(); - - expect(findArtifactsBlock().exists()).toBe(true); - }); - }); -}); diff --git a/spec/frontend/jobs/components/job/stages_dropdown_spec.js b/spec/frontend/jobs/components/job/stages_dropdown_spec.js deleted file mode 100644 index c42edc62183..00000000000 --- a/spec/frontend/jobs/components/job/stages_dropdown_spec.js +++ /dev/null @@ -1,191 +0,0 @@ -import { GlDisclosureDropdown, GlLink, GlSprintf } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; -import { Mousetrap } from '~/lib/mousetrap'; -import { extendedWrapper } from 'helpers/vue_test_utils_helper'; -import StagesDropdown from '~/jobs/components/job/sidebar/stages_dropdown.vue'; -import CiIcon from '~/vue_shared/components/ci_icon.vue'; -import * as copyToClipboard from '~/behaviors/copy_to_clipboard'; -import { - mockPipelineWithoutRef, - mockPipelineWithoutMR, - mockPipelineWithAttachedMR, - mockPipelineDetached, -} from '../../mock_data'; - -describe('Stages Dropdown', () => { - let wrapper; - - const findStatus = () => wrapper.findComponent(CiIcon); - const findDropdown = () => wrapper.findComponent(GlDisclosureDropdown); - const findSelectedStageText = () => findDropdown().props('toggleText'); - - const findPipelineInfoText = () => wrapper.findByTestId('pipeline-info').text(); - - const createComponent = (props) => { - wrapper = extendedWrapper( - shallowMount(StagesDropdown, { - propsData: { - stages: [], - selectedStage: 'deploy', - ...props, - }, - stubs: { - GlSprintf, - GlLink, - }, - }), - ); - }; - - describe('without a merge request pipeline', () => { - beforeEach(() => { - createComponent({ - pipeline: mockPipelineWithoutMR, - stages: [{ name: 'build' }, { name: 'test' }], - }); - }); - - it('renders pipeline status', () => { - expect(findStatus().exists()).toBe(true); - }); - - it('renders dropdown with stages', () => { - expect(findDropdown().props('items')).toEqual([ - expect.objectContaining({ text: 'build' }), - expect.objectContaining({ text: 'test' }), - ]); - }); - - it('renders selected stage', () => { - expect(findSelectedStageText()).toBe('deploy'); - }); - }); - - describe('pipelineInfo', () => { - const allElements = [ - 'pipeline-path', - 'mr-link', - 'source-ref-link', - 'copy-source-ref-link', - 'source-branch-link', - 'copy-source-branch-link', - 'target-branch-link', - 'copy-target-branch-link', - ]; - describe.each([ - [ - 'does not have a ref', - { - pipeline: mockPipelineWithoutRef, - text: `Pipeline #${mockPipelineWithoutRef.id}`, - foundElements: [ - { testId: 'pipeline-path', props: [{ href: mockPipelineWithoutRef.path }] }, - ], - }, - ], - [ - 'hasRef but not triggered by MR', - { - pipeline: mockPipelineWithoutMR, - text: `Pipeline #${mockPipelineWithoutMR.id} for ${mockPipelineWithoutMR.ref.name}`, - foundElements: [ - { testId: 'pipeline-path', props: [{ href: mockPipelineWithoutMR.path }] }, - { testId: 'source-ref-link', props: [{ href: mockPipelineWithoutMR.ref.path }] }, - { testId: 'copy-source-ref-link', props: [{ text: mockPipelineWithoutMR.ref.name }] }, - ], - }, - ], - [ - 'hasRef and MR but not MR pipeline', - { - pipeline: mockPipelineDetached, - text: `Pipeline #${mockPipelineDetached.id} for !${mockPipelineDetached.merge_request.iid} with ${mockPipelineDetached.merge_request.source_branch}`, - foundElements: [ - { testId: 'pipeline-path', props: [{ href: mockPipelineDetached.path }] }, - { testId: 'mr-link', props: [{ href: mockPipelineDetached.merge_request.path }] }, - { - testId: 'source-branch-link', - props: [{ href: mockPipelineDetached.merge_request.source_branch_path }], - }, - { - testId: 'copy-source-branch-link', - props: [{ text: mockPipelineDetached.merge_request.source_branch }], - }, - ], - }, - ], - [ - 'hasRef and MR and MR pipeline', - { - pipeline: mockPipelineWithAttachedMR, - text: `Pipeline #${mockPipelineWithAttachedMR.id} for !${mockPipelineWithAttachedMR.merge_request.iid} with ${mockPipelineWithAttachedMR.merge_request.source_branch} into ${mockPipelineWithAttachedMR.merge_request.target_branch}`, - foundElements: [ - { testId: 'pipeline-path', props: [{ href: mockPipelineWithAttachedMR.path }] }, - { testId: 'mr-link', props: [{ href: mockPipelineWithAttachedMR.merge_request.path }] }, - { - testId: 'source-branch-link', - props: [{ href: mockPipelineWithAttachedMR.merge_request.source_branch_path }], - }, - { - testId: 'copy-source-branch-link', - props: [{ text: mockPipelineWithAttachedMR.merge_request.source_branch }], - }, - { - testId: 'target-branch-link', - props: [{ href: mockPipelineWithAttachedMR.merge_request.target_branch_path }], - }, - { - testId: 'copy-target-branch-link', - props: [{ text: mockPipelineWithAttachedMR.merge_request.target_branch }], - }, - ], - }, - ], - ])('%s', (_, { pipeline, text, foundElements }) => { - beforeEach(() => { - createComponent({ - pipeline, - }); - }); - - it('should render the text', () => { - expect(findPipelineInfoText()).toMatchInterpolatedText(text); - }); - - it('should find components with props', () => { - foundElements.forEach((element) => { - element.props.forEach((prop) => { - const key = Object.keys(prop)[0]; - expect(wrapper.findByTestId(element.testId).attributes(key)).toBe(prop[key]); - }); - }); - }); - - it('should not find components', () => { - const foundTestIds = foundElements.map((element) => element.testId); - allElements - .filter((testId) => !foundTestIds.includes(testId)) - .forEach((testId) => { - expect(wrapper.findByTestId(testId).exists()).toBe(false); - }); - }); - }); - }); - - describe('mousetrap', () => { - it.each([ - ['copy-source-ref-link', mockPipelineWithoutMR], - ['copy-source-branch-link', mockPipelineWithAttachedMR], - ])( - 'calls clickCopyToClipboardButton with `%s` button when `b` is pressed', - (button, pipeline) => { - const copyToClipboardMock = jest.spyOn(copyToClipboard, 'clickCopyToClipboardButton'); - createComponent({ pipeline }); - - Mousetrap.trigger('b'); - - expect(copyToClipboardMock).toHaveBeenCalledWith(wrapper.findByTestId(button).element); - }, - ); - }); -}); diff --git a/spec/frontend/jobs/components/job/stuck_block_spec.js b/spec/frontend/jobs/components/job/stuck_block_spec.js deleted file mode 100644 index 0f014a9222b..00000000000 --- a/spec/frontend/jobs/components/job/stuck_block_spec.js +++ /dev/null @@ -1,94 +0,0 @@ -import { GlBadge, GlLink } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; -import StuckBlock from '~/jobs/components/job/stuck_block.vue'; - -describe('Stuck Block Job component', () => { - let wrapper; - - const createWrapper = (props) => { - wrapper = shallowMount(StuckBlock, { - propsData: { - ...props, - }, - }); - }; - - const tags = ['docker', 'gitlab-org']; - - const findStuckNoActiveRunners = () => - wrapper.find('[data-testid="job-stuck-no-active-runners"]'); - const findStuckNoRunners = () => wrapper.find('[data-testid="job-stuck-no-runners"]'); - const findStuckWithTags = () => wrapper.find('[data-testid="job-stuck-with-tags"]'); - const findRunnerPathLink = () => wrapper.findComponent(GlLink); - const findAllBadges = () => wrapper.findAllComponents(GlBadge); - - describe('with no runners for project', () => { - beforeEach(() => { - createWrapper({ - hasOfflineRunnersForProject: true, - runnersPath: '/root/project/runners#js-runners-settings', - }); - }); - - it('renders only information about project not having runners', () => { - expect(findStuckNoRunners().exists()).toBe(true); - expect(findStuckWithTags().exists()).toBe(false); - expect(findStuckNoActiveRunners().exists()).toBe(false); - }); - - it('renders link to runners page', () => { - expect(findRunnerPathLink().attributes('href')).toBe( - '/root/project/runners#js-runners-settings', - ); - }); - }); - - describe('with tags', () => { - beforeEach(() => { - createWrapper({ - hasOfflineRunnersForProject: false, - tags, - runnersPath: '/root/project/runners#js-runners-settings', - }); - }); - - it('renders information about the tags not being set', () => { - expect(findStuckWithTags().exists()).toBe(true); - expect(findStuckNoActiveRunners().exists()).toBe(false); - expect(findStuckNoRunners().exists()).toBe(false); - }); - - it('renders tags', () => { - findAllBadges().wrappers.forEach((badgeElt, index) => { - return expect(badgeElt.text()).toBe(tags[index]); - }); - }); - - it('renders link to runners page', () => { - expect(findRunnerPathLink().attributes('href')).toBe( - '/root/project/runners#js-runners-settings', - ); - }); - }); - - describe('without active runners', () => { - beforeEach(() => { - createWrapper({ - hasOfflineRunnersForProject: false, - runnersPath: '/root/project/runners#js-runners-settings', - }); - }); - - it('renders information about project not having runners', () => { - expect(findStuckNoActiveRunners().exists()).toBe(true); - expect(findStuckNoRunners().exists()).toBe(false); - expect(findStuckWithTags().exists()).toBe(false); - }); - - it('renders link to runners page', () => { - expect(findRunnerPathLink().attributes('href')).toBe( - '/root/project/runners#js-runners-settings', - ); - }); - }); -}); diff --git a/spec/frontend/jobs/components/job/trigger_block_spec.js b/spec/frontend/jobs/components/job/trigger_block_spec.js deleted file mode 100644 index 8bb2c1f3ad8..00000000000 --- a/spec/frontend/jobs/components/job/trigger_block_spec.js +++ /dev/null @@ -1,81 +0,0 @@ -import { GlButton, GlTableLite } from '@gitlab/ui'; -import { mount } from '@vue/test-utils'; -import TriggerBlock from '~/jobs/components/job/sidebar/trigger_block.vue'; - -describe('Trigger block', () => { - let wrapper; - - 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); - const findVariableKey = (index) => wrapper.findAll('[data-testid="trigger-build-key"]').at(index); - - const createComponent = (props) => { - wrapper = mount(TriggerBlock, { - propsData: { - ...props, - }, - }); - }; - - describe('with short token and no variables', () => { - it('renders short token', () => { - createComponent({ - trigger: { - short_token: '0a666b2', - variables: [], - }, - }); - - expect(findShortToken().text()).toContain('0a666b2'); - }); - }); - - describe('without variables or short token', () => { - beforeEach(() => { - createComponent({ trigger: { variables: [] } }); - }); - - it('does not render short token', () => { - expect(findShortToken().exists()).toBe(false); - }); - - it('does not render variables', () => { - expect(findRevealButton().exists()).toBe(false); - expect(findVariableTable().exists()).toBe(false); - }); - }); - - describe('with variables', () => { - describe('hide/reveal variables', () => { - it('should toggle variables on click', async () => { - const hiddenValue = '••••••'; - const gcsVar = { key: 'UPLOAD_TO_GCS', value: 'false', public: false }; - const s3Var = { key: 'UPLOAD_TO_S3', value: 'true', public: false }; - - createComponent({ - trigger: { - variables: [gcsVar, s3Var], - }, - }); - - expect(findRevealButton().text()).toBe('Reveal values'); - - expect(findVariableValue(0).text()).toBe(hiddenValue); - expect(findVariableValue(1).text()).toBe(hiddenValue); - - expect(findVariableKey(0).text()).toBe(gcsVar.key); - expect(findVariableKey(1).text()).toBe(s3Var.key); - - await findRevealButton().trigger('click'); - - expect(findRevealButton().text()).toBe('Hide values'); - - expect(findVariableValue(0).text()).toBe(gcsVar.value); - expect(findVariableValue(1).text()).toBe(s3Var.value); - }); - }); - }); -}); diff --git a/spec/frontend/jobs/components/job/unmet_prerequisites_block_spec.js b/spec/frontend/jobs/components/job/unmet_prerequisites_block_spec.js deleted file mode 100644 index 1072cdd6781..00000000000 --- a/spec/frontend/jobs/components/job/unmet_prerequisites_block_spec.js +++ /dev/null @@ -1,37 +0,0 @@ -import { GlAlert, GlLink } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; -import UnmetPrerequisitesBlock from '~/jobs/components/job/unmet_prerequisites_block.vue'; - -describe('Unmet Prerequisites Block Job component', () => { - let wrapper; - const helpPath = '/user/project/clusters/index.html#troubleshooting-failed-deployment-jobs'; - - const createComponent = () => { - wrapper = shallowMount(UnmetPrerequisitesBlock, { - propsData: { - helpPath, - }, - }); - }; - - beforeEach(() => { - createComponent(); - }); - - it('renders an alert with the correct message', () => { - const container = wrapper.findComponent(GlAlert); - const alertMessage = - 'This job failed because the necessary resources were not successfully created.'; - - expect(container).not.toBeNull(); - expect(container.text()).toContain(alertMessage); - }); - - it('renders link to help page', () => { - const helpLink = wrapper.findComponent(GlLink); - - expect(helpLink).not.toBeNull(); - expect(helpLink.text()).toContain('More information'); - expect(helpLink.attributes().href).toEqual(helpPath); - }); -}); diff --git a/spec/frontend/jobs/components/log/collapsible_section_spec.js b/spec/frontend/jobs/components/log/collapsible_section_spec.js deleted file mode 100644 index 5adedea28a5..00000000000 --- a/spec/frontend/jobs/components/log/collapsible_section_spec.js +++ /dev/null @@ -1,71 +0,0 @@ -import { mount } from '@vue/test-utils'; -import { nextTick } from 'vue'; -import CollapsibleSection from '~/jobs/components/log/collapsible_section.vue'; -import { collapsibleSectionClosed, collapsibleSectionOpened } from './mock_data'; - -describe('Job Log Collapsible Section', () => { - let wrapper; - - const jobLogEndpoint = 'jobs/335'; - - const findCollapsibleLine = () => wrapper.find('.collapsible-line'); - const findCollapsibleLineSvg = () => wrapper.find('.collapsible-line svg'); - - const createComponent = (props = {}) => { - wrapper = mount(CollapsibleSection, { - propsData: { - ...props, - }, - }); - }; - - describe('with closed section', () => { - beforeEach(() => { - createComponent({ - section: collapsibleSectionClosed, - jobLogEndpoint, - }); - }); - - it('renders clickable header line', () => { - expect(findCollapsibleLine().attributes('role')).toBe('button'); - }); - - it('renders an icon with the closed state', () => { - expect(findCollapsibleLineSvg().attributes('data-testid')).toBe('chevron-lg-right-icon'); - }); - }); - - describe('with opened section', () => { - beforeEach(() => { - createComponent({ - section: collapsibleSectionOpened, - jobLogEndpoint, - }); - }); - - it('renders clickable header line', () => { - expect(findCollapsibleLine().attributes('role')).toBe('button'); - }); - - it('renders an icon with the open state', () => { - expect(findCollapsibleLineSvg().attributes('data-testid')).toBe('chevron-lg-down-icon'); - }); - - it('renders collapsible lines content', () => { - expect(wrapper.findAll('.js-line').length).toEqual(collapsibleSectionOpened.lines.length); - }); - }); - - it('emits onClickCollapsibleLine on click', async () => { - createComponent({ - section: collapsibleSectionOpened, - jobLogEndpoint, - }); - - findCollapsibleLine().trigger('click'); - - await nextTick(); - expect(wrapper.emitted('onClickCollapsibleLine').length).toBe(1); - }); -}); diff --git a/spec/frontend/jobs/components/log/duration_badge_spec.js b/spec/frontend/jobs/components/log/duration_badge_spec.js deleted file mode 100644 index 644d05366a0..00000000000 --- a/spec/frontend/jobs/components/log/duration_badge_spec.js +++ /dev/null @@ -1,26 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import DurationBadge from '~/jobs/components/log/duration_badge.vue'; - -describe('Job Log Duration Badge', () => { - let wrapper; - - const data = { - duration: '00:30:01', - }; - - const createComponent = (props = {}) => { - wrapper = shallowMount(DurationBadge, { - propsData: { - ...props, - }, - }); - }; - - beforeEach(() => { - createComponent(data); - }); - - it('renders provided duration', () => { - expect(wrapper.text()).toBe(data.duration); - }); -}); diff --git a/spec/frontend/jobs/components/log/line_header_spec.js b/spec/frontend/jobs/components/log/line_header_spec.js deleted file mode 100644 index c02d8c22655..00000000000 --- a/spec/frontend/jobs/components/log/line_header_spec.js +++ /dev/null @@ -1,119 +0,0 @@ -import { mount } from '@vue/test-utils'; -import { nextTick } from 'vue'; -import setWindowLocation from 'helpers/set_window_location_helper'; -import DurationBadge from '~/jobs/components/log/duration_badge.vue'; -import LineHeader from '~/jobs/components/log/line_header.vue'; -import LineNumber from '~/jobs/components/log/line_number.vue'; - -describe('Job Log Header Line', () => { - let wrapper; - - const data = { - line: { - content: [ - { - text: 'Running with gitlab-runner 12.1.0 (de7731dd)', - style: 'term-fg-l-green', - }, - ], - lineNumber: 76, - }, - isClosed: true, - path: '/jashkenas/underscore/-/jobs/335', - }; - - const createComponent = (props = {}) => { - wrapper = mount(LineHeader, { - propsData: { - ...props, - }, - }); - }; - - describe('line', () => { - beforeEach(() => { - createComponent(data); - }); - - it('renders the line number component', () => { - expect(wrapper.findComponent(LineNumber).exists()).toBe(true); - }); - - it('renders a span the provided text', () => { - expect(wrapper.find('span').text()).toBe(data.line.content[0].text); - }); - - it('renders the provided style as a class attribute', () => { - expect(wrapper.find('span').classes()).toContain(data.line.content[0].style); - }); - }); - - describe('when isCloses is true', () => { - beforeEach(() => { - createComponent({ ...data, isClosed: true }); - }); - - it('sets icon name to be chevron-lg-right', () => { - expect(wrapper.vm.iconName).toEqual('chevron-lg-right'); - }); - }); - - describe('when isCloses is false', () => { - beforeEach(() => { - createComponent({ ...data, isClosed: false }); - }); - - it('sets icon name to be chevron-lg-down', () => { - expect(wrapper.vm.iconName).toEqual('chevron-lg-down'); - }); - }); - - describe('on click', () => { - beforeEach(() => { - createComponent(data); - }); - - it('emits toggleLine event', async () => { - wrapper.trigger('click'); - - await nextTick(); - expect(wrapper.emitted().toggleLine.length).toBe(1); - }); - }); - - describe('with duration', () => { - beforeEach(() => { - createComponent({ ...data, duration: '00:10' }); - }); - - it('renders the duration badge', () => { - expect(wrapper.findComponent(DurationBadge).exists()).toBe(true); - }); - }); - - describe('line highlighting', () => { - describe('with hash', () => { - beforeEach(() => { - setWindowLocation(`http://foo.com/root/ci-project/-/jobs/6353#L77`); - - createComponent(data); - }); - - it('highlights line', () => { - expect(wrapper.classes()).toContain('gl-bg-gray-700'); - }); - }); - - describe('without hash', () => { - beforeEach(() => { - setWindowLocation(`http://foo.com/root/ci-project/-/jobs/6353`); - - createComponent(data); - }); - - it('does not highlight line', () => { - expect(wrapper.classes()).not.toContain('gl-bg-gray-700'); - }); - }); - }); -}); diff --git a/spec/frontend/jobs/components/log/line_number_spec.js b/spec/frontend/jobs/components/log/line_number_spec.js deleted file mode 100644 index 4130c124a30..00000000000 --- a/spec/frontend/jobs/components/log/line_number_spec.js +++ /dev/null @@ -1,35 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import LineNumber from '~/jobs/components/log/line_number.vue'; - -describe('Job Log Line Number', () => { - let wrapper; - - const data = { - lineNumber: 0, - path: '/jashkenas/underscore/-/jobs/335', - }; - - const createComponent = (props = {}) => { - wrapper = shallowMount(LineNumber, { - propsData: { - ...props, - }, - }); - }; - - beforeEach(() => { - createComponent(data); - }); - - it('renders incremented lineNunber by 1', () => { - expect(wrapper.text()).toBe('1'); - }); - - it('renders link with lineNumber as an ID', () => { - expect(wrapper.attributes().id).toBe('L1'); - }); - - it('links to the provided path with line number as anchor', () => { - expect(wrapper.attributes().href).toBe(`${data.path}#L1`); - }); -}); diff --git a/spec/frontend/jobs/components/log/line_spec.js b/spec/frontend/jobs/components/log/line_spec.js deleted file mode 100644 index fad7a03beef..00000000000 --- a/spec/frontend/jobs/components/log/line_spec.js +++ /dev/null @@ -1,267 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import Line from '~/jobs/components/log/line.vue'; -import LineNumber from '~/jobs/components/log/line_number.vue'; -import setWindowLocation from 'helpers/set_window_location_helper'; - -const httpUrl = 'http://example.com'; -const httpsUrl = 'https://example.com'; -const queryUrl = 'https://example.com?param=val'; - -const mockProps = ({ text = 'Running with gitlab-runner 12.1.0 (de7731dd)' } = {}) => ({ - line: { - content: [ - { - text, - style: 'term-fg-l-green', - }, - ], - lineNumber: 0, - }, - path: '/jashkenas/underscore/-/jobs/335', -}); - -describe('Job Log Line', () => { - let wrapper; - let data; - - const createComponent = (props = {}) => { - wrapper = shallowMount(Line, { - propsData: { - ...props, - }, - }); - }; - - const findLine = () => wrapper.find('span'); - const findLink = () => findLine().find('a'); - const findLinks = () => findLine().findAll('a'); - const findLinkAttributeByIndex = (i) => findLinks().at(i).attributes(); - - beforeEach(() => { - data = mockProps(); - createComponent(data); - }); - - it('renders the line number component', () => { - expect(wrapper.findComponent(LineNumber).exists()).toBe(true); - }); - - it('renders a span the provided text', () => { - expect(findLine().text()).toBe(data.line.content[0].text); - }); - - it('renders the provided style as a class attribute', () => { - expect(findLine().classes()).toContain(data.line.content[0].style); - }); - - describe('job urls as links', () => { - it('renders an http link', () => { - createComponent(mockProps({ text: httpUrl })); - - expect(findLink().text()).toBe(httpUrl); - expect(findLink().attributes().href).toBe(httpUrl); - }); - - it('renders an https link', () => { - createComponent(mockProps({ text: httpsUrl })); - - expect(findLink().text()).toBe(httpsUrl); - expect(findLink().attributes().href).toBe(httpsUrl); - }); - - it('renders a link with rel nofollow and noopener', () => { - createComponent(mockProps({ text: httpsUrl })); - - expect(findLink().attributes().rel).toBe('nofollow noopener noreferrer'); - }); - - it('renders a link with corresponding styles', () => { - createComponent(mockProps({ text: httpsUrl })); - - expect(findLink().classes()).toEqual(['gl-reset-color!', 'gl-text-decoration-underline']); - }); - - it('renders links with queries, surrounded by questions marks', () => { - createComponent(mockProps({ text: `Did you see my url ${queryUrl}??` })); - - expect(findLine().text()).toBe('Did you see my url https://example.com?param=val??'); - expect(findLinkAttributeByIndex(0).href).toBe(queryUrl); - }); - - it('renders links with queries, surrounded by exclamation marks', () => { - createComponent(mockProps({ text: `No! The ${queryUrl}!?` })); - - expect(findLine().text()).toBe('No! The https://example.com?param=val!?'); - expect(findLinkAttributeByIndex(0).href).toBe(queryUrl); - }); - - it('renders links that have brackets `[]` in their parameters', () => { - const url = `${httpUrl}?label_name[]=frontend`; - - createComponent(mockProps({ text: url })); - - expect(findLine().text()).toBe(url); - expect(findLinks().at(0).text()).toBe(url); - expect(findLinks().at(0).attributes('href')).toBe(url); - }); - - it('renders multiple links surrounded by text', () => { - createComponent( - mockProps({ text: `Well, my HTTP url: ${httpUrl} and my HTTPS url: ${httpsUrl}` }), - ); - expect(findLine().text()).toBe( - 'Well, my HTTP url: http://example.com and my HTTPS url: https://example.com', - ); - - expect(findLinks()).toHaveLength(2); - - expect(findLinkAttributeByIndex(0).href).toBe(httpUrl); - expect(findLinkAttributeByIndex(1).href).toBe(httpsUrl); - }); - - it('renders multiple links surrounded by text, with other symbols', () => { - createComponent( - mockProps({ text: `${httpUrl}, ${httpUrl}: ${httpsUrl}; ${httpsUrl}. ${httpsUrl}...` }), - ); - expect(findLine().text()).toBe( - 'http://example.com, http://example.com: https://example.com; https://example.com. https://example.com...', - ); - - expect(findLinks()).toHaveLength(5); - - expect(findLinkAttributeByIndex(0).href).toBe(httpUrl); - expect(findLinkAttributeByIndex(1).href).toBe(httpUrl); - expect(findLinkAttributeByIndex(2).href).toBe(httpsUrl); - expect(findLinkAttributeByIndex(3).href).toBe(httpsUrl); - expect(findLinkAttributeByIndex(4).href).toBe(httpsUrl); - }); - - it('renders multiple links surrounded by brackets', () => { - createComponent(mockProps({ text: `(${httpUrl}) <${httpUrl}> {${httpsUrl}}` })); - expect(findLine().text()).toBe( - '(http://example.com) <http://example.com> {https://example.com}', - ); - - const links = findLinks(); - - expect(links).toHaveLength(3); - - expect(links.at(0).text()).toBe(httpUrl); - expect(links.at(0).attributes('href')).toBe(httpUrl); - - expect(links.at(1).text()).toBe(httpUrl); - expect(links.at(1).attributes('href')).toBe(httpUrl); - - expect(links.at(2).text()).toBe(httpsUrl); - expect(links.at(2).attributes('href')).toBe(httpsUrl); - }); - - it('renders text with symbols in it', () => { - const text = 'apt-get update < /dev/null > /dev/null'; - createComponent(mockProps({ text })); - - expect(findLine().text()).toBe(text); - }); - - const jshref = 'javascript:doEvil();'; // eslint-disable-line no-script-url - - it.each` - type | text - ${'html link'} | ${'<a href="#">linked</a>'} - ${'html script'} | ${'<script>doEvil();</script>'} - ${'html strong'} | ${'<strong>highlighted</strong>'} - ${'js'} | ${jshref} - ${'file'} | ${'file:///a-file'} - ${'ftp'} | ${'ftp://example.com/file'} - ${'email'} | ${'email@example.com'} - ${'no scheme'} | ${'example.com/page'} - `('does not render a $type link', ({ text }) => { - createComponent(mockProps({ text })); - expect(findLink().exists()).toBe(false); - }); - }); - - describe('job log search', () => { - const mockSearchResults = [ - { - offset: 1533, - content: [{ text: '$ echo "82.71"', style: 'term-fg-l-green term-bold' }], - section: 'step-script', - lineNumber: 20, - }, - { offset: 1560, content: [{ text: '82.71' }], section: 'step-script', lineNumber: 21 }, - ]; - - it('applies highlight class to search result elements', () => { - createComponent({ - line: { - offset: 1560, - content: [{ text: '82.71' }], - section: 'step-script', - lineNumber: 21, - }, - path: '/root/ci-project/-/jobs/1089', - searchResults: mockSearchResults, - }); - - expect(wrapper.classes()).toContain('gl-bg-gray-700'); - }); - - it('does not apply highlight class to search result elements', () => { - createComponent({ - line: { - offset: 1560, - content: [{ text: 'docker' }], - section: 'step-script', - lineNumber: 29, - }, - path: '/root/ci-project/-/jobs/1089', - searchResults: mockSearchResults, - }); - - expect(wrapper.classes()).not.toContain('gl-bg-gray-700'); - }); - }); - - describe('job log hash highlighting', () => { - describe('with hash', () => { - beforeEach(() => { - setWindowLocation(`http://foo.com/root/ci-project/-/jobs/6353#L77`); - }); - - it('applies highlight class to job log line', () => { - createComponent({ - line: { - offset: 24526, - content: [{ text: 'job log content' }], - section: 'custom-section', - lineNumber: 76, - }, - path: '/root/ci-project/-/jobs/6353', - }); - - expect(wrapper.classes()).toContain('gl-bg-gray-700'); - }); - }); - - describe('without hash', () => { - beforeEach(() => { - setWindowLocation(`http://foo.com/root/ci-project/-/jobs/6353`); - }); - - it('does not apply highlight class to job log line', () => { - createComponent({ - line: { - offset: 24500, - content: [{ text: 'line' }], - section: 'custom-section', - lineNumber: 10, - }, - path: '/root/ci-project/-/jobs/6353', - }); - - expect(wrapper.classes()).not.toContain('gl-bg-gray-700'); - }); - }); - }); -}); diff --git a/spec/frontend/jobs/components/log/log_spec.js b/spec/frontend/jobs/components/log/log_spec.js deleted file mode 100644 index 9407b340950..00000000000 --- a/spec/frontend/jobs/components/log/log_spec.js +++ /dev/null @@ -1,135 +0,0 @@ -import { mount } from '@vue/test-utils'; -import Vue from 'vue'; -// eslint-disable-next-line no-restricted-imports -import Vuex from 'vuex'; -import waitForPromises from 'helpers/wait_for_promises'; -import { scrollToElement } from '~/lib/utils/common_utils'; -import Log from '~/jobs/components/log/log.vue'; -import LogLineHeader from '~/jobs/components/log/line_header.vue'; -import { logLinesParser } from '~/jobs/store/utils'; -import { jobLog } from './mock_data'; - -jest.mock('~/lib/utils/common_utils', () => ({ - ...jest.requireActual('~/lib/utils/common_utils'), - scrollToElement: jest.fn(), -})); - -describe('Job Log', () => { - let wrapper; - let actions; - let state; - let store; - let toggleCollapsibleLineMock; - - Vue.use(Vuex); - - const createComponent = () => { - wrapper = mount(Log, { - store, - }); - }; - - beforeEach(() => { - toggleCollapsibleLineMock = jest.fn(); - actions = { - toggleCollapsibleLine: toggleCollapsibleLineMock, - }; - - state = { - jobLog: logLinesParser(jobLog), - jobLogEndpoint: 'jobs/id', - }; - - store = new Vuex.Store({ - actions, - state, - }); - }); - - const findCollapsibleLine = () => wrapper.findComponent(LogLineHeader); - - describe('line numbers', () => { - beforeEach(() => { - createComponent(); - }); - - it('renders a line number for each open line', () => { - expect(wrapper.find('#L1').text()).toBe('1'); - expect(wrapper.find('#L2').text()).toBe('2'); - expect(wrapper.find('#L3').text()).toBe('3'); - }); - - it('links to the provided path and correct line number', () => { - expect(wrapper.find('#L1').attributes('href')).toBe(`${state.jobLogEndpoint}#L1`); - }); - }); - - describe('collapsible sections', () => { - beforeEach(() => { - createComponent(); - }); - - it('renders a clickable header section', () => { - expect(findCollapsibleLine().attributes('role')).toBe('button'); - }); - - it('renders an icon with the open state', () => { - expect(findCollapsibleLine().find('[data-testid="chevron-lg-down-icon"]').exists()).toBe( - true, - ); - }); - - describe('on click header section', () => { - it('calls toggleCollapsibleLine', () => { - findCollapsibleLine().trigger('click'); - - expect(toggleCollapsibleLineMock).toHaveBeenCalled(); - }); - }); - }); - - describe('anchor scrolling', () => { - afterEach(() => { - window.location.hash = ''; - }); - - describe('when hash is not present', () => { - it('does not scroll to line number', async () => { - createComponent(); - - await waitForPromises(); - - expect(wrapper.find('#L6').exists()).toBe(false); - expect(scrollToElement).not.toHaveBeenCalled(); - }); - }); - - describe('when hash is present', () => { - beforeEach(() => { - window.location.hash = '#L6'; - }); - - it('scrolls to line number', async () => { - createComponent(); - - state.jobLog = logLinesParser(jobLog, [], '#L6'); - await waitForPromises(); - - expect(scrollToElement).toHaveBeenCalledTimes(1); - - state.jobLog = logLinesParser(jobLog, [], '#L7'); - await waitForPromises(); - - expect(scrollToElement).toHaveBeenCalledTimes(1); - }); - - it('line number within collapsed section is visible', () => { - state.jobLog = logLinesParser(jobLog, [], '#L6'); - - createComponent(); - - expect(wrapper.find('#L6').exists()).toBe(true); - }); - }); - }); -}); diff --git a/spec/frontend/jobs/components/log/mock_data.js b/spec/frontend/jobs/components/log/mock_data.js deleted file mode 100644 index fa51b92a044..00000000000 --- a/spec/frontend/jobs/components/log/mock_data.js +++ /dev/null @@ -1,218 +0,0 @@ -export const jobLog = [ - { - offset: 1000, - content: [{ text: 'Running with gitlab-runner 12.1.0 (de7731dd)' }], - }, - { - offset: 1001, - content: [{ text: ' on docker-auto-scale-com 8a6210b8' }], - }, - { - offset: 1002, - content: [ - { - text: 'Using Docker executor with image dev.gitlab.org3', - }, - ], - section: 'prepare-executor', - section_header: true, - }, - { - offset: 1003, - content: [{ text: 'Starting service postgres:9.6.14 ...', style: 'text-green' }], - section: 'prepare-executor', - }, - { - offset: 1004, - content: [ - { - text: 'Restore cache', - style: 'term-fg-l-cyan term-bold', - }, - ], - section: 'restore-cache', - section_header: true, - section_options: { - collapsed: 'true', - }, - }, - { - offset: 1005, - content: [ - { - text: 'Checking cache for ruby-gems-debian-bullseye-ruby-3.0-16...', - style: 'term-fg-l-green term-bold', - }, - ], - section: 'restore-cache', - }, -]; - -export const utilsMockData = [ - { - offset: 1001, - content: [{ text: ' on docker-auto-scale-com 8a6210b8' }], - }, - { - offset: 1002, - content: [ - { - text: - 'Using Docker executor with image dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.6.6-golang-1.14-git-2.28-lfs-2.9-chrome-84-node-12.x-yarn-1.21-postgresql-11-graphicsmagick-1.3.34', - }, - ], - section: 'prepare-executor', - section_header: true, - }, - { - offset: 1003, - content: [{ text: 'Starting service postgres:9.6.14 ...' }], - section: 'prepare-executor', - }, - { - offset: 1004, - content: [{ text: 'Pulling docker image postgres:9.6.14 ...', style: 'term-fg-l-green' }], - section: 'prepare-executor', - }, - { - offset: 1005, - content: [], - section: 'prepare-executor', - section_duration: '10:00', - }, -]; - -export const originalTrace = [ - { - offset: 1, - content: [ - { - text: 'Downloading', - }, - ], - }, -]; - -export const regularIncremental = [ - { - offset: 2, - content: [ - { - text: 'log line', - }, - ], - }, -]; - -export const regularIncrementalRepeated = [ - { - offset: 1, - content: [ - { - text: 'log line', - }, - ], - }, -]; - -export const headerTrace = [ - { - offset: 1, - section_header: true, - content: [ - { - text: 'log line', - }, - ], - section: 'section', - }, -]; - -export const headerTraceIncremental = [ - { - offset: 1, - section_header: true, - content: [ - { - text: 'updated log line', - }, - ], - section: 'section', - }, -]; - -export const collapsibleTrace = [ - { - offset: 1, - section_header: true, - content: [ - { - text: 'log line', - }, - ], - section: 'section', - }, - { - offset: 2, - content: [ - { - text: 'log line', - }, - ], - section: 'section', - }, -]; - -export const collapsibleTraceIncremental = [ - { - offset: 2, - content: [ - { - text: 'updated log line', - }, - ], - section: 'section', - }, -]; - -export const collapsibleSectionClosed = { - offset: 5, - section_header: true, - isHeader: true, - isClosed: true, - line: { - content: [{ text: 'foo' }], - section: 'prepare-script', - lineNumber: 1, - }, - section_duration: '00:03', - lines: [ - { - offset: 80, - content: [{ text: 'this is a collapsible nested section' }], - section: 'prepare-script', - lineNumber: 3, - }, - ], -}; - -export const collapsibleSectionOpened = { - offset: 5, - section_header: true, - isHeader: true, - isClosed: false, - line: { - content: [{ text: 'foo' }], - section: 'prepare-script', - lineNumber: 1, - }, - section_duration: '00:03', - lines: [ - { - offset: 80, - content: [{ text: 'this is a collapsible nested section' }], - section: 'prepare-script', - lineNumber: 3, - }, - ], -}; diff --git a/spec/frontend/jobs/components/table/cells/actions_cell_spec.js b/spec/frontend/jobs/components/table/cells/actions_cell_spec.js deleted file mode 100644 index f2d249b6014..00000000000 --- a/spec/frontend/jobs/components/table/cells/actions_cell_spec.js +++ /dev/null @@ -1,240 +0,0 @@ -import { GlModal } from '@gitlab/ui'; -import Vue, { nextTick } from 'vue'; -import VueApollo from 'vue-apollo'; -import createMockApollo from 'helpers/mock_apollo_helper'; -import waitForPromises from 'helpers/wait_for_promises'; -import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; -import { redirectTo } from '~/lib/utils/url_utility'; // eslint-disable-line import/no-deprecated -import ActionsCell from '~/jobs/components/table/cells/actions_cell.vue'; -import eventHub from '~/jobs/components/table/event_hub'; -import JobPlayMutation from '~/jobs/components/table/graphql/mutations/job_play.mutation.graphql'; -import JobRetryMutation from '~/jobs/components/table/graphql/mutations/job_retry.mutation.graphql'; -import JobUnscheduleMutation from '~/jobs/components/table/graphql/mutations/job_unschedule.mutation.graphql'; -import JobCancelMutation from '~/jobs/components/table/graphql/mutations/job_cancel.mutation.graphql'; -import { - mockJobsNodes, - mockJobsNodesAsGuest, - playMutationResponse, - retryMutationResponse, - unscheduleMutationResponse, - cancelMutationResponse, -} from '../../../mock_data'; - -jest.mock('~/lib/utils/url_utility'); - -Vue.use(VueApollo); - -describe('Job actions cell', () => { - let wrapper; - - const findMockJob = (jobName, nodes = mockJobsNodes) => { - const job = nodes.find(({ name }) => name === jobName); - expect(job).toBeDefined(); // ensure job is present - return job; - }; - - const mockJob = findMockJob('build'); - const cancelableJob = findMockJob('cancelable'); - const playableJob = findMockJob('playable'); - const retryableJob = findMockJob('retryable'); - const failedJob = findMockJob('failed'); - const scheduledJob = findMockJob('scheduled'); - const jobWithArtifact = findMockJob('with_artifact'); - const cannotPlayJob = findMockJob('playable', mockJobsNodesAsGuest); - const cannotRetryJob = findMockJob('retryable', mockJobsNodesAsGuest); - const cannotPlayScheduledJob = findMockJob('scheduled', mockJobsNodesAsGuest); - - const findRetryButton = () => wrapper.findByTestId('retry'); - const findPlayButton = () => wrapper.findByTestId('play'); - const findCancelButton = () => wrapper.findByTestId('cancel-button'); - const findDownloadArtifactsButton = () => wrapper.findByTestId('download-artifacts'); - const findCountdownButton = () => wrapper.findByTestId('countdown'); - const findPlayScheduledJobButton = () => wrapper.findByTestId('play-scheduled'); - const findUnscheduleButton = () => wrapper.findByTestId('unschedule'); - - const findModal = () => wrapper.findComponent(GlModal); - - const playMutationHandler = jest.fn().mockResolvedValue(playMutationResponse); - const retryMutationHandler = jest.fn().mockResolvedValue(retryMutationResponse); - const unscheduleMutationHandler = jest.fn().mockResolvedValue(unscheduleMutationResponse); - const cancelMutationHandler = jest.fn().mockResolvedValue(cancelMutationResponse); - - const $toast = { - show: jest.fn(), - }; - - const createMockApolloProvider = (requestHandlers) => { - return createMockApollo(requestHandlers); - }; - - const createComponent = (job, requestHandlers, props = {}) => { - wrapper = shallowMountExtended(ActionsCell, { - propsData: { - job, - ...props, - }, - apolloProvider: createMockApolloProvider(requestHandlers), - mocks: { - $toast, - }, - }); - }; - - it('displays the artifacts download button with correct link', () => { - createComponent(jobWithArtifact); - - expect(findDownloadArtifactsButton().attributes('href')).toBe( - jobWithArtifact.artifacts.nodes[0].downloadPath, - ); - }); - - it('does not display an artifacts download button', () => { - createComponent(mockJob); - - expect(findDownloadArtifactsButton().exists()).toBe(false); - }); - - it.each` - button | action | jobType - ${findPlayButton} | ${'play'} | ${cannotPlayJob} - ${findRetryButton} | ${'retry'} | ${cannotRetryJob} - ${findPlayScheduledJobButton} | ${'play scheduled'} | ${cannotPlayScheduledJob} - `('does not display the $action button if user cannot update build', ({ button, jobType }) => { - createComponent(jobType); - - expect(button().exists()).toBe(false); - }); - - it.each` - button | action | jobType - ${findPlayButton} | ${'play'} | ${playableJob} - ${findRetryButton} | ${'retry'} | ${retryableJob} - ${findDownloadArtifactsButton} | ${'download artifacts'} | ${jobWithArtifact} - ${findCancelButton} | ${'cancel'} | ${cancelableJob} - `('displays the $action button', ({ button, jobType }) => { - createComponent(jobType); - - expect(button().exists()).toBe(true); - }); - - it.each` - button | action | jobType | mutationFile | handler | jobId - ${findPlayButton} | ${'play'} | ${playableJob} | ${JobPlayMutation} | ${playMutationHandler} | ${playableJob.id} - ${findRetryButton} | ${'retry'} | ${retryableJob} | ${JobRetryMutation} | ${retryMutationHandler} | ${retryableJob.id} - ${findCancelButton} | ${'cancel'} | ${cancelableJob} | ${JobCancelMutation} | ${cancelMutationHandler} | ${cancelableJob.id} - `('performs the $action mutation', ({ button, jobType, mutationFile, handler, jobId }) => { - createComponent(jobType, [[mutationFile, handler]]); - - button().vm.$emit('click'); - - expect(handler).toHaveBeenCalledWith({ id: jobId }); - }); - - it.each` - button | action | jobType | mutationFile | handler - ${findUnscheduleButton} | ${'unschedule'} | ${scheduledJob} | ${JobUnscheduleMutation} | ${unscheduleMutationHandler} - ${findCancelButton} | ${'cancel'} | ${cancelableJob} | ${JobCancelMutation} | ${cancelMutationHandler} - `( - 'the mutation action $action emits the jobActionPerformed event', - async ({ button, jobType, mutationFile, handler }) => { - jest.spyOn(eventHub, '$emit').mockImplementation(() => {}); - - createComponent(jobType, [[mutationFile, handler]]); - - button().vm.$emit('click'); - - await waitForPromises(); - - expect(eventHub.$emit).toHaveBeenCalledWith('jobActionPerformed'); - expect(redirectTo).not.toHaveBeenCalled(); // eslint-disable-line import/no-deprecated - }, - ); - - it.each` - button | action | jobType | mutationFile | handler | redirectLink - ${findPlayButton} | ${'play'} | ${playableJob} | ${JobPlayMutation} | ${playMutationHandler} | ${'/root/project/-/jobs/1986'} - ${findRetryButton} | ${'retry'} | ${retryableJob} | ${JobRetryMutation} | ${retryMutationHandler} | ${'/root/project/-/jobs/1985'} - `( - 'the mutation action $action redirects to the job', - async ({ button, jobType, mutationFile, handler, redirectLink }) => { - jest.spyOn(eventHub, '$emit').mockImplementation(() => {}); - - createComponent(jobType, [[mutationFile, handler]]); - - button().vm.$emit('click'); - - await waitForPromises(); - - expect(redirectTo).toHaveBeenCalledWith(redirectLink); // eslint-disable-line import/no-deprecated - expect(eventHub.$emit).not.toHaveBeenCalled(); - }, - ); - - it.each` - button | action | jobType - ${findPlayButton} | ${'play'} | ${playableJob} - ${findRetryButton} | ${'retry'} | ${retryableJob} - ${findCancelButton} | ${'cancel'} | ${cancelableJob} - ${findUnscheduleButton} | ${'unschedule'} | ${scheduledJob} - `('disables the $action button after first request', async ({ button, jobType }) => { - createComponent(jobType); - - expect(button().props('disabled')).toBe(false); - - button().vm.$emit('click'); - - await waitForPromises(); - - expect(button().props('disabled')).toBe(true); - }); - - describe('Retry button title', () => { - it('displays retry title when job has failed and is retryable', () => { - createComponent(failedJob); - - expect(findRetryButton().attributes('title')).toBe('Retry'); - }); - - it('displays run again title when job has passed and is retryable', () => { - createComponent(retryableJob); - - expect(findRetryButton().attributes('title')).toBe('Run again'); - }); - }); - - describe('Scheduled Jobs', () => { - const today = () => new Date('2021-08-31'); - - beforeEach(() => { - jest.spyOn(Date, 'now').mockImplementation(today); - }); - - it('displays the countdown, play and unschedule buttons', () => { - createComponent(scheduledJob); - - expect(findCountdownButton().exists()).toBe(true); - expect(findPlayScheduledJobButton().exists()).toBe(true); - expect(findUnscheduleButton().exists()).toBe(true); - }); - - it('unschedules a job', () => { - createComponent(scheduledJob, [[JobUnscheduleMutation, unscheduleMutationHandler]]); - - findUnscheduleButton().vm.$emit('click'); - - expect(unscheduleMutationHandler).toHaveBeenCalledWith({ - id: scheduledJob.id, - }); - }); - - it('shows the play job confirmation modal', async () => { - createComponent(scheduledJob); - - findPlayScheduledJobButton().vm.$emit('click'); - - await nextTick(); - - expect(findModal().exists()).toBe(true); - }); - }); -}); diff --git a/spec/frontend/jobs/components/table/cells/duration_cell_spec.js b/spec/frontend/jobs/components/table/cells/duration_cell_spec.js deleted file mode 100644 index d015edb0e91..00000000000 --- a/spec/frontend/jobs/components/table/cells/duration_cell_spec.js +++ /dev/null @@ -1,77 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import { extendedWrapper } from 'helpers/vue_test_utils_helper'; -import DurationCell from '~/jobs/components/table/cells/duration_cell.vue'; - -describe('Duration Cell', () => { - let wrapper; - - const findJobDuration = () => wrapper.findByTestId('job-duration'); - const findJobFinishedTime = () => wrapper.findByTestId('job-finished-time'); - const findDurationIcon = () => wrapper.findByTestId('duration-icon'); - const findFinishedTimeIcon = () => wrapper.findByTestId('finished-time-icon'); - - const createComponent = (props) => { - wrapper = extendedWrapper( - shallowMount(DurationCell, { - propsData: { - job: { - ...props, - }, - }, - }), - ); - }; - - it('does not display duration or finished time when no properties are present', () => { - createComponent(); - - expect(findJobDuration().exists()).toBe(false); - expect(findJobFinishedTime().exists()).toBe(false); - }); - - it('displays duration and finished time when both properties are present', () => { - const props = { - duration: 7, - finishedAt: '2021-04-26T13:37:52Z', - }; - - createComponent(props); - - expect(findJobDuration().exists()).toBe(true); - expect(findJobFinishedTime().exists()).toBe(true); - }); - - it('displays only the duration of the job when the duration property is present', () => { - const props = { - duration: 7, - }; - - createComponent(props); - - expect(findJobDuration().exists()).toBe(true); - expect(findJobFinishedTime().exists()).toBe(false); - }); - - it('displays only the finished time of the job when the finshedAt property is present', () => { - const props = { - finishedAt: '2021-04-26T13:37:52Z', - }; - - createComponent(props); - - expect(findJobFinishedTime().exists()).toBe(true); - expect(findJobDuration().exists()).toBe(false); - }); - - it('displays icons for finished time and duration', () => { - const props = { - duration: 7, - finishedAt: '2021-04-26T13:37:52Z', - }; - - createComponent(props); - - expect(findFinishedTimeIcon().props('name')).toBe('calendar'); - expect(findDurationIcon().props('name')).toBe('timer'); - }); -}); diff --git a/spec/frontend/jobs/components/table/cells/job_cell_spec.js b/spec/frontend/jobs/components/table/cells/job_cell_spec.js deleted file mode 100644 index 73e37eed5f1..00000000000 --- a/spec/frontend/jobs/components/table/cells/job_cell_spec.js +++ /dev/null @@ -1,142 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import { extendedWrapper } from 'helpers/vue_test_utils_helper'; -import { getIdFromGraphQLId } from '~/graphql_shared/utils'; -import JobCell from '~/jobs/components/table/cells/job_cell.vue'; -import { mockJobsNodes, mockJobsNodesAsGuest } from '../../../mock_data'; - -describe('Job Cell', () => { - let wrapper; - - const findMockJob = (jobName, nodes = mockJobsNodes) => { - const job = nodes.find(({ name }) => name === jobName); - expect(job).toBeDefined(); // ensure job is present - return job; - }; - - const mockJob = findMockJob('build'); - const jobCreatedByTag = findMockJob('created_by_tag'); - const pendingJob = findMockJob('pending'); - const jobAsGuest = findMockJob('build', mockJobsNodesAsGuest); - - const findJobIdLink = () => wrapper.findByTestId('job-id-link'); - const findJobIdNoLink = () => wrapper.findByTestId('job-id-limited-access'); - const findJobRef = () => wrapper.findByTestId('job-ref'); - const findJobSha = () => wrapper.findByTestId('job-sha'); - const findLabelIcon = () => wrapper.findByTestId('label-icon'); - const findForkIcon = () => wrapper.findByTestId('fork-icon'); - const findStuckIcon = () => wrapper.findByTestId('stuck-icon'); - const findAllTagBadges = () => wrapper.findAllByTestId('job-tag-badge'); - - const findBadgeById = (id) => wrapper.findByTestId(id); - - const createComponent = (job = mockJob) => { - wrapper = extendedWrapper( - shallowMount(JobCell, { - propsData: { - job, - }, - }), - ); - }; - - describe('Job Id', () => { - it('displays the job id and links to the job', () => { - createComponent(); - - const expectedJobId = `#${getIdFromGraphQLId(mockJob.id)}`; - - expect(findJobIdLink().text()).toBe(expectedJobId); - expect(findJobIdLink().attributes('href')).toBe(mockJob.detailedStatus.detailsPath); - expect(findJobIdNoLink().exists()).toBe(false); - }); - - it('display the job id with no link', () => { - createComponent(jobAsGuest); - - const expectedJobId = `#${getIdFromGraphQLId(jobAsGuest.id)}`; - - expect(findJobIdNoLink().text()).toBe(expectedJobId); - expect(findJobIdNoLink().exists()).toBe(true); - expect(findJobIdLink().exists()).toBe(false); - }); - }); - - describe('Ref of the job', () => { - it('displays the ref name and links to the ref', () => { - createComponent(); - - expect(findJobRef().text()).toBe(mockJob.refName); - expect(findJobRef().attributes('href')).toBe(mockJob.refPath); - }); - - it('displays fork icon when job is not created by tag', () => { - createComponent(); - - expect(findForkIcon().exists()).toBe(true); - expect(findLabelIcon().exists()).toBe(false); - }); - - it('displays label icon when job is created by a tag', () => { - createComponent(jobCreatedByTag); - - expect(findLabelIcon().exists()).toBe(true); - expect(findForkIcon().exists()).toBe(false); - }); - }); - - describe('Commit of the job', () => { - beforeEach(() => { - createComponent(); - }); - - it('displays the sha and links to the commit', () => { - expect(findJobSha().text()).toBe(mockJob.shortSha); - expect(findJobSha().attributes('href')).toBe(mockJob.commitPath); - }); - }); - - describe('Job badges', () => { - it('displays tags of the job', () => { - const mockJobWithTags = { - tags: ['tag-1', 'tag-2', 'tag-3'], - }; - - createComponent(mockJobWithTags); - - expect(findAllTagBadges()).toHaveLength(mockJobWithTags.tags.length); - }); - - it.each` - testId | text - ${'manual-job-badge'} | ${'manual'} - ${'triggered-job-badge'} | ${'triggered'} - ${'fail-job-badge'} | ${'allowed to fail'} - ${'delayed-job-badge'} | ${'delayed'} - `('displays the static $text badge', ({ testId, text }) => { - createComponent({ - manualJob: true, - triggered: true, - allowFailure: true, - scheduledAt: '2021-03-09T14:58:50+00:00', - }); - - expect(findBadgeById(testId).exists()).toBe(true); - expect(findBadgeById(testId).text()).toBe(text); - }); - }); - - describe('Job icons', () => { - it('stuck icon is not shown if job is not stuck', () => { - createComponent(); - - expect(findStuckIcon().exists()).toBe(false); - }); - - it('stuck icon is shown if job is pending', () => { - createComponent(pendingJob); - - expect(findStuckIcon().exists()).toBe(true); - expect(findStuckIcon().attributes('name')).toBe('warning'); - }); - }); -}); diff --git a/spec/frontend/jobs/components/table/cells/pipeline_cell_spec.js b/spec/frontend/jobs/components/table/cells/pipeline_cell_spec.js deleted file mode 100644 index 3d424b20964..00000000000 --- a/spec/frontend/jobs/components/table/cells/pipeline_cell_spec.js +++ /dev/null @@ -1,78 +0,0 @@ -import { GlAvatar } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; -import { extendedWrapper } from 'helpers/vue_test_utils_helper'; -import { getIdFromGraphQLId } from '~/graphql_shared/utils'; -import PipelineCell from '~/jobs/components/table/cells/pipeline_cell.vue'; - -const mockJobWithoutUser = { - id: 'gid://gitlab/Ci::Build/2264', - pipeline: { - id: 'gid://gitlab/Ci::Pipeline/460', - path: '/root/ci-project/-/pipelines/460', - }, -}; - -const mockJobWithUser = { - id: 'gid://gitlab/Ci::Build/2264', - pipeline: { - id: 'gid://gitlab/Ci::Pipeline/460', - path: '/root/ci-project/-/pipelines/460', - user: { - avatarUrl: - 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', - webPath: '/root', - }, - }, -}; - -describe('Pipeline Cell', () => { - let wrapper; - - const findPipelineId = () => wrapper.findByTestId('pipeline-id'); - const findPipelineUserLink = () => wrapper.findByTestId('pipeline-user-link'); - const findUserAvatar = () => wrapper.findComponent(GlAvatar); - - const createComponent = (props = mockJobWithUser) => { - wrapper = extendedWrapper( - shallowMount(PipelineCell, { - propsData: { - job: props, - }, - }), - ); - }; - - describe('Pipeline Id', () => { - beforeEach(() => { - createComponent(); - }); - - it('displays the pipeline id and links to the pipeline', () => { - const expectedPipelineId = `#${getIdFromGraphQLId(mockJobWithUser.pipeline.id)}`; - - expect(findPipelineId().text()).toBe(expectedPipelineId); - expect(findPipelineId().attributes('href')).toBe(mockJobWithUser.pipeline.path); - }); - }); - - describe('Pipeline created by', () => { - const apiWrapperText = 'API'; - - it('shows and links to the pipeline user', () => { - createComponent(); - - expect(findPipelineUserLink().exists()).toBe(true); - expect(findPipelineUserLink().attributes('href')).toBe(mockJobWithUser.pipeline.user.webPath); - expect(findUserAvatar().attributes('src')).toBe(mockJobWithUser.pipeline.user.avatarUrl); - expect(wrapper.text()).not.toContain(apiWrapperText); - }); - - it('shows pipeline was created by the API', () => { - createComponent(mockJobWithoutUser); - - expect(findPipelineUserLink().exists()).toBe(false); - expect(findUserAvatar().exists()).toBe(false); - expect(wrapper.text()).toContain(apiWrapperText); - }); - }); -}); diff --git a/spec/frontend/jobs/components/table/graphql/cache_config_spec.js b/spec/frontend/jobs/components/table/graphql/cache_config_spec.js deleted file mode 100644 index e3b1ca1cce3..00000000000 --- a/spec/frontend/jobs/components/table/graphql/cache_config_spec.js +++ /dev/null @@ -1,106 +0,0 @@ -import cacheConfig from '~/jobs/components/table/graphql/cache_config'; -import { - CIJobConnectionExistingCache, - CIJobConnectionIncomingCache, - CIJobConnectionIncomingCacheRunningStatus, -} from '../../../mock_data'; - -const firstLoadArgs = { first: 3, statuses: 'PENDING' }; -const runningArgs = { first: 3, statuses: 'RUNNING' }; - -describe('jobs/components/table/graphql/cache_config', () => { - describe('when fetching data with the same statuses', () => { - it('should contain cache nodes and a status when merging caches on first load', () => { - const res = cacheConfig.typePolicies.CiJobConnection.merge({}, CIJobConnectionIncomingCache, { - args: firstLoadArgs, - }); - - expect(res.nodes).toHaveLength(CIJobConnectionIncomingCache.nodes.length); - expect(res.statuses).toBe('PENDING'); - }); - - it('should add to existing caches when merging caches after first load', () => { - const res = cacheConfig.typePolicies.CiJobConnection.merge( - CIJobConnectionExistingCache, - CIJobConnectionIncomingCache, - { - args: firstLoadArgs, - }, - ); - - expect(res.nodes).toHaveLength( - CIJobConnectionIncomingCache.nodes.length + CIJobConnectionExistingCache.nodes.length, - ); - }); - - 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, - }); - - expect(res.pageInfo).toEqual( - expect.objectContaining({ - __typename: 'PageInfo', - endCursor: 'eyJpZCI6IjIwNTEifQ', - hasNextPage: true, - hasPreviousPage: false, - startCursor: 'eyJpZCI6IjIxNzMifQ', - }), - ); - }); - }); - - describe('when fetching data with different statuses', () => { - it('should reset cache when a cache already exists', () => { - const res = cacheConfig.typePolicies.CiJobConnection.merge( - CIJobConnectionExistingCache, - CIJobConnectionIncomingCacheRunningStatus, - { - args: runningArgs, - }, - ); - - expect(res.nodes).not.toEqual(CIJobConnectionExistingCache.nodes); - expect(res.nodes).toHaveLength(CIJobConnectionIncomingCacheRunningStatus.nodes.length); - }); - }); - - describe('when incoming data has no nodes', () => { - it('should return existing cache', () => { - const res = cacheConfig.typePolicies.CiJobConnection.merge( - CIJobConnectionExistingCache, - { __typename: 'CiJobConnection', count: 500 }, - { - args: { statuses: 'SUCCESS' }, - }, - ); - - const expectedResponse = { - ...CIJobConnectionExistingCache, - statuses: 'SUCCESS', - }; - - expect(res).toEqual(expectedResponse); - }); - }); -}); diff --git a/spec/frontend/jobs/components/table/job_table_app_spec.js b/spec/frontend/jobs/components/table/job_table_app_spec.js deleted file mode 100644 index 032b83ca22b..00000000000 --- a/spec/frontend/jobs/components/table/job_table_app_spec.js +++ /dev/null @@ -1,338 +0,0 @@ -import { GlAlert, GlEmptyState, GlIntersectionObserver, GlLoadingIcon } from '@gitlab/ui'; -import { mount, shallowMount } from '@vue/test-utils'; -import Vue, { nextTick } 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 { TEST_HOST } from 'spec/test_constants'; -import { createAlert } from '~/alert'; -import getJobsQuery from '~/jobs/components/table/graphql/queries/get_jobs.query.graphql'; -import getJobsCountQuery from '~/jobs/components/table/graphql/queries/get_jobs_count.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 JobsFilteredSearch from '~/jobs/components/filtered_search/jobs_filtered_search.vue'; -import JobsSkeletonLoader from '~/pages/admin/jobs/components/jobs_skeleton_loader.vue'; -import * as urlUtils from '~/lib/utils/url_utility'; -import { - mockJobsResponsePaginated, - mockJobsResponseEmpty, - mockFailedSearchToken, - mockJobsCountResponse, -} from '../../mock_data'; - -const projectPath = 'gitlab-org/gitlab'; -Vue.use(VueApollo); - -jest.mock('~/alert'); - -describe('Job table app', () => { - let wrapper; - - const successHandler = jest.fn().mockResolvedValue(mockJobsResponsePaginated); - const failedHandler = jest.fn().mockRejectedValue(new Error('GraphQL error')); - const emptyHandler = jest.fn().mockResolvedValue(mockJobsResponseEmpty); - - const countSuccessHandler = jest.fn().mockResolvedValue(mockJobsCountResponse); - - const findSkeletonLoader = () => wrapper.findComponent(JobsSkeletonLoader); - 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'); - - const createMockApolloProvider = (handler, countHandler) => { - const requestHandlers = [ - [getJobsQuery, handler], - [getJobsCountQuery, countHandler], - ]; - - return createMockApollo(requestHandlers); - }; - - const createComponent = ({ - handler = successHandler, - countHandler = countSuccessHandler, - mountFn = shallowMount, - } = {}) => { - wrapper = mountFn(JobsTableApp, { - provide: { - fullPath: projectPath, - }, - apolloProvider: createMockApolloProvider(handler, countHandler), - }); - }; - - describe('loading state', () => { - it('should display skeleton loader when loading', () => { - createComponent(); - - 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', () => { - createComponent(); - - findTabs().vm.$emit('fetchJobsByStatus', null); - - expect(findSkeletonLoader().exists()).toBe(true); - expect(findLoadingSpinner().exists()).toBe(false); - }); - }); - - describe('loaded state', () => { - beforeEach(async () => { - createComponent(); - - await waitForPromises(); - }); - - 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 () => { - expect(successHandler).toHaveBeenCalledTimes(1); - - await findTabs().vm.$emit('fetchJobsByStatus'); - - expect(successHandler).toHaveBeenCalledTimes(2); - }); - - it('avoids refetch jobs query when scope has not changed', async () => { - expect(successHandler).toHaveBeenCalledTimes(1); - - await findTabs().vm.$emit('fetchJobsByStatus', null); - - expect(successHandler).toHaveBeenCalledTimes(1); - }); - - it('should refetch jobs count query when the amount jobs and count do not match', async () => { - expect(countSuccessHandler).toHaveBeenCalledTimes(1); - - // after applying filter a new count is fetched - findFilteredSearch().vm.$emit('filterJobsBySearch', [mockFailedSearchToken]); - - expect(countSuccessHandler).toHaveBeenCalledTimes(2); - - // tab is switched to `finished`, no count - await findTabs().vm.$emit('fetchJobsByStatus', ['FAILED', 'SUCCESS', 'CANCELED']); - - // tab is switched back to `all`, the old filter count has to be overwritten with new count - await findTabs().vm.$emit('fetchJobsByStatus', null); - - expect(countSuccessHandler).toHaveBeenCalledTimes(3); - }); - - describe('when infinite scrolling is triggered', () => { - it('does not display a skeleton loader', () => { - triggerInfiniteScroll(); - - expect(findSkeletonLoader().exists()).toBe(false); - }); - - it('handles infinite scrolling by calling fetch more', async () => { - triggerInfiniteScroll(); - - await nextTick(); - - const pageSize = 30; - - expect(findLoadingSpinner().exists()).toBe(true); - - await waitForPromises(); - - expect(findLoadingSpinner().exists()).toBe(false); - - expect(successHandler).toHaveBeenLastCalledWith({ - first: pageSize, - fullPath: projectPath, - after: mockJobsResponsePaginated.data.project.jobs.pageInfo.endCursor, - }); - }); - }); - }); - - describe('error state', () => { - it('should show an alert if there is an error fetching the jobs data', async () => { - createComponent({ handler: failedHandler }); - - await waitForPromises(); - - expect(findAlert().text()).toBe('There was an error fetching the jobs for your project.'); - expect(findTable().exists()).toBe(false); - }); - - it('should show an alert if there is an error fetching the jobs count data', async () => { - createComponent({ handler: successHandler, countHandler: failedHandler }); - - await waitForPromises(); - - expect(findAlert().text()).toBe( - 'There was an error fetching the number of jobs for your project.', - ); - }); - - it('jobs table should still load if count query fails', async () => { - createComponent({ handler: successHandler, countHandler: failedHandler }); - - await waitForPromises(); - - expect(findTable().exists()).toBe(true); - }); - - it('jobs count should be zero if count query fails', async () => { - createComponent({ handler: successHandler, countHandler: failedHandler }); - - await waitForPromises(); - - expect(findTabs().props('allJobsCount')).toBe(0); - }); - }); - - describe('empty state', () => { - it('should display empty state if there are no jobs and tab scope is null', async () => { - createComponent({ handler: emptyHandler, mountFn: mount }); - - await waitForPromises(); - - expect(findEmptyState().exists()).toBe(true); - expect(findTable().exists()).toBe(false); - }); - - it('should not display empty state if there are jobs and tab scope is not null', async () => { - createComponent({ handler: successHandler, mountFn: mount }); - - await waitForPromises(); - - expect(findEmptyState().exists()).toBe(false); - 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(); - - expect(successHandler).toHaveBeenCalledTimes(1); - - await findFilteredSearch().vm.$emit('filterJobsBySearch', [mockFailedSearchToken]); - - expect(successHandler).toHaveBeenCalledTimes(2); - }); - - it('refetches jobs count query when filtering', async () => { - createComponent(); - - expect(countSuccessHandler).toHaveBeenCalledTimes(1); - - await findFilteredSearch().vm.$emit('filterJobsBySearch', [mockFailedSearchToken]); - - expect(countSuccessHandler).toHaveBeenCalledTimes(2); - }); - - 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(); - - expect(successHandler).toHaveBeenCalledTimes(1); - expect(countSuccessHandler).toHaveBeenCalledTimes(1); - - await findFilteredSearch().vm.$emit('filterJobsBySearch', ['raw text']); - - expect(createAlert).toHaveBeenCalledWith(expectedWarning); - expect(successHandler).toHaveBeenCalledTimes(1); - expect(countSuccessHandler).toHaveBeenCalledTimes(1); - }); - - it('updates URL query string when filtering jobs by status', async () => { - createComponent(); - - jest.spyOn(urlUtils, 'updateHistory'); - - await findFilteredSearch().vm.$emit('filterJobsBySearch', [mockFailedSearchToken]); - - expect(urlUtils.updateHistory).toHaveBeenCalledWith({ - url: `${TEST_HOST}/?statuses=FAILED`, - }); - }); - - it('resets query param after clearing tokens', () => { - createComponent(); - - jest.spyOn(urlUtils, 'updateHistory'); - - findFilteredSearch().vm.$emit('filterJobsBySearch', [mockFailedSearchToken]); - - expect(successHandler).toHaveBeenCalledWith({ - first: 30, - fullPath: 'gitlab-org/gitlab', - statuses: 'FAILED', - }); - expect(countSuccessHandler).toHaveBeenCalledWith({ - fullPath: 'gitlab-org/gitlab', - statuses: 'FAILED', - }); - expect(urlUtils.updateHistory).toHaveBeenCalledWith({ - url: `${TEST_HOST}/?statuses=FAILED`, - }); - - findFilteredSearch().vm.$emit('filterJobsBySearch', []); - - expect(urlUtils.updateHistory).toHaveBeenCalledWith({ - url: `${TEST_HOST}/`, - }); - - expect(successHandler).toHaveBeenCalledWith({ - first: 30, - fullPath: 'gitlab-org/gitlab', - statuses: null, - }); - expect(countSuccessHandler).toHaveBeenCalledWith({ - fullPath: 'gitlab-org/gitlab', - statuses: null, - }); - }); - }); -}); diff --git a/spec/frontend/jobs/components/table/jobs_table_empty_state_spec.js b/spec/frontend/jobs/components/table/jobs_table_empty_state_spec.js deleted file mode 100644 index 05b066a9edc..00000000000 --- a/spec/frontend/jobs/components/table/jobs_table_empty_state_spec.js +++ /dev/null @@ -1,37 +0,0 @@ -import { GlEmptyState } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; -import JobsTableEmptyState from '~/jobs/components/table/jobs_table_empty_state.vue'; - -describe('Jobs table empty state', () => { - let wrapper; - - const pipelineEditorPath = '/root/project/-/ci/editor'; - const emptyStateSvgPath = 'assets/jobs-empty-state.svg'; - - const findEmptyState = () => wrapper.findComponent(GlEmptyState); - - const createComponent = () => { - wrapper = shallowMount(JobsTableEmptyState, { - provide: { - pipelineEditorPath, - emptyStateSvgPath, - }, - }); - }; - - beforeEach(() => { - createComponent(); - }); - - it('displays empty state', () => { - expect(findEmptyState().exists()).toBe(true); - }); - - it('links to the pipeline editor', () => { - expect(findEmptyState().props('primaryButtonLink')).toBe(pipelineEditorPath); - }); - - it('shows an empty state image', () => { - expect(findEmptyState().props('svgPath')).toBe(emptyStateSvgPath); - }); -}); diff --git a/spec/frontend/jobs/components/table/jobs_table_spec.js b/spec/frontend/jobs/components/table/jobs_table_spec.js deleted file mode 100644 index 654b6d1c130..00000000000 --- a/spec/frontend/jobs/components/table/jobs_table_spec.js +++ /dev/null @@ -1,98 +0,0 @@ -import { GlTable } from '@gitlab/ui'; -import { mount } from '@vue/test-utils'; -import { extendedWrapper } from 'helpers/vue_test_utils_helper'; -import JobsTable from '~/jobs/components/table/jobs_table.vue'; -import CiBadgeLink from '~/vue_shared/components/ci_badge_link.vue'; -import { DEFAULT_FIELDS_ADMIN } from '~/pages/admin/jobs/components/constants'; -import ProjectCell from '~/pages/admin/jobs/components/table/cell/project_cell.vue'; -import RunnerCell from '~/pages/admin/jobs/components/table/cells/runner_cell.vue'; -import { mockJobsNodes, mockAllJobsNodes } from '../../mock_data'; - -describe('Jobs Table', () => { - let wrapper; - - const findTable = () => wrapper.findComponent(GlTable); - const findCiBadgeLink = () => wrapper.findComponent(CiBadgeLink); - const findTableRows = () => wrapper.findAllByTestId('jobs-table-row'); - const findJobStage = () => wrapper.findByTestId('job-stage-name'); - const findJobName = () => wrapper.findByTestId('job-name'); - const findJobProject = () => wrapper.findComponent(ProjectCell); - const findJobRunner = () => wrapper.findComponent(RunnerCell); - const findAllCoverageJobs = () => wrapper.findAllByTestId('job-coverage'); - - const createComponent = (props = {}) => { - wrapper = extendedWrapper( - mount(JobsTable, { - propsData: { - ...props, - }, - }), - ); - }; - - describe('jobs table', () => { - beforeEach(() => { - createComponent({ jobs: mockJobsNodes }); - }); - - it('displays the jobs table', () => { - expect(findTable().exists()).toBe(true); - }); - - it('displays correct number of job rows', () => { - expect(findTableRows()).toHaveLength(mockJobsNodes.length); - }); - - it('displays job status', () => { - expect(findCiBadgeLink().exists()).toBe(true); - }); - - it('displays the job stage and name', () => { - const [firstJob] = mockJobsNodes; - - expect(findJobStage().text()).toBe(firstJob.stage.name); - expect(findJobName().text()).toBe(firstJob.name); - }); - - it('displays the coverage for only jobs that have coverage', () => { - const jobsThatHaveCoverage = mockJobsNodes.filter((job) => job.coverage !== null); - - jobsThatHaveCoverage.forEach((job, index) => { - expect(findAllCoverageJobs().at(index).text()).toBe(`${job.coverage}%`); - }); - expect(findAllCoverageJobs()).toHaveLength(jobsThatHaveCoverage.length); - }); - }); - - describe('regular user', () => { - beforeEach(() => { - createComponent({ jobs: mockJobsNodes }); - }); - - it('hides the job runner', () => { - expect(findJobRunner().exists()).toBe(false); - }); - - it('hides the job project link', () => { - expect(findJobProject().exists()).toBe(false); - }); - }); - - describe('admin mode', () => { - beforeEach(() => { - createComponent({ jobs: mockAllJobsNodes, tableFields: DEFAULT_FIELDS_ADMIN, admin: true }); - }); - - it('displays the runner cell', () => { - expect(findJobRunner().exists()).toBe(true); - }); - - it('displays the project cell', () => { - expect(findJobProject().exists()).toBe(true); - }); - - it('displays correct number of job rows', () => { - expect(findTableRows()).toHaveLength(mockAllJobsNodes.length); - }); - }); -}); diff --git a/spec/frontend/jobs/components/table/jobs_table_tabs_spec.js b/spec/frontend/jobs/components/table/jobs_table_tabs_spec.js deleted file mode 100644 index d20a732508a..00000000000 --- a/spec/frontend/jobs/components/table/jobs_table_tabs_spec.js +++ /dev/null @@ -1,81 +0,0 @@ -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'; -import JobsTableTabs from '~/jobs/components/table/jobs_table_tabs.vue'; -import CancelJobs from '~/pages/admin/jobs/components/cancel_jobs.vue'; - -describe('Jobs Table Tabs', () => { - let wrapper; - - const defaultProps = { - allJobsCount: 286, - loading: false, - }; - - const adminProps = { - ...defaultProps, - showCancelAllJobsButton: true, - }; - - const statuses = { - success: 'SUCCESS', - failed: 'FAILED', - canceled: 'CANCELED', - }; - - const findAllTab = () => wrapper.findByTestId('jobs-all-tab'); - const findFinishedTab = () => wrapper.findByTestId('jobs-finished-tab'); - const findCancelJobsButton = () => wrapper.findAllComponents(CancelJobs); - - const triggerTabChange = (index) => wrapper.findAllComponents(GlTab).at(index).vm.$emit('click'); - - const createComponent = (props = defaultProps) => { - wrapper = extendedWrapper( - mount(JobsTableTabs, { - provide: { - jobStatuses: { - ...statuses, - }, - }, - propsData: { - ...props, - }, - }), - ); - }; - - beforeEach(() => { - createComponent(); - }); - - 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` - 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]] }); - }); - - it('does not displays cancel all jobs button', () => { - expect(findCancelJobsButton().exists()).toBe(false); - }); - - describe('admin mode', () => { - it('displays cancel all jobs button', () => { - createComponent(adminProps); - - expect(findCancelJobsButton().exists()).toBe(true); - }); - }); -}); diff --git a/spec/frontend/jobs/mixins/delayed_job_mixin_spec.js b/spec/frontend/jobs/mixins/delayed_job_mixin_spec.js deleted file mode 100644 index 098a63719fe..00000000000 --- a/spec/frontend/jobs/mixins/delayed_job_mixin_spec.js +++ /dev/null @@ -1,119 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import { nextTick } from 'vue'; -import delayedJobFixture from 'test_fixtures/jobs/delayed.json'; -import delayedJobMixin from '~/jobs/mixins/delayed_job_mixin'; - -describe('DelayedJobMixin', () => { - let wrapper; - const dummyComponent = { - props: { - job: { - type: Object, - required: true, - }, - }, - mixins: [delayedJobMixin], - template: '<div>{{remainingTime}}</div>', - }; - - describe('if job is empty object', () => { - beforeEach(() => { - wrapper = shallowMount(dummyComponent, { - propsData: { - job: {}, - }, - }); - }); - - it('sets remaining time to 00:00:00', () => { - expect(wrapper.text()).toBe('00:00:00'); - }); - - it('does not update remaining time after mounting', async () => { - await nextTick(); - - expect(wrapper.text()).toBe('00:00:00'); - }); - }); - - describe('in REST component', () => { - describe('if job is delayed job', () => { - let remainingTimeInMilliseconds = 42000; - - beforeEach(async () => { - jest - .spyOn(Date, 'now') - .mockImplementation( - () => new Date(delayedJobFixture.scheduled_at).getTime() - remainingTimeInMilliseconds, - ); - - wrapper = shallowMount(dummyComponent, { - propsData: { - job: delayedJobFixture, - }, - }); - - await nextTick(); - }); - - it('sets remaining time', () => { - expect(wrapper.text()).toBe('00:00:42'); - }); - - it('updates remaining time', async () => { - remainingTimeInMilliseconds = 41000; - jest.advanceTimersByTime(1000); - - await nextTick(); - expect(wrapper.text()).toBe('00:00:41'); - }); - }); - }); - - describe('in GraphQL component', () => { - const mockGraphQlJob = { - name: 'build_b', - scheduledAt: new Date(delayedJobFixture.scheduled_at), - status: { - icon: 'status_success', - tooltip: 'passed', - hasDetails: true, - detailsPath: '/root/abcd-dag/-/jobs/1515', - group: 'success', - action: null, - }, - }; - - describe('if job is delayed job', () => { - let remainingTimeInMilliseconds = 42000; - - beforeEach(async () => { - jest - .spyOn(Date, 'now') - .mockImplementation( - () => mockGraphQlJob.scheduledAt.getTime() - remainingTimeInMilliseconds, - ); - - wrapper = shallowMount(dummyComponent, { - propsData: { - job: mockGraphQlJob, - }, - }); - - await nextTick(); - }); - - it('sets remaining time', () => { - expect(wrapper.text()).toBe('00:00:42'); - }); - - it('updates remaining time', async () => { - remainingTimeInMilliseconds = 41000; - jest.advanceTimersByTime(1000); - - await nextTick(); - expect(wrapper.text()).toBe('00:00:41'); - }); - }); - }); -}); diff --git a/spec/frontend/jobs/mock_data.js b/spec/frontend/jobs/mock_data.js deleted file mode 100644 index 253e669e889..00000000000 --- a/spec/frontend/jobs/mock_data.js +++ /dev/null @@ -1,1628 +0,0 @@ -import mockJobsCount from 'test_fixtures/graphql/jobs/get_jobs_count.query.graphql.json'; -import mockAllJobsCount from 'test_fixtures/graphql/jobs/get_all_jobs_count.query.graphql.json'; -import mockJobsEmpty from 'test_fixtures/graphql/jobs/get_jobs.query.graphql.empty.json'; -import mockAllJobsEmpty from 'test_fixtures/graphql/jobs/get_all_jobs.query.graphql.empty.json'; -import mockJobsPaginated from 'test_fixtures/graphql/jobs/get_jobs.query.graphql.paginated.json'; -import mockAllJobsPaginated from 'test_fixtures/graphql/jobs/get_all_jobs.query.graphql.paginated.json'; -import mockJobs from 'test_fixtures/graphql/jobs/get_jobs.query.graphql.json'; -import mockAllJobs from 'test_fixtures/graphql/jobs/get_all_jobs.query.graphql.json'; -import mockJobsAsGuest from 'test_fixtures/graphql/jobs/get_jobs.query.graphql.as_guest.json'; -import mockCancelableJobsCount from 'test_fixtures/graphql/jobs/get_cancelable_jobs_count.query.graphql.json'; -import { TEST_HOST } from 'spec/test_constants'; -import { TOKEN_TYPE_STATUS } from '~/vue_shared/components/filtered_search_bar/constants'; - -const threeWeeksAgo = new Date(); -threeWeeksAgo.setDate(threeWeeksAgo.getDate() - 21); - -// Fixtures generated at spec/frontend/fixtures/jobs.rb -export const mockJobsResponsePaginated = mockJobsPaginated; -export const mockAllJobsResponsePaginated = mockAllJobsPaginated; -export const mockJobsResponseEmpty = mockJobsEmpty; -export const mockAllJobsResponseEmpty = mockAllJobsEmpty; -export const mockJobsNodes = mockJobs.data.project.jobs.nodes; -export const mockAllJobsNodes = mockAllJobs.data.jobs.nodes; -export const mockJobsNodesAsGuest = mockJobsAsGuest.data.project.jobs.nodes; -export const mockJobsCountResponse = mockJobsCount; -export const mockAllJobsCountResponse = mockAllJobsCount; -export const mockCancelableJobsCountResponse = mockCancelableJobsCount; - -export const stages = [ - { - name: 'build', - title: 'build: running', - groups: [ - { - name: 'build:linux', - size: 1, - status: { - icon: 'status_pending', - text: 'pending', - label: 'pending', - group: 'pending', - tooltip: 'pending', - has_details: true, - details_path: '/gitlab-org/gitlab-shell/-/jobs/1180', - illustration: { - image: 'illustrations/pending_job_empty.svg', - size: 'svg-430', - title: 'This job has not started yet', - content: 'This job is in pending state and is waiting to be picked by a runner', - }, - favicon: - '/assets/ci_favicons/favicon_status_pending-5bdf338420e5221ca24353b6bff1c9367189588750632e9a871b7af09ff6a2ae.png', - action: { - icon: 'cancel', - title: 'Cancel', - path: '/gitlab-org/gitlab-shell/-/jobs/1180/cancel', - method: 'post', - }, - }, - jobs: [ - { - id: 1180, - name: 'build:linux', - started: false, - build_path: '/gitlab-org/gitlab-shell/-/jobs/1180', - cancel_path: '/gitlab-org/gitlab-shell/-/jobs/1180/cancel', - playable: false, - created_at: '2018-09-28T11:09:57.229Z', - updated_at: '2018-09-28T11:09:57.503Z', - status: { - icon: 'status_pending', - text: 'pending', - label: 'pending', - group: 'pending', - tooltip: 'pending', - has_details: true, - details_path: '/gitlab-org/gitlab-shell/-/jobs/1180', - illustration: { - image: 'illustrations/pending_job_empty.svg', - size: 'svg-430', - title: 'This job has not started yet', - content: 'This job is in pending state and is waiting to be picked by a runner', - }, - favicon: - '/assets/ci_favicons/favicon_status_pending-5bdf338420e5221ca24353b6bff1c9367189588750632e9a871b7af09ff6a2ae.png', - action: { - icon: 'cancel', - title: 'Cancel', - path: '/gitlab-org/gitlab-shell/-/jobs/1180/cancel', - method: 'post', - }, - }, - }, - ], - }, - { - name: 'build:osx', - size: 1, - status: { - icon: 'status_success', - text: 'passed', - label: 'passed', - group: 'success', - tooltip: 'passed', - has_details: true, - details_path: '/gitlab-org/gitlab-shell/-/jobs/444', - illustration: { - image: 'illustrations/skipped-job_empty.svg', - size: 'svg-430', - title: 'This job does not have a trace.', - }, - favicon: - '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', - action: { - icon: 'retry', - title: 'Retry', - path: '/gitlab-org/gitlab-shell/-/jobs/444/retry', - method: 'post', - }, - }, - jobs: [ - { - id: 444, - name: 'build:osx', - started: '2018-05-18T05:32:20.655Z', - build_path: '/gitlab-org/gitlab-shell/-/jobs/444', - retry_path: '/gitlab-org/gitlab-shell/-/jobs/444/retry', - playable: false, - created_at: '2018-05-18T15:32:54.364Z', - updated_at: '2018-05-18T15:32:54.364Z', - status: { - icon: 'status_success', - text: 'passed', - label: 'passed', - group: 'success', - tooltip: 'passed', - has_details: true, - details_path: '/gitlab-org/gitlab-shell/-/jobs/444', - illustration: { - image: 'illustrations/skipped-job_empty.svg', - size: 'svg-430', - title: 'This job does not have a trace.', - }, - favicon: - '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', - action: { - icon: 'retry', - title: 'Retry', - path: '/gitlab-org/gitlab-shell/-/jobs/444/retry', - method: 'post', - }, - }, - }, - ], - }, - ], - status: { - icon: 'status_running', - text: 'running', - label: 'running', - group: 'running', - tooltip: 'running', - has_details: true, - details_path: '/gitlab-org/gitlab-shell/pipelines/27#build', - illustration: null, - favicon: - '/assets/ci_favicons/favicon_status_running-9c635b2419a8e1ec991c993061b89cc5aefc0743bb238ecd0c381e7741a70e8c.png', - }, - path: '/gitlab-org/gitlab-shell/pipelines/27#build', - dropdown_path: '/gitlab-org/gitlab-shell/pipelines/27/stage.json?stage=build', - }, - { - name: 'test', - title: 'test: passed with warnings', - groups: [ - { - name: 'jenkins', - size: 1, - status: { - icon: 'status_success', - text: 'passed', - label: null, - group: 'success', - tooltip: null, - has_details: false, - details_path: null, - illustration: null, - favicon: - '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', - }, - jobs: [ - { - id: 459, - name: 'jenkins', - started: '2018-05-18T09:32:20.658Z', - build_path: '/gitlab-org/gitlab-shell/-/jobs/459', - playable: false, - created_at: '2018-05-18T15:32:55.330Z', - updated_at: '2018-05-18T15:32:55.330Z', - status: { - icon: 'status_success', - text: 'passed', - label: null, - group: 'success', - tooltip: null, - has_details: false, - details_path: null, - illustration: null, - favicon: - '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', - }, - }, - ], - }, - { - name: 'rspec:linux', - size: 3, - status: { - icon: 'status_success', - text: 'passed', - label: 'passed', - group: 'success', - tooltip: 'passed', - has_details: false, - details_path: null, - illustration: null, - favicon: - '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', - }, - jobs: [ - { - id: 445, - name: 'rspec:linux 0 3', - started: '2018-05-18T07:32:20.655Z', - build_path: '/gitlab-org/gitlab-shell/-/jobs/445', - retry_path: '/gitlab-org/gitlab-shell/-/jobs/445/retry', - playable: false, - created_at: '2018-05-18T15:32:54.425Z', - updated_at: '2018-05-18T15:32:54.425Z', - status: { - icon: 'status_success', - text: 'passed', - label: 'passed', - group: 'success', - tooltip: 'passed', - has_details: true, - details_path: '/gitlab-org/gitlab-shell/-/jobs/445', - illustration: { - image: 'illustrations/skipped-job_empty.svg', - size: 'svg-430', - title: 'This job does not have a trace.', - }, - favicon: - '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', - action: { - icon: 'retry', - title: 'Retry', - path: '/gitlab-org/gitlab-shell/-/jobs/445/retry', - method: 'post', - }, - }, - }, - { - id: 446, - name: 'rspec:linux 1 3', - started: '2018-05-18T07:32:20.655Z', - build_path: '/gitlab-org/gitlab-shell/-/jobs/446', - retry_path: '/gitlab-org/gitlab-shell/-/jobs/446/retry', - playable: false, - created_at: '2018-05-18T15:32:54.506Z', - updated_at: '2018-05-18T15:32:54.506Z', - status: { - icon: 'status_success', - text: 'passed', - label: 'passed', - group: 'success', - tooltip: 'passed', - has_details: true, - details_path: '/gitlab-org/gitlab-shell/-/jobs/446', - illustration: { - image: 'illustrations/skipped-job_empty.svg', - size: 'svg-430', - title: 'This job does not have a trace.', - }, - favicon: - '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', - action: { - icon: 'retry', - title: 'Retry', - path: '/gitlab-org/gitlab-shell/-/jobs/446/retry', - method: 'post', - }, - }, - }, - { - id: 447, - name: 'rspec:linux 2 3', - started: '2018-05-18T07:32:20.656Z', - build_path: '/gitlab-org/gitlab-shell/-/jobs/447', - retry_path: '/gitlab-org/gitlab-shell/-/jobs/447/retry', - playable: false, - created_at: '2018-05-18T15:32:54.572Z', - updated_at: '2018-05-18T15:32:54.572Z', - status: { - icon: 'status_success', - text: 'passed', - label: 'passed', - group: 'success', - tooltip: 'passed', - has_details: true, - details_path: '/gitlab-org/gitlab-shell/-/jobs/447', - illustration: { - image: 'illustrations/skipped-job_empty.svg', - size: 'svg-430', - title: 'This job does not have a trace.', - }, - favicon: - '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', - action: { - icon: 'retry', - title: 'Retry', - path: '/gitlab-org/gitlab-shell/-/jobs/447/retry', - method: 'post', - }, - }, - }, - ], - }, - { - name: 'rspec:osx', - size: 1, - status: { - icon: 'status_success', - text: 'passed', - label: 'passed', - group: 'success', - tooltip: 'passed', - has_details: true, - details_path: '/gitlab-org/gitlab-shell/-/jobs/452', - illustration: { - image: 'illustrations/skipped-job_empty.svg', - size: 'svg-430', - title: 'This job does not have a trace.', - }, - favicon: - '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', - action: { - icon: 'retry', - title: 'Retry', - path: '/gitlab-org/gitlab-shell/-/jobs/452/retry', - method: 'post', - }, - }, - jobs: [ - { - id: 452, - name: 'rspec:osx', - started: '2018-05-18T07:32:20.657Z', - build_path: '/gitlab-org/gitlab-shell/-/jobs/452', - retry_path: '/gitlab-org/gitlab-shell/-/jobs/452/retry', - playable: false, - created_at: '2018-05-18T15:32:54.920Z', - updated_at: '2018-05-18T15:32:54.920Z', - status: { - icon: 'status_success', - text: 'passed', - label: 'passed', - group: 'success', - tooltip: 'passed', - has_details: true, - details_path: '/gitlab-org/gitlab-shell/-/jobs/452', - illustration: { - image: 'illustrations/skipped-job_empty.svg', - size: 'svg-430', - title: 'This job does not have a trace.', - }, - favicon: - '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', - action: { - icon: 'retry', - title: 'Retry', - path: '/gitlab-org/gitlab-shell/-/jobs/452/retry', - method: 'post', - }, - }, - }, - ], - }, - { - name: 'rspec:windows', - size: 3, - status: { - icon: 'status_success', - text: 'passed', - label: 'passed', - group: 'success', - tooltip: 'passed', - has_details: false, - details_path: null, - illustration: null, - favicon: - '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', - }, - jobs: [ - { - id: 448, - name: 'rspec:windows 0 3', - started: '2018-05-18T07:32:20.656Z', - build_path: '/gitlab-org/gitlab-shell/-/jobs/448', - retry_path: '/gitlab-org/gitlab-shell/-/jobs/448/retry', - playable: false, - created_at: '2018-05-18T15:32:54.639Z', - updated_at: '2018-05-18T15:32:54.639Z', - status: { - icon: 'status_success', - text: 'passed', - label: 'passed', - group: 'success', - tooltip: 'passed', - has_details: true, - details_path: '/gitlab-org/gitlab-shell/-/jobs/448', - illustration: { - image: 'illustrations/skipped-job_empty.svg', - size: 'svg-430', - title: 'This job does not have a trace.', - }, - favicon: - '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', - action: { - icon: 'retry', - title: 'Retry', - path: '/gitlab-org/gitlab-shell/-/jobs/448/retry', - method: 'post', - }, - }, - }, - { - id: 449, - name: 'rspec:windows 1 3', - started: '2018-05-18T07:32:20.656Z', - build_path: '/gitlab-org/gitlab-shell/-/jobs/449', - retry_path: '/gitlab-org/gitlab-shell/-/jobs/449/retry', - playable: false, - created_at: '2018-05-18T15:32:54.703Z', - updated_at: '2018-05-18T15:32:54.703Z', - status: { - icon: 'status_success', - text: 'passed', - label: 'passed', - group: 'success', - tooltip: 'passed', - has_details: true, - details_path: '/gitlab-org/gitlab-shell/-/jobs/449', - illustration: { - image: 'illustrations/skipped-job_empty.svg', - size: 'svg-430', - title: 'This job does not have a trace.', - }, - favicon: - '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', - action: { - icon: 'retry', - title: 'Retry', - path: '/gitlab-org/gitlab-shell/-/jobs/449/retry', - method: 'post', - }, - }, - }, - { - id: 451, - name: 'rspec:windows 2 3', - started: '2018-05-18T07:32:20.657Z', - build_path: '/gitlab-org/gitlab-shell/-/jobs/451', - retry_path: '/gitlab-org/gitlab-shell/-/jobs/451/retry', - playable: false, - created_at: '2018-05-18T15:32:54.853Z', - updated_at: '2018-05-18T15:32:54.853Z', - status: { - icon: 'status_success', - text: 'passed', - label: 'passed', - group: 'success', - tooltip: 'passed', - has_details: true, - details_path: '/gitlab-org/gitlab-shell/-/jobs/451', - illustration: { - image: 'illustrations/skipped-job_empty.svg', - size: 'svg-430', - title: 'This job does not have a trace.', - }, - favicon: - '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', - action: { - icon: 'retry', - title: 'Retry', - path: '/gitlab-org/gitlab-shell/-/jobs/451/retry', - method: 'post', - }, - }, - }, - ], - }, - { - name: 'spinach:linux', - size: 1, - status: { - icon: 'status_success', - text: 'passed', - label: 'passed', - group: 'success', - tooltip: 'passed', - has_details: true, - details_path: '/gitlab-org/gitlab-shell/-/jobs/453', - illustration: { - image: 'illustrations/skipped-job_empty.svg', - size: 'svg-430', - title: 'This job does not have a trace.', - }, - favicon: - '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', - action: { - icon: 'retry', - title: 'Retry', - path: '/gitlab-org/gitlab-shell/-/jobs/453/retry', - method: 'post', - }, - }, - jobs: [ - { - id: 453, - name: 'spinach:linux', - started: '2018-05-18T07:32:20.657Z', - build_path: '/gitlab-org/gitlab-shell/-/jobs/453', - retry_path: '/gitlab-org/gitlab-shell/-/jobs/453/retry', - playable: false, - created_at: '2018-05-18T15:32:54.993Z', - updated_at: '2018-05-18T15:32:54.993Z', - status: { - icon: 'status_success', - text: 'passed', - label: 'passed', - group: 'success', - tooltip: 'passed', - has_details: true, - details_path: '/gitlab-org/gitlab-shell/-/jobs/453', - illustration: { - image: 'illustrations/skipped-job_empty.svg', - size: 'svg-430', - title: 'This job does not have a trace.', - }, - favicon: - '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', - action: { - icon: 'retry', - title: 'Retry', - path: '/gitlab-org/gitlab-shell/-/jobs/453/retry', - method: 'post', - }, - }, - }, - ], - }, - { - name: 'spinach:osx', - size: 1, - status: { - icon: 'status_warning', - text: 'failed', - label: 'failed (allowed to fail)', - group: 'failed-with-warnings', - tooltip: 'failed - (unknown failure) (allowed to fail)', - has_details: true, - details_path: '/gitlab-org/gitlab-shell/-/jobs/454', - illustration: { - image: 'illustrations/skipped-job_empty.svg', - size: 'svg-430', - title: 'This job does not have a trace.', - }, - favicon: - '/assets/ci_favicons/favicon_status_failed-41304d7f7e3828808b0c26771f0309e55296819a9beea3ea9fbf6689d9857c12.png', - action: { - icon: 'retry', - title: 'Retry', - path: '/gitlab-org/gitlab-shell/-/jobs/454/retry', - method: 'post', - }, - }, - jobs: [ - { - id: 454, - name: 'spinach:osx', - started: '2018-05-18T07:32:20.657Z', - build_path: '/gitlab-org/gitlab-shell/-/jobs/454', - retry_path: '/gitlab-org/gitlab-shell/-/jobs/454/retry', - playable: false, - created_at: '2018-05-18T15:32:55.053Z', - updated_at: '2018-05-18T15:32:55.053Z', - status: { - icon: 'status_warning', - text: 'failed', - label: 'failed (allowed to fail)', - group: 'failed-with-warnings', - tooltip: 'failed - (unknown failure) (allowed to fail)', - has_details: true, - details_path: '/gitlab-org/gitlab-shell/-/jobs/454', - illustration: { - image: 'illustrations/skipped-job_empty.svg', - size: 'svg-430', - title: 'This job does not have a trace.', - }, - favicon: - '/assets/ci_favicons/favicon_status_failed-41304d7f7e3828808b0c26771f0309e55296819a9beea3ea9fbf6689d9857c12.png', - action: { - icon: 'retry', - title: 'Retry', - path: '/gitlab-org/gitlab-shell/-/jobs/454/retry', - method: 'post', - }, - }, - callout_message: 'There is an unknown failure, please try again', - recoverable: true, - }, - ], - }, - ], - status: { - icon: 'status_warning', - text: 'passed', - label: 'passed with warnings', - group: 'success-with-warnings', - tooltip: 'passed', - has_details: true, - details_path: '/gitlab-org/gitlab-shell/pipelines/27#test', - illustration: null, - favicon: - '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', - }, - path: '/gitlab-org/gitlab-shell/pipelines/27#test', - dropdown_path: '/gitlab-org/gitlab-shell/pipelines/27/stage.json?stage=test', - }, - { - name: 'deploy', - title: 'deploy: running', - groups: [ - { - name: 'production', - size: 1, - status: { - icon: 'status_created', - text: 'created', - label: 'created', - group: 'created', - tooltip: 'created', - has_details: true, - details_path: '/gitlab-org/gitlab-shell/-/jobs/457', - illustration: { - image: 'illustrations/job_not_triggered.svg', - size: 'svg-306', - title: 'This job has not been triggered yet', - content: - 'This job depends on upstream jobs that need to succeed in order for this job to be triggered', - }, - favicon: - '/assets/ci_favicons/favicon_status_created-4b975aa976d24e5a3ea7cd9a5713e6ce2cd9afd08b910415e96675de35f64955.png', - action: { - icon: 'cancel', - title: 'Cancel', - path: '/gitlab-org/gitlab-shell/-/jobs/457/cancel', - method: 'post', - }, - }, - jobs: [ - { - id: 457, - name: 'production', - started: false, - build_path: '/gitlab-org/gitlab-shell/-/jobs/457', - cancel_path: '/gitlab-org/gitlab-shell/-/jobs/457/cancel', - playable: false, - created_at: '2018-05-18T15:32:55.259Z', - updated_at: '2018-09-28T11:09:57.454Z', - status: { - icon: 'status_created', - text: 'created', - label: 'created', - group: 'created', - tooltip: 'created', - has_details: true, - details_path: '/gitlab-org/gitlab-shell/-/jobs/457', - illustration: { - image: 'illustrations/job_not_triggered.svg', - size: 'svg-306', - title: 'This job has not been triggered yet', - content: - 'This job depends on upstream jobs that need to succeed in order for this job to be triggered', - }, - favicon: - '/assets/ci_favicons/favicon_status_created-4b975aa976d24e5a3ea7cd9a5713e6ce2cd9afd08b910415e96675de35f64955.png', - action: { - icon: 'cancel', - title: 'Cancel', - path: '/gitlab-org/gitlab-shell/-/jobs/457/cancel', - method: 'post', - }, - }, - }, - ], - }, - { - name: 'staging', - size: 1, - status: { - icon: 'status_success', - text: 'passed', - label: 'passed', - group: 'success', - tooltip: 'passed', - has_details: true, - details_path: '/gitlab-org/gitlab-shell/-/jobs/455', - illustration: { - image: 'illustrations/skipped-job_empty.svg', - size: 'svg-430', - title: 'This job does not have a trace.', - }, - favicon: - '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', - action: { - icon: 'retry', - title: 'Retry', - path: '/gitlab-org/gitlab-shell/-/jobs/455/retry', - method: 'post', - }, - }, - jobs: [ - { - id: 455, - name: 'staging', - started: '2018-05-18T09:32:20.658Z', - build_path: '/gitlab-org/gitlab-shell/-/jobs/455', - retry_path: '/gitlab-org/gitlab-shell/-/jobs/455/retry', - playable: false, - created_at: '2018-05-18T15:32:55.119Z', - updated_at: '2018-05-18T15:32:55.119Z', - status: { - icon: 'status_success', - text: 'passed', - label: 'passed', - group: 'success', - tooltip: 'passed', - has_details: true, - details_path: '/gitlab-org/gitlab-shell/-/jobs/455', - illustration: { - image: 'illustrations/skipped-job_empty.svg', - size: 'svg-430', - title: 'This job does not have a trace.', - }, - favicon: - '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', - action: { - icon: 'retry', - title: 'Retry', - path: '/gitlab-org/gitlab-shell/-/jobs/455/retry', - method: 'post', - }, - }, - }, - ], - }, - { - name: 'stop staging', - size: 1, - status: { - icon: 'status_created', - text: 'created', - label: 'created', - group: 'created', - tooltip: 'created', - has_details: true, - details_path: '/gitlab-org/gitlab-shell/-/jobs/456', - illustration: { - image: 'illustrations/job_not_triggered.svg', - size: 'svg-306', - title: 'This job has not been triggered yet', - content: - 'This job depends on upstream jobs that need to succeed in order for this job to be triggered', - }, - favicon: - '/assets/ci_favicons/favicon_status_created-4b975aa976d24e5a3ea7cd9a5713e6ce2cd9afd08b910415e96675de35f64955.png', - action: { - icon: 'cancel', - title: 'Cancel', - path: '/gitlab-org/gitlab-shell/-/jobs/456/cancel', - method: 'post', - }, - }, - jobs: [ - { - id: 456, - name: 'stop staging', - started: false, - build_path: '/gitlab-org/gitlab-shell/-/jobs/456', - cancel_path: '/gitlab-org/gitlab-shell/-/jobs/456/cancel', - playable: false, - created_at: '2018-05-18T15:32:55.205Z', - updated_at: '2018-09-28T11:09:57.396Z', - status: { - icon: 'status_created', - text: 'created', - label: 'created', - group: 'created', - tooltip: 'created', - has_details: true, - details_path: '/gitlab-org/gitlab-shell/-/jobs/456', - illustration: { - image: 'illustrations/job_not_triggered.svg', - size: 'svg-306', - title: 'This job has not been triggered yet', - content: - 'This job depends on upstream jobs that need to succeed in order for this job to be triggered', - }, - favicon: - '/assets/ci_favicons/favicon_status_created-4b975aa976d24e5a3ea7cd9a5713e6ce2cd9afd08b910415e96675de35f64955.png', - action: { - icon: 'cancel', - title: 'Cancel', - path: '/gitlab-org/gitlab-shell/-/jobs/456/cancel', - method: 'post', - }, - }, - }, - ], - }, - ], - status: { - icon: 'status_running', - text: 'running', - label: 'running', - group: 'running', - tooltip: 'running', - has_details: true, - details_path: '/gitlab-org/gitlab-shell/pipelines/27#deploy', - illustration: null, - favicon: - '/assets/ci_favicons/favicon_status_running-9c635b2419a8e1ec991c993061b89cc5aefc0743bb238ecd0c381e7741a70e8c.png', - }, - path: '/gitlab-org/gitlab-shell/pipelines/27#deploy', - dropdown_path: '/gitlab-org/gitlab-shell/pipelines/27/stage.json?stage=deploy', - }, - { - name: 'notify', - title: 'notify: manual action', - groups: [ - { - name: 'slack', - size: 1, - status: { - icon: 'status_manual', - text: 'manual', - label: 'manual play action', - group: 'manual', - tooltip: 'manual action', - has_details: true, - details_path: '/gitlab-org/gitlab-shell/-/jobs/458', - illustration: { - image: 'illustrations/manual_action.svg', - size: 'svg-394', - title: 'This job requires a manual action', - content: - 'This job depends on a user to trigger its process. Often they are used to deploy code to production environments', - }, - favicon: - '/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png', - action: { - icon: 'play', - title: 'Play', - path: '/gitlab-org/gitlab-shell/-/jobs/458/play', - method: 'post', - }, - }, - jobs: [ - { - id: 458, - name: 'slack', - started: null, - build_path: '/gitlab-org/gitlab-shell/-/jobs/458', - play_path: '/gitlab-org/gitlab-shell/-/jobs/458/play', - playable: true, - created_at: '2018-05-18T15:32:55.303Z', - updated_at: '2018-05-18T15:34:08.535Z', - status: { - icon: 'status_manual', - text: 'manual', - label: 'manual play action', - group: 'manual', - tooltip: 'manual action', - has_details: true, - details_path: '/gitlab-org/gitlab-shell/-/jobs/458', - illustration: { - image: 'illustrations/manual_action.svg', - size: 'svg-394', - title: 'This job requires a manual action', - content: - 'This job depends on a user to trigger its process. Often they are used to deploy code to production environments', - }, - favicon: - '/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png', - action: { - icon: 'play', - title: 'Play', - path: '/gitlab-org/gitlab-shell/-/jobs/458/play', - method: 'post', - }, - }, - }, - ], - }, - ], - status: { - icon: 'status_manual', - text: 'manual', - label: 'manual action', - group: 'manual', - tooltip: 'manual action', - has_details: true, - details_path: '/gitlab-org/gitlab-shell/pipelines/27#notify', - illustration: null, - favicon: - '/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png', - }, - path: '/gitlab-org/gitlab-shell/pipelines/27#notify', - dropdown_path: '/gitlab-org/gitlab-shell/pipelines/27/stage.json?stage=notify', - }, -]; - -export const statuses = { - success: 'SUCCESS', - failed: 'FAILED', - canceled: 'CANCELED', - pending: 'PENDING', - running: 'RUNNING', -}; - -export default { - id: 4757, - artifact: { - locked: false, - }, - name: 'test', - stage: 'build', - build_path: '/root/ci-mock/-/jobs/4757', - retry_path: '/root/ci-mock/-/jobs/4757/retry', - cancel_path: '/root/ci-mock/-/jobs/4757/cancel', - new_issue_path: '/root/ci-mock/issues/new', - playable: false, - complete: true, - created_at: threeWeeksAgo.toISOString(), - updated_at: threeWeeksAgo.toISOString(), - finished_at: threeWeeksAgo.toISOString(), - queued_duration: 9.54, - status: { - icon: 'status_success', - text: 'passed', - label: 'passed', - group: 'success', - has_details: true, - details_path: `${TEST_HOST}/root/ci-mock/-/jobs/4757`, - favicon: - '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png', - action: { - icon: 'retry', - title: 'Retry', - path: '/root/ci-mock/-/jobs/4757/retry', - method: 'post', - }, - }, - coverage: 20, - erased_at: threeWeeksAgo.toISOString(), - erased: false, - duration: 6.785563, - tags: ['tag'], - user: { - name: 'Root', - username: 'root', - id: 1, - state: 'active', - avatar_url: - 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon', - web_url: 'http://localhost:3000/root', - }, - erase_path: '/root/ci-mock/-/jobs/4757/erase', - artifacts: [null], - runner: { - id: 1, - short_sha: 'ABCDEFGH', - description: 'local ci runner', - edit_path: '/root/ci-mock/runners/1/edit', - }, - pipeline: { - id: 140, - user: { - name: 'Root', - username: 'root', - id: 1, - state: 'active', - avatar_url: - 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon', - web_url: 'http://localhost:3000/root', - }, - active: false, - coverage: null, - source: 'unknown', - created_at: '2017-05-24T09:59:58.634Z', - updated_at: '2017-06-01T17:32:00.062Z', - path: '/root/ci-mock/pipelines/140', - flags: { - latest: true, - stuck: false, - yaml_errors: false, - retryable: false, - cancelable: false, - }, - details: { - status: { - icon: 'status_success', - text: 'passed', - label: 'passed', - group: 'success', - has_details: true, - details_path: '/root/ci-mock/pipelines/140', - favicon: - '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png', - }, - duration: 6, - finished_at: '2017-06-01T17:32:00.042Z', - stages: [ - { - dropdown_path: '/jashkenas/underscore/pipelines/16/stage.json?stage=build', - name: 'build', - path: '/jashkenas/underscore/pipelines/16#build', - status: { - icon: 'status_success', - text: 'passed', - label: 'passed', - group: 'success', - tooltip: 'passed', - }, - title: 'build: passed', - }, - { - dropdown_path: '/jashkenas/underscore/pipelines/16/stage.json?stage=test', - name: 'test', - path: '/jashkenas/underscore/pipelines/16#test', - status: { - icon: 'status_warning', - text: 'passed', - label: 'passed with warnings', - group: 'success-with-warnings', - }, - title: 'test: passed with warnings', - }, - ], - }, - ref: { - name: 'abc', - path: '/root/ci-mock/commits/abc', - tag: false, - branch: true, - }, - commit: { - id: 'c58647773a6b5faf066d4ad6ff2c9fbba5f180f6', - short_id: 'c5864777', - title: 'Add new file', - created_at: '2017-05-24T10:59:52.000+01:00', - parent_ids: ['798e5f902592192afaba73f4668ae30e56eae492'], - message: 'Add new file', - author_name: 'Root', - author_email: 'admin@example.com', - authored_date: '2017-05-24T10:59:52.000+01:00', - committer_name: 'Root', - committer_email: 'admin@example.com', - committed_date: '2017-05-24T10:59:52.000+01:00', - author: { - name: 'Root', - username: 'root', - id: 1, - state: 'active', - avatar_url: - 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon', - web_url: 'http://localhost:3000/root', - }, - author_gravatar_url: - 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon', - commit_url: - 'http://localhost:3000/root/ci-mock/commit/c58647773a6b5faf066d4ad6ff2c9fbba5f180f6', - commit_path: '/root/ci-mock/commit/c58647773a6b5faf066d4ad6ff2c9fbba5f180f6', - }, - }, - metadata: { - timeout_human_readable: '1m 40s', - timeout_source: 'runner', - }, - merge_request: { - iid: 2, - path: '/root/ci-mock/merge_requests/2', - }, - raw_path: '/root/ci-mock/builds/4757/raw', - has_trace: true, -}; - -export const failedJobStatus = { - icon: 'status_warning', - text: 'failed', - label: 'failed (allowed to fail)', - group: 'failed-with-warnings', - tooltip: 'failed - (unknown failure) (allowed to fail)', - has_details: true, - details_path: '/gitlab-org/gitlab-shell/-/jobs/454', - illustration: { - image: 'illustrations/skipped-job_empty.svg', - size: 'svg-430', - title: 'This job does not have a trace.', - }, - favicon: - '/assets/ci_favicons/favicon_status_failed-41304d7f7e3828808b0c26771f0309e55296819a9beea3ea9fbf6689d9857c12.png', - action: { - icon: 'retry', - title: 'Retry', - path: '/gitlab-org/gitlab-shell/-/jobs/454/retry', - method: 'post', - }, -}; - -export const jobsInStage = { - name: 'build', - title: 'build: running', - latest_statuses: [ - { - id: 1180, - name: 'build:linux', - started: false, - build_path: '/gitlab-org/gitlab-shell/-/jobs/1180', - cancel_path: '/gitlab-org/gitlab-shell/-/jobs/1180/cancel', - playable: false, - created_at: '2018-09-28T11:09:57.229Z', - updated_at: '2018-09-28T11:09:57.503Z', - status: { - icon: 'status_pending', - text: 'pending', - label: 'pending', - group: 'pending', - tooltip: 'pending', - has_details: true, - details_path: '/gitlab-org/gitlab-shell/-/jobs/1180', - illustration: { - image: 'illustrations/pending_job_empty.svg', - size: 'svg-430', - title: 'This job has not started yet', - content: 'This job is in pending state and is waiting to be picked by a runner', - }, - favicon: - '/assets/ci_favicons/favicon_status_pending-5bdf338420e5221ca24353b6bff1c9367189588750632e9a871b7af09ff6a2ae.png', - action: { - icon: 'cancel', - title: 'Cancel', - path: '/gitlab-org/gitlab-shell/-/jobs/1180/cancel', - method: 'post', - }, - }, - }, - { - id: 444, - name: 'build:osx', - started: '2018-05-18T05:32:20.655Z', - build_path: '/gitlab-org/gitlab-shell/-/jobs/444', - retry_path: '/gitlab-org/gitlab-shell/-/jobs/444/retry', - playable: false, - created_at: '2018-05-18T15:32:54.364Z', - updated_at: '2018-05-18T15:32:54.364Z', - status: { - icon: 'status_success', - text: 'passed', - label: 'passed', - group: 'success', - tooltip: 'passed', - has_details: true, - details_path: '/gitlab-org/gitlab-shell/-/jobs/444', - illustration: { - image: 'illustrations/skipped-job_empty.svg', - size: 'svg-430', - title: 'This job does not have a trace.', - }, - favicon: - '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', - action: { - icon: 'retry', - title: 'Retry', - path: '/gitlab-org/gitlab-shell/-/jobs/444/retry', - method: 'post', - }, - }, - }, - ], - retried: [ - { - id: 443, - name: 'build:linux', - started: '2018-05-18T06:32:20.655Z', - build_path: '/gitlab-org/gitlab-shell/-/jobs/443', - retry_path: '/gitlab-org/gitlab-shell/-/jobs/443/retry', - playable: false, - created_at: '2018-05-18T15:32:54.296Z', - updated_at: '2018-05-18T15:32:54.296Z', - status: { - icon: 'status_success', - text: 'passed', - label: 'passed', - group: 'success', - tooltip: 'passed (retried)', - has_details: true, - details_path: '/gitlab-org/gitlab-shell/-/jobs/443', - illustration: { - image: 'illustrations/skipped-job_empty.svg', - size: 'svg-430', - title: 'This job does not have a trace.', - }, - favicon: - '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', - action: { - icon: 'retry', - title: 'Retry', - path: '/gitlab-org/gitlab-shell/-/jobs/443/retry', - method: 'post', - }, - }, - }, - ], - status: { - icon: 'status_running', - text: 'running', - label: 'running', - group: 'running', - tooltip: 'running', - has_details: true, - details_path: '/gitlab-org/gitlab-shell/pipelines/27#build', - illustration: null, - favicon: - '/assets/ci_favicons/favicon_status_running-9c635b2419a8e1ec991c993061b89cc5aefc0743bb238ecd0c381e7741a70e8c.png', - }, - path: '/gitlab-org/gitlab-shell/pipelines/27#build', - dropdown_path: '/gitlab-org/gitlab-shell/pipelines/27/stage.json?stage=build', -}; - -export const mockPipelineWithoutMR = { - id: 28029444, - details: { - status: { - details_path: '/gitlab-org/gitlab-foss/pipelines/28029444', - group: 'success', - has_details: true, - icon: 'status_success', - label: 'passed', - text: 'passed', - tooltip: 'passed', - }, - }, - path: 'pipeline/28029444', - ref: { - name: 'test-branch', - }, -}; - -export const mockPipelineWithoutRef = { - ...mockPipelineWithoutMR, - ref: null, -}; - -export const mockPipelineWithAttachedMR = { - id: 28029444, - details: { - status: { - details_path: '/gitlab-org/gitlab-foss/pipelines/28029444', - group: 'success', - has_details: true, - icon: 'status_success', - label: 'passed', - text: 'passed', - tooltip: 'passed', - }, - }, - path: 'pipeline/28029444', - flags: { - merge_request_pipeline: true, - detached_merge_request_pipeline: false, - }, - merge_request: { - iid: 1234, - path: '/root/detached-merge-request-pipelines/-/merge_requests/1', - title: 'Update README.md', - source_branch: 'feature-1234', - source_branch_path: '/root/detached-merge-request-pipelines/branches/feature-1234', - target_branch: 'main', - target_branch_path: '/root/detached-merge-request-pipelines/branches/main', - }, - ref: { - name: 'test-branch', - }, -}; - -export const mockPipelineDetached = { - id: 28029444, - details: { - status: { - details_path: '/gitlab-org/gitlab-foss/pipelines/28029444', - group: 'success', - has_details: true, - icon: 'status_success', - label: 'passed', - text: 'passed', - tooltip: 'passed', - }, - }, - path: 'pipeline/28029444', - flags: { - merge_request_pipeline: false, - detached_merge_request_pipeline: true, - }, - merge_request: { - iid: 1234, - path: '/root/detached-merge-request-pipelines/-/merge_requests/1', - title: 'Update README.md', - source_branch: 'feature-1234', - source_branch_path: '/root/detached-merge-request-pipelines/branches/feature-1234', - target_branch: 'main', - target_branch_path: '/root/detached-merge-request-pipelines/branches/main', - }, - ref: { - name: 'test-branch', - }, -}; - -export const CIJobConnectionIncomingCache = { - __typename: 'CiJobConnection', - pageInfo: { - __typename: 'PageInfo', - endCursor: 'eyJpZCI6IjIwNTEifQ', - hasNextPage: true, - hasPreviousPage: false, - startCursor: 'eyJpZCI6IjIxNzMifQ', - }, - nodes: [ - { __ref: 'CiJob:gid://gitlab/Ci::Build/2057' }, - { __ref: 'CiJob:gid://gitlab/Ci::Build/2056' }, - { __ref: 'CiJob:gid://gitlab/Ci::Build/2051' }, - ], -}; - -export const CIJobConnectionIncomingCacheRunningStatus = { - __typename: 'CiJobConnection', - pageInfo: { - __typename: 'PageInfo', - endCursor: 'eyJpZCI6IjIwNTEifQ', - hasNextPage: true, - hasPreviousPage: false, - startCursor: 'eyJpZCI6IjIxNzMifQ', - }, - nodes: [ - { __ref: 'CiJob:gid://gitlab/Ci::Build/2000' }, - { __ref: 'CiJob:gid://gitlab/Ci::Build/2001' }, - { __ref: 'CiJob:gid://gitlab/Ci::Build/2002' }, - ], -}; - -export const CIJobConnectionExistingCache = { - pageInfo: { - __typename: 'PageInfo', - endCursor: 'eyJpZCI6IjIwNTEifQ', - hasNextPage: true, - hasPreviousPage: false, - startCursor: 'eyJpZCI6IjIxNzMifQ', - }, - nodes: [ - { __ref: 'CiJob:gid://gitlab/Ci::Build/2100' }, - { __ref: 'CiJob:gid://gitlab/Ci::Build/2101' }, - { __ref: 'CiJob:gid://gitlab/Ci::Build/2102' }, - ], - statuses: 'PENDING', -}; - -export const mockFailedSearchToken = { - type: TOKEN_TYPE_STATUS, - value: { data: 'FAILED', operator: '=' }, -}; - -export const retryMutationResponse = { - data: { - jobRetry: { - job: { - __typename: 'CiJob', - id: '"gid://gitlab/Ci::Build/1985"', - detailedStatus: { - detailsPath: '/root/project/-/jobs/1985', - id: 'pending-1985-1985', - __typename: 'DetailedStatus', - }, - }, - errors: [], - __typename: 'JobRetryPayload', - }, - }, -}; - -export const playMutationResponse = { - data: { - jobPlay: { - job: { - __typename: 'CiJob', - id: '"gid://gitlab/Ci::Build/1986"', - detailedStatus: { - detailsPath: '/root/project/-/jobs/1986', - id: 'pending-1986-1986', - __typename: 'DetailedStatus', - }, - }, - errors: [], - __typename: 'JobRetryPayload', - }, - }, -}; - -export const cancelMutationResponse = { - data: { - jobCancel: { - job: { - __typename: 'CiJob', - id: '"gid://gitlab/Ci::Build/1987"', - detailedStatus: { - detailsPath: '/root/project/-/jobs/1987', - id: 'pending-1987-1987', - __typename: 'DetailedStatus', - }, - }, - errors: [], - __typename: 'JobRetryPayload', - }, - }, -}; - -export const unscheduleMutationResponse = { - data: { - jobUnschedule: { - job: { - __typename: 'CiJob', - id: '"gid://gitlab/Ci::Build/1988"', - detailedStatus: { - detailsPath: '/root/project/-/jobs/1988', - id: 'pending-1988-1988', - __typename: 'DetailedStatus', - }, - }, - errors: [], - __typename: 'JobRetryPayload', - }, - }, -}; - -export const mockJobLog = [ - { offset: 0, content: [{ text: 'Running with gitlab-runner 15.0.0 (febb2a09)' }], lineNumber: 0 }, - { offset: 54, content: [{ text: ' on colima-docker EwM9WzgD' }], lineNumber: 1 }, - { - isClosed: false, - isHeader: true, - line: { - offset: 91, - content: [{ text: 'Resolving secrets', style: 'term-fg-l-cyan term-bold' }], - section: 'resolve-secrets', - section_header: true, - lineNumber: 2, - section_duration: '00:00', - }, - lines: [], - }, - { - isClosed: false, - isHeader: true, - line: { - offset: 218, - content: [{ text: 'Preparing the "docker" executor', style: 'term-fg-l-cyan term-bold' }], - section: 'prepare-executor', - section_header: true, - lineNumber: 4, - section_duration: '00:01', - }, - lines: [ - { - offset: 317, - content: [{ text: 'Using Docker executor with image ruby:2.7 ...' }], - section: 'prepare-executor', - lineNumber: 5, - }, - { - offset: 372, - content: [{ text: 'Pulling docker image ruby:2.7 ...' }], - section: 'prepare-executor', - lineNumber: 6, - }, - { - offset: 415, - content: [ - { - text: - 'Using docker image sha256:55106bf6ba7f452c38d01ea760affc6ceb67d4b60068ffadab98d1b7b007668c for ruby:2.7 with digest ruby@sha256:23d08a4bae1a12ee3fce017f83204fcf9a02243443e4a516e65e5ff73810a449 ...', - }, - ], - section: 'prepare-executor', - lineNumber: 7, - }, - ], - }, - { - isClosed: false, - isHeader: true, - line: { - offset: 665, - content: [{ text: 'Preparing environment', style: 'term-fg-l-cyan term-bold' }], - section: 'prepare-script', - section_header: true, - lineNumber: 9, - section_duration: '00:01', - }, - lines: [ - { - offset: 752, - content: [ - { text: 'Running on runner-ewm9wzgd-project-20-concurrent-0 via 8ea689ec6969...' }, - ], - section: 'prepare-script', - lineNumber: 10, - }, - ], - }, - { - isClosed: false, - isHeader: true, - line: { - offset: 865, - content: [{ text: 'Getting source from Git repository', style: 'term-fg-l-cyan term-bold' }], - section: 'get-sources', - section_header: true, - lineNumber: 12, - section_duration: '00:01', - }, - lines: [ - { - offset: 962, - content: [ - { - text: 'Fetching changes with git depth set to 20...', - style: 'term-fg-l-green term-bold', - }, - ], - section: 'get-sources', - lineNumber: 13, - }, - { - offset: 1019, - content: [ - { text: 'Reinitialized existing Git repository in /builds/root/ci-project/.git/' }, - ], - section: 'get-sources', - lineNumber: 14, - }, - { - offset: 1090, - content: [{ text: 'Checking out e0f63d76 as main...', style: 'term-fg-l-green term-bold' }], - section: 'get-sources', - lineNumber: 15, - }, - { - offset: 1136, - content: [{ text: 'Skipping Git submodules setup', style: 'term-fg-l-green term-bold' }], - section: 'get-sources', - lineNumber: 16, - }, - ], - }, - { - isClosed: false, - isHeader: true, - line: { - offset: 1217, - content: [ - { - text: 'Executing "step_script" stage of the job script', - style: 'term-fg-l-cyan term-bold', - }, - ], - section: 'step-script', - section_header: true, - lineNumber: 18, - section_duration: '00:00', - }, - lines: [ - { - offset: 1327, - content: [ - { - text: - 'Using docker image sha256:55106bf6ba7f452c38d01ea760affc6ceb67d4b60068ffadab98d1b7b007668c for ruby:2.7 with digest ruby@sha256:23d08a4bae1a12ee3fce017f83204fcf9a02243443e4a516e65e5ff73810a449 ...', - }, - ], - section: 'step-script', - lineNumber: 19, - }, - { - offset: 1533, - content: [{ text: '$ echo "82.71"', style: 'term-fg-l-green term-bold' }], - section: 'step-script', - lineNumber: 20, - }, - { offset: 1560, content: [{ text: '82.71' }], section: 'step-script', lineNumber: 21 }, - ], - }, - { - offset: 1605, - content: [{ text: 'Job succeeded', style: 'term-fg-l-green term-bold' }], - lineNumber: 23, - }, -]; diff --git a/spec/frontend/jobs/store/actions_spec.js b/spec/frontend/jobs/store/actions_spec.js deleted file mode 100644 index 73a158d52d8..00000000000 --- a/spec/frontend/jobs/store/actions_spec.js +++ /dev/null @@ -1,502 +0,0 @@ -import MockAdapter from 'axios-mock-adapter'; -import { TEST_HOST } from 'helpers/test_constants'; -import testAction from 'helpers/vuex_action_helper'; -import { - setJobEndpoint, - setJobLogOptions, - clearEtagPoll, - stopPolling, - requestJob, - fetchJob, - receiveJobSuccess, - receiveJobError, - scrollTop, - scrollBottom, - requestJobLog, - fetchJobLog, - startPollingJobLog, - stopPollingJobLog, - receiveJobLogSuccess, - receiveJobLogError, - toggleCollapsibleLine, - requestJobsForStage, - fetchJobsForStage, - receiveJobsForStageSuccess, - receiveJobsForStageError, - hideSidebar, - showSidebar, - toggleSidebar, -} from '~/jobs/store/actions'; -import * as types from '~/jobs/store/mutation_types'; -import state from '~/jobs/store/state'; -import axios from '~/lib/utils/axios_utils'; -import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status'; - -describe('Job State actions', () => { - let mockedState; - - beforeEach(() => { - mockedState = state(); - }); - - describe('setJobEndpoint', () => { - it('should commit SET_JOB_ENDPOINT mutation', () => { - return testAction( - setJobEndpoint, - 'job/872324.json', - mockedState, - [{ type: types.SET_JOB_ENDPOINT, payload: 'job/872324.json' }], - [], - ); - }); - }); - - describe('setJobLogOptions', () => { - it('should commit SET_JOB_LOG_OPTIONS mutation', () => { - return testAction( - setJobLogOptions, - { pagePath: 'job/872324/trace.json' }, - mockedState, - [{ type: types.SET_JOB_LOG_OPTIONS, payload: { pagePath: 'job/872324/trace.json' } }], - [], - ); - }); - }); - - describe('hideSidebar', () => { - it('should commit HIDE_SIDEBAR mutation', () => { - return testAction(hideSidebar, null, mockedState, [{ type: types.HIDE_SIDEBAR }], []); - }); - }); - - describe('showSidebar', () => { - it('should commit SHOW_SIDEBAR mutation', () => { - return testAction(showSidebar, null, mockedState, [{ type: types.SHOW_SIDEBAR }], []); - }); - }); - - describe('toggleSidebar', () => { - describe('when isSidebarOpen is true', () => { - it('should dispatch hideSidebar', () => { - return testAction(toggleSidebar, null, mockedState, [], [{ type: 'hideSidebar' }]); - }); - }); - - describe('when isSidebarOpen is false', () => { - it('should dispatch showSidebar', () => { - mockedState.isSidebarOpen = false; - - return testAction(toggleSidebar, null, mockedState, [], [{ type: 'showSidebar' }]); - }); - }); - }); - - describe('requestJob', () => { - it('should commit REQUEST_JOB mutation', () => { - return testAction(requestJob, null, mockedState, [{ type: types.REQUEST_JOB }], []); - }); - }); - - describe('fetchJob', () => { - let mock; - - beforeEach(() => { - mockedState.jobEndpoint = `${TEST_HOST}/endpoint.json`; - mock = new MockAdapter(axios); - }); - - afterEach(() => { - mock.restore(); - stopPolling(); - clearEtagPoll(); - }); - - describe('success', () => { - it('dispatches requestJob and receiveJobSuccess', () => { - mock - .onGet(`${TEST_HOST}/endpoint.json`) - .replyOnce(HTTP_STATUS_OK, { id: 121212, name: 'karma' }); - - return testAction( - fetchJob, - null, - mockedState, - [], - [ - { - type: 'requestJob', - }, - { - payload: { id: 121212, name: 'karma' }, - type: 'receiveJobSuccess', - }, - ], - ); - }); - }); - - describe('error', () => { - beforeEach(() => { - mock.onGet(`${TEST_HOST}/endpoint.json`).reply(HTTP_STATUS_INTERNAL_SERVER_ERROR); - }); - - it('dispatches requestJob and receiveJobError', () => { - return testAction( - fetchJob, - null, - mockedState, - [], - [ - { - type: 'requestJob', - }, - { - type: 'receiveJobError', - }, - ], - ); - }); - }); - }); - - describe('receiveJobSuccess', () => { - it('should commit RECEIVE_JOB_SUCCESS mutation', () => { - return testAction( - receiveJobSuccess, - { id: 121232132 }, - mockedState, - [{ type: types.RECEIVE_JOB_SUCCESS, payload: { id: 121232132 } }], - [], - ); - }); - }); - - describe('receiveJobError', () => { - it('should commit RECEIVE_JOB_ERROR mutation', () => { - return testAction( - receiveJobError, - null, - mockedState, - [{ type: types.RECEIVE_JOB_ERROR }], - [], - ); - }); - }); - - describe('scrollTop', () => { - it('should dispatch toggleScrollButtons action', () => { - return testAction(scrollTop, null, mockedState, [], [{ type: 'toggleScrollButtons' }]); - }); - }); - - describe('scrollBottom', () => { - it('should dispatch toggleScrollButtons action', () => { - return testAction(scrollBottom, null, mockedState, [], [{ type: 'toggleScrollButtons' }]); - }); - }); - - describe('requestJobLog', () => { - it('should commit REQUEST_JOB_LOG mutation', () => { - return testAction(requestJobLog, null, mockedState, [{ type: types.REQUEST_JOB_LOG }], []); - }); - }); - - describe('fetchJobLog', () => { - let mock; - - beforeEach(() => { - mockedState.jobLogEndpoint = `${TEST_HOST}/endpoint`; - mock = new MockAdapter(axios); - }); - - afterEach(() => { - mock.restore(); - stopPolling(); - clearEtagPoll(); - }); - - describe('success', () => { - it('dispatches requestJobLog, receiveJobLogSuccess and stopPollingJobLog when job is complete', () => { - mock.onGet(`${TEST_HOST}/endpoint/trace.json`).replyOnce(HTTP_STATUS_OK, { - html: 'I, [2018-08-17T22:57:45.707325 #1841] INFO -- :', - complete: true, - }); - - return testAction( - fetchJobLog, - null, - mockedState, - [], - [ - { - type: 'toggleScrollisInBottom', - payload: true, - }, - { - payload: { - html: 'I, [2018-08-17T22:57:45.707325 #1841] INFO -- :', - complete: true, - }, - type: 'receiveJobLogSuccess', - }, - { - type: 'stopPollingJobLog', - }, - ], - ); - }); - - describe('when job is incomplete', () => { - let jobLogPayload; - - beforeEach(() => { - jobLogPayload = { - html: 'I, [2018-08-17T22:57:45.707325 #1841] INFO -- :', - complete: false, - }; - - mock.onGet(`${TEST_HOST}/endpoint/trace.json`).replyOnce(HTTP_STATUS_OK, jobLogPayload); - }); - - it('dispatches startPollingJobLog', () => { - return testAction( - fetchJobLog, - null, - mockedState, - [], - [ - { type: 'toggleScrollisInBottom', payload: true }, - { type: 'receiveJobLogSuccess', payload: jobLogPayload }, - { type: 'startPollingJobLog' }, - ], - ); - }); - - it('does not dispatch startPollingJobLog when timeout is non-empty', () => { - mockedState.jobLogTimeout = 1; - - return testAction( - fetchJobLog, - null, - mockedState, - [], - [ - { type: 'toggleScrollisInBottom', payload: true }, - { type: 'receiveJobLogSuccess', payload: jobLogPayload }, - ], - ); - }); - }); - }); - - describe('error', () => { - beforeEach(() => { - mock.onGet(`${TEST_HOST}/endpoint/trace.json`).reply(HTTP_STATUS_INTERNAL_SERVER_ERROR); - }); - - it('dispatches requestJobLog and receiveJobLogError', () => { - return testAction( - fetchJobLog, - null, - mockedState, - [], - [ - { - type: 'receiveJobLogError', - }, - ], - ); - }); - }); - }); - - describe('startPollingJobLog', () => { - let dispatch; - let commit; - - beforeEach(() => { - dispatch = jest.fn(); - commit = jest.fn(); - - startPollingJobLog({ dispatch, commit }); - }); - - afterEach(() => { - jest.clearAllTimers(); - }); - - it('should save the timeout id but not call fetchJobLog', () => { - expect(commit).toHaveBeenCalledWith(types.SET_JOB_LOG_TIMEOUT, expect.any(Number)); - expect(commit.mock.calls[0][1]).toBeGreaterThan(0); - - expect(dispatch).not.toHaveBeenCalledWith('fetchJobLog'); - }); - - describe('after timeout has passed', () => { - beforeEach(() => { - jest.advanceTimersByTime(4000); - }); - - it('should clear the timeout id and fetchJobLog', () => { - expect(commit).toHaveBeenCalledWith(types.SET_JOB_LOG_TIMEOUT, 0); - expect(dispatch).toHaveBeenCalledWith('fetchJobLog'); - }); - }); - }); - - describe('stopPollingJobLog', () => { - let origTimeout; - - beforeEach(() => { - // Can't use spyOn(window, 'clearTimeout') because this caused unrelated specs to timeout - // https://gitlab.com/gitlab-org/gitlab/-/merge_requests/23838#note_280277727 - origTimeout = window.clearTimeout; - window.clearTimeout = jest.fn(); - }); - - afterEach(() => { - window.clearTimeout = origTimeout; - }); - - it('should commit STOP_POLLING_JOB_LOG mutation', async () => { - const jobLogTimeout = 7; - - await testAction( - stopPollingJobLog, - null, - { ...mockedState, jobLogTimeout }, - [{ type: types.SET_JOB_LOG_TIMEOUT, payload: 0 }, { type: types.STOP_POLLING_JOB_LOG }], - [], - ); - expect(window.clearTimeout).toHaveBeenCalledWith(jobLogTimeout); - }); - }); - - describe('receiveJobLogSuccess', () => { - it('should commit RECEIVE_JOB_LOG_SUCCESS mutation', () => { - return testAction( - receiveJobLogSuccess, - 'hello world', - mockedState, - [{ type: types.RECEIVE_JOB_LOG_SUCCESS, payload: 'hello world' }], - [], - ); - }); - }); - - describe('receiveJobLogError', () => { - it('should commit stop polling job log', () => { - return testAction(receiveJobLogError, null, mockedState, [], [{ type: 'stopPollingJobLog' }]); - }); - }); - - describe('toggleCollapsibleLine', () => { - it('should commit TOGGLE_COLLAPSIBLE_LINE mutation', () => { - return testAction( - toggleCollapsibleLine, - { isClosed: true }, - mockedState, - [{ type: types.TOGGLE_COLLAPSIBLE_LINE, payload: { isClosed: true } }], - [], - ); - }); - }); - - describe('requestJobsForStage', () => { - it('should commit REQUEST_JOBS_FOR_STAGE mutation', () => { - return testAction( - requestJobsForStage, - { name: 'deploy' }, - mockedState, - [{ type: types.REQUEST_JOBS_FOR_STAGE, payload: { name: 'deploy' } }], - [], - ); - }); - }); - - describe('fetchJobsForStage', () => { - let mock; - - beforeEach(() => { - mock = new MockAdapter(axios); - }); - - afterEach(() => { - mock.restore(); - }); - - describe('success', () => { - it('dispatches requestJobsForStage and receiveJobsForStageSuccess', () => { - mock.onGet(`${TEST_HOST}/jobs.json`).replyOnce(HTTP_STATUS_OK, { - latest_statuses: [{ id: 121212, name: 'build' }], - retried: [], - }); - - return testAction( - fetchJobsForStage, - { dropdown_path: `${TEST_HOST}/jobs.json` }, - mockedState, - [], - [ - { - type: 'requestJobsForStage', - payload: { dropdown_path: `${TEST_HOST}/jobs.json` }, - }, - { - payload: [{ id: 121212, name: 'build' }], - type: 'receiveJobsForStageSuccess', - }, - ], - ); - }); - }); - - describe('error', () => { - beforeEach(() => { - mock.onGet(`${TEST_HOST}/jobs.json`).reply(HTTP_STATUS_INTERNAL_SERVER_ERROR); - }); - - it('dispatches requestJobsForStage and receiveJobsForStageError', () => { - return testAction( - fetchJobsForStage, - { dropdown_path: `${TEST_HOST}/jobs.json` }, - mockedState, - [], - [ - { - type: 'requestJobsForStage', - payload: { dropdown_path: `${TEST_HOST}/jobs.json` }, - }, - { - type: 'receiveJobsForStageError', - }, - ], - ); - }); - }); - }); - - describe('receiveJobsForStageSuccess', () => { - it('should commit RECEIVE_JOBS_FOR_STAGE_SUCCESS mutation', () => { - return testAction( - receiveJobsForStageSuccess, - [{ id: 121212, name: 'karma' }], - mockedState, - [{ type: types.RECEIVE_JOBS_FOR_STAGE_SUCCESS, payload: [{ id: 121212, name: 'karma' }] }], - [], - ); - }); - }); - - describe('receiveJobsForStageError', () => { - it('should commit RECEIVE_JOBS_FOR_STAGE_ERROR mutation', () => { - return testAction( - receiveJobsForStageError, - null, - mockedState, - [{ type: types.RECEIVE_JOBS_FOR_STAGE_ERROR }], - [], - ); - }); - }); -}); diff --git a/spec/frontend/jobs/store/getters_spec.js b/spec/frontend/jobs/store/getters_spec.js deleted file mode 100644 index c13b051c672..00000000000 --- a/spec/frontend/jobs/store/getters_spec.js +++ /dev/null @@ -1,245 +0,0 @@ -import * as getters from '~/jobs/store/getters'; -import state from '~/jobs/store/state'; - -describe('Job Store Getters', () => { - let localState; - - beforeEach(() => { - localState = state(); - }); - - describe('headerTime', () => { - describe('when the job has started key', () => { - it('returns started_at value', () => { - const started = '2018-08-31T16:20:49.023Z'; - const startedAt = '2018-08-31T16:20:49.023Z'; - localState.job.started_at = startedAt; - localState.job.started = started; - - expect(getters.headerTime(localState)).toEqual(startedAt); - }); - }); - - describe('when the job does not have started key', () => { - it('returns created_at value', () => { - const created = '2018-08-31T16:20:49.023Z'; - localState.job.created_at = created; - - expect(getters.headerTime(localState)).toEqual(created); - }); - }); - }); - - describe('shouldRenderCalloutMessage', () => { - describe('with status and callout message', () => { - it('returns true', () => { - localState.job.callout_message = 'Callout message'; - localState.job.status = { icon: 'passed' }; - - expect(getters.shouldRenderCalloutMessage(localState)).toEqual(true); - }); - }); - - describe('without status & with callout message', () => { - it('returns false', () => { - localState.job.callout_message = 'Callout message'; - - expect(getters.shouldRenderCalloutMessage(localState)).toEqual(false); - }); - }); - - describe('with status & without callout message', () => { - it('returns false', () => { - localState.job.status = { icon: 'passed' }; - - expect(getters.shouldRenderCalloutMessage(localState)).toEqual(false); - }); - }); - }); - - describe('shouldRenderTriggeredLabel', () => { - describe('when started equals null', () => { - it('returns false', () => { - localState.job.started_at = null; - - expect(getters.shouldRenderTriggeredLabel(localState)).toEqual(false); - }); - }); - - describe('when started equals string', () => { - it('returns true', () => { - localState.job.started_at = '2018-08-31T16:20:49.023Z'; - - expect(getters.shouldRenderTriggeredLabel(localState)).toEqual(true); - }); - }); - }); - - describe('hasEnvironment', () => { - describe('without `deployment_status`', () => { - it('returns false', () => { - expect(getters.hasEnvironment(localState)).toEqual(false); - }); - }); - - describe('with an empty object for `deployment_status`', () => { - it('returns false', () => { - localState.job.deployment_status = {}; - - expect(getters.hasEnvironment(localState)).toEqual(false); - }); - }); - - describe('when `deployment_status` is defined and not empty', () => { - it('returns true', () => { - localState.job.deployment_status = { - status: 'creating', - environment: { - last_deployment: {}, - }, - }; - - expect(getters.hasEnvironment(localState)).toEqual(true); - }); - }); - }); - - describe('hasJobLog', () => { - describe('when has_trace is true', () => { - it('returns true', () => { - localState.job.has_trace = true; - localState.job.status = {}; - - expect(getters.hasJobLog(localState)).toEqual(true); - }); - }); - - describe('when job is running', () => { - it('returns true', () => { - localState.job.has_trace = false; - localState.job.status = { group: 'running' }; - - expect(getters.hasJobLog(localState)).toEqual(true); - }); - }); - - describe('when has_trace is false and job is not running', () => { - it('returns false', () => { - localState.job.has_trace = false; - localState.job.status = { group: 'pending' }; - - expect(getters.hasJobLog(localState)).toEqual(false); - }); - }); - }); - - describe('emptyStateIllustration', () => { - describe('with defined illustration', () => { - it('returns the state illustration object', () => { - localState.job.status = { - illustration: { - path: 'foo', - }, - }; - - expect(getters.emptyStateIllustration(localState)).toEqual({ path: 'foo' }); - }); - }); - - describe('when illustration is not defined', () => { - it('returns an empty object', () => { - expect(getters.emptyStateIllustration(localState)).toEqual({}); - }); - }); - }); - - describe('shouldRenderSharedRunnerLimitWarning', () => { - describe('without runners information', () => { - it('returns false', () => { - expect(getters.shouldRenderSharedRunnerLimitWarning(localState)).toEqual(false); - }); - }); - - describe('with runners information', () => { - describe('when used quota is less than limit', () => { - it('returns false', () => { - localState.job.runners = { - quota: { - used: 33, - limit: 2000, - }, - available: true, - online: true, - }; - - expect(getters.shouldRenderSharedRunnerLimitWarning(localState)).toEqual(false); - }); - }); - - describe('when used quota is equal to limit', () => { - it('returns true', () => { - localState.job.runners = { - quota: { - used: 2000, - limit: 2000, - }, - available: true, - online: true, - }; - - expect(getters.shouldRenderSharedRunnerLimitWarning(localState)).toEqual(true); - }); - }); - - describe('when used quota is bigger than limit', () => { - it('returns true', () => { - localState.job.runners = { - quota: { - used: 2002, - limit: 2000, - }, - available: true, - online: true, - }; - - expect(getters.shouldRenderSharedRunnerLimitWarning(localState)).toEqual(true); - }); - }); - }); - }); - - describe('hasOfflineRunnersForProject', () => { - describe('with available and offline runners', () => { - it('returns true', () => { - localState.job.runners = { - available: true, - online: false, - }; - - expect(getters.hasOfflineRunnersForProject(localState)).toEqual(true); - }); - }); - - describe('with non available runners', () => { - it('returns false', () => { - localState.job.runners = { - available: false, - online: false, - }; - - expect(getters.hasOfflineRunnersForProject(localState)).toEqual(false); - }); - }); - - describe('with online runners', () => { - it('returns false', () => { - localState.job.runners = { - available: false, - online: true, - }; - - expect(getters.hasOfflineRunnersForProject(localState)).toEqual(false); - }); - }); - }); -}); diff --git a/spec/frontend/jobs/store/helpers.js b/spec/frontend/jobs/store/helpers.js deleted file mode 100644 index 402ae58971a..00000000000 --- a/spec/frontend/jobs/store/helpers.js +++ /dev/null @@ -1,5 +0,0 @@ -import state from '~/jobs/store/state'; - -export const resetStore = (store) => { - store.replaceState(state()); -}; diff --git a/spec/frontend/jobs/store/mutations_spec.js b/spec/frontend/jobs/store/mutations_spec.js deleted file mode 100644 index 89cda3b0544..00000000000 --- a/spec/frontend/jobs/store/mutations_spec.js +++ /dev/null @@ -1,269 +0,0 @@ -import * as types from '~/jobs/store/mutation_types'; -import mutations from '~/jobs/store/mutations'; -import state from '~/jobs/store/state'; - -describe('Jobs Store Mutations', () => { - let stateCopy; - - const html = - 'I, [2018-08-17T22:57:45.707325 #1841] INFO -- : Writing /builds/ab89e95b0fa0b9272ea0c797b76908f24d36992630e9325273a4ce3.png<br>I'; - - beforeEach(() => { - stateCopy = state(); - }); - - describe('SET_JOB_ENDPOINT', () => { - it('should set jobEndpoint', () => { - mutations[types.SET_JOB_ENDPOINT](stateCopy, 'job/21312321.json'); - - expect(stateCopy.jobEndpoint).toEqual('job/21312321.json'); - }); - }); - - describe('HIDE_SIDEBAR', () => { - it('should set isSidebarOpen to false', () => { - mutations[types.HIDE_SIDEBAR](stateCopy); - - expect(stateCopy.isSidebarOpen).toEqual(false); - }); - }); - - describe('SHOW_SIDEBAR', () => { - it('should set isSidebarOpen to true', () => { - mutations[types.SHOW_SIDEBAR](stateCopy); - - expect(stateCopy.isSidebarOpen).toEqual(true); - }); - }); - - describe('RECEIVE_JOB_LOG_SUCCESS', () => { - describe('when job log has state', () => { - it('sets jobLogState', () => { - const stateLog = - 'eyJvZmZzZXQiOjczNDQ1MSwibl9vcGVuX3RhZ3MiOjAsImZnX2NvbG9yIjpudWxsLCJiZ19jb2xvciI6bnVsbCwic3R5bGVfbWFzayI6MH0='; - mutations[types.RECEIVE_JOB_LOG_SUCCESS](stateCopy, { - state: stateLog, - }); - - expect(stateCopy.jobLogState).toEqual(stateLog); - }); - }); - - describe('when jobLogSize is smaller than the total size', () => { - it('sets isJobLogSizeVisible to true', () => { - mutations[types.RECEIVE_JOB_LOG_SUCCESS](stateCopy, { total: 51184600, size: 1231 }); - - expect(stateCopy.isJobLogSizeVisible).toEqual(true); - }); - }); - - describe('when jobLogSize is bigger than the total size', () => { - it('sets isJobLogSizeVisible to false', () => { - const copy = { ...stateCopy, jobLogSize: 5118460, size: 2321312 }; - - mutations[types.RECEIVE_JOB_LOG_SUCCESS](copy, { total: 511846 }); - - expect(copy.isJobLogSizeVisible).toEqual(false); - }); - }); - - it('sets job log size and isJobLogComplete', () => { - mutations[types.RECEIVE_JOB_LOG_SUCCESS](stateCopy, { - append: true, - html, - size: 511846, - complete: true, - lines: [], - }); - - expect(stateCopy.jobLogSize).toEqual(511846); - expect(stateCopy.isJobLogComplete).toEqual(true); - }); - - describe('with new job log', () => { - describe('log.lines', () => { - describe('when append is true', () => { - it('sets the parsed log', () => { - mutations[types.RECEIVE_JOB_LOG_SUCCESS](stateCopy, { - append: true, - size: 511846, - complete: true, - lines: [ - { - offset: 1, - content: [{ text: 'Running with gitlab-runner 11.12.1 (5a147c92)' }], - }, - ], - }); - - expect(stateCopy.jobLog).toEqual([ - { - offset: 1, - content: [{ text: 'Running with gitlab-runner 11.12.1 (5a147c92)' }], - lineNumber: 0, - }, - ]); - }); - }); - - describe('when it is defined', () => { - it('sets the parsed log', () => { - mutations[types.RECEIVE_JOB_LOG_SUCCESS](stateCopy, { - append: false, - size: 511846, - complete: true, - lines: [ - { offset: 0, content: [{ text: 'Running with gitlab-runner 11.11.1 (5a147c92)' }] }, - ], - }); - - expect(stateCopy.jobLog).toEqual([ - { - offset: 0, - content: [{ text: 'Running with gitlab-runner 11.11.1 (5a147c92)' }], - lineNumber: 0, - }, - ]); - }); - }); - - describe('when it is null', () => { - it('sets the default value', () => { - mutations[types.RECEIVE_JOB_LOG_SUCCESS](stateCopy, { - append: true, - html, - size: 511846, - complete: false, - lines: null, - }); - - expect(stateCopy.jobLog).toEqual([]); - }); - }); - }); - }); - }); - - describe('SET_JOB_LOG_TIMEOUT', () => { - it('sets the jobLogTimeout id', () => { - const id = 7; - - expect(stateCopy.jobLogTimeout).not.toEqual(id); - - mutations[types.SET_JOB_LOG_TIMEOUT](stateCopy, id); - - expect(stateCopy.jobLogTimeout).toEqual(id); - }); - }); - - describe('STOP_POLLING_JOB_LOG', () => { - it('sets isJobLogComplete to true', () => { - mutations[types.STOP_POLLING_JOB_LOG](stateCopy); - - expect(stateCopy.isJobLogComplete).toEqual(true); - }); - }); - - describe('TOGGLE_COLLAPSIBLE_LINE', () => { - it('toggles the `isClosed` property of the provided object', () => { - const section = { isClosed: true }; - mutations[types.TOGGLE_COLLAPSIBLE_LINE](stateCopy, section); - expect(section.isClosed).toEqual(false); - }); - }); - - describe('REQUEST_JOB', () => { - it('sets isLoading to true', () => { - mutations[types.REQUEST_JOB](stateCopy); - - expect(stateCopy.isLoading).toEqual(true); - }); - }); - - describe('RECEIVE_JOB_SUCCESS', () => { - it('sets is loading to false', () => { - mutations[types.RECEIVE_JOB_SUCCESS](stateCopy, { id: 1312321 }); - - expect(stateCopy.isLoading).toEqual(false); - }); - - it('sets hasError to false', () => { - mutations[types.RECEIVE_JOB_SUCCESS](stateCopy, { id: 1312321 }); - - expect(stateCopy.hasError).toEqual(false); - }); - - it('sets job data', () => { - mutations[types.RECEIVE_JOB_SUCCESS](stateCopy, { id: 1312321 }); - - expect(stateCopy.job).toEqual({ id: 1312321 }); - }); - - it('sets selectedStage when the selectedStage is empty', () => { - expect(stateCopy.selectedStage).toEqual(''); - mutations[types.RECEIVE_JOB_SUCCESS](stateCopy, { id: 1312321, stage: 'deploy' }); - - expect(stateCopy.selectedStage).toEqual('deploy'); - }); - - it('does not set selectedStage when the selectedStage is not More', () => { - stateCopy.selectedStage = 'notify'; - - expect(stateCopy.selectedStage).toEqual('notify'); - mutations[types.RECEIVE_JOB_SUCCESS](stateCopy, { id: 1312321, stage: 'deploy' }); - - expect(stateCopy.selectedStage).toEqual('notify'); - }); - }); - - describe('RECEIVE_JOB_ERROR', () => { - it('resets job data', () => { - mutations[types.RECEIVE_JOB_ERROR](stateCopy); - - expect(stateCopy.isLoading).toEqual(false); - expect(stateCopy.job).toEqual({}); - }); - }); - - describe('REQUEST_JOBS_FOR_STAGE', () => { - it('sets isLoadingJobs to true', () => { - mutations[types.REQUEST_JOBS_FOR_STAGE](stateCopy, { name: 'deploy' }); - - expect(stateCopy.isLoadingJobs).toEqual(true); - }); - - it('sets selectedStage', () => { - mutations[types.REQUEST_JOBS_FOR_STAGE](stateCopy, { name: 'deploy' }); - - expect(stateCopy.selectedStage).toEqual('deploy'); - }); - }); - - describe('RECEIVE_JOBS_FOR_STAGE_SUCCESS', () => { - beforeEach(() => { - mutations[types.RECEIVE_JOBS_FOR_STAGE_SUCCESS](stateCopy, [{ name: 'karma' }]); - }); - - it('sets isLoadingJobs to false', () => { - expect(stateCopy.isLoadingJobs).toEqual(false); - }); - - it('sets jobs', () => { - expect(stateCopy.jobs).toEqual([{ name: 'karma' }]); - }); - }); - - describe('RECEIVE_JOBS_FOR_STAGE_ERROR', () => { - beforeEach(() => { - mutations[types.RECEIVE_JOBS_FOR_STAGE_ERROR](stateCopy); - }); - - it('sets isLoadingJobs to false', () => { - expect(stateCopy.isLoadingJobs).toEqual(false); - }); - - it('resets jobs', () => { - expect(stateCopy.jobs).toEqual([]); - }); - }); -}); diff --git a/spec/frontend/jobs/store/utils_spec.js b/spec/frontend/jobs/store/utils_spec.js deleted file mode 100644 index 37a6722c555..00000000000 --- a/spec/frontend/jobs/store/utils_spec.js +++ /dev/null @@ -1,510 +0,0 @@ -import { - logLinesParser, - updateIncrementalJobLog, - parseHeaderLine, - parseLine, - addDurationToHeader, - isCollapsibleSection, - findOffsetAndRemove, - getIncrementalLineNumber, -} from '~/jobs/store/utils'; -import { - utilsMockData, - originalTrace, - regularIncremental, - regularIncrementalRepeated, - headerTrace, - headerTraceIncremental, - collapsibleTrace, - collapsibleTraceIncremental, -} from '../components/log/mock_data'; - -describe('Jobs Store Utils', () => { - describe('parseHeaderLine', () => { - it('returns a new object with the header keys and the provided line parsed', () => { - const headerLine = { content: [{ text: 'foo' }] }; - const parsedHeaderLine = parseHeaderLine(headerLine, 2); - - expect(parsedHeaderLine).toEqual({ - isClosed: false, - isHeader: true, - line: { - ...headerLine, - lineNumber: 2, - }, - lines: [], - }); - }); - - it('pre-closes a section when specified in options', () => { - const headerLine = { content: [{ text: 'foo' }], section_options: { collapsed: 'true' } }; - - const parsedHeaderLine = parseHeaderLine(headerLine, 2); - - expect(parsedHeaderLine.isClosed).toBe(true); - }); - - it('expands all pre-closed sections if hash is present', () => { - const headerLine = { content: [{ text: 'foo' }], section_options: { collapsed: 'true' } }; - - const parsedHeaderLine = parseHeaderLine(headerLine, 2, '#L33'); - - expect(parsedHeaderLine.isClosed).toBe(false); - }); - }); - - describe('parseLine', () => { - it('returns a new object with the lineNumber key added to the provided line object', () => { - const line = { content: [{ text: 'foo' }] }; - const parsed = parseLine(line, 1); - expect(parsed.content).toEqual(line.content); - expect(parsed.lineNumber).toEqual(1); - }); - }); - - describe('addDurationToHeader', () => { - const duration = { - offset: 106, - content: [], - section: 'prepare-script', - section_duration: '00:03', - }; - - it('adds the section duration to the correct header', () => { - const parsed = [ - { - isClosed: false, - isHeader: true, - line: { - section: 'prepare-script', - content: [{ text: 'foo' }], - }, - lines: [], - }, - { - isClosed: false, - isHeader: true, - line: { - section: 'foo-bar', - content: [{ text: 'foo' }], - }, - lines: [], - }, - ]; - - addDurationToHeader(parsed, duration); - - expect(parsed[0].line.section_duration).toEqual(duration.section_duration); - expect(parsed[1].line.section_duration).toEqual(undefined); - }); - - it('does not add the section duration when the headers do not match', () => { - const parsed = [ - { - isClosed: false, - isHeader: true, - line: { - section: 'bar-foo', - content: [{ text: 'foo' }], - }, - lines: [], - }, - { - isClosed: false, - isHeader: true, - line: { - section: 'foo-bar', - content: [{ text: 'foo' }], - }, - lines: [], - }, - ]; - addDurationToHeader(parsed, duration); - - expect(parsed[0].line.section_duration).toEqual(undefined); - expect(parsed[1].line.section_duration).toEqual(undefined); - }); - - it('does not add when content has no headers', () => { - const parsed = [ - { - section: 'bar-foo', - content: [{ text: 'foo' }], - lineNumber: 1, - }, - { - section: 'foo-bar', - content: [{ text: 'foo' }], - lineNumber: 2, - }, - ]; - - addDurationToHeader(parsed, duration); - - expect(parsed[0].line).toEqual(undefined); - expect(parsed[1].line).toEqual(undefined); - }); - }); - - describe('isCollapsibleSection', () => { - const header = { - isHeader: true, - line: { - section: 'foo', - }, - }; - const line = { - lineNumber: 1, - section: 'foo', - content: [], - }; - - it('returns true when line belongs to the last section', () => { - expect(isCollapsibleSection([header], header, { section: 'foo', content: [] })).toEqual(true); - }); - - it('returns false when last line was not an header', () => { - expect(isCollapsibleSection([line], line, { section: 'bar' })).toEqual(false); - }); - - it('returns false when accumulator is empty', () => { - expect(isCollapsibleSection([], { isHeader: true }, { section: 'bar' })).toEqual(false); - }); - - it('returns false when section_duration is defined', () => { - expect(isCollapsibleSection([header], header, { section_duration: '10:00' })).toEqual(false); - }); - - it('returns false when `section` is not a match', () => { - expect(isCollapsibleSection([header], header, { section: 'bar' })).toEqual(false); - }); - - it('returns false when no parameters are provided', () => { - expect(isCollapsibleSection()).toEqual(false); - }); - }); - describe('logLinesParser', () => { - let result; - - beforeEach(() => { - result = logLinesParser(utilsMockData); - }); - - describe('regular line', () => { - it('adds a lineNumber property with correct index', () => { - expect(result[0].lineNumber).toEqual(0); - expect(result[1].line.lineNumber).toEqual(1); - }); - }); - - describe('collapsible section', () => { - it('adds a `isClosed` property', () => { - expect(result[1].isClosed).toEqual(false); - }); - - it('adds a `isHeader` property', () => { - expect(result[1].isHeader).toEqual(true); - }); - - it('creates a lines array property with the content of the collapsible section', () => { - expect(result[1].lines.length).toEqual(2); - expect(result[1].lines[0].content).toEqual(utilsMockData[2].content); - expect(result[1].lines[1].content).toEqual(utilsMockData[3].content); - }); - }); - - describe('section duration', () => { - it('adds the section information to the header section', () => { - expect(result[1].line.section_duration).toEqual(utilsMockData[4].section_duration); - }); - - it('does not add section duration as a line', () => { - expect(result[1].lines.includes(utilsMockData[4])).toEqual(false); - }); - }); - }); - - describe('findOffsetAndRemove', () => { - describe('when last item is header', () => { - const existingLog = [ - { - isHeader: true, - isClosed: false, - line: { content: [{ text: 'bar' }], offset: 10, lineNumber: 1 }, - }, - ]; - - describe('and matches the offset', () => { - it('returns an array with the item removed', () => { - const newData = [{ offset: 10, content: [{ text: 'foobar' }] }]; - const result = findOffsetAndRemove(newData, existingLog); - - expect(result).toEqual([]); - }); - }); - - describe('and does not match the offset', () => { - it('returns the provided existing log', () => { - const newData = [{ offset: 110, content: [{ text: 'foobar' }] }]; - const result = findOffsetAndRemove(newData, existingLog); - - expect(result).toEqual(existingLog); - }); - }); - }); - - describe('when last item is a regular line', () => { - const existingLog = [{ content: [{ text: 'bar' }], offset: 10, lineNumber: 1 }]; - - describe('and matches the offset', () => { - it('returns an array with the item removed', () => { - const newData = [{ offset: 10, content: [{ text: 'foobar' }] }]; - const result = findOffsetAndRemove(newData, existingLog); - - expect(result).toEqual([]); - }); - }); - - describe('and does not match the fofset', () => { - it('returns the provided old log', () => { - const newData = [{ offset: 101, content: [{ text: 'foobar' }] }]; - const result = findOffsetAndRemove(newData, existingLog); - - expect(result).toEqual(existingLog); - }); - }); - }); - - describe('when last item is nested', () => { - const existingLog = [ - { - isHeader: true, - isClosed: false, - lines: [{ offset: 101, content: [{ text: 'foobar' }], lineNumber: 2 }], - line: { - offset: 10, - lineNumber: 1, - section_duration: '10:00', - }, - }, - ]; - - describe('and matches the offset', () => { - it('returns an array with the last nested line item removed', () => { - const newData = [{ offset: 101, content: [{ text: 'foobar' }] }]; - - const result = findOffsetAndRemove(newData, existingLog); - expect(result[0].lines).toEqual([]); - }); - }); - - describe('and does not match the offset', () => { - it('returns the provided old log', () => { - const newData = [{ offset: 120, content: [{ text: 'foobar' }] }]; - - const result = findOffsetAndRemove(newData, existingLog); - expect(result).toEqual(existingLog); - }); - }); - }); - - describe('when no data is provided', () => { - it('returns an empty array', () => { - const result = findOffsetAndRemove(); - expect(result).toEqual([]); - }); - }); - }); - - describe('getIncrementalLineNumber', () => { - describe('when last line is 0', () => { - it('returns 1', () => { - const log = [ - { - content: [], - lineNumber: 0, - }, - ]; - - expect(getIncrementalLineNumber(log)).toEqual(1); - }); - }); - - describe('with unnested line', () => { - it('returns the lineNumber of the last item in the array', () => { - const log = [ - { - content: [], - lineNumber: 10, - }, - { - content: [], - lineNumber: 101, - }, - ]; - - expect(getIncrementalLineNumber(log)).toEqual(102); - }); - }); - - describe('when last line is the header section', () => { - it('returns the lineNumber of the last item in the array', () => { - const log = [ - { - content: [], - lineNumber: 10, - }, - { - isHeader: true, - line: { - lineNumber: 101, - content: [], - }, - lines: [], - }, - ]; - - expect(getIncrementalLineNumber(log)).toEqual(102); - }); - }); - - describe('when last line is a nested line', () => { - it('returns the lineNumber of the last item in the nested array', () => { - const log = [ - { - content: [], - lineNumber: 10, - }, - { - isHeader: true, - line: { - lineNumber: 101, - content: [], - }, - lines: [ - { - lineNumber: 102, - content: [], - }, - { lineNumber: 103, content: [] }, - ], - }, - ]; - - expect(getIncrementalLineNumber(log)).toEqual(104); - }); - }); - }); - - describe('updateIncrementalJobLog', () => { - describe('without repeated section', () => { - it('concats and parses both arrays', () => { - const oldLog = logLinesParser(originalTrace); - const result = updateIncrementalJobLog(regularIncremental, oldLog); - - expect(result).toEqual([ - { - offset: 1, - content: [ - { - text: 'Downloading', - }, - ], - lineNumber: 0, - }, - { - offset: 2, - content: [ - { - text: 'log line', - }, - ], - lineNumber: 1, - }, - ]); - }); - }); - - describe('with regular line repeated offset', () => { - it('updates the last line and formats with the incremental part', () => { - const oldLog = logLinesParser(originalTrace); - const result = updateIncrementalJobLog(regularIncrementalRepeated, oldLog); - - expect(result).toEqual([ - { - offset: 1, - content: [ - { - text: 'log line', - }, - ], - lineNumber: 0, - }, - ]); - }); - }); - - describe('with header line repeated', () => { - it('updates the header line and formats with the incremental part', () => { - const oldLog = logLinesParser(headerTrace); - const result = updateIncrementalJobLog(headerTraceIncremental, oldLog); - - expect(result).toEqual([ - { - isClosed: false, - isHeader: true, - line: { - offset: 1, - section_header: true, - content: [ - { - text: 'updated log line', - }, - ], - section: 'section', - lineNumber: 0, - }, - lines: [], - }, - ]); - }); - }); - - describe('with collapsible line repeated', () => { - it('updates the collapsible line and formats with the incremental part', () => { - const oldLog = logLinesParser(collapsibleTrace); - const result = updateIncrementalJobLog(collapsibleTraceIncremental, oldLog); - - expect(result).toEqual([ - { - isClosed: false, - isHeader: true, - line: { - offset: 1, - section_header: true, - content: [ - { - text: 'log line', - }, - ], - section: 'section', - lineNumber: 0, - }, - lines: [ - { - offset: 2, - content: [ - { - text: 'updated log line', - }, - ], - section: 'section', - lineNumber: 1, - }, - ], - }, - ]); - }); - }); - }); -}); |