diff options
Diffstat (limited to 'spec/javascripts')
94 files changed, 8260 insertions, 3177 deletions
diff --git a/spec/javascripts/blob/balsamiq/balsamiq_viewer_spec.js b/spec/javascripts/blob/balsamiq/balsamiq_viewer_spec.js index fd73fb4bfcc..d175c8ba853 100644 --- a/spec/javascripts/blob/balsamiq/balsamiq_viewer_spec.js +++ b/spec/javascripts/blob/balsamiq/balsamiq_viewer_spec.js @@ -1,8 +1,10 @@ import sqljs from 'sql.js'; +import axios from '~/lib/utils/axios_utils'; import BalsamiqViewer from '~/blob/balsamiq/balsamiq_viewer'; import ClassSpecHelper from '../../helpers/class_spec_helper'; describe('BalsamiqViewer', () => { + const mockArrayBuffer = new ArrayBuffer(10); let balsamiqViewer; let viewer; @@ -19,44 +21,65 @@ describe('BalsamiqViewer', () => { }); describe('loadFile', () => { - let xhr; - let loadFile; + let bv; const endpoint = 'endpoint'; + const requestSuccess = Promise.resolve({ + data: mockArrayBuffer, + status: 200, + }); beforeEach(() => { - xhr = jasmine.createSpyObj('xhr', ['open', 'send']); + viewer = {}; + bv = new BalsamiqViewer(viewer); + }); - balsamiqViewer = jasmine.createSpyObj('balsamiqViewer', ['renderFile']); + it('should call `axios.get` on `endpoint` param with responseType set to `arraybuffer', () => { + spyOn(axios, 'get').and.returnValue(requestSuccess); + spyOn(bv, 'renderFile').and.stub(); - spyOn(window, 'XMLHttpRequest').and.returnValue(xhr); + bv.loadFile(endpoint); - loadFile = BalsamiqViewer.prototype.loadFile.call(balsamiqViewer, endpoint); + expect(axios.get).toHaveBeenCalledWith( + endpoint, + jasmine.objectContaining({ + responseType: 'arraybuffer', + }), + ); }); - it('should call .open', () => { - expect(xhr.open).toHaveBeenCalledWith('GET', endpoint, true); - }); + it('should call `renderFile` on request success', done => { + spyOn(axios, 'get').and.returnValue(requestSuccess); + spyOn(bv, 'renderFile').and.callFake(() => {}); - it('should set .responseType', () => { - expect(xhr.responseType).toBe('arraybuffer'); + bv.loadFile(endpoint) + .then(() => { + expect(bv.renderFile).toHaveBeenCalledWith(mockArrayBuffer); + }) + .then(done) + .catch(done.fail); }); - it('should call .send', () => { - expect(xhr.send).toHaveBeenCalled(); - }); + it('should not call `renderFile` on request failure', done => { + spyOn(axios, 'get').and.returnValue(Promise.reject()); + spyOn(bv, 'renderFile'); - it('should return a promise', () => { - expect(loadFile).toEqual(jasmine.any(Promise)); + bv.loadFile(endpoint) + .then(() => { + done.fail('Expected loadFile to throw error!'); + }) + .catch(() => { + expect(bv.renderFile).not.toHaveBeenCalled(); + }) + .then(done) + .catch(done.fail); }); }); describe('renderFile', () => { let container; - let loadEvent; let previews; beforeEach(() => { - loadEvent = { target: { response: {} } }; viewer = jasmine.createSpyObj('viewer', ['appendChild']); previews = [document.createElement('ul'), document.createElement('ul')]; @@ -73,11 +96,11 @@ describe('BalsamiqViewer', () => { container = containerElement; }); - BalsamiqViewer.prototype.renderFile.call(balsamiqViewer, loadEvent); + BalsamiqViewer.prototype.renderFile.call(balsamiqViewer, mockArrayBuffer); }); it('should call .initDatabase', () => { - expect(balsamiqViewer.initDatabase).toHaveBeenCalledWith(loadEvent.target.response); + expect(balsamiqViewer.initDatabase).toHaveBeenCalledWith(mockArrayBuffer); }); it('should call .getPreviews', () => { diff --git a/spec/javascripts/blob/viewer/index_spec.js b/spec/javascripts/blob/viewer/index_spec.js index 4ac15ca5aa2..06c06613887 100644 --- a/spec/javascripts/blob/viewer/index_spec.js +++ b/spec/javascripts/blob/viewer/index_spec.js @@ -101,7 +101,7 @@ describe('Blob viewer', () => { it('has tooltip when disabled', () => { expect(copyButton.getAttribute('data-original-title')).toBe( - 'Switch to the source to copy it to the clipboard', + 'Switch to the source to copy the file contents', ); }); @@ -136,7 +136,7 @@ describe('Blob viewer', () => { document.querySelector('.js-blob-viewer-switch-btn[data-viewer="simple"]').click(); setTimeout(() => { - expect(copyButton.getAttribute('data-original-title')).toBe('Copy source to clipboard'); + expect(copyButton.getAttribute('data-original-title')).toBe('Copy file contents'); done(); }); diff --git a/spec/javascripts/boards/board_card_spec.js b/spec/javascripts/boards/board_card_spec.js index 13b708a03d5..9f441ca319e 100644 --- a/spec/javascripts/boards/board_card_spec.js +++ b/spec/javascripts/boards/board_card_spec.js @@ -67,6 +67,16 @@ describe('Board card', () => { expect(vm.issueDetailVisible).toBe(true); }); + it("returns false when multiSelect doesn't contain issue", () => { + expect(vm.multiSelectVisible).toBe(false); + }); + + it('returns true when multiSelect contains issue', () => { + boardsStore.multiSelect.list = [vm.issue]; + + expect(vm.multiSelectVisible).toBe(true); + }); + it('adds user-can-drag class if not disabled', () => { expect(vm.$el.classList.contains('user-can-drag')).toBe(true); }); @@ -180,7 +190,7 @@ describe('Board card', () => { triggerEvent('mousedown'); triggerEvent('mouseup'); - expect(eventHub.$emit).toHaveBeenCalledWith('newDetailIssue', vm.issue); + expect(eventHub.$emit).toHaveBeenCalledWith('newDetailIssue', vm.issue, undefined); expect(boardsStore.detail.list).toEqual(vm.list); }); @@ -203,7 +213,7 @@ describe('Board card', () => { triggerEvent('mousedown'); triggerEvent('mouseup'); - expect(eventHub.$emit).toHaveBeenCalledWith('clearDetailIssue'); + expect(eventHub.$emit).toHaveBeenCalledWith('clearDetailIssue', undefined); }); }); }); diff --git a/spec/javascripts/boards/boards_store_spec.js b/spec/javascripts/boards/boards_store_spec.js index 11352140ba4..678fe5befa8 100644 --- a/spec/javascripts/boards/boards_store_spec.js +++ b/spec/javascripts/boards/boards_store_spec.js @@ -12,6 +12,7 @@ import '~/boards/services/board_service'; import boardsStore from '~/boards/stores/boards_store'; import eventHub from '~/boards/eventhub'; import { listObj, listObjDuplicate, boardsMockInterceptor, mockBoardService } from './mock_data'; +import waitForPromises from '../../frontend/helpers/wait_for_promises'; describe('Store', () => { let mock; @@ -29,6 +30,13 @@ describe('Store', () => { }), ); + spyOn(gl.boardService, 'moveMultipleIssues').and.callFake( + () => + new Promise(resolve => { + resolve(); + }), + ); + Cookies.set('issue_board_welcome_hidden', 'false', { expires: 365 * 10, path: '', @@ -376,4 +384,128 @@ describe('Store', () => { expect(state.currentBoard).toEqual(dummyBoard); }); }); + + describe('toggleMultiSelect', () => { + let basicIssueObj; + + beforeAll(() => { + basicIssueObj = { id: 987654 }; + }); + + afterEach(() => { + boardsStore.clearMultiSelect(); + }); + + it('adds issue when not present', () => { + boardsStore.toggleMultiSelect(basicIssueObj); + + const selectedIds = boardsStore.multiSelect.list.map(x => x.id); + + expect(selectedIds.includes(basicIssueObj.id)).toEqual(true); + }); + + it('removes issue when issue is present', () => { + boardsStore.toggleMultiSelect(basicIssueObj); + let selectedIds = boardsStore.multiSelect.list.map(x => x.id); + + expect(selectedIds.includes(basicIssueObj.id)).toEqual(true); + + boardsStore.toggleMultiSelect(basicIssueObj); + selectedIds = boardsStore.multiSelect.list.map(x => x.id); + + expect(selectedIds.includes(basicIssueObj.id)).toEqual(false); + }); + }); + + describe('clearMultiSelect', () => { + it('clears all the multi selected issues', () => { + const issue1 = { id: 12345 }; + const issue2 = { id: 12346 }; + + boardsStore.toggleMultiSelect(issue1); + boardsStore.toggleMultiSelect(issue2); + + expect(boardsStore.multiSelect.list.length).toEqual(2); + + boardsStore.clearMultiSelect(); + + expect(boardsStore.multiSelect.list.length).toEqual(0); + }); + }); + + describe('moveMultipleIssuesToList', () => { + it('move issues on the new index', done => { + const listOne = boardsStore.addList(listObj); + const listTwo = boardsStore.addList(listObjDuplicate); + + expect(boardsStore.state.lists.length).toBe(2); + + setTimeout(() => { + expect(listOne.issues.length).toBe(1); + expect(listTwo.issues.length).toBe(1); + + boardsStore.moveMultipleIssuesToList({ + listFrom: listOne, + listTo: listTwo, + issues: listOne.issues, + newIndex: 0, + }); + + expect(listTwo.issues.length).toBe(1); + + done(); + }, 0); + }); + }); + + describe('moveMultipleIssuesInList', () => { + it('moves multiple issues in list', done => { + const issueObj = { + title: 'Issue #1', + id: 12345, + iid: 2, + confidential: false, + labels: [], + assignees: [], + }; + const issue1 = new ListIssue(issueObj); + const issue2 = new ListIssue({ + ...issueObj, + title: 'Issue #2', + id: 12346, + }); + + const list = boardsStore.addList(listObj); + + waitForPromises() + .then(() => { + list.addIssue(issue1); + list.addIssue(issue2); + + expect(list.issues.length).toBe(3); + expect(list.issues[0].id).not.toBe(issue2.id); + + boardsStore.moveMultipleIssuesInList({ + list, + issues: [issue1, issue2], + oldIndicies: [0], + newIndex: 1, + idArray: [1, 12345, 12346], + }); + + expect(list.issues[0].id).toBe(issue1.id); + + expect(gl.boardService.moveMultipleIssues).toHaveBeenCalledWith({ + ids: [issue1.id, issue2.id], + fromListId: null, + toListId: null, + moveBeforeId: 1, + moveAfterId: null, + }); + + done(); + }) + .catch(done.fail); + }); + }); }); diff --git a/spec/javascripts/boards/issue_card_spec.js b/spec/javascripts/boards/issue_card_spec.js index 8a20911cc66..314e051665e 100644 --- a/spec/javascripts/boards/issue_card_spec.js +++ b/spec/javascripts/boards/issue_card_spec.js @@ -32,7 +32,10 @@ describe('Issue card component', () => { beforeEach(() => { setFixtures('<div class="test-container"></div>'); - list = listObj; + list = { + ...listObj, + type: 'label', + }; issue = new ListIssue({ title: 'Testing', id: 1, @@ -42,6 +45,7 @@ describe('Issue card component', () => { assignees: [], reference_path: '#1', real_path: '/test/1', + weight: 1, }); component = new Vue({ @@ -240,8 +244,8 @@ describe('Issue card component', () => { Vue.nextTick(() => done()); }); - it('renders list label', () => { - expect(component.$el.querySelectorAll('.badge').length).toBe(2); + it('does not render list label but renders all other labels', () => { + expect(component.$el.querySelectorAll('.badge').length).toBe(1); }); it('renders label', () => { @@ -277,7 +281,7 @@ describe('Issue card component', () => { Vue.nextTick() .then(() => { - expect(component.$el.querySelectorAll('.badge').length).toBe(2); + expect(component.$el.querySelectorAll('.badge').length).toBe(1); expect(component.$el.textContent).not.toContain('closed'); done(); @@ -285,10 +289,4 @@ describe('Issue card component', () => { .catch(done.fail); }); }); - - describe('weights', () => { - it('not shows weight component', () => { - expect(component.$el.querySelector('.board-card-weight')).toBeNull(); - }); - }); }); diff --git a/spec/javascripts/boards/mock_data.js b/spec/javascripts/boards/mock_data.js index 50ad1442873..41b8f567e08 100644 --- a/spec/javascripts/boards/mock_data.js +++ b/spec/javascripts/boards/mock_data.js @@ -15,7 +15,7 @@ export const listObj = { weight: 3, label: { id: 5000, - title: 'Testing', + title: 'Test', color: 'red', description: 'testing;', textColor: 'white', @@ -30,7 +30,7 @@ export const listObjDuplicate = { weight: 3, label: { id: listObj.label.id, - title: 'Testing', + title: 'Test', color: 'red', description: 'testing;', }, diff --git a/spec/javascripts/commit/commit_pipeline_status_component_spec.js b/spec/javascripts/commit/commit_pipeline_status_component_spec.js deleted file mode 100644 index f6b36e88a5f..00000000000 --- a/spec/javascripts/commit/commit_pipeline_status_component_spec.js +++ /dev/null @@ -1,106 +0,0 @@ -import Vue from 'vue'; -import MockAdapter from 'axios-mock-adapter'; -import axios from '~/lib/utils/axios_utils'; -import commitPipelineStatus from '~/projects/tree/components/commit_pipeline_status_component.vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; - -describe('Commit pipeline status component', () => { - let vm; - let Component; - let mock; - const mockCiStatus = { - details_path: '/root/hello-world/pipelines/1', - favicon: 'canceled.ico', - group: 'canceled', - has_details: true, - icon: 'status_canceled', - label: 'canceled', - text: 'canceled', - }; - - beforeEach(() => { - Component = Vue.extend(commitPipelineStatus); - }); - - describe('While polling pipeline data successfully', () => { - beforeEach(() => { - mock = new MockAdapter(axios); - mock.onGet('/dummy/endpoint').reply(() => { - const res = Promise.resolve([ - 200, - { - pipelines: [ - { - details: { - status: mockCiStatus, - }, - }, - ], - }, - ]); - return res; - }); - vm = mountComponent(Component, { - endpoint: '/dummy/endpoint', - }); - }); - - afterEach(() => { - vm.poll.stop(); - vm.$destroy(); - mock.restore(); - }); - - it('shows the loading icon when polling is starting', done => { - expect(vm.$el.querySelector('.loading-container')).not.toBe(null); - setTimeout(() => { - expect(vm.$el.querySelector('.loading-container')).toBe(null); - done(); - }); - }); - - it('contains a ciStatus when the polling is successful ', done => { - setTimeout(() => { - expect(vm.ciStatus).toEqual(mockCiStatus); - done(); - }); - }); - - it('contains a ci-status icon when polling is successful', done => { - setTimeout(() => { - expect(vm.$el.querySelector('.ci-status-icon')).not.toBe(null); - expect(vm.$el.querySelector('.ci-status-icon').classList).toContain( - `ci-status-icon-${mockCiStatus.group}`, - ); - done(); - }); - }); - }); - - describe('When polling data was not successful', () => { - beforeEach(() => { - mock = new MockAdapter(axios); - mock.onGet('/dummy/endpoint').reply(502, {}); - vm = new Component({ - props: { - endpoint: '/dummy/endpoint', - }, - }); - }); - - afterEach(() => { - vm.poll.stop(); - vm.$destroy(); - mock.restore(); - }); - - it('calls an errorCallback', done => { - spyOn(vm, 'errorCallback').and.callThrough(); - vm.$mount(); - setTimeout(() => { - expect(vm.errorCallback.calls.count()).toEqual(1); - done(); - }); - }); - }); -}); diff --git a/spec/javascripts/create_cluster/.eslintrc.yml b/spec/javascripts/create_cluster/.eslintrc.yml new file mode 100644 index 00000000000..14e318a2f3e --- /dev/null +++ b/spec/javascripts/create_cluster/.eslintrc.yml @@ -0,0 +1,3 @@ +rules: + # https://gitlab.com/gitlab-org/gitlab/issues/33025 + promise/no-nesting: off diff --git a/spec/javascripts/create_cluster/gke_cluster/components/gke_project_id_dropdown_spec.js b/spec/javascripts/create_cluster/gke_cluster/components/gke_project_id_dropdown_spec.js index 809da3f9088..016ecfb35b8 100644 --- a/spec/javascripts/create_cluster/gke_cluster/components/gke_project_id_dropdown_spec.js +++ b/spec/javascripts/create_cluster/gke_cluster/components/gke_project_id_dropdown_spec.js @@ -4,6 +4,7 @@ import { createStore } from '~/create_cluster/gke_cluster/store'; import { SET_PROJECTS } from '~/create_cluster/gke_cluster/store/mutation_types'; import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; import { emptyProjectMock, selectedProjectMock } from '../mock_data'; +import { gapi } from '../helpers'; const componentConfig = { docsUrl: 'https://console.cloud.google.com/home/dashboard', @@ -32,6 +33,16 @@ describe('GkeProjectIdDropdown', () => { let vm; let store; + let originalGapi; + beforeAll(() => { + originalGapi = window.gapi; + window.gapi = gapi(); + }); + + afterAll(() => { + window.gapi = originalGapi; + }); + beforeEach(() => { store = createStore(); vm = createComponent(store); diff --git a/spec/javascripts/create_cluster/gke_cluster/stores/actions_spec.js b/spec/javascripts/create_cluster/gke_cluster/stores/actions_spec.js index a7591cc38c7..7ceaeace82f 100644 --- a/spec/javascripts/create_cluster/gke_cluster/stores/actions_spec.js +++ b/spec/javascripts/create_cluster/gke_cluster/stores/actions_spec.js @@ -64,7 +64,15 @@ describe('GCP Cluster Dropdown Store Actions', () => { }); describe('async fetch methods', () => { - window.gapi = gapi(); + let originalGapi; + beforeAll(() => { + originalGapi = window.gapi; + window.gapi = gapi(); + }); + + afterAll(() => { + window.gapi = originalGapi; + }); describe('fetchProjects', () => { it('fetches projects from Google API', done => { diff --git a/spec/javascripts/flash_spec.js b/spec/javascripts/flash_spec.js index bd8608b6bac..28fa87ac097 100644 --- a/spec/javascripts/flash_spec.js +++ b/spec/javascripts/flash_spec.js @@ -176,7 +176,7 @@ describe('Flash', () => { it('removes element after clicking', () => { flash('test', 'alert', document, null, false, true); - document.querySelector('.flash-alert').click(); + document.querySelector('.flash-alert .js-close-icon').click(); expect(document.querySelector('.flash-alert')).toBeNull(); @@ -210,7 +210,13 @@ describe('Flash', () => { describe('removeFlashClickListener', () => { beforeEach(() => { - document.body.innerHTML += '<div class="flash-container"><div class="flash"></div></div>'; + document.body.innerHTML += ` + <div class="flash-container"> + <div class="flash"> + <div class="close-icon js-close-icon"></div> + </div> + </div> + `; }); it('removes global flash on click', done => { @@ -218,7 +224,7 @@ describe('Flash', () => { removeFlashClickListener(flashEl, false); - flashEl.click(); + flashEl.querySelector('.js-close-icon').click(); setTimeout(() => { expect(document.querySelector('.flash')).toBeNull(); diff --git a/spec/javascripts/frequent_items/components/app_spec.js b/spec/javascripts/frequent_items/components/app_spec.js index 6814f656f5d..36dd8604d08 100644 --- a/spec/javascripts/frequent_items/components/app_spec.js +++ b/spec/javascripts/frequent_items/components/app_spec.js @@ -236,8 +236,15 @@ describe('Frequent Items App Component', () => { .then(() => { expect(vm.$el.querySelector('.loading-animation')).toBeDefined(); }) + + // This test waits for multiple ticks in order to allow the responses to + // propagate through each interceptor installed on the Axios instance. + // This shouldn't be necessary; this test should be refactored to avoid this. + // https://gitlab.com/gitlab-org/gitlab/issues/32479 + .then(vm.$nextTick) .then(vm.$nextTick) .then(vm.$nextTick) + .then(() => { expect(vm.$el.querySelectorAll('.frequent-items-list-container li').length).toBe( mockSearchedProjects.length, diff --git a/spec/javascripts/header_spec.js b/spec/javascripts/header_spec.js index 0ddf589f368..c36d3be1b22 100644 --- a/spec/javascripts/header_spec.js +++ b/spec/javascripts/header_spec.js @@ -20,26 +20,26 @@ describe('Header', function() { }); it('should update todos-count after receiving the todo:toggle event', () => { - triggerToggle('5'); + triggerToggle(5); expect($(todosPendingCount).text()).toEqual('5'); }); it('should hide todos-count when it is 0', () => { - triggerToggle('0'); + triggerToggle(0); expect(isTodosCountHidden()).toEqual(true); }); it('should show todos-count when it is more than 0', () => { - triggerToggle('10'); + triggerToggle(10); expect(isTodosCountHidden()).toEqual(false); }); describe('when todos-count is 1000', () => { beforeEach(() => { - triggerToggle('1000'); + triggerToggle(1000); }); it('should show todos-count', () => { diff --git a/spec/javascripts/helpers/tracking_helper.js b/spec/javascripts/helpers/tracking_helper.js new file mode 100644 index 00000000000..68c1bd2dbca --- /dev/null +++ b/spec/javascripts/helpers/tracking_helper.js @@ -0,0 +1,25 @@ +import Tracking from '~/tracking'; + +export default Tracking; + +let document; +let handlers; + +export function mockTracking(category = '_category_', documentOverride, spyMethod) { + document = documentOverride || window.document; + window.snowplow = () => {}; + Tracking.bindDocument(category, document); + return spyMethod ? spyMethod(Tracking, 'event') : null; +} + +export function unmockTracking() { + window.snowplow = undefined; + handlers.forEach(event => document.removeEventListener(event.name, event.func)); +} + +export function triggerEvent(selectorOrEl, eventName = 'click') { + const event = new Event(eventName, { bubbles: true }); + const el = typeof selectorOrEl === 'string' ? document.querySelector(selectorOrEl) : selectorOrEl; + + el.dispatchEvent(event); +} diff --git a/spec/javascripts/helpers/vue_resource_helper.js b/spec/javascripts/helpers/vue_resource_helper.js deleted file mode 100644 index 0f58af09933..00000000000 --- a/spec/javascripts/helpers/vue_resource_helper.js +++ /dev/null @@ -1,11 +0,0 @@ -// eslint-disable-next-line import/prefer-default-export -export const headersInterceptor = (request, next) => { - next(response => { - const headers = {}; - response.headers.forEach((value, key) => { - headers[key] = value; - }); - // eslint-disable-next-line no-param-reassign - response.headers = headers; - }); -}; diff --git a/spec/javascripts/ide/components/branches/search_list_spec.js b/spec/javascripts/ide/components/branches/search_list_spec.js deleted file mode 100644 index 72a3c2d5dcd..00000000000 --- a/spec/javascripts/ide/components/branches/search_list_spec.js +++ /dev/null @@ -1,80 +0,0 @@ -import Vue from 'vue'; -import store from '~/ide/stores'; -import * as types from '~/ide/stores/modules/branches/mutation_types'; -import List from '~/ide/components/branches/search_list.vue'; -import { createComponentWithStore } from '../../../helpers/vue_mount_component_helper'; -import { branches as testBranches } from '../../mock_data'; -import { resetStore } from '../../helpers'; - -describe('IDE branches search list', () => { - const Component = Vue.extend(List); - let vm; - - beforeEach(() => { - vm = createComponentWithStore(Component, store, {}); - - spyOn(vm, 'fetchBranches'); - - vm.$mount(); - }); - - afterEach(() => { - vm.$destroy(); - - resetStore(store); - }); - - it('calls fetch on mounted', () => { - expect(vm.fetchBranches).toHaveBeenCalledWith({ - search: '', - }); - }); - - it('renders loading icon', done => { - vm.$store.state.branches.isLoading = true; - - vm.$nextTick() - .then(() => { - expect(vm.$el).toContainElement('.loading-container'); - }) - .then(done) - .catch(done.fail); - }); - - it('renders branches not found when search is not empty', done => { - vm.search = 'testing'; - - vm.$nextTick(() => { - expect(vm.$el).toContainText('No branches found'); - - done(); - }); - }); - - describe('with branches', () => { - const currentBranch = testBranches[1]; - - beforeEach(done => { - vm.$store.state.currentBranchId = currentBranch.name; - vm.$store.commit(`branches/${types.RECEIVE_BRANCHES_SUCCESS}`, testBranches); - - vm.$nextTick(done); - }); - - it('renders list', () => { - const elementText = Array.from(vm.$el.querySelectorAll('li strong')).map(x => - x.textContent.trim(), - ); - - expect(elementText).toEqual(testBranches.map(x => x.name)); - }); - - it('renders check next to active branch', () => { - const checkedText = Array.from(vm.$el.querySelectorAll('li')) - .filter(x => x.querySelector('.ide-search-list-current-icon svg')) - .map(x => x.querySelector('strong').textContent.trim()); - - expect(checkedText).toEqual([currentBranch.name]); - }); - }); -}); diff --git a/spec/javascripts/ide/components/commit_sidebar/list_item_spec.js b/spec/javascripts/ide/components/commit_sidebar/list_item_spec.js index bf48d7bfdad..c1dcd4928a0 100644 --- a/spec/javascripts/ide/components/commit_sidebar/list_item_spec.js +++ b/spec/javascripts/ide/components/commit_sidebar/list_item_spec.js @@ -2,12 +2,14 @@ import Vue from 'vue'; import store from '~/ide/stores'; import listItem from '~/ide/components/commit_sidebar/list_item.vue'; import router from '~/ide/ide_router'; +import { trimText } from 'spec/helpers/text_helper'; import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; import { file, resetStore } from '../../helpers'; describe('Multi-file editor commit sidebar list item', () => { let vm; let f; + let findPathEl; beforeEach(() => { const Component = Vue.extend(listItem); @@ -21,6 +23,8 @@ describe('Multi-file editor commit sidebar list item', () => { actionComponent: 'stage-button', activeFileKey: `staged-${f.key}`, }).$mount(); + + findPathEl = vm.$el.querySelector('.multi-file-commit-list-path'); }); afterEach(() => { @@ -29,15 +33,39 @@ describe('Multi-file editor commit sidebar list item', () => { resetStore(store); }); + const findPathText = () => trimText(findPathEl.textContent); + it('renders file path', () => { - expect(vm.$el.querySelector('.multi-file-commit-list-path').textContent).toContain(f.path); + expect(findPathText()).toContain(f.path); + }); + + it('correctly renders renamed entries', done => { + Vue.set(vm.file, 'prevName', 'Old name'); + + vm.$nextTick() + .then(() => { + expect(findPathText()).toEqual(`Old name → ${f.name}`); + }) + .then(done) + .catch(done.fail); + }); + + it('correctly renders entry, the name of which did not change after rename (as within a folder)', done => { + Vue.set(vm.file, 'prevName', f.name); + + vm.$nextTick() + .then(() => { + expect(findPathText()).toEqual(f.name); + }) + .then(done) + .catch(done.fail); }); it('opens a closed file in the editor when clicking the file path', done => { spyOn(vm, 'openPendingTab').and.callThrough(); spyOn(router, 'push'); - vm.$el.querySelector('.multi-file-commit-list-path').click(); + findPathEl.click(); setTimeout(() => { expect(vm.openPendingTab).toHaveBeenCalled(); @@ -52,7 +80,7 @@ describe('Multi-file editor commit sidebar list item', () => { spyOn(vm, 'updateViewer').and.callThrough(); spyOn(router, 'push'); - vm.$el.querySelector('.multi-file-commit-list-path').click(); + findPathEl.click(); setTimeout(() => { expect(vm.updateViewer).toHaveBeenCalledWith('diff'); diff --git a/spec/javascripts/ide/components/error_message_spec.js b/spec/javascripts/ide/components/error_message_spec.js deleted file mode 100644 index 80d6c7fd564..00000000000 --- a/spec/javascripts/ide/components/error_message_spec.js +++ /dev/null @@ -1,106 +0,0 @@ -import Vue from 'vue'; -import store from '~/ide/stores'; -import ErrorMessage from '~/ide/components/error_message.vue'; -import { createComponentWithStore } from '../../helpers/vue_mount_component_helper'; -import { resetStore } from '../helpers'; - -describe('IDE error message component', () => { - const Component = Vue.extend(ErrorMessage); - let vm; - - beforeEach(() => { - vm = createComponentWithStore(Component, store, { - message: { - text: 'error message', - action: null, - actionText: null, - }, - }).$mount(); - }); - - afterEach(() => { - vm.$destroy(); - resetStore(vm.$store); - }); - - it('renders error message', () => { - expect(vm.$el.textContent).toContain('error message'); - }); - - it('clears error message on click', () => { - spyOn(vm, 'setErrorMessage'); - - vm.$el.click(); - - expect(vm.setErrorMessage).toHaveBeenCalledWith(null); - }); - - describe('with action', () => { - let actionSpy; - - beforeEach(done => { - actionSpy = jasmine.createSpy('action').and.returnValue(Promise.resolve()); - - vm.message.action = actionSpy; - vm.message.actionText = 'test action'; - vm.message.actionPayload = 'testActionPayload'; - - vm.$nextTick(done); - }); - - it('renders action button', () => { - expect(vm.$el.querySelector('.flash-action')).not.toBe(null); - expect(vm.$el.textContent).toContain('test action'); - }); - - it('does not clear error message on click', () => { - spyOn(vm, 'setErrorMessage'); - - vm.$el.click(); - - expect(vm.setErrorMessage).not.toHaveBeenCalled(); - }); - - it('dispatches action', done => { - vm.$el.querySelector('.flash-action').click(); - - vm.$nextTick(() => { - expect(actionSpy).toHaveBeenCalledWith('testActionPayload'); - - done(); - }); - }); - - it('does not dispatch action when already loading', () => { - vm.isLoading = true; - - vm.$el.querySelector('.flash-action').click(); - - expect(actionSpy).not.toHaveBeenCalledWith(); - }); - - it('resets isLoading after click', done => { - vm.$el.querySelector('.flash-action').click(); - - expect(vm.isLoading).toBe(true); - - setTimeout(() => { - expect(vm.isLoading).toBe(false); - - done(); - }); - }); - - it('shows loading icon when isLoading is true', done => { - expect(vm.$el.querySelector('.loading-container').style.display).not.toBe(''); - - vm.isLoading = true; - - vm.$nextTick(() => { - expect(vm.$el.querySelector('.loading-container').style.display).toBe(''); - - done(); - }); - }); - }); -}); diff --git a/spec/javascripts/ide/components/file_row_extra_spec.js b/spec/javascripts/ide/components/file_row_extra_spec.js index d7fed3f0681..86146fcef69 100644 --- a/spec/javascripts/ide/components/file_row_extra_spec.js +++ b/spec/javascripts/ide/components/file_row_extra_spec.js @@ -139,6 +139,27 @@ describe('IDE extra file row component', () => { done(); }); }); + + it('shows when file is renamed', done => { + vm.file.prevPath = 'original-file'; + + vm.$nextTick(() => { + expect(vm.$el.querySelector('.file-changed-icon')).not.toBe(null); + + done(); + }); + }); + + it('hides when file is renamed', done => { + vm.file.prevPath = 'original-file'; + vm.file.type = 'tree'; + + vm.$nextTick(() => { + expect(vm.$el.querySelector('.file-changed-icon')).toBe(null); + + done(); + }); + }); }); describe('merge request icon', () => { diff --git a/spec/javascripts/ide/components/file_templates/dropdown_spec.js b/spec/javascripts/ide/components/file_templates/dropdown_spec.js deleted file mode 100644 index 898796f4fa0..00000000000 --- a/spec/javascripts/ide/components/file_templates/dropdown_spec.js +++ /dev/null @@ -1,201 +0,0 @@ -import $ from 'jquery'; -import Vue from 'vue'; -import { createStore } from '~/ide/stores'; -import Dropdown from '~/ide/components/file_templates/dropdown.vue'; -import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; -import { resetStore } from '../../helpers'; - -describe('IDE file templates dropdown component', () => { - let Component; - let vm; - - beforeAll(() => { - Component = Vue.extend(Dropdown); - }); - - beforeEach(() => { - const store = createStore(); - - vm = createComponentWithStore(Component, store, { - label: 'Test', - }).$mount(); - }); - - afterEach(() => { - vm.$destroy(); - resetStore(vm.$store); - }); - - describe('async', () => { - beforeEach(() => { - vm.isAsyncData = true; - }); - - it('calls async store method on Bootstrap dropdown event', () => { - spyOn(vm, 'fetchTemplateTypes').and.stub(); - - $(vm.$el).trigger('show.bs.dropdown'); - - expect(vm.fetchTemplateTypes).toHaveBeenCalled(); - }); - - it('renders templates when async', done => { - vm.$store.state.fileTemplates.templates = [ - { - name: 'test', - }, - ]; - - vm.$nextTick(() => { - expect(vm.$el.querySelector('.dropdown-content').textContent).toContain('test'); - - done(); - }); - }); - - it('renders loading icon when isLoading is true', done => { - vm.$store.state.fileTemplates.isLoading = true; - - vm.$nextTick(() => { - expect(vm.$el.querySelector('.loading-container')).not.toBe(null); - - done(); - }); - }); - - it('searches template data', () => { - vm.$store.state.fileTemplates.templates = [ - { - name: 'test', - }, - ]; - vm.searchable = true; - vm.search = 'hello'; - - expect(vm.outputData).toEqual([]); - }); - - it('does not filter data is searchable is false', () => { - vm.$store.state.fileTemplates.templates = [ - { - name: 'test', - }, - ]; - vm.search = 'hello'; - - expect(vm.outputData).toEqual([ - { - name: 'test', - }, - ]); - }); - - it('calls clickItem on click', done => { - spyOn(vm, 'clickItem').and.stub(); - - vm.$store.state.fileTemplates.templates = [ - { - name: 'test', - }, - ]; - - vm.$nextTick(() => { - vm.$el.querySelector('.dropdown-content button').click(); - - expect(vm.clickItem).toHaveBeenCalledWith({ - name: 'test', - }); - - done(); - }); - }); - - it('renders input when searchable is true', done => { - vm.searchable = true; - - vm.$nextTick(() => { - expect(vm.$el.querySelector('.dropdown-input')).not.toBe(null); - - done(); - }); - }); - - it('does not render input when searchable is true & showLoading is true', done => { - vm.searchable = true; - vm.$store.state.fileTemplates.isLoading = true; - - vm.$nextTick(() => { - expect(vm.$el.querySelector('.dropdown-input')).toBe(null); - - done(); - }); - }); - }); - - describe('sync', () => { - beforeEach(done => { - vm.data = [ - { - name: 'test sync', - }, - ]; - - vm.$nextTick(done); - }); - - it('renders props data', () => { - expect(vm.$el.querySelector('.dropdown-content').textContent).toContain('test sync'); - }); - - it('renders input when searchable is true', done => { - vm.searchable = true; - - vm.$nextTick(() => { - expect(vm.$el.querySelector('.dropdown-input')).not.toBe(null); - - done(); - }); - }); - - it('calls clickItem on click', done => { - spyOn(vm, 'clickItem').and.stub(); - - vm.$nextTick(() => { - vm.$el.querySelector('.dropdown-content button').click(); - - expect(vm.clickItem).toHaveBeenCalledWith({ - name: 'test sync', - }); - - done(); - }); - }); - - it('searches template data', () => { - vm.searchable = true; - vm.search = 'hello'; - - expect(vm.outputData).toEqual([]); - }); - - it('does not filter data is searchable is false', () => { - vm.search = 'hello'; - - expect(vm.outputData).toEqual([ - { - name: 'test sync', - }, - ]); - }); - - it('renders dropdown title', done => { - vm.title = 'Test title'; - - vm.$nextTick(() => { - expect(vm.$el.querySelector('.dropdown-title').textContent).toContain('Test title'); - - done(); - }); - }); - }); -}); diff --git a/spec/javascripts/ide/components/ide_tree_list_spec.js b/spec/javascripts/ide/components/ide_tree_list_spec.js index 554bd1ae3b5..f63007c7dd2 100644 --- a/spec/javascripts/ide/components/ide_tree_list_spec.js +++ b/spec/javascripts/ide/components/ide_tree_list_spec.js @@ -58,20 +58,6 @@ describe('IDE tree list', () => { it('renders list of files', () => { expect(vm.$el.textContent).toContain('fileName'); }); - - it('does not render moved entries', done => { - const tree = [file('moved entry'), file('normal entry')]; - tree[0].moved = true; - store.state.trees['abcproject/master'].tree = tree; - const container = vm.$el.querySelector('.ide-tree-body'); - - vm.$nextTick(() => { - expect(container.children.length).toBe(1); - expect(vm.$el.textContent).not.toContain('moved entry'); - expect(vm.$el.textContent).toContain('normal entry'); - done(); - }); - }); }); describe('empty-branch state', () => { diff --git a/spec/javascripts/ide/components/jobs/list_spec.js b/spec/javascripts/ide/components/jobs/list_spec.js deleted file mode 100644 index b24853c56fa..00000000000 --- a/spec/javascripts/ide/components/jobs/list_spec.js +++ /dev/null @@ -1,67 +0,0 @@ -import Vue from 'vue'; -import StageList from '~/ide/components/jobs/list.vue'; -import { createStore } from '~/ide/stores'; -import { createComponentWithStore } from '../../../helpers/vue_mount_component_helper'; -import { stages, jobs } from '../../mock_data'; - -describe('IDE stages list', () => { - const Component = Vue.extend(StageList); - let vm; - - beforeEach(() => { - const store = createStore(); - - vm = createComponentWithStore(Component, store, { - stages: stages.map((mappedState, i) => ({ - ...mappedState, - id: i, - dropdownPath: mappedState.dropdown_path, - jobs: [...jobs], - isLoading: false, - isCollapsed: false, - })), - loading: false, - }); - - spyOn(vm, 'fetchJobs'); - spyOn(vm, 'toggleStageCollapsed'); - - vm.$mount(); - }); - - afterEach(() => { - vm.$destroy(); - }); - - it('renders list of stages', () => { - expect(vm.$el.querySelectorAll('.card').length).toBe(2); - }); - - it('renders loading icon when no stages & is loading', done => { - vm.stages = []; - vm.loading = true; - - vm.$nextTick(() => { - expect(vm.$el.querySelector('.loading-container')).not.toBe(null); - - done(); - }); - }); - - it('calls toggleStageCollapsed when clicking stage header', done => { - vm.$el.querySelector('.card-header').click(); - - vm.$nextTick(() => { - expect(vm.toggleStageCollapsed).toHaveBeenCalledWith(0); - - done(); - }); - }); - - it('calls fetchJobs when stage is mounted', () => { - expect(vm.fetchJobs.calls.count()).toBe(stages.length); - - expect(vm.fetchJobs.calls.argsFor(0)).toEqual([vm.stages[0]]); - expect(vm.fetchJobs.calls.argsFor(1)).toEqual([vm.stages[1]]); - }); -}); diff --git a/spec/javascripts/ide/components/merge_requests/list_spec.js b/spec/javascripts/ide/components/merge_requests/list_spec.js deleted file mode 100644 index 55e4f46d9ca..00000000000 --- a/spec/javascripts/ide/components/merge_requests/list_spec.js +++ /dev/null @@ -1,159 +0,0 @@ -import Vue from 'vue'; -import store from '~/ide/stores'; -import List from '~/ide/components/merge_requests/list.vue'; -import { createComponentWithStore } from '../../../helpers/vue_mount_component_helper'; -import { mergeRequests } from '../../mock_data'; -import { resetStore } from '../../helpers'; - -describe('IDE merge requests list', () => { - const Component = Vue.extend(List); - let vm; - - beforeEach(() => { - vm = createComponentWithStore(Component, store, {}); - - spyOn(vm, 'fetchMergeRequests'); - - vm.$mount(); - }); - - afterEach(() => { - vm.$destroy(); - - resetStore(vm.$store); - }); - - it('calls fetch on mounted', () => { - expect(vm.fetchMergeRequests).toHaveBeenCalledWith({ - search: '', - type: '', - }); - }); - - it('renders loading icon', done => { - vm.$store.state.mergeRequests.isLoading = true; - - vm.$nextTick(() => { - expect(vm.$el.querySelector('.loading-container')).not.toBe(null); - - done(); - }); - }); - - it('renders no search results text when search is not empty', done => { - vm.search = 'testing'; - - vm.$nextTick(() => { - expect(vm.$el.textContent).toContain('No merge requests found'); - - done(); - }); - }); - - it('clicking on search type, sets currentSearchType and loads merge requests', done => { - vm.onSearchFocus(); - - vm.$nextTick() - .then(() => { - vm.$el.querySelector('li button').click(); - - return vm.$nextTick(); - }) - .then(() => { - expect(vm.currentSearchType).toEqual(vm.$options.searchTypes[0]); - expect(vm.fetchMergeRequests).toHaveBeenCalledWith({ - type: vm.currentSearchType.type, - search: '', - }); - }) - .then(done) - .catch(done.fail); - }); - - describe('with merge requests', () => { - beforeEach(done => { - vm.$store.state.mergeRequests.mergeRequests.push({ - ...mergeRequests[0], - projectPathWithNamespace: 'gitlab-org/gitlab-ce', - }); - - vm.$nextTick(done); - }); - - it('renders list', () => { - expect(vm.$el.querySelectorAll('li').length).toBe(1); - expect(vm.$el.querySelector('li').textContent).toContain(mergeRequests[0].title); - }); - }); - - describe('searchMergeRequests', () => { - beforeEach(() => { - spyOn(vm, 'loadMergeRequests'); - - jasmine.clock().install(); - }); - - afterEach(() => { - jasmine.clock().uninstall(); - }); - - it('calls loadMergeRequests on input in search field', () => { - const event = new Event('input'); - - vm.$el.querySelector('input').dispatchEvent(event); - - jasmine.clock().tick(300); - - expect(vm.loadMergeRequests).toHaveBeenCalled(); - }); - }); - - describe('onSearchFocus', () => { - it('shows search types', done => { - vm.$el.querySelector('input').dispatchEvent(new Event('focus')); - - expect(vm.hasSearchFocus).toBe(true); - expect(vm.showSearchTypes).toBe(true); - - vm.$nextTick() - .then(() => { - const expectedSearchTypes = vm.$options.searchTypes.map(x => x.label); - const renderedSearchTypes = Array.from(vm.$el.querySelectorAll('li')).map(x => - x.textContent.trim(), - ); - - expect(renderedSearchTypes).toEqual(expectedSearchTypes); - }) - .then(done) - .catch(done.fail); - }); - - it('does not show search types, if already has search value', () => { - vm.search = 'lorem ipsum'; - vm.$el.querySelector('input').dispatchEvent(new Event('focus')); - - expect(vm.hasSearchFocus).toBe(true); - expect(vm.showSearchTypes).toBe(false); - }); - - it('does not show search types, if already has a search type', () => { - vm.currentSearchType = {}; - vm.$el.querySelector('input').dispatchEvent(new Event('focus')); - - expect(vm.hasSearchFocus).toBe(true); - expect(vm.showSearchTypes).toBe(false); - }); - - it('resets hasSearchFocus when search changes', done => { - vm.hasSearchFocus = true; - vm.search = 'something else'; - - vm.$nextTick() - .then(() => { - expect(vm.hasSearchFocus).toBe(false); - }) - .then(done) - .catch(done.fail); - }); - }); -}); diff --git a/spec/javascripts/ide/components/pipelines/list_spec.js b/spec/javascripts/ide/components/pipelines/list_spec.js deleted file mode 100644 index 80829f2358a..00000000000 --- a/spec/javascripts/ide/components/pipelines/list_spec.js +++ /dev/null @@ -1,137 +0,0 @@ -import Vue from 'vue'; -import MockAdapter from 'axios-mock-adapter'; -import axios from '~/lib/utils/axios_utils'; -import { createStore } from '~/ide/stores'; -import List from '~/ide/components/pipelines/list.vue'; -import { createComponentWithStore } from '../../../helpers/vue_mount_component_helper'; -import { pipelines, projectData, stages, jobs } from '../../mock_data'; - -describe('IDE pipelines list', () => { - const Component = Vue.extend(List); - let vm; - let mock; - - const findLoadingState = () => vm.$el.querySelector('.loading-container'); - - beforeEach(done => { - const store = createStore(); - - mock = new MockAdapter(axios); - - store.state.currentProjectId = 'abc/def'; - store.state.currentBranchId = 'master'; - store.state.projects['abc/def'] = { - ...projectData, - path_with_namespace: 'abc/def', - branches: { - master: { commit: { id: '123' } }, - }, - }; - store.state.links = { ciHelpPagePath: gl.TEST_HOST }; - store.state.pipelinesEmptyStateSvgPath = gl.TEST_HOST; - store.state.pipelines.stages = stages.map((mappedState, i) => ({ - ...mappedState, - id: i, - dropdownPath: mappedState.dropdown_path, - jobs: [...jobs], - isLoading: false, - isCollapsed: false, - })); - - mock - .onGet('/abc/def/commit/123/pipelines') - .replyOnce(200, { pipelines: [...pipelines] }, { 'poll-interval': '-1' }); - - vm = createComponentWithStore(Component, store).$mount(); - - setTimeout(done); - }); - - afterEach(done => { - vm.$destroy(); - mock.restore(); - - vm.$store - .dispatch('pipelines/stopPipelinePolling') - .then(() => vm.$store.dispatch('pipelines/clearEtagPoll')) - .then(done) - .catch(done.fail); - }); - - it('renders pipeline data', () => { - expect(vm.$el.textContent).toContain('#1'); - }); - - it('renders CI icon', () => { - expect(vm.$el.querySelector('.ci-status-icon-failed')).not.toBe(null); - }); - - it('renders list of jobs', () => { - expect(vm.$el.querySelectorAll('.tab-pane:first-child .ide-job-item').length).toBe( - jobs.length * stages.length, - ); - }); - - it('renders list of failed jobs on failed jobs tab', done => { - vm.$el.querySelectorAll('.tab-links a')[1].click(); - - vm.$nextTick(() => { - expect(vm.$el.querySelectorAll('.tab-pane.active .ide-job-item').length).toBe(2); - - done(); - }); - }); - - describe('YAML error', () => { - it('renders YAML error', done => { - vm.$store.state.pipelines.latestPipeline.yamlError = 'test yaml error'; - - vm.$nextTick(() => { - expect(vm.$el.textContent).toContain('Found errors in your .gitlab-ci.yml:'); - expect(vm.$el.textContent).toContain('test yaml error'); - - done(); - }); - }); - }); - - describe('empty state', () => { - it('renders pipelines empty state', done => { - vm.$store.state.pipelines.latestPipeline = null; - - vm.$nextTick(() => { - expect(vm.$el.querySelector('.empty-state')).not.toBe(null); - - done(); - }); - }); - }); - - describe('loading state', () => { - beforeEach(() => { - vm.$store.state.pipelines.isLoadingPipeline = true; - }); - - it('does not render when pipeline has loaded before', done => { - vm.$store.state.pipelines.hasLoadedPipeline = true; - - vm.$nextTick() - .then(() => { - expect(findLoadingState()).toBe(null); - }) - .then(done) - .catch(done.fail); - }); - - it('renders loading state when there is no latest pipeline', done => { - vm.$store.state.pipelines.hasLoadedPipeline = false; - - vm.$nextTick() - .then(() => { - expect(findLoadingState()).not.toBe(null); - }) - .then(done) - .catch(done.fail); - }); - }); -}); diff --git a/spec/javascripts/ide/components/repo_editor_spec.js b/spec/javascripts/ide/components/repo_editor_spec.js index 0701b773e17..d1b43df74b9 100644 --- a/spec/javascripts/ide/components/repo_editor_spec.js +++ b/spec/javascripts/ide/components/repo_editor_spec.js @@ -410,10 +410,23 @@ describe('RepoEditor', () => { describe('initEditor', () => { beforeEach(() => { + vm.file.tempFile = false; spyOn(vm.editor, 'createInstance'); spyOnProperty(vm, 'shouldHideEditor').and.returnValue(true); }); + it('does not fetch file information for temp entries', done => { + vm.file.tempFile = true; + + vm.initEditor(); + vm.$nextTick() + .then(() => { + expect(vm.getFileData).not.toHaveBeenCalled(); + }) + .then(done) + .catch(done.fail); + }); + it('is being initialised for files without content even if shouldHideEditor is `true`', done => { vm.file.content = ''; vm.file.raw = ''; @@ -429,16 +442,13 @@ describe('RepoEditor', () => { }); it('does not initialize editor for files already with content', done => { - expect(vm.getFileData.calls.count()).toEqual(1); - expect(vm.getRawFileData.calls.count()).toEqual(1); - vm.file.content = 'foo'; vm.initEditor(); vm.$nextTick() .then(() => { - expect(vm.getFileData.calls.count()).toEqual(1); - expect(vm.getRawFileData.calls.count()).toEqual(1); + expect(vm.getFileData).not.toHaveBeenCalled(); + expect(vm.getRawFileData).not.toHaveBeenCalled(); expect(vm.editor.createInstance).not.toHaveBeenCalled(); }) .then(done) @@ -446,23 +456,56 @@ describe('RepoEditor', () => { }); }); - it('calls removePendingTab when old file is pending', done => { - spyOnProperty(vm, 'shouldHideEditor').and.returnValue(true); - spyOn(vm, 'removePendingTab'); + describe('updates on file changes', () => { + beforeEach(() => { + spyOn(vm, 'initEditor'); + }); - vm.file.pending = true; + it('calls removePendingTab when old file is pending', done => { + spyOnProperty(vm, 'shouldHideEditor').and.returnValue(true); + spyOn(vm, 'removePendingTab'); - vm.$nextTick() - .then(() => { - vm.file = file('testing'); - vm.file.content = 'foo'; // need to prevent full cycle of initEditor + vm.file.pending = true; + + vm.$nextTick() + .then(() => { + vm.file = file('testing'); + vm.file.content = 'foo'; // need to prevent full cycle of initEditor - return vm.$nextTick(); - }) - .then(() => { - expect(vm.removePendingTab).toHaveBeenCalled(); - }) - .then(done) - .catch(done.fail); + return vm.$nextTick(); + }) + .then(() => { + expect(vm.removePendingTab).toHaveBeenCalled(); + }) + .then(done) + .catch(done.fail); + }); + + it('does not call initEditor if the file did not change', done => { + Vue.set(vm, 'file', vm.file); + + vm.$nextTick() + .then(() => { + expect(vm.initEditor).not.toHaveBeenCalled(); + }) + .then(done) + .catch(done.fail); + }); + + it('calls initEditor when file key is changed', done => { + expect(vm.initEditor).not.toHaveBeenCalled(); + + Vue.set(vm, 'file', { + ...vm.file, + key: 'new', + }); + + vm.$nextTick() + .then(() => { + expect(vm.initEditor).toHaveBeenCalled(); + }) + .then(done) + .catch(done.fail); + }); }); }); diff --git a/spec/javascripts/ide/mock_data.js b/spec/javascripts/ide/mock_data.js index c02c7e5d45e..27f0ad01f54 100644 --- a/spec/javascripts/ide/mock_data.js +++ b/spec/javascripts/ide/mock_data.js @@ -1,226 +1 @@ -export const projectData = { - id: 1, - name: 'abcproject', - web_url: '', - avatar_url: '', - path: '', - name_with_namespace: 'namespace/abcproject', - branches: { - master: { - treeId: 'abcproject/master', - can_push: true, - commit: { - id: '123', - }, - }, - }, - mergeRequests: {}, - merge_requests_enabled: true, - default_branch: 'master', -}; - -export const pipelines = [ - { - id: 1, - ref: 'master', - sha: '123', - details: { - status: { - icon: 'status_failed', - group: 'failed', - text: 'Failed', - }, - }, - commit: { id: '123' }, - }, - { - id: 2, - ref: 'master', - sha: '213', - details: { - status: { - icon: 'status_failed', - group: 'failed', - text: 'Failed', - }, - }, - commit: { id: '213' }, - }, -]; - -export const stages = [ - { - dropdown_path: `${gl.TEST_HOST}/testing`, - name: 'build', - status: { - icon: 'status_failed', - group: 'failed', - text: 'failed', - }, - }, - { - dropdown_path: 'testing', - name: 'test', - status: { - icon: 'status_failed', - group: 'failed', - text: 'failed', - }, - }, -]; - -export const jobs = [ - { - id: 1, - name: 'test', - path: 'testing', - status: { - icon: 'status_success', - text: 'passed', - }, - stage: 'test', - duration: 1, - started: new Date(), - }, - { - id: 2, - name: 'test 2', - path: 'testing2', - status: { - icon: 'status_success', - text: 'passed', - }, - stage: 'test', - duration: 1, - started: new Date(), - }, - { - id: 3, - name: 'test 3', - path: 'testing3', - status: { - icon: 'status_success', - text: 'passed', - }, - stage: 'test', - duration: 1, - started: new Date(), - }, - { - id: 4, - name: 'test 4', - path: 'testing4', - status: { - icon: 'status_failed', - text: 'failed', - }, - stage: 'build', - duration: 1, - started: new Date(), - }, -]; - -export const fullPipelinesResponse = { - data: { - count: { - all: 2, - }, - pipelines: [ - { - id: '51', - path: 'test', - commit: { - id: '123', - }, - details: { - status: { - icon: 'status_failed', - text: 'failed', - }, - stages: [...stages], - }, - }, - { - id: '50', - commit: { - id: 'abc123def456ghi789jkl', - }, - details: { - status: { - icon: 'status_success', - text: 'passed', - }, - stages: [...stages], - }, - }, - ], - }, -}; - -export const mergeRequests = [ - { - id: 1, - iid: 1, - title: 'Test merge request', - project_id: 1, - web_url: `${gl.TEST_HOST}/namespace/project-path/merge_requests/1`, - }, -]; - -export const branches = [ - { - id: 1, - name: 'master', - commit: { - message: 'Update master branch', - committed_date: '2018-08-01T00:20:05Z', - }, - can_push: true, - protected: true, - default: true, - }, - { - id: 2, - name: 'protected/no-access', - commit: { - message: 'Update some stuff', - committed_date: '2018-08-02T00:00:05Z', - }, - can_push: false, - protected: true, - default: false, - }, - { - id: 3, - name: 'protected/access', - commit: { - message: 'Update some stuff', - committed_date: '2018-08-02T00:00:05Z', - }, - can_push: true, - protected: true, - default: false, - }, - { - id: 4, - name: 'regular', - commit: { - message: 'Update some more stuff', - committed_date: '2018-06-30T00:20:05Z', - }, - can_push: true, - protected: false, - default: false, - }, - { - id: 5, - name: 'regular/no-access', - commit: { - message: 'Update some more stuff', - committed_date: '2018-06-30T00:20:05Z', - }, - can_push: false, - protected: false, - default: false, - }, -]; +export * from '../../frontend/ide/mock_data'; diff --git a/spec/javascripts/ide/stores/actions/project_spec.js b/spec/javascripts/ide/stores/actions/project_spec.js index 8ecb6129c63..bcc7b5d5e46 100644 --- a/spec/javascripts/ide/stores/actions/project_spec.js +++ b/spec/javascripts/ide/stores/actions/project_spec.js @@ -6,8 +6,10 @@ import { createNewBranchFromDefault, showEmptyState, openBranch, + loadFile, + loadBranch, } from '~/ide/stores/actions'; -import store from '~/ide/stores'; +import { createStore } from '~/ide/stores'; import service from '~/ide/services'; import api from '~/api'; import router from '~/ide/ide_router'; @@ -16,8 +18,10 @@ import testAction from '../../../helpers/vuex_action_helper'; describe('IDE store project actions', () => { let mock; + let store; beforeEach(() => { + store = createStore(); mock = new MockAdapter(axios); store.state.projects['abc/def'] = { @@ -231,28 +235,139 @@ describe('IDE store project actions', () => { }); }); + describe('loadFile', () => { + beforeEach(() => { + Object.assign(store.state, { + entries: { + foo: { pending: false }, + 'foo/bar-pending': { pending: true }, + 'foo/bar': { pending: false }, + }, + }); + spyOn(store, 'dispatch'); + }); + + it('does nothing, if basePath is not given', () => { + loadFile(store, { basePath: undefined }); + + expect(store.dispatch).not.toHaveBeenCalled(); + }); + + it('handles tree entry action, if basePath is given and the entry is not pending', () => { + loadFile(store, { basePath: 'foo/bar/' }); + + expect(store.dispatch).toHaveBeenCalledWith( + 'handleTreeEntryAction', + store.state.entries['foo/bar'], + ); + }); + + it('does not handle tree entry action, if entry is pending', () => { + loadFile(store, { basePath: 'foo/bar-pending/' }); + + expect(store.dispatch).not.toHaveBeenCalledWith('handleTreeEntryAction', jasmine.anything()); + }); + + it('creates a new temp file supplied via URL if the file does not exist yet', () => { + loadFile(store, { basePath: 'not-existent.md' }); + + expect(store.dispatch.calls.count()).toBe(1); + + expect(store.dispatch).not.toHaveBeenCalledWith('handleTreeEntryAction', jasmine.anything()); + + expect(store.dispatch).toHaveBeenCalledWith('createTempEntry', { + name: 'not-existent.md', + type: 'blob', + }); + }); + }); + + describe('loadBranch', () => { + const projectId = 'abc/def'; + const branchId = '123-lorem'; + + it('fetches branch data', done => { + spyOn(store, 'dispatch').and.returnValue(Promise.resolve()); + + loadBranch(store, { projectId, branchId }) + .then(() => { + expect(store.dispatch.calls.allArgs()).toEqual([ + ['getBranchData', { projectId, branchId }], + ['getMergeRequestsForBranch', { projectId, branchId }], + ['getFiles', { projectId, branchId }], + ]); + }) + .then(done) + .catch(done.fail); + }); + + it('shows an error if branch can not be fetched', done => { + spyOn(store, 'dispatch').and.returnValue(Promise.reject()); + + loadBranch(store, { projectId, branchId }) + .then(done.fail) + .catch(() => { + expect(store.dispatch.calls.allArgs()).toEqual([ + ['getBranchData', { projectId, branchId }], + ['showBranchNotFoundError', branchId], + ]); + done(); + }); + }); + }); + describe('openBranch', () => { + const projectId = 'abc/def'; + const branchId = '123-lorem'; + const branch = { - projectId: 'abc/def', - branchId: '123-lorem', + projectId, + branchId, }; beforeEach(() => { - store.state.entries = { - foo: { pending: false }, - 'foo/bar-pending': { pending: true }, - 'foo/bar': { pending: false }, - }; + Object.assign(store.state, { + entries: { + foo: { pending: false }, + 'foo/bar-pending': { pending: true }, + 'foo/bar': { pending: false }, + }, + }); + }); + + it('loads file right away if the branch has already been fetched', done => { + spyOn(store, 'dispatch'); + + Object.assign(store.state, { + projects: { + [projectId]: { + branches: { + [branchId]: { foo: 'bar' }, + }, + }, + }, + }); + + openBranch(store, branch) + .then(() => { + expect(store.dispatch.calls.allArgs()).toEqual([['loadFile', { basePath: undefined }]]); + }) + .then(done) + .catch(done.fail); }); describe('empty repo', () => { beforeEach(() => { spyOn(store, 'dispatch').and.returnValue(Promise.resolve()); - store.state.currentProjectId = 'abc/def'; - store.state.projects['abc/def'] = { - empty_repo: true, - }; + Object.assign(store.state, { + currentProjectId: 'abc/def', + projects: { + 'abc/def': { + empty_repo: true, + }, + }, + }); }); afterEach(() => { @@ -262,10 +377,7 @@ describe('IDE store project actions', () => { it('dispatches showEmptyState action right away', done => { openBranch(store, branch) .then(() => { - expect(store.dispatch.calls.allArgs()).toEqual([ - ['setCurrentBranchId', branch.branchId], - ['showEmptyState', branch], - ]); + expect(store.dispatch.calls.allArgs()).toEqual([['showEmptyState', branch]]); done(); }) .catch(done.fail); @@ -281,56 +393,14 @@ describe('IDE store project actions', () => { openBranch(store, branch) .then(() => { expect(store.dispatch.calls.allArgs()).toEqual([ - ['setCurrentBranchId', branch.branchId], - ['getBranchData', branch], - ['getMergeRequestsForBranch', branch], - ['getFiles', branch], + ['setCurrentBranchId', branchId], + ['loadBranch', { projectId, branchId }], + ['loadFile', { basePath: undefined }], ]); }) .then(done) .catch(done.fail); }); - - it('handles tree entry action, if basePath is given', done => { - openBranch(store, { ...branch, basePath: 'foo/bar/' }) - .then(() => { - expect(store.dispatch).toHaveBeenCalledWith( - 'handleTreeEntryAction', - store.state.entries['foo/bar'], - ); - }) - .then(done) - .catch(done.fail); - }); - - it('does not handle tree entry action, if entry is pending', done => { - openBranch(store, { ...branch, basePath: 'foo/bar-pending' }) - .then(() => { - expect(store.dispatch).not.toHaveBeenCalledWith( - 'handleTreeEntryAction', - jasmine.anything(), - ); - }) - .then(done) - .catch(done.fail); - }); - - it('creates a new file supplied via URL if the file does not exist yet', done => { - openBranch(store, { ...branch, basePath: 'not-existent.md' }) - .then(() => { - expect(store.dispatch).not.toHaveBeenCalledWith( - 'handleTreeEntryAction', - jasmine.anything(), - ); - - expect(store.dispatch).toHaveBeenCalledWith('createTempEntry', { - name: 'not-existent.md', - type: 'blob', - }); - }) - .then(done) - .catch(done.fail); - }); }); describe('non-existent branch', () => { @@ -342,9 +412,8 @@ describe('IDE store project actions', () => { openBranch(store, branch) .then(() => { expect(store.dispatch.calls.allArgs()).toEqual([ - ['setCurrentBranchId', branch.branchId], - ['getBranchData', branch], - ['showBranchNotFoundError', branch.branchId], + ['setCurrentBranchId', branchId], + ['loadBranch', { projectId, branchId }], ]); }) .then(done) diff --git a/spec/javascripts/ide/stores/actions_spec.js b/spec/javascripts/ide/stores/actions_spec.js index 8504fb3f42b..7e77b859fdd 100644 --- a/spec/javascripts/ide/stores/actions_spec.js +++ b/spec/javascripts/ide/stores/actions_spec.js @@ -13,12 +13,15 @@ import actions, { createTempEntry, } from '~/ide/stores/actions'; import axios from '~/lib/utils/axios_utils'; -import store from '~/ide/stores'; +import { createStore } from '~/ide/stores'; import * as types from '~/ide/stores/mutation_types'; import router from '~/ide/ide_router'; import { resetStore, file } from '../helpers'; import testAction from '../../helpers/vuex_action_helper'; import MockAdapter from 'axios-mock-adapter'; +import eventHub from '~/ide/eventhub'; + +const store = createStore(); describe('Multi-file store actions', () => { beforeEach(() => { @@ -451,6 +454,24 @@ describe('Multi-file store actions', () => { done, ); }); + + it('does not dispatch for parent, if parent does not exist', done => { + const f = { + ...file(), + path: 'test', + parentPath: 'testing', + }; + store.state.entries[f.path] = f; + + testAction( + updateTempFlagForEntry, + { file: f, tempFile: false }, + store.state, + [{ type: 'UPDATE_TEMP_FLAG', payload: { path: f.path, tempFile: false } }], + [], + done, + ); + }); }); describe('setCurrentBranchId', () => { @@ -540,82 +561,298 @@ describe('Multi-file store actions', () => { done, ); }); - }); - describe('renameEntry', () => { - it('renames entry', done => { - store.state.entries.test = { - tree: [], + it('if renamed, reverts the rename before deleting', () => { + const testEntry = { + path: 'test', + name: 'test', + prevPath: 'lorem/ipsum', + prevName: 'ipsum', + prevParentPath: 'lorem', }; + store.state.entries = { test: testEntry }; testAction( - renameEntry, - { path: 'test', name: 'new-name', entryPath: null, parentPath: 'parent-path' }, + deleteEntry, + testEntry.path, store.state, + [], [ { - type: types.RENAME_ENTRY, - payload: { path: 'test', name: 'new-name', entryPath: null, parentPath: 'parent-path' }, - }, - { - type: types.TOGGLE_FILE_CHANGED, + type: 'renameEntry', payload: { - file: store.state.entries['parent-path/new-name'], - changed: true, + path: testEntry.path, + name: testEntry.prevName, + parentPath: testEntry.prevParentPath, }, }, + { + type: 'deleteEntry', + payload: testEntry.prevPath, + }, ], - [{ type: 'triggerFilesChange' }], - done, ); }); + }); - it('renames all entries in tree', done => { - store.state.entries.test = { - type: 'tree', - tree: [ - { - path: 'tree-1', - }, - { - path: 'tree-2', + describe('renameEntry', () => { + describe('purging of file model cache', () => { + beforeEach(() => { + spyOn(eventHub, '$emit'); + }); + + it('does not purge model cache for temporary entries that got renamed', done => { + Object.assign(store.state.entries, { + test: { + ...file('test'), + key: 'foo-key', + type: 'blob', + tempFile: true, }, - ], - }; + }); - testAction( - renameEntry, - { path: 'test', name: 'new-name', parentPath: 'parent-path' }, - store.state, - [ - { - type: types.RENAME_ENTRY, - payload: { path: 'test', name: 'new-name', entryPath: null, parentPath: 'parent-path' }, + store + .dispatch('renameEntry', { + path: 'test', + name: 'new', + }) + .then(() => { + expect(eventHub.$emit.calls.allArgs()).not.toContain( + 'editor.update.model.dispose.foo-bar', + ); + }) + .then(done) + .catch(done.fail); + }); + + it('purges model cache for renamed entry', done => { + Object.assign(store.state.entries, { + test: { + ...file('test'), + key: 'foo-key', + type: 'blob', + tempFile: false, }, - ], - [ - { - type: 'renameEntry', - payload: { - path: 'test', - name: 'new-name', - entryPath: 'tree-1', - parentPath: 'parent-path/new-name', + }); + + store + .dispatch('renameEntry', { + path: 'test', + name: 'new', + }) + .then(() => { + expect(eventHub.$emit).toHaveBeenCalled(); + expect(eventHub.$emit).toHaveBeenCalledWith(`editor.update.model.dispose.foo-key`); + }) + .then(done) + .catch(done.fail); + }); + }); + + describe('single entry', () => { + let origEntry; + let renamedEntry; + + beforeEach(() => { + // Need to insert both because `testAction` doesn't actually call the mutation + origEntry = file('orig', 'orig', 'blob'); + renamedEntry = { + ...file('renamed', 'renamed', 'blob'), + prevKey: origEntry.key, + prevName: origEntry.name, + prevPath: origEntry.path, + }; + + Object.assign(store.state.entries, { + orig: origEntry, + renamed: renamedEntry, + }); + }); + + afterEach(() => { + resetStore(store); + }); + + it('by default renames an entry and adds to changed', done => { + testAction( + renameEntry, + { path: 'orig', name: 'renamed' }, + store.state, + [ + { + type: types.RENAME_ENTRY, + payload: { + path: 'orig', + name: 'renamed', + parentPath: undefined, + }, }, - }, - { - type: 'renameEntry', - payload: { - path: 'test', - name: 'new-name', - entryPath: 'tree-2', - parentPath: 'parent-path/new-name', + { + type: types.ADD_FILE_TO_CHANGED, + payload: 'renamed', + }, + ], + [{ type: 'triggerFilesChange' }], + done, + ); + }); + + it('if not changed, completely unstages entry if renamed to original', done => { + testAction( + renameEntry, + { path: 'renamed', name: 'orig' }, + store.state, + [ + { + type: types.RENAME_ENTRY, + payload: { + path: 'renamed', + name: 'orig', + parentPath: undefined, + }, + }, + { + type: types.REMOVE_FILE_FROM_STAGED_AND_CHANGED, + payload: origEntry, + }, + ], + [{ type: 'triggerFilesChange' }], + done, + ); + }); + + it('if already in changed, does not add to change', done => { + store.state.changedFiles.push(renamedEntry); + + testAction( + renameEntry, + { path: 'orig', name: 'renamed' }, + store.state, + [jasmine.objectContaining({ type: types.RENAME_ENTRY })], + [{ type: 'triggerFilesChange' }], + done, + ); + }); + + it('routes to the renamed file if the original file has been opened', done => { + Object.assign(store.state.entries.orig, { + opened: true, + url: '/foo-bar.md', + }); + + store + .dispatch('renameEntry', { + path: 'orig', + name: 'renamed', + }) + .then(() => { + expect(router.push.calls.count()).toBe(1); + expect(router.push).toHaveBeenCalledWith(`/project/foo-bar.md`); + }) + .then(done) + .catch(done.fail); + }); + }); + + describe('folder', () => { + let folder; + let file1; + let file2; + + beforeEach(() => { + folder = file('folder', 'folder', 'tree'); + file1 = file('file-1', 'file-1', 'blob', folder); + file2 = file('file-2', 'file-2', 'blob', folder); + + folder.tree = [file1, file2]; + + Object.assign(store.state.entries, { + [folder.path]: folder, + [file1.path]: file1, + [file2.path]: file2, + }); + }); + + it('updates entries in a folder correctly, when folder is renamed', done => { + store + .dispatch('renameEntry', { + path: 'folder', + name: 'new-folder', + }) + .then(() => { + const keys = Object.keys(store.state.entries); + + expect(keys.length).toBe(3); + expect(keys.indexOf('new-folder')).toBe(0); + expect(keys.indexOf('new-folder/file-1')).toBe(1); + expect(keys.indexOf('new-folder/file-2')).toBe(2); + }) + .then(done) + .catch(done.fail); + }); + + it('discards renaming of an entry if the root folder is renamed back to a previous name', done => { + const rootFolder = file('old-folder', 'old-folder', 'tree'); + const testEntry = file('test', 'test', 'blob', rootFolder); + + Object.assign(store.state, { + entries: { + 'old-folder': { + ...rootFolder, + tree: [testEntry], }, + 'old-folder/test': testEntry, }, - { type: 'triggerFilesChange' }, - ], - done, - ); + }); + + store + .dispatch('renameEntry', { + path: 'old-folder', + name: 'new-folder', + }) + .then(() => { + const { entries } = store.state; + + expect(Object.keys(entries).length).toBe(2); + expect(entries['old-folder']).toBeUndefined(); + expect(entries['old-folder/test']).toBeUndefined(); + + expect(entries['new-folder']).toBeDefined(); + expect(entries['new-folder/test']).toEqual( + jasmine.objectContaining({ + path: 'new-folder/test', + name: 'test', + prevPath: 'old-folder/test', + prevName: 'test', + }), + ); + }) + .then(() => + store.dispatch('renameEntry', { + path: 'new-folder', + name: 'old-folder', + }), + ) + .then(() => { + const { entries } = store.state; + + expect(Object.keys(entries).length).toBe(2); + expect(entries['new-folder']).toBeUndefined(); + expect(entries['new-folder/test']).toBeUndefined(); + + expect(entries['old-folder']).toBeDefined(); + expect(entries['old-folder/test']).toEqual( + jasmine.objectContaining({ + path: 'old-folder/test', + name: 'test', + prevPath: undefined, + prevName: undefined, + }), + ); + }) + .then(done) + .catch(done.fail); + }); }); }); diff --git a/spec/javascripts/ide/stores/modules/commit/actions_spec.js b/spec/javascripts/ide/stores/modules/commit/actions_spec.js index 091b454c0d2..95d927065f0 100644 --- a/spec/javascripts/ide/stores/modules/commit/actions_spec.js +++ b/spec/javascripts/ide/stores/modules/commit/actions_spec.js @@ -1,5 +1,5 @@ import rootActions from '~/ide/stores/actions'; -import store from '~/ide/stores'; +import { createStore } from '~/ide/stores'; import service from '~/ide/services'; import router from '~/ide/ide_router'; import eventHub from '~/ide/eventhub'; @@ -11,6 +11,7 @@ import { resetStore, file } from 'spec/ide/helpers'; import testAction from '../../../../helpers/vuex_action_helper'; const TEST_COMMIT_SHA = '123456789'; +const store = createStore(); describe('IDE commit module actions', () => { beforeEach(() => { @@ -59,7 +60,9 @@ describe('IDE commit module actions', () => { }); it('sets shouldCreateMR to true if "Create new MR" option is visible', done => { - store.state.shouldHideNewMrOption = false; + Object.assign(store.state, { + shouldHideNewMrOption: false, + }); testAction( actions.updateCommitAction, @@ -78,7 +81,9 @@ describe('IDE commit module actions', () => { }); it('sets shouldCreateMR to false if "Create new MR" option is hidden', done => { - store.state.shouldHideNewMrOption = true; + Object.assign(store.state, { + shouldHideNewMrOption: true, + }); testAction( actions.updateCommitAction, @@ -172,24 +177,31 @@ describe('IDE commit module actions', () => { content: 'file content', }); - store.state.currentProjectId = 'abcproject'; - store.state.currentBranchId = 'master'; - store.state.projects.abcproject = { - web_url: 'web_url', - branches: { - master: { - workingReference: '', - commit: { - short_id: TEST_COMMIT_SHA, + Object.assign(store.state, { + currentProjectId: 'abcproject', + currentBranchId: 'master', + projects: { + abcproject: { + web_url: 'web_url', + branches: { + master: { + workingReference: '', + commit: { + short_id: TEST_COMMIT_SHA, + }, + }, }, }, }, - }; - store.state.stagedFiles.push(f, { - ...file('changedFile2'), - changed: true, + stagedFiles: [ + f, + { + ...file('changedFile2'), + changed: true, + }, + ], + openFiles: store.state.stagedFiles, }); - store.state.openFiles = store.state.stagedFiles; store.state.stagedFiles.forEach(stagedFile => { store.state.entries[stagedFile.path] = stagedFile; @@ -275,40 +287,40 @@ describe('IDE commit module actions', () => { document.body.innerHTML += '<div class="flash-container"></div>'; - store.state.currentProjectId = 'abcproject'; - store.state.currentBranchId = 'master'; - store.state.projects.abcproject = { - web_url: 'webUrl', - branches: { - master: { - workingReference: '1', - commit: { - id: TEST_COMMIT_SHA, - }, - }, - }, - }; - const f = { ...file('changed'), type: 'blob', active: true, lastCommitSha: TEST_COMMIT_SHA, }; - store.state.stagedFiles.push(f); - store.state.changedFiles = [ - { - ...f, - }, - ]; - store.state.openFiles = store.state.changedFiles; - store.state.openFiles.forEach(localF => { - store.state.entries[localF.path] = localF; + Object.assign(store.state, { + stagedFiles: [f], + changedFiles: [f], + openFiles: [f], + currentProjectId: 'abcproject', + currentBranchId: 'master', + projects: { + abcproject: { + web_url: 'webUrl', + branches: { + master: { + workingReference: '1', + commit: { + id: TEST_COMMIT_SHA, + }, + }, + }, + }, + }, }); store.state.commit.commitAction = '2'; store.state.commit.commitMessage = 'testing 123'; + + store.state.openFiles.forEach(localF => { + store.state.entries[localF.path] = localF; + }); }); afterEach(() => { @@ -473,18 +485,16 @@ describe('IDE commit module actions', () => { }); it('resets changed files before redirecting', done => { + visitUrl = visitUrl.and.callFake(() => { + expect(store.state.stagedFiles.length).toBe(0); + done(); + }); + spyOn(eventHub, '$on'); store.state.commit.commitAction = '3'; - store - .dispatch('commit/commitChanges') - .then(() => { - expect(store.state.stagedFiles.length).toBe(0); - - done(); - }) - .catch(done.fail); + store.dispatch('commit/commitChanges').catch(done.fail); }); }); }); diff --git a/spec/javascripts/ide/stores/mutations/file_spec.js b/spec/javascripts/ide/stores/mutations/file_spec.js index 064e66cef64..7c46bf55318 100644 --- a/spec/javascripts/ide/stores/mutations/file_spec.js +++ b/spec/javascripts/ide/stores/mutations/file_spec.js @@ -356,16 +356,16 @@ describe('IDE store file mutations', () => { }); describe('STAGE_CHANGE', () => { - it('adds file into stagedFiles array', () => { + beforeEach(() => { mutations.STAGE_CHANGE(localState, localFile.path); + }); + it('adds file into stagedFiles array', () => { expect(localState.stagedFiles.length).toBe(1); expect(localState.stagedFiles[0]).toEqual(localFile); }); it('updates stagedFile if it is already staged', () => { - mutations.STAGE_CHANGE(localState, localFile.path); - localFile.raw = 'testing 123'; mutations.STAGE_CHANGE(localState, localFile.path); @@ -373,19 +373,6 @@ describe('IDE store file mutations', () => { expect(localState.stagedFiles.length).toBe(1); expect(localState.stagedFiles[0].raw).toEqual('testing 123'); }); - - it('adds already-staged file to `replacedFiles`', () => { - localFile.raw = 'already-staged'; - - mutations.STAGE_CHANGE(localState, localFile.path); - - localFile.raw = 'testing 123'; - - mutations.STAGE_CHANGE(localState, localFile.path); - - expect(localState.replacedFiles.length).toBe(1); - expect(localState.replacedFiles[0].raw).toEqual('already-staged'); - }); }); describe('UNSTAGE_CHANGE', () => { diff --git a/spec/javascripts/ide/stores/mutations_spec.js b/spec/javascripts/ide/stores/mutations_spec.js index 2470c99e300..7dd5d323f69 100644 --- a/spec/javascripts/ide/stores/mutations_spec.js +++ b/spec/javascripts/ide/stores/mutations_spec.js @@ -79,16 +79,6 @@ describe('Multi-file store mutations', () => { }); }); - describe('CLEAR_REPLACED_FILES', () => { - it('clears replacedFiles array', () => { - localState.replacedFiles.push('a'); - - mutations.CLEAR_REPLACED_FILES(localState); - - expect(localState.replacedFiles.length).toBe(0); - }); - }); - describe('UPDATE_VIEWER', () => { it('sets viewer state', () => { mutations.UPDATE_VIEWER(localState, 'diff'); @@ -311,8 +301,7 @@ describe('Multi-file store mutations', () => { describe('UPDATE_FILE_AFTER_COMMIT', () => { it('updates URLs if prevPath is set', () => { const f = { - ...file(), - path: 'test', + ...file('test'), prevPath: 'testing-123', rawPath: `${gl.TEST_HOST}/testing-123`, permalink: `${gl.TEST_HOST}/testing-123`, @@ -325,19 +314,26 @@ describe('Multi-file store mutations', () => { mutations.UPDATE_FILE_AFTER_COMMIT(localState, { file: f, lastCommit: { commit: {} } }); - expect(f.rawPath).toBe(`${gl.TEST_HOST}/test`); - expect(f.permalink).toBe(`${gl.TEST_HOST}/test`); - expect(f.commitsPath).toBe(`${gl.TEST_HOST}/test`); - expect(f.blamePath).toBe(`${gl.TEST_HOST}/test`); - expect(f.replaces).toBe(false); + expect(f).toEqual( + jasmine.objectContaining({ + rawPath: `${gl.TEST_HOST}/test`, + permalink: `${gl.TEST_HOST}/test`, + commitsPath: `${gl.TEST_HOST}/test`, + blamePath: `${gl.TEST_HOST}/test`, + replaces: false, + prevId: undefined, + prevPath: undefined, + prevName: undefined, + prevUrl: undefined, + prevKey: undefined, + }), + ); }); }); describe('OPEN_NEW_ENTRY_MODAL', () => { it('sets entryModal', () => { - localState.entries.testPath = { - ...file(), - }; + localState.entries.testPath = file(); mutations.OPEN_NEW_ENTRY_MODAL(localState, { type: 'test', path: 'testPath' }); @@ -356,58 +352,178 @@ describe('Multi-file store mutations', () => { }; localState.currentProjectId = 'gitlab-ce'; localState.currentBranchId = 'master'; - localState.entries.oldPath = { - ...file(), - type: 'blob', - name: 'oldPath', - path: 'oldPath', - url: `${gl.TEST_HOST}/oldPath`, + localState.entries = { + oldPath: file('oldPath', 'oldPath', 'blob'), }; }); - it('creates new renamed entry', () => { + it('updates existing entry without creating a new one', () => { + mutations.RENAME_ENTRY(localState, { + path: 'oldPath', + name: 'newPath', + parentPath: '', + }); + + expect(localState.entries).toEqual({ + newPath: jasmine.objectContaining({ + path: 'newPath', + prevPath: 'oldPath', + }), + }); + }); + + it('correctly handles consecutive renames for the same entry', () => { mutations.RENAME_ENTRY(localState, { path: 'oldPath', name: 'newPath', + parentPath: '', + }); + + mutations.RENAME_ENTRY(localState, { + path: 'newPath', + name: 'newestPath', + parentPath: '', + }); + + expect(localState.entries).toEqual({ + newestPath: jasmine.objectContaining({ + path: 'newestPath', + prevPath: 'oldPath', + }), + }); + }); + + it('correctly handles the same entry within a consecutively renamed folder', () => { + const oldPath = file('root-folder/oldPath', 'root-folder/oldPath', 'blob'); + localState.entries = { + 'root-folder': { + ...file('root-folder', 'root-folder', 'tree'), + tree: [oldPath], + }, + 'root-folder/oldPath': oldPath, + }; + Object.assign(localState.entries['root-folder/oldPath'], { + parentPath: 'root-folder', + url: 'root-folder/oldPath-blob-root-folder/oldPath', + }); + + mutations.RENAME_ENTRY(localState, { + path: 'root-folder/oldPath', + name: 'renamed-folder/oldPath', entryPath: null, parentPath: '', }); + mutations.RENAME_ENTRY(localState, { + path: 'renamed-folder/oldPath', + name: 'simply-renamed/oldPath', + entryPath: null, + parentPath: '', + }); + + expect(localState.entries).toEqual({ + 'root-folder': jasmine.objectContaining({ + path: 'root-folder', + }), + 'simply-renamed/oldPath': jasmine.objectContaining({ + path: 'simply-renamed/oldPath', + prevPath: 'root-folder/oldPath', + }), + }); + }); + + it('renames entry, preserving old parameters', () => { + Object.assign(localState.entries.oldPath, { + url: `project/-/oldPath`, + }); + const oldPathData = localState.entries.oldPath; + + mutations.RENAME_ENTRY(localState, { + path: 'oldPath', + name: 'newPath', + parentPath: '', + }); + expect(localState.entries.newPath).toEqual({ - ...localState.entries.oldPath, + ...oldPathData, id: 'newPath', - name: 'newPath', - key: 'newPath-blob-oldPath', path: 'newPath', - tempFile: true, + name: 'newPath', + url: `project/-/newPath`, + key: jasmine.stringMatching('newPath'), + + prevId: 'oldPath', + prevName: 'oldPath', prevPath: 'oldPath', - tree: [], - parentPath: '', - url: `${gl.TEST_HOST}/newPath`, - moved: jasmine.anything(), - movedPath: jasmine.anything(), - opened: false, + prevUrl: `project/-/oldPath`, + prevKey: oldPathData.key, + prevParentPath: oldPathData.parentPath, }); }); - it('adds new entry to changedFiles', () => { - mutations.RENAME_ENTRY(localState, { path: 'oldPath', name: 'newPath' }); + it('does not store previous attributes on temp files', () => { + Object.assign(localState.entries.oldPath, { + tempFile: true, + }); + mutations.RENAME_ENTRY(localState, { + path: 'oldPath', + name: 'newPath', + entryPath: null, + parentPath: '', + }); - expect(localState.changedFiles.length).toBe(1); - expect(localState.changedFiles[0].path).toBe('newPath'); - }); + expect(localState.entries.newPath).not.toEqual( + jasmine.objectContaining({ + prevId: jasmine.anything(), + prevName: jasmine.anything(), + prevPath: jasmine.anything(), + prevUrl: jasmine.anything(), + prevKey: jasmine.anything(), + prevParentPath: jasmine.anything(), + }), + ); + }); + + it('properly handles files with spaces in name', () => { + const path = 'my fancy path'; + const newPath = 'new path'; + const oldEntry = { + ...file(path, path, 'blob'), + url: `project/-/${encodeURI(path)}`, + }; - it('sets oldEntry as moved', () => { - mutations.RENAME_ENTRY(localState, { path: 'oldPath', name: 'newPath' }); + localState.entries[path] = oldEntry; - expect(localState.entries.oldPath.moved).toBe(true); + mutations.RENAME_ENTRY(localState, { + path, + name: newPath, + entryPath: null, + parentPath: '', + }); + + expect(localState.entries[newPath]).toEqual({ + ...oldEntry, + id: newPath, + path: newPath, + name: newPath, + url: `project/-/new%20path`, + key: jasmine.stringMatching(newPath), + + prevId: path, + prevName: path, + prevPath: path, + prevUrl: `project/-/my%20fancy%20path`, + prevKey: oldEntry.key, + prevParentPath: oldEntry.parentPath, + }); }); - it('adds to parents tree', () => { - localState.entries.oldPath.parentPath = 'parentPath'; - localState.entries.parentPath = { - ...file(), + it('adds to parent tree', () => { + const parentEntry = { + ...file('parentPath', 'parentPath', 'tree'), + tree: [localState.entries.oldPath], }; + localState.entries.parentPath = parentEntry; mutations.RENAME_ENTRY(localState, { path: 'oldPath', @@ -416,7 +532,180 @@ describe('Multi-file store mutations', () => { parentPath: 'parentPath', }); - expect(localState.entries.parentPath.tree.length).toBe(1); + expect(parentEntry.tree.length).toBe(1); + expect(parentEntry.tree[0].name).toBe('newPath'); + }); + + it('sorts tree after renaming an entry', () => { + const alpha = file('alpha', 'alpha', 'blob'); + const beta = file('beta', 'beta', 'blob'); + const gamma = file('gamma', 'gamma', 'blob'); + localState.entries = { alpha, beta, gamma }; + + localState.trees['gitlab-ce/master'].tree = [alpha, beta, gamma]; + + mutations.RENAME_ENTRY(localState, { + path: 'alpha', + name: 'theta', + entryPath: null, + parentPath: '', + }); + + expect(localState.trees['gitlab-ce/master'].tree).toEqual([ + jasmine.objectContaining({ name: 'beta' }), + jasmine.objectContaining({ name: 'gamma' }), + jasmine.objectContaining({ + path: 'theta', + name: 'theta', + }), + ]); + }); + + it('updates openFiles with the renamed one if the original one is open', () => { + Object.assign(localState.entries.oldPath, { + opened: true, + type: 'blob', + }); + Object.assign(localState, { + openFiles: [localState.entries.oldPath], + }); + + mutations.RENAME_ENTRY(localState, { path: 'oldPath', name: 'newPath' }); + + expect(localState.openFiles.length).toBe(1); + expect(localState.openFiles[0].path).toBe('newPath'); + }); + + it('does not add renamed entry to changedFiles', () => { + mutations.RENAME_ENTRY(localState, { path: 'oldPath', name: 'newPath' }); + + expect(localState.changedFiles.length).toBe(0); + }); + + it('updates existing changedFiles entry with the renamed one', () => { + const origFile = { + ...file('oldPath', 'oldPath', 'blob'), + content: 'Foo', + }; + + Object.assign(localState, { + changedFiles: [origFile], + }); + Object.assign(localState.entries, { + oldPath: origFile, + }); + + mutations.RENAME_ENTRY(localState, { path: 'oldPath', name: 'newPath' }); + + expect(localState.changedFiles).toEqual([ + jasmine.objectContaining({ + path: 'newPath', + content: 'Foo', + }), + ]); + }); + + it('correctly saves original values if an entry is renamed multiple times', () => { + const original = { ...localState.entries.oldPath }; + const paramsToCheck = ['prevId', 'prevPath', 'prevName', 'prevUrl']; + const expectedObj = paramsToCheck.reduce( + (o, param) => ({ ...o, [param]: original[param.replace('prev', '').toLowerCase()] }), + {}, + ); + + mutations.RENAME_ENTRY(localState, { path: 'oldPath', name: 'newPath' }); + + expect(localState.entries.newPath).toEqual(jasmine.objectContaining(expectedObj)); + + mutations.RENAME_ENTRY(localState, { path: 'newPath', name: 'newer' }); + + expect(localState.entries.newer).toEqual(jasmine.objectContaining(expectedObj)); + }); + + describe('renaming back to original', () => { + beforeEach(() => { + const renamedEntry = { + ...file('renamed', 'renamed', 'blob'), + prevId: 'lorem/orig', + prevPath: 'lorem/orig', + prevName: 'orig', + prevUrl: 'project/-/loren/orig', + prevKey: 'lorem/orig', + prevParentPath: 'lorem', + }; + + localState.entries = { + renamed: renamedEntry, + }; + + mutations.RENAME_ENTRY(localState, { path: 'renamed', name: 'orig', parentPath: 'lorem' }); + }); + + it('renames entry and clears prev properties', () => { + expect(localState.entries).toEqual({ + 'lorem/orig': jasmine.objectContaining({ + id: 'lorem/orig', + path: 'lorem/orig', + name: 'orig', + prevId: undefined, + prevPath: undefined, + prevName: undefined, + prevUrl: undefined, + prevKey: undefined, + prevParentPath: undefined, + }), + }); + }); + }); + + describe('key updates', () => { + beforeEach(() => { + const rootFolder = file('rootFolder', 'rootFolder', 'tree'); + localState.entries = { + rootFolder, + oldPath: file('oldPath', 'oldPath', 'blob'), + 'oldPath.txt': file('oldPath.txt', 'oldPath.txt', 'blob'), + 'rootFolder/oldPath.md': file('oldPath.md', 'oldPath.md', 'blob', rootFolder), + }; + }); + + it('sets properly constucted key while preserving the original one', () => { + const key = 'oldPath.txt-blob-oldPath.txt'; + localState.entries['oldPath.txt'].key = key; + mutations.RENAME_ENTRY(localState, { path: 'oldPath.txt', name: 'newPath.md' }); + + expect(localState.entries['newPath.md'].key).toBe('newPath.md-blob-newPath.md'); + expect(localState.entries['newPath.md'].prevKey).toBe(key); + }); + + it('correctly updates key for an entry without an extension', () => { + localState.entries.oldPath.key = 'oldPath-blob-oldPath'; + mutations.RENAME_ENTRY(localState, { path: 'oldPath', name: 'newPath.md' }); + + expect(localState.entries['newPath.md'].key).toBe('newPath.md-blob-newPath.md'); + }); + + it('correctly updates key when new name does not have an extension', () => { + localState.entries['oldPath.txt'].key = 'oldPath.txt-blob-oldPath.txt'; + mutations.RENAME_ENTRY(localState, { path: 'oldPath.txt', name: 'newPath' }); + + expect(localState.entries.newPath.key).toBe('newPath-blob-newPath'); + }); + + it('correctly updates key when renaming an entry in a folder', () => { + localState.entries['rootFolder/oldPath.md'].key = + 'rootFolder/oldPath.md-blob-rootFolder/oldPath.md'; + mutations.RENAME_ENTRY(localState, { + path: 'rootFolder/oldPath.md', + name: 'newPath.md', + entryPath: null, + parentPath: 'rootFolder', + }); + + expect(localState.entries['rootFolder/newPath.md'].key).toBe( + 'rootFolder/newPath.md-blob-rootFolder/newPath.md', + ); + }); }); }); }); diff --git a/spec/javascripts/ide/stores/utils_spec.js b/spec/javascripts/ide/stores/utils_spec.js index 0fc9519a6bf..a477d4fc200 100644 --- a/spec/javascripts/ide/stores/utils_spec.js +++ b/spec/javascripts/ide/stores/utils_spec.js @@ -237,31 +237,6 @@ describe('Multi-file store utils', () => { }); describe('getCommitFiles', () => { - it('returns list of files excluding moved files', () => { - const files = [ - { - path: 'a', - type: 'blob', - deleted: true, - }, - { - path: 'c', - type: 'blob', - moved: true, - }, - ]; - - const flattendFiles = utils.getCommitFiles(files); - - expect(flattendFiles).toEqual([ - { - path: 'a', - type: 'blob', - deleted: true, - }, - ]); - }); - it('filters out folders from the list', () => { const files = [ { @@ -422,4 +397,204 @@ describe('Multi-file store utils', () => { expect(res[1].tree[0].opened).toEqual(true); }); }); + + describe('escapeFileUrl', () => { + it('encodes URL excluding the slashes', () => { + expect(utils.escapeFileUrl('/foo-bar/file.md')).toBe('/foo-bar/file.md'); + expect(utils.escapeFileUrl('foo bar/file.md')).toBe('foo%20bar/file.md'); + expect(utils.escapeFileUrl('foo/bar/file.md')).toBe('foo/bar/file.md'); + }); + }); + + describe('swapInStateArray', () => { + let localState; + + beforeEach(() => { + localState = []; + }); + + it('swaps existing entry with a new one', () => { + const file1 = { + ...file('old'), + key: 'foo', + }; + const file2 = file('new'); + const arr = [file1]; + + Object.assign(localState, { + dummyArray: arr, + entries: { + new: file2, + }, + }); + + utils.swapInStateArray(localState, 'dummyArray', 'foo', 'new'); + + expect(localState.dummyArray.length).toBe(1); + expect(localState.dummyArray[0]).toBe(file2); + }); + + it('does not add an item if it does not exist yet in array', () => { + const file1 = file('file'); + Object.assign(localState, { + dummyArray: [], + entries: { + file: file1, + }, + }); + + utils.swapInStateArray(localState, 'dummyArray', 'foo', 'file'); + + expect(localState.dummyArray.length).toBe(0); + }); + }); + + describe('swapInParentTreeWithSorting', () => { + let localState; + let branchInfo; + const currentProjectId = '123-foo'; + const currentBranchId = 'master'; + + beforeEach(() => { + localState = { + currentBranchId, + currentProjectId, + trees: { + [`${currentProjectId}/${currentBranchId}`]: { + tree: [], + }, + }, + entries: { + oldPath: file('oldPath', 'oldPath', 'blob'), + newPath: file('newPath', 'newPath', 'blob'), + parentPath: file('parentPath', 'parentPath', 'tree'), + }, + }; + branchInfo = localState.trees[`${currentProjectId}/${currentBranchId}`]; + }); + + it('does not change tree if newPath is not supplied', () => { + branchInfo.tree = [localState.entries.oldPath]; + + utils.swapInParentTreeWithSorting(localState, 'oldPath', undefined, undefined); + + expect(branchInfo.tree).toEqual([localState.entries.oldPath]); + }); + + describe('oldPath to replace is not defined: simple addition to tree', () => { + it('adds to tree on the state if there is no parent for the entry', () => { + expect(branchInfo.tree.length).toBe(0); + + utils.swapInParentTreeWithSorting(localState, undefined, 'oldPath', undefined); + + expect(branchInfo.tree.length).toBe(1); + expect(branchInfo.tree[0].name).toBe('oldPath'); + + utils.swapInParentTreeWithSorting(localState, undefined, 'newPath', undefined); + + expect(branchInfo.tree.length).toBe(2); + expect(branchInfo.tree).toEqual([ + jasmine.objectContaining({ name: 'newPath' }), + jasmine.objectContaining({ name: 'oldPath' }), + ]); + }); + + it('adds to parent tree if it is supplied', () => { + utils.swapInParentTreeWithSorting(localState, undefined, 'newPath', 'parentPath'); + + expect(localState.entries.parentPath.tree.length).toBe(1); + expect(localState.entries.parentPath.tree).toEqual([ + jasmine.objectContaining({ name: 'newPath' }), + ]); + + localState.entries.parentPath.tree = [localState.entries.oldPath]; + + utils.swapInParentTreeWithSorting(localState, undefined, 'newPath', 'parentPath'); + + expect(localState.entries.parentPath.tree.length).toBe(2); + expect(localState.entries.parentPath.tree).toEqual([ + jasmine.objectContaining({ name: 'newPath' }), + jasmine.objectContaining({ name: 'oldPath' }), + ]); + }); + }); + + describe('swapping of the items', () => { + it('swaps entries if both paths are supplied', () => { + branchInfo.tree = [localState.entries.oldPath]; + + utils.swapInParentTreeWithSorting(localState, localState.entries.oldPath.key, 'newPath'); + + expect(branchInfo.tree).toEqual([jasmine.objectContaining({ name: 'newPath' })]); + + utils.swapInParentTreeWithSorting(localState, localState.entries.newPath.key, 'oldPath'); + + expect(branchInfo.tree).toEqual([jasmine.objectContaining({ name: 'oldPath' })]); + }); + + it('sorts tree after swapping the entries', () => { + const alpha = file('alpha', 'alpha', 'blob'); + const beta = file('beta', 'beta', 'blob'); + const gamma = file('gamma', 'gamma', 'blob'); + const theta = file('theta', 'theta', 'blob'); + localState.entries = { alpha, beta, gamma, theta }; + + branchInfo.tree = [alpha, beta, gamma]; + + utils.swapInParentTreeWithSorting(localState, alpha.key, 'theta'); + + expect(branchInfo.tree).toEqual([ + jasmine.objectContaining({ name: 'beta' }), + jasmine.objectContaining({ name: 'gamma' }), + jasmine.objectContaining({ name: 'theta' }), + ]); + + utils.swapInParentTreeWithSorting(localState, gamma.key, 'alpha'); + + expect(branchInfo.tree).toEqual([ + jasmine.objectContaining({ name: 'alpha' }), + jasmine.objectContaining({ name: 'beta' }), + jasmine.objectContaining({ name: 'theta' }), + ]); + + utils.swapInParentTreeWithSorting(localState, beta.key, 'gamma'); + + expect(branchInfo.tree).toEqual([ + jasmine.objectContaining({ name: 'alpha' }), + jasmine.objectContaining({ name: 'gamma' }), + jasmine.objectContaining({ name: 'theta' }), + ]); + }); + }); + }); + + describe('cleanTrailingSlash', () => { + [ + { input: '', output: '' }, + { input: 'abc', output: 'abc' }, + { input: 'abc/', output: 'abc' }, + { input: 'abc/def', output: 'abc/def' }, + { input: 'abc/def/', output: 'abc/def' }, + ].forEach(({ input, output }) => { + it(`cleans trailing slash from string "${input}"`, () => { + expect(utils.cleanTrailingSlash(input)).toEqual(output); + }); + }); + }); + + describe('pathsAreEqual', () => { + [ + { args: ['abc', 'abc'], output: true }, + { args: ['abc', 'def'], output: false }, + { args: ['abc/', 'abc'], output: true }, + { args: ['abc/abc', 'abc'], output: false }, + { args: ['/', ''], output: true }, + { args: ['', '/'], output: true }, + { args: [false, '/'], output: true }, + ].forEach(({ args, output }) => { + it(`cleans and tests equality (${JSON.stringify(args)})`, () => { + expect(utils.pathsAreEqual(...args)).toEqual(output); + }); + }); + }); }); diff --git a/spec/javascripts/integrations/integration_settings_form_spec.js b/spec/javascripts/integrations/integration_settings_form_spec.js index 069e2cb07b5..82d1f815ca8 100644 --- a/spec/javascripts/integrations/integration_settings_form_spec.js +++ b/spec/javascripts/integrations/integration_settings_form_spec.js @@ -126,6 +126,7 @@ describe('IntegrationSettingsForm', () => { spyOn(axios, 'put').and.callThrough(); integrationSettingsForm = new IntegrationSettingsForm('.js-integration-settings-form'); + // eslint-disable-next-line no-jquery/no-serialize formData = integrationSettingsForm.$form.serialize(); }); diff --git a/spec/javascripts/issue_show/components/app_spec.js b/spec/javascripts/issue_show/components/app_spec.js index 2770743937e..9fce040fd8c 100644 --- a/spec/javascripts/issue_show/components/app_spec.js +++ b/spec/javascripts/issue_show/components/app_spec.js @@ -1,3 +1,5 @@ +/* eslint-disable no-unused-vars */ +import GLDropdown from '~/gl_dropdown'; import Vue from 'vue'; import MockAdapter from 'axios-mock-adapter'; import axios from '~/lib/utils/axios_utils'; @@ -21,6 +23,14 @@ describe('Issuable output', () => { beforeEach(done => { setFixtures(` <div> + <div class="detail-page-description content-block"> + <details open> + <summary>One</summary> + </details> + <details> + <summary>Two</summary> + </details> + </div> <div class="flash-container"></div> <span id="task_status"></span> </div> @@ -52,6 +62,7 @@ describe('Issuable output', () => { markdownDocsPath: '/', projectNamespace: '/', projectPath: '/', + issuableTemplateNamesPath: '/issuable-templates-path', }, }).$mount(); @@ -129,11 +140,11 @@ describe('Issuable output', () => { }); it('does not update formState if form is already open', done => { - vm.openForm(); + vm.updateAndShowForm(); vm.state.titleText = 'testing 123'; - vm.openForm(); + vm.updateAndShowForm(); Vue.nextTick(() => { expect(vm.store.formState.title).not.toBe('testing 123'); @@ -284,7 +295,7 @@ describe('Issuable output', () => { }); }); - it('shows error mesage from backend if exists', done => { + it('shows error message from backend if exists', done => { const msg = 'Custom error message from backend'; spyOn(vm.service, 'updateIssuable').and.callFake( // eslint-disable-next-line prefer-promise-reject-errors @@ -405,20 +416,20 @@ describe('Issuable output', () => { }); }); - describe('open form', () => { + describe('updateAndShowForm', () => { it('shows locked warning if form is open & data is different', done => { vm.$nextTick() .then(() => { - vm.openForm(); + vm.updateAndShowForm(); vm.poll.makeRequest(); + + return new Promise(resolve => { + vm.$watch('formState.lockedWarningVisible', value => { + if (value) resolve(); + }); + }); }) - // Wait for the request - .then(vm.$nextTick) - // Wait for the successCallback to update the store state - .then(vm.$nextTick) - // Wait for the new state to flow to the Vue components - .then(vm.$nextTick) .then(() => { expect(vm.formState.lockedWarningVisible).toEqual(true); expect(vm.formState.lock_version).toEqual(1); @@ -429,6 +440,41 @@ describe('Issuable output', () => { }); }); + describe('requestTemplatesAndShowForm', () => { + beforeEach(() => { + spyOn(vm, 'updateAndShowForm'); + }); + + it('shows the form if template names request is successful', done => { + const mockData = [{ name: 'Bug' }]; + mock.onGet('/issuable-templates-path').reply(() => Promise.resolve([200, mockData])); + + vm.requestTemplatesAndShowForm() + .then(() => { + expect(vm.updateAndShowForm).toHaveBeenCalledWith(mockData); + }) + .then(done) + .catch(done.fail); + }); + + it('shows the form if template names request failed', done => { + mock + .onGet('/issuable-templates-path') + .reply(() => Promise.reject(new Error('something went wrong'))); + + vm.requestTemplatesAndShowForm() + .then(() => { + expect(document.querySelector('.flash-container .flash-text').textContent).toContain( + 'Error updating issue', + ); + + expect(vm.updateAndShowForm).toHaveBeenCalledWith(); + }) + .then(done) + .catch(done.fail); + }); + }); + describe('show inline edit button', () => { it('should not render by default', () => { expect(vm.$el.querySelector('.title-container .note-action-button')).toBeDefined(); diff --git a/spec/javascripts/jobs/components/environments_block_spec.js b/spec/javascripts/jobs/components/environments_block_spec.js index 4bbc5f5a348..64a59d659a7 100644 --- a/spec/javascripts/jobs/components/environments_block_spec.js +++ b/spec/javascripts/jobs/components/environments_block_spec.js @@ -2,6 +2,9 @@ import Vue from 'vue'; import component from '~/jobs/components/environments_block.vue'; import mountComponent from '../../helpers/vue_mount_component_helper'; +const TEST_CLUSTER_NAME = 'test_cluster'; +const TEST_CLUSTER_PATH = 'path/to/test_cluster'; + describe('Environments block', () => { const Component = Vue.extend(component); let vm; @@ -20,22 +23,53 @@ describe('Environments block', () => { const lastDeployment = { iid: 'deployment', deployable: { build_path: 'bar' } }; + const createEnvironmentWithLastDeployment = () => ({ + ...environment, + last_deployment: { ...lastDeployment }, + }); + + const createEnvironmentWithCluster = () => ({ + ...environment, + last_deployment: { + ...lastDeployment, + cluster: { name: TEST_CLUSTER_NAME, path: TEST_CLUSTER_PATH }, + }, + }); + + const createComponent = (deploymentStatus = {}) => { + vm = mountComponent(Component, { + deploymentStatus, + iconStatus: status, + }); + }; + + const findText = () => vm.$el.textContent.trim(); + const findJobDeploymentLink = () => vm.$el.querySelector('.js-job-deployment-link'); + const findEnvironmentLink = () => vm.$el.querySelector('.js-environment-link'); + const findClusterLink = () => vm.$el.querySelector('.js-job-cluster-link'); + afterEach(() => { vm.$destroy(); }); describe('with last deployment', () => { it('renders info for most recent deployment', () => { - vm = mountComponent(Component, { - deploymentStatus: { - status: 'last', - environment, - }, - iconStatus: status, + createComponent({ + status: 'last', + environment, }); - expect(vm.$el.textContent.trim()).toEqual( - 'This job is the most recent deployment to environment.', + expect(findText()).toEqual('This job is deployed to environment.'); + }); + + it('renders info with cluster', () => { + createComponent({ + status: 'last', + environment: createEnvironmentWithCluster(), + }); + + expect(findText()).toEqual( + `This job is deployed to environment using cluster ${TEST_CLUSTER_NAME}.`, ); }); }); @@ -43,133 +77,106 @@ describe('Environments block', () => { describe('with out of date deployment', () => { describe('with last deployment', () => { it('renders info for out date and most recent', () => { - vm = mountComponent(Component, { - deploymentStatus: { - status: 'out_of_date', - environment: Object.assign({}, environment, { - last_deployment: lastDeployment, - }), - }, - iconStatus: status, + createComponent({ + status: 'out_of_date', + environment: createEnvironmentWithLastDeployment(), }); - expect(vm.$el.textContent.trim()).toEqual( - 'This job is an out-of-date deployment to environment. View the most recent deployment #deployment.', + expect(findText()).toEqual( + 'This job is an out-of-date deployment to environment. View the most recent deployment.', ); - expect(vm.$el.querySelector('.js-job-deployment-link').getAttribute('href')).toEqual('bar'); + expect(findJobDeploymentLink().getAttribute('href')).toEqual('bar'); + }); + + it('renders info with cluster', () => { + createComponent({ + status: 'out_of_date', + environment: createEnvironmentWithCluster(), + }); + + expect(findText()).toEqual( + `This job is an out-of-date deployment to environment using cluster ${TEST_CLUSTER_NAME}. View the most recent deployment.`, + ); }); }); describe('without last deployment', () => { it('renders info about out of date deployment', () => { - vm = mountComponent(Component, { - deploymentStatus: { - status: 'out_of_date', - environment, - }, - iconStatus: status, + createComponent({ + status: 'out_of_date', + environment, }); - expect(vm.$el.textContent.trim()).toEqual( - 'This job is an out-of-date deployment to environment.', - ); + expect(findText()).toEqual('This job is an out-of-date deployment to environment.'); }); }); }); describe('with failed deployment', () => { it('renders info about failed deployment', () => { - vm = mountComponent(Component, { - deploymentStatus: { - status: 'failed', - environment, - }, - iconStatus: status, + createComponent({ + status: 'failed', + environment, }); - expect(vm.$el.textContent.trim()).toEqual( - 'The deployment of this job to environment did not succeed.', - ); + expect(findText()).toEqual('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', () => { - vm = mountComponent(Component, { - deploymentStatus: { - status: 'creating', - environment: Object.assign({}, environment, { - last_deployment: lastDeployment, - }), - }, - iconStatus: status, + createComponent({ + status: 'creating', + environment: createEnvironmentWithLastDeployment(), }); - expect(vm.$el.textContent.trim()).toEqual( - 'This job is creating a deployment to environment and will overwrite the latest deployment.', + expect(findText()).toEqual( + 'This job is creating a deployment to environment. This will overwrite the latest deployment.', ); - expect(vm.$el.querySelector('.js-job-deployment-link').getAttribute('href')).toEqual('bar'); + expect(findJobDeploymentLink().getAttribute('href')).toEqual('bar'); + expect(findEnvironmentLink().getAttribute('href')).toEqual(environment.environment_path); + expect(findClusterLink()).toBeNull(); }); }); describe('without last deployment', () => { it('renders info about failed deployment', () => { - vm = mountComponent(Component, { - deploymentStatus: { - status: 'creating', - environment, - }, - iconStatus: status, + createComponent({ + status: 'creating', + environment, }); - expect(vm.$el.textContent.trim()).toEqual( - 'This job is creating a deployment to environment.', - ); + expect(findText()).toEqual('This job is creating a deployment to environment.'); }); }); describe('without environment', () => { it('does not render environment link', () => { - vm = mountComponent(Component, { - deploymentStatus: { - status: 'creating', - environment: null, - }, - iconStatus: status, + createComponent({ + status: 'creating', + environment: null, }); - expect(vm.$el.querySelector('.js-environment-link')).toBeNull(); + expect(findEnvironmentLink()).toBeNull(); }); }); }); describe('with a cluster', () => { it('renders the cluster link', () => { - const cluster = { - name: 'the-cluster', - path: '/the-cluster-path', - }; - vm = mountComponent(Component, { - deploymentStatus: { - status: 'last', - environment: Object.assign({}, environment, { - last_deployment: { - ...lastDeployment, - cluster, - }, - }), - }, - iconStatus: status, + createComponent({ + status: 'last', + environment: createEnvironmentWithCluster(), }); - expect(vm.$el.textContent.trim()).toContain('Cluster the-cluster was used.'); - - expect(vm.$el.querySelector('.js-job-cluster-link').getAttribute('href')).toEqual( - '/the-cluster-path', + expect(findText()).toEqual( + `This job is deployed to environment using cluster ${TEST_CLUSTER_NAME}.`, ); + + expect(findClusterLink().getAttribute('href')).toEqual(TEST_CLUSTER_PATH); }); describe('when the cluster is missing the path', () => { @@ -177,39 +184,20 @@ describe('Environments block', () => { const cluster = { name: 'the-cluster', }; - vm = mountComponent(Component, { - deploymentStatus: { - status: 'last', - environment: Object.assign({}, environment, { - last_deployment: { - ...lastDeployment, - cluster, - }, - }), - }, - iconStatus: status, - }); - - expect(vm.$el.textContent.trim()).toContain('Cluster the-cluster was used.'); - - expect(vm.$el.querySelector('.js-job-cluster-link')).toBeNull(); - }); - }); - }); - - describe('without a cluster', () => { - it('does not render a cluster link', () => { - vm = mountComponent(Component, { - deploymentStatus: { + createComponent({ status: 'last', environment: Object.assign({}, environment, { - last_deployment: lastDeployment, + last_deployment: { + ...lastDeployment, + cluster, + }, }), - }, - iconStatus: status, - }); + }); + + expect(findText()).toContain('using cluster the-cluster.'); - expect(vm.$el.querySelector('.js-job-cluster-link')).toBeNull(); + expect(findClusterLink()).toBeNull(); + }); }); }); }); diff --git a/spec/javascripts/jobs/components/job_log_spec.js b/spec/javascripts/jobs/components/job_log_spec.js index 24bb6b9a48b..dd58f234394 100644 --- a/spec/javascripts/jobs/components/job_log_spec.js +++ b/spec/javascripts/jobs/components/job_log_spec.js @@ -3,7 +3,6 @@ import component from '~/jobs/components/job_log.vue'; import createStore from '~/jobs/store'; import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; import { resetStore } from '../store/helpers'; -import { logWithCollapsibleSections } from '../mock_data'; describe('Job Log', () => { const Component = Vue.extend(component); @@ -63,60 +62,4 @@ describe('Job Log', () => { expect(vm.$el.querySelector('.js-log-animation')).toBeNull(); }); }); - - describe('Collapsible sections', () => { - beforeEach(() => { - vm = mountComponentWithStore(Component, { - props: { - trace: logWithCollapsibleSections.html, - isComplete: true, - }, - store, - }); - }); - - it('renders open arrow', () => { - expect(vm.$el.querySelector('.fa-caret-down')).not.toBeNull(); - }); - - it('toggles hidden class to the sibilings rows when arrow is clicked', done => { - vm.$nextTick() - .then(() => { - const { section } = vm.$el.querySelector('.js-section-start').dataset; - vm.$el.querySelector('.js-section-start').click(); - - vm.$el.querySelectorAll(`.js-s-${section}:not(.js-section-header)`).forEach(el => { - expect(el.classList.contains('hidden')).toEqual(true); - }); - - vm.$el.querySelector('.js-section-start').click(); - - vm.$el.querySelectorAll(`.js-s-${section}:not(.js-section-header)`).forEach(el => { - expect(el.classList.contains('hidden')).toEqual(false); - }); - }) - .then(done) - .catch(done.fail); - }); - - it('toggles hidden class to the sibilings rows when header section is clicked', done => { - vm.$nextTick() - .then(() => { - const { section } = vm.$el.querySelector('.js-section-header').dataset; - vm.$el.querySelector('.js-section-header').click(); - - vm.$el.querySelectorAll(`.js-s-${section}:not(.js-section-header)`).forEach(el => { - expect(el.classList.contains('hidden')).toEqual(true); - }); - - vm.$el.querySelector('.js-section-header').click(); - - vm.$el.querySelectorAll(`.js-s-${section}:not(.js-section-header)`).forEach(el => { - expect(el.classList.contains('hidden')).toEqual(false); - }); - }) - .then(done) - .catch(done.fail); - }); - }); }); diff --git a/spec/javascripts/labels_issue_sidebar_spec.js b/spec/javascripts/labels_issue_sidebar_spec.js index ccf439aac74..5ae5643aefc 100644 --- a/spec/javascripts/labels_issue_sidebar_spec.js +++ b/spec/javascripts/labels_issue_sidebar_spec.js @@ -5,6 +5,7 @@ import MockAdapter from 'axios-mock-adapter'; import axios from '~/lib/utils/axios_utils'; import IssuableContext from '~/issuable_context'; import LabelsSelect from '~/labels_select'; +import _ from 'underscore'; import '~/gl_dropdown'; import 'select2'; @@ -15,6 +16,35 @@ import '~/users_select'; let saveLabelCount = 0; let mock; +function testLabelClicks(labelOrder, done) { + $('.edit-link') + .get(0) + .click(); + + setTimeout(() => { + const labelsInDropdown = $('.dropdown-content a'); + + expect(labelsInDropdown.length).toBe(10); + + const arrayOfLabels = labelsInDropdown.get(); + const randomArrayOfLabels = _.shuffle(arrayOfLabels); + randomArrayOfLabels.forEach((label, i) => { + if (i < saveLabelCount) { + $(label).click(); + } + }); + + $('.edit-link') + .get(0) + .click(); + + setTimeout(() => { + expect($('.sidebar-collapsed-icon').attr('data-original-title')).toBe(labelOrder); + done(); + }, 0); + }, 0); +} + describe('Issue dropdown sidebar', () => { preloadFixtures('static/issue_sidebar_label.html'); @@ -29,7 +59,7 @@ describe('Issue dropdown sidebar', () => { mock.onGet('/root/test/labels.json').reply(() => { const labels = Array(10) .fill() - .map((_, i) => ({ + .map((_val, i) => ({ id: i, title: `test ${i}`, color: '#5CB85C', @@ -41,7 +71,7 @@ describe('Issue dropdown sidebar', () => { mock.onPut('/root/test/issues/2.json').reply(() => { const labels = Array(saveLabelCount) .fill() - .map((_, i) => ({ + .map((_val, i) => ({ id: i, title: `test ${i}`, color: '#5CB85C', @@ -57,61 +87,11 @@ describe('Issue dropdown sidebar', () => { it('changes collapsed tooltip when changing labels when less than 5', done => { saveLabelCount = 5; - $('.edit-link') - .get(0) - .click(); - - setTimeout(() => { - expect($('.dropdown-content a').length).toBe(10); - - $('.dropdown-content a').each(function(i) { - if (i < saveLabelCount) { - $(this) - .get(0) - .click(); - } - }); - - $('.edit-link') - .get(0) - .click(); - - setTimeout(() => { - expect($('.sidebar-collapsed-icon').attr('data-original-title')).toBe( - 'test 0, test 1, test 2, test 3, test 4', - ); - done(); - }, 0); - }, 0); + testLabelClicks('test 0, test 1, test 2, test 3, test 4', done); }); it('changes collapsed tooltip when changing labels when more than 5', done => { saveLabelCount = 6; - $('.edit-link') - .get(0) - .click(); - - setTimeout(() => { - expect($('.dropdown-content a').length).toBe(10); - - $('.dropdown-content a').each(function(i) { - if (i < saveLabelCount) { - $(this) - .get(0) - .click(); - } - }); - - $('.edit-link') - .get(0) - .click(); - - setTimeout(() => { - expect($('.sidebar-collapsed-icon').attr('data-original-title')).toBe( - 'test 0, test 1, test 2, test 3, test 4, and 1 more', - ); - done(); - }, 0); - }, 0); + testLabelClicks('test 0, test 1, test 2, test 3, test 4, and 1 more', done); }); }); diff --git a/spec/javascripts/lazy_loader_spec.js b/spec/javascripts/lazy_loader_spec.js index f3fb792c62d..82ab73c2170 100644 --- a/spec/javascripts/lazy_loader_spec.js +++ b/spec/javascripts/lazy_loader_spec.js @@ -62,7 +62,7 @@ describe('LazyLoader', function() { waitForAttributeChange(newImg, ['data-src', 'src']), ]) .then(() => { - expect(LazyLoader.loadImage).toHaveBeenCalled(); + expect(LazyLoader.loadImage).toHaveBeenCalledWith(newImg); expect(newImg.getAttribute('src')).toBe(testPath); expect(newImg).toHaveClass('js-lazy-loaded'); done(); @@ -79,7 +79,7 @@ describe('LazyLoader', function() { scrollIntoViewPromise(newImg) .then(waitForPromises) .then(() => { - expect(LazyLoader.loadImage).not.toHaveBeenCalled(); + expect(LazyLoader.loadImage).not.toHaveBeenCalledWith(newImg); expect(newImg).not.toHaveClass('js-lazy-loaded'); done(); }) @@ -98,7 +98,7 @@ describe('LazyLoader', function() { scrollIntoViewPromise(newImg) .then(waitForPromises) .then(() => { - expect(LazyLoader.loadImage).not.toHaveBeenCalled(); + expect(LazyLoader.loadImage).not.toHaveBeenCalledWith(newImg); expect(newImg).not.toHaveClass('js-lazy-loaded'); done(); }) @@ -121,7 +121,7 @@ describe('LazyLoader', function() { ]) .then(waitForPromises) .then(() => { - expect(LazyLoader.loadImage).toHaveBeenCalled(); + expect(LazyLoader.loadImage).toHaveBeenCalledWith(newImg); expect(newImg).toHaveClass('js-lazy-loaded'); done(); }) @@ -156,7 +156,7 @@ describe('LazyLoader', function() { Promise.all([scrollIntoViewPromise(img), waitForAttributeChange(img, ['data-src', 'src'])]) .then(() => { - expect(LazyLoader.loadImage).toHaveBeenCalled(); + expect(LazyLoader.loadImage).toHaveBeenCalledWith(img); expect(img.getAttribute('src')).toBe(originalDataSrc); expect(img).toHaveClass('js-lazy-loaded'); done(); @@ -176,7 +176,7 @@ describe('LazyLoader', function() { waitForAttributeChange(newImg, ['data-src', 'src']), ]) .then(() => { - expect(LazyLoader.loadImage).toHaveBeenCalled(); + expect(LazyLoader.loadImage).toHaveBeenCalledWith(newImg); expect(newImg.getAttribute('src')).toBe(testPath); expect(newImg).toHaveClass('js-lazy-loaded'); done(); @@ -193,7 +193,7 @@ describe('LazyLoader', function() { scrollIntoViewPromise(newImg) .then(waitForPromises) .then(() => { - expect(LazyLoader.loadImage).not.toHaveBeenCalled(); + expect(LazyLoader.loadImage).not.toHaveBeenCalledWith(newImg); expect(newImg).not.toHaveClass('js-lazy-loaded'); done(); }) @@ -212,7 +212,7 @@ describe('LazyLoader', function() { scrollIntoViewPromise(newImg) .then(waitForPromises) .then(() => { - expect(LazyLoader.loadImage).not.toHaveBeenCalled(); + expect(LazyLoader.loadImage).not.toHaveBeenCalledWith(newImg); expect(newImg).not.toHaveClass('js-lazy-loaded'); done(); }) @@ -234,7 +234,7 @@ describe('LazyLoader', function() { waitForAttributeChange(newImg, ['data-src', 'src']), ]) .then(() => { - expect(LazyLoader.loadImage).toHaveBeenCalled(); + expect(LazyLoader.loadImage).toHaveBeenCalledWith(newImg); expect(newImg).toHaveClass('js-lazy-loaded'); done(); }) diff --git a/spec/javascripts/lib/utils/common_utils_spec.js b/spec/javascripts/lib/utils/common_utils_spec.js index 85949f2ae86..8956bc92e6b 100644 --- a/spec/javascripts/lib/utils/common_utils_spec.js +++ b/spec/javascripts/lib/utils/common_utils_spec.js @@ -943,4 +943,14 @@ describe('common_utils', () => { expect(commonUtils.isScopedLabel({ title: 'foobar' })).toBe(false); }); }); + + describe('getDashPath', () => { + it('returns the path following /-/', () => { + expect(commonUtils.getDashPath('/some/-/url-with-dashes-/')).toEqual('url-with-dashes-/'); + }); + + it('returns null when no path follows /-/', () => { + expect(commonUtils.getDashPath('/some/url')).toEqual(null); + }); + }); }); diff --git a/spec/javascripts/line_highlighter_spec.js b/spec/javascripts/line_highlighter_spec.js index a75470b4db8..f8f835ffdef 100644 --- a/spec/javascripts/line_highlighter_spec.js +++ b/spec/javascripts/line_highlighter_spec.js @@ -1,4 +1,4 @@ -/* eslint-disable no-var, prefer-template, no-else-return, dot-notation, no-return-assign, no-new, no-underscore-dangle */ +/* eslint-disable no-var, no-else-return, dot-notation, no-return-assign, no-new, no-underscore-dangle */ import $ from 'jquery'; import LineHighlighter from '~/line_highlighter'; @@ -8,10 +8,10 @@ describe('LineHighlighter', function() { preloadFixtures('static/line_highlighter.html'); clickLine = function(number, eventData = {}) { if ($.isEmptyObject(eventData)) { - return $('#L' + number).click(); + return $(`#L${number}`).click(); } else { const e = $.Event('click', eventData); - return $('#L' + number).trigger(e); + return $(`#L${number}`).trigger(e); } }; beforeEach(function() { @@ -42,9 +42,9 @@ describe('LineHighlighter', function() { var line; new LineHighlighter({ hash: '#L5-25' }); - expect($('.' + this.css).length).toBe(21); + expect($(`.${this.css}`).length).toBe(21); for (line = 5; line <= 25; line += 1) { - expect($('#LC' + line)).toHaveClass(this.css); + expect($(`#LC${line}`)).toHaveClass(this.css); } }); @@ -130,7 +130,7 @@ describe('LineHighlighter', function() { }); expect($('#LC13')).toHaveClass(this.css); - expect($('.' + this.css).length).toBe(1); + expect($(`.${this.css}`).length).toBe(1); }); it('sets the hash', function() { @@ -152,9 +152,9 @@ describe('LineHighlighter', function() { shiftKey: true, }); - expect($('.' + this.css).length).toBe(6); + expect($(`.${this.css}`).length).toBe(6); for (line = 15; line <= 20; line += 1) { - expect($('#LC' + line)).toHaveClass(this.css); + expect($(`#LC${line}`)).toHaveClass(this.css); } }); @@ -165,9 +165,9 @@ describe('LineHighlighter', function() { shiftKey: true, }); - expect($('.' + this.css).length).toBe(6); + expect($(`.${this.css}`).length).toBe(6); for (line = 5; line <= 10; line += 1) { - expect($('#LC' + line)).toHaveClass(this.css); + expect($(`#LC${line}`)).toHaveClass(this.css); } }); }); @@ -188,9 +188,9 @@ describe('LineHighlighter', function() { shiftKey: true, }); - expect($('.' + this.css).length).toBe(6); + expect($(`.${this.css}`).length).toBe(6); for (line = 5; line <= 10; line += 1) { - expect($('#LC' + line)).toHaveClass(this.css); + expect($(`#LC${line}`)).toHaveClass(this.css); } }); @@ -200,9 +200,9 @@ describe('LineHighlighter', function() { shiftKey: true, }); - expect($('.' + this.css).length).toBe(6); + expect($(`.${this.css}`).length).toBe(6); for (line = 10; line <= 15; line += 1) { - expect($('#LC' + line)).toHaveClass(this.css); + expect($(`#LC${line}`)).toHaveClass(this.css); } }); }); diff --git a/spec/javascripts/monitoring/charts/time_series_spec.js b/spec/javascripts/monitoring/charts/time_series_spec.js index f6a5ed03c0d..5c718135b90 100644 --- a/spec/javascripts/monitoring/charts/time_series_spec.js +++ b/spec/javascripts/monitoring/charts/time_series_spec.js @@ -60,6 +60,18 @@ describe('Time series component', () => { expect(timeSeriesChart.find('.js-graph-widgets').text()).toBe(mockWidgets); }); + it('allows user to override max value label text using prop', () => { + timeSeriesChart.setProps({ legendMaxText: 'legendMaxText' }); + + expect(timeSeriesChart.props().legendMaxText).toBe('legendMaxText'); + }); + + it('allows user to override average value label text using prop', () => { + timeSeriesChart.setProps({ legendAverageText: 'averageText' }); + + expect(timeSeriesChart.props().legendAverageText).toBe('averageText'); + }); + describe('methods', () => { describe('formatTooltipText', () => { const mockDate = deploymentData[0].created_at; @@ -140,6 +152,16 @@ describe('Time series component', () => { expect(timeSeriesChart.vm.svgs[mockSvgName]).toBe(`path://${mockSvgPathContent}`); }); }); + + it('contains an svg object within an array to properly render icon', () => { + timeSeriesChart.vm.$nextTick(() => { + expect(timeSeriesChart.vm.chartOptions.dataZoom).toEqual([ + { + handleIcon: `path://${mockSvgPathContent}`, + }, + ]); + }); + }); }); describe('onResize', () => { diff --git a/spec/javascripts/monitoring/components/dashboard_spec.js b/spec/javascripts/monitoring/components/dashboard_spec.js index 6ce32d21f45..75df2ce3103 100644 --- a/spec/javascripts/monitoring/components/dashboard_spec.js +++ b/spec/javascripts/monitoring/components/dashboard_spec.js @@ -1,9 +1,9 @@ import Vue from 'vue'; import { shallowMount, createLocalVue } from '@vue/test-utils'; import { GlToast } from '@gitlab/ui'; +import VueDraggable from 'vuedraggable'; import MockAdapter from 'axios-mock-adapter'; import Dashboard from '~/monitoring/components/dashboard.vue'; -import { timeWindows, timeWindowsKeyNames } from '~/monitoring/constants'; import * as types from '~/monitoring/stores/mutation_types'; import { createStore } from '~/monitoring/stores'; import axios from '~/lib/utils/axios_utils'; @@ -36,6 +36,12 @@ const propsData = { validateQueryPath: '', }; +const resetSpy = spy => { + if (spy) { + spy.calls.reset(); + } +}; + export default propsData; describe('Dashboard', () => { @@ -51,11 +57,6 @@ describe('Dashboard', () => { <div class="layout-page"></div> `); - window.gon = { - ...window.gon, - ee: false, - }; - store = createStore(); mock = new MockAdapter(axios); DashboardComponent = Vue.extend(Dashboard); @@ -100,10 +101,15 @@ describe('Dashboard', () => { }); describe('requests information to the server', () => { + let spy; beforeEach(() => { mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse); }); + afterEach(() => { + resetSpy(spy); + }); + it('shows up a loading state', done => { component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), @@ -276,7 +282,7 @@ describe('Dashboard', () => { }); }); - it('renders the time window dropdown with a set of options', done => { + it('renders the datetimepicker dropdown', done => { component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), propsData: { @@ -286,17 +292,9 @@ describe('Dashboard', () => { }, store, }); - const numberOfTimeWindows = Object.keys(timeWindows).length; setTimeout(() => { - const timeWindowDropdown = component.$el.querySelector('.js-time-window-dropdown'); - const timeWindowDropdownEls = component.$el.querySelectorAll( - '.js-time-window-dropdown .dropdown-item', - ); - - expect(timeWindowDropdown).not.toBeNull(); - expect(timeWindowDropdownEls.length).toEqual(numberOfTimeWindows); - + expect(component.$el.querySelector('.js-time-window-dropdown')).not.toBeNull(); done(); }); }); @@ -333,8 +331,8 @@ describe('Dashboard', () => { }); it('shows a specific time window selected from the url params', done => { - const start = 1564439536; - const end = 1564441336; + const start = '2019-10-01T18:27:47.000Z'; + const end = '2019-10-01T18:57:47.000Z'; spyOnDependency(Dashboard, 'getTimeDiff').and.returnValue({ start, end, @@ -359,7 +357,7 @@ describe('Dashboard', () => { }); }); - it('defaults to the eight hours time window for non valid url parameters', done => { + it('shows an error message if invalid url parameters are passed', done => { spyOnDependency(Dashboard, 'getParameterValues').and.returnValue([ '<script>alert("XSS")</script>', ]); @@ -370,15 +368,111 @@ describe('Dashboard', () => { store, }); - Vue.nextTick(() => { - expect(component.selectedTimeWindowKey).toEqual(timeWindowsKeyNames.eightHours); + spy = spyOn(component, 'showInvalidDateError'); + component.$mount(); + component.$nextTick(() => { + expect(component.showInvalidDateError).toHaveBeenCalled(); done(); }); }); }); - // https://gitlab.com/gitlab-org/gitlab-foss/issues/66922 + describe('drag and drop function', () => { + let wrapper; + let expectedPanelCount; // also called metrics, naming to be improved: https://gitlab.com/gitlab-org/gitlab/issues/31565 + const findDraggables = () => wrapper.findAll(VueDraggable); + const findEnabledDraggables = () => findDraggables().filter(f => !f.attributes('disabled')); + const findDraggablePanels = () => wrapper.findAll('.js-draggable-panel'); + const findRearrangeButton = () => wrapper.find('.js-rearrange-button'); + + beforeEach(done => { + mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse); + expectedPanelCount = metricsGroupsAPIResponse.data.reduce( + (acc, d) => d.metrics.length + acc, + 0, + ); + store.dispatch('monitoringDashboard/setFeatureFlags', { additionalPanelTypesEnabled: true }); + + wrapper = shallowMount(DashboardComponent, { + localVue, + sync: false, + propsData: { ...propsData, hasMetrics: true }, + store, + }); + + // not using $nextTicket becuase we must wait for the dashboard + // to be populated with the mock data results. + setTimeout(done); + }); + + it('wraps vuedraggable', () => { + expect(findDraggablePanels().exists()).toBe(true); + expect(findDraggablePanels().length).toEqual(expectedPanelCount); + }); + + it('is disabled by default', () => { + expect(findRearrangeButton().exists()).toBe(false); + expect(findEnabledDraggables().length).toBe(0); + }); + + describe('when rearrange is enabled', () => { + beforeEach(done => { + wrapper.setProps({ rearrangePanelsAvailable: true }); + wrapper.vm.$nextTick(done); + }); + + it('displays rearrange button', () => { + expect(findRearrangeButton().exists()).toBe(true); + }); + + describe('when rearrange button is clicked', () => { + const findFirstDraggableRemoveButton = () => + findDraggablePanels() + .at(0) + .find('.js-draggable-remove'); + + beforeEach(done => { + findRearrangeButton().vm.$emit('click'); + wrapper.vm.$nextTick(done); + }); + + it('it enables draggables', () => { + expect(findRearrangeButton().attributes('pressed')).toBeTruthy(); + expect(findEnabledDraggables()).toEqual(findDraggables()); + }); + + it('shows a remove button, which removes a panel', done => { + expect(findFirstDraggableRemoveButton().isEmpty()).toBe(false); + + expect(findDraggablePanels().length).toEqual(expectedPanelCount); + findFirstDraggableRemoveButton().trigger('click'); + + wrapper.vm.$nextTick(() => { + // At present graphs will not be removed in backend + // See https://gitlab.com/gitlab-org/gitlab/issues/27835 + expect(findDraggablePanels().length).toEqual(expectedPanelCount - 1); + done(); + }); + }); + + it('it disables draggables when clicked again', done => { + findRearrangeButton().vm.$emit('click'); + wrapper.vm.$nextTick(() => { + expect(findRearrangeButton().attributes('pressed')).toBeFalsy(); + expect(findEnabledDraggables().length).toBe(0); + done(); + }); + }); + }); + }); + + afterEach(() => { + wrapper.destroy(); + }); + }); + + // https://gitlab.com/gitlab-org/gitlab-ce/issues/66922 // eslint-disable-next-line jasmine/no-disabled-tests xdescribe('link to chart', () => { let wrapper; @@ -527,7 +621,6 @@ describe('Dashboard', () => { component.$store.dispatch('monitoringDashboard/setFeatureFlags', { prometheusEndpoint: false, - multipleDashboardsEnabled: true, }); component.$store.commit( diff --git a/spec/javascripts/monitoring/store/actions_spec.js b/spec/javascripts/monitoring/store/actions_spec.js index 955a39e03a5..1bd74f59282 100644 --- a/spec/javascripts/monitoring/store/actions_spec.js +++ b/spec/javascripts/monitoring/store/actions_spec.js @@ -240,8 +240,6 @@ describe('Monitoring store actions', () => { const response = metricsDashboardResponse; response.all_dashboards = dashboardGitResponse; - state.multipleDashboardsEnabled = true; - receiveMetricsDashboardSuccess({ state, commit, dispatch }, { response, params }); expect(commit).toHaveBeenCalledWith(types.SET_ALL_DASHBOARDS, dashboardGitResponse); diff --git a/spec/javascripts/monitoring/store/mutations_spec.js b/spec/javascripts/monitoring/store/mutations_spec.js index bdb68a80a8a..bdddd83358c 100644 --- a/spec/javascripts/monitoring/store/mutations_spec.js +++ b/spec/javascripts/monitoring/store/mutations_spec.js @@ -7,6 +7,7 @@ import { metricsDashboardResponse, dashboardGitResponse, } from '../mock_data'; +import { uniqMetricsId } from '~/monitoring/stores/utils'; describe('Monitoring mutations', () => { let stateCopy; @@ -128,6 +129,7 @@ describe('Monitoring mutations', () => { describe('SET_QUERY_RESULT', () => { const metricId = 12; + const id = 'system_metrics_kubernetes_container_memory_total'; const result = [{ values: [[0, 1], [1, 1], [1, 3]] }]; beforeEach(() => { @@ -146,12 +148,13 @@ describe('Monitoring mutations', () => { }); it('sets metricsWithData value', () => { + const uniqId = uniqMetricsId({ metric_id: metricId, id }); mutations[types.SET_QUERY_RESULT](stateCopy, { - metricId, + metricId: uniqId, result, }); - expect(stateCopy.metricsWithData).toEqual([12]); + expect(stateCopy.metricsWithData).toEqual([uniqId]); }); it('does not store empty results', () => { diff --git a/spec/javascripts/monitoring/store/utils_spec.js b/spec/javascripts/monitoring/store/utils_spec.js index 73dd370ffb3..98388ac19f8 100644 --- a/spec/javascripts/monitoring/store/utils_spec.js +++ b/spec/javascripts/monitoring/store/utils_spec.js @@ -1,4 +1,4 @@ -import { groupQueriesByChartInfo } from '~/monitoring/stores/utils'; +import { groupQueriesByChartInfo, normalizeMetric, uniqMetricsId } from '~/monitoring/stores/utils'; describe('groupQueriesByChartInfo', () => { let input; @@ -12,7 +12,11 @@ describe('groupQueriesByChartInfo', () => { ]; output = [ - { title: 'title', y_label: 'MB', queries: [{ metricId: null }, { metricId: null }] }, + { + title: 'title', + y_label: 'MB', + queries: [{ metricId: null }, { metricId: null }], + }, { title: 'new title', y_label: 'MB', queries: [{ metricId: null }] }, ]; @@ -35,3 +39,36 @@ describe('groupQueriesByChartInfo', () => { expect(groupQueriesByChartInfo(input)).toEqual(output); }); }); + +describe('normalizeMetric', () => { + [ + { args: [], expected: 'undefined_undefined' }, + { args: [undefined], expected: 'undefined_undefined' }, + { args: [{ id: 'something' }], expected: 'undefined_something' }, + { args: [{ id: 45 }], expected: 'undefined_45' }, + { args: [{ metric_id: 5 }], expected: '5_undefined' }, + { args: [{ metric_id: 'something' }], expected: 'something_undefined' }, + { + args: [{ metric_id: 5, id: 'system_metrics_kubernetes_container_memory_total' }], + expected: '5_system_metrics_kubernetes_container_memory_total', + }, + ].forEach(({ args, expected }) => { + it(`normalizes metric to "${expected}" with args=${JSON.stringify(args)}`, () => { + expect(normalizeMetric(...args)).toEqual({ metric_id: expected }); + }); + }); +}); + +describe('uniqMetricsId', () => { + [ + { input: { id: 1 }, expected: 'undefined_1' }, + { input: { metric_id: 2 }, expected: '2_undefined' }, + { input: { metric_id: 2, id: 21 }, expected: '2_21' }, + { input: { metric_id: 22, id: 1 }, expected: '22_1' }, + { input: { metric_id: 'aaa', id: '_a' }, expected: 'aaa__a' }, + ].forEach(({ input, expected }) => { + it(`creates unique metric ID with ${JSON.stringify(input)}`, () => { + expect(uniqMetricsId(input)).toEqual(expected); + }); + }); +}); diff --git a/spec/javascripts/monitoring/utils_spec.js b/spec/javascripts/monitoring/utils_spec.js index e22e8cdc03d..512dd2a0eb3 100644 --- a/spec/javascripts/monitoring/utils_spec.js +++ b/spec/javascripts/monitoring/utils_spec.js @@ -1,5 +1,14 @@ -import { getTimeDiff, graphDataValidatorForValues } from '~/monitoring/utils'; -import { timeWindows } from '~/monitoring/constants'; +import { + getTimeDiff, + getTimeWindow, + graphDataValidatorForValues, + isDateTimePickerInputValid, + truncateZerosInDateTime, + stringToISODate, + ISODateToString, + isValidDate, +} from '~/monitoring/utils'; +import { timeWindows, timeWindowsKeyNames } from '~/monitoring/constants'; import { graphDataPrometheusQuery, graphDataPrometheusQueryRange } from './mock_data'; describe('getTimeDiff', () => { @@ -39,6 +48,55 @@ describe('getTimeDiff', () => { }); }); +describe('getTimeWindow', () => { + [ + { + args: [ + { + start: '2019-10-01T18:27:47.000Z', + end: '2019-10-01T21:27:47.000Z', + }, + ], + expected: timeWindowsKeyNames.threeHours, + }, + { + args: [ + { + start: '2019-10-01T28:27:47.000Z', + end: '2019-10-01T21:27:47.000Z', + }, + ], + expected: null, + }, + { + args: [ + { + start: '', + end: '', + }, + ], + expected: null, + }, + { + args: [ + { + start: null, + end: null, + }, + ], + expected: null, + }, + { + args: [{}], + expected: null, + }, + ].forEach(({ args, expected }) => { + it(`returns "${expected}" with args=${JSON.stringify(args)}`, () => { + expect(getTimeWindow(...args)).toEqual(expected); + }); + }); +}); + describe('graphDataValidatorForValues', () => { /* * When dealing with a metric using the query format, e.g. @@ -62,3 +120,190 @@ describe('graphDataValidatorForValues', () => { expect(validGraphData).toBe(true); }); }); + +describe('stringToISODate', () => { + ['', 'null', undefined, 'abc'].forEach(input => { + it(`throws error for invalid input like ${input}`, done => { + try { + stringToISODate(input); + } catch (e) { + expect(e).toBeDefined(); + done(); + } + }); + }); + [ + { + input: '2019-09-09 01:01:01', + output: '2019-09-09T01:01:01Z', + }, + { + input: '2019-09-09 00:00:00', + output: '2019-09-09T00:00:00Z', + }, + { + input: '2019-09-09 23:59:59', + output: '2019-09-09T23:59:59Z', + }, + { + input: '2019-09-09', + output: '2019-09-09T00:00:00Z', + }, + ].forEach(({ input, output }) => { + it(`returns ${output} from ${input}`, () => { + expect(stringToISODate(input)).toBe(output); + }); + }); +}); + +describe('ISODateToString', () => { + [ + { + input: new Date('2019-09-09T00:00:00.000Z'), + output: '2019-09-09 00:00:00', + }, + { + input: new Date('2019-09-09T07:00:00.000Z'), + output: '2019-09-09 07:00:00', + }, + ].forEach(({ input, output }) => { + it(`ISODateToString return ${output} for ${input}`, () => { + expect(ISODateToString(input)).toBe(output); + }); + }); +}); + +describe('truncateZerosInDateTime', () => { + [ + { + input: '', + output: '', + }, + { + input: '2019-10-10', + output: '2019-10-10', + }, + { + input: '2019-10-10 00:00:01', + output: '2019-10-10 00:00:01', + }, + { + input: '2019-10-10 00:00:00', + output: '2019-10-10', + }, + ].forEach(({ input, output }) => { + it(`truncateZerosInDateTime return ${output} for ${input}`, () => { + expect(truncateZerosInDateTime(input)).toBe(output); + }); + }); +}); + +describe('isValidDate', () => { + [ + { + input: '2019-09-09T00:00:00.000Z', + output: true, + }, + { + input: '2019-09-09T000:00.000Z', + output: false, + }, + { + input: 'a2019-09-09T000:00.000Z', + output: false, + }, + { + input: '2019-09-09T', + output: false, + }, + { + input: '2019-09-09', + output: true, + }, + { + input: '2019-9-9', + output: true, + }, + { + input: '2019-9-', + output: true, + }, + { + input: '2019--', + output: false, + }, + { + input: '2019', + output: true, + }, + { + input: '', + output: false, + }, + { + input: null, + output: false, + }, + ].forEach(({ input, output }) => { + it(`isValidDate return ${output} for ${input}`, () => { + expect(isValidDate(input)).toBe(output); + }); + }); +}); + +describe('isDateTimePickerInputValid', () => { + [ + { + input: null, + output: false, + }, + { + input: '', + output: false, + }, + { + input: 'xxxx-xx-xx', + output: false, + }, + { + input: '9999-99-19', + output: false, + }, + { + input: '2019-19-23', + output: false, + }, + { + input: '2019-09-23', + output: true, + }, + { + input: '2019-09-23 x', + output: false, + }, + { + input: '2019-09-29 0:0:0', + output: false, + }, + { + input: '2019-09-29 00:00:00', + output: true, + }, + { + input: '2019-09-29 24:24:24', + output: false, + }, + { + input: '2019-09-29 23:24:24', + output: true, + }, + { + input: '2019-09-29 23:24:24 ', + output: false, + }, + ].forEach(({ input, output }) => { + it(`returns ${output} for ${input}`, () => { + expect(isDateTimePickerInputValid(input)).toBe(output); + }); + }); +}); diff --git a/spec/javascripts/notes/components/discussion_filter_spec.js b/spec/javascripts/notes/components/discussion_filter_spec.js index 1c366aee8e2..7524de36ac5 100644 --- a/spec/javascripts/notes/components/discussion_filter_spec.js +++ b/spec/javascripts/notes/components/discussion_filter_spec.js @@ -160,5 +160,28 @@ describe('DiscussionFilter component', () => { done(); }); }); + + it('fetches discussions when there is a hash', done => { + window.location.hash = `note_${discussionMock.notes[0].id}`; + vm.currentValue = discussionFiltersMock[2].value; + spyOn(vm, 'selectFilter'); + vm.handleLocationHash(); + + vm.$nextTick(() => { + expect(vm.selectFilter).toHaveBeenCalled(); + done(); + }); + }); + + it('does not fetch discussions when there is no hash', done => { + window.location.hash = ''; + spyOn(vm, 'selectFilter'); + vm.handleLocationHash(); + + vm.$nextTick(() => { + expect(vm.selectFilter).not.toHaveBeenCalled(); + done(); + }); + }); }); }); diff --git a/spec/javascripts/notes/stores/mutation_spec.js b/spec/javascripts/notes/stores/mutation_spec.js index 4a640d589fb..ade4725dd68 100644 --- a/spec/javascripts/notes/stores/mutation_spec.js +++ b/spec/javascripts/notes/stores/mutation_spec.js @@ -48,9 +48,22 @@ describe('Notes Store mutations', () => { }); describe('ADD_NEW_REPLY_TO_DISCUSSION', () => { + const newReply = Object.assign({}, note, { discussion_id: discussionMock.id }); + + let state; + + beforeEach(() => { + state = { discussions: [{ ...discussionMock }] }; + }); + it('should add a reply to a specific discussion', () => { - const state = { discussions: [discussionMock] }; - const newReply = Object.assign({}, note, { discussion_id: discussionMock.id }); + mutations.ADD_NEW_REPLY_TO_DISCUSSION(state, newReply); + + expect(state.discussions[0].notes.length).toEqual(4); + }); + + it('should not add the note if it already exists in the discussion', () => { + mutations.ADD_NEW_REPLY_TO_DISCUSSION(state, newReply); mutations.ADD_NEW_REPLY_TO_DISCUSSION(state, newReply); expect(state.discussions[0].notes.length).toEqual(4); diff --git a/spec/javascripts/pager_spec.js b/spec/javascripts/pager_spec.js index 93efc139254..c95a8400c6c 100644 --- a/spec/javascripts/pager_spec.js +++ b/spec/javascripts/pager_spec.js @@ -63,9 +63,9 @@ describe('pager', () => { describe('getOld', () => { const urlRegex = /(.*)some_list(.*)$/; - function mockSuccess() { + function mockSuccess(count = 0) { axiosMock.onGet(urlRegex).reply(200, { - count: 0, + count, html: '', }); } @@ -142,5 +142,21 @@ describe('pager', () => { done(); }); }); + + it('disables if return count is less than limit', done => { + Pager.offset = 0; + Pager.limit = 20; + + mockSuccess(1); + spyOn(Pager.loading, 'hide'); + Pager.getOld(); + + setTimeout(() => { + expect(Pager.loading.hide).toHaveBeenCalled(); + expect(Pager.disable).toBe(true); + + done(); + }); + }); }); }); diff --git a/spec/javascripts/performance_bar/components/detailed_metric_spec.js b/spec/javascripts/performance_bar/components/detailed_metric_spec.js deleted file mode 100644 index 0486b5fa3db..00000000000 --- a/spec/javascripts/performance_bar/components/detailed_metric_spec.js +++ /dev/null @@ -1,109 +0,0 @@ -import Vue from 'vue'; -import detailedMetric from '~/performance_bar/components/detailed_metric.vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; - -describe('detailedMetric', () => { - let vm; - - afterEach(() => { - vm.$destroy(); - }); - - describe('when the current request has no details', () => { - beforeEach(() => { - vm = mountComponent(Vue.extend(detailedMetric), { - currentRequest: {}, - metric: 'gitaly', - header: 'Gitaly calls', - details: 'details', - keys: ['feature', 'request'], - }); - }); - - it('does not render the element', () => { - expect(vm.$el.innerHTML).toEqual(undefined); - }); - }); - - describe('when the current request has details', () => { - const requestDetails = [ - { duration: '100', feature: 'find_commit', request: 'abcdef', backtrace: ['hello', 'world'] }, - { duration: '23', feature: 'rebase_in_progress', request: '', backtrace: ['world', 'hello'] }, - ]; - - beforeEach(() => { - vm = mountComponent(Vue.extend(detailedMetric), { - currentRequest: { - details: { - gitaly: { - duration: '123ms', - calls: '456', - details: requestDetails, - }, - }, - }, - metric: 'gitaly', - header: 'Gitaly calls', - keys: ['feature', 'request'], - }); - }); - - it('diplays details', () => { - expect(vm.$el.innerText.replace(/\s+/g, ' ')).toContain('123ms / 456'); - }); - - it('adds a modal with a table of the details', () => { - vm.$el - .querySelectorAll('.performance-bar-modal td:nth-child(1)') - .forEach((duration, index) => { - expect(duration.innerText).toContain(requestDetails[index].duration); - }); - - vm.$el - .querySelectorAll('.performance-bar-modal td:nth-child(2)') - .forEach((feature, index) => { - expect(feature.innerText).toContain(requestDetails[index].feature); - }); - - vm.$el - .querySelectorAll('.performance-bar-modal td:nth-child(2)') - .forEach((request, index) => { - expect(request.innerText).toContain(requestDetails[index].request); - }); - - expect(vm.$el.querySelector('.text-expander.js-toggle-button')).not.toBeNull(); - - vm.$el.querySelectorAll('.performance-bar-modal td:nth-child(2)').forEach(request => { - expect(request.innerText).toContain('world'); - }); - }); - - it('displays the metric title', () => { - expect(vm.$el.innerText).toContain('gitaly'); - }); - - describe('when using a custom metric title', () => { - beforeEach(() => { - vm = mountComponent(Vue.extend(detailedMetric), { - currentRequest: { - details: { - gitaly: { - duration: '123ms', - calls: '456', - details: requestDetails, - }, - }, - }, - metric: 'gitaly', - title: 'custom', - header: 'Gitaly calls', - keys: ['feature', 'request'], - }); - }); - - it('displays the custom title', () => { - expect(vm.$el.innerText).toContain('custom'); - }); - }); - }); -}); diff --git a/spec/javascripts/performance_bar/components/performance_bar_app_spec.js b/spec/javascripts/performance_bar/components/performance_bar_app_spec.js deleted file mode 100644 index 7926db44429..00000000000 --- a/spec/javascripts/performance_bar/components/performance_bar_app_spec.js +++ /dev/null @@ -1,29 +0,0 @@ -import Vue from 'vue'; -import performanceBarApp from '~/performance_bar/components/performance_bar_app.vue'; -import PerformanceBarStore from '~/performance_bar/stores/performance_bar_store'; - -import mountComponent from 'spec/helpers/vue_mount_component_helper'; - -describe('performance bar app', () => { - let vm; - - beforeEach(() => { - const store = new PerformanceBarStore(); - - vm = mountComponent(Vue.extend(performanceBarApp), { - store, - env: 'development', - requestId: '123', - peekUrl: '/-/peek/results', - profileUrl: '?lineprofiler=true', - }); - }); - - afterEach(() => { - vm.$destroy(); - }); - - it('sets the class to match the environment', () => { - expect(vm.$el.getAttribute('class')).toContain('development'); - }); -}); diff --git a/spec/javascripts/performance_bar/components/request_selector_spec.js b/spec/javascripts/performance_bar/components/request_selector_spec.js deleted file mode 100644 index 3c2169de877..00000000000 --- a/spec/javascripts/performance_bar/components/request_selector_spec.js +++ /dev/null @@ -1,46 +0,0 @@ -import Vue from 'vue'; -import requestSelector from '~/performance_bar/components/request_selector.vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; - -describe('request selector', () => { - const requests = [ - { id: '123', url: 'https://gitlab.com/' }, - { - id: '456', - url: 'https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/1', - }, - { - id: '789', - url: 'https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/1.json?serializer=widget', - }, - ]; - - let vm; - - beforeEach(() => { - vm = mountComponent(Vue.extend(requestSelector), { - requests, - currentRequest: requests[1], - }); - }); - - afterEach(() => { - vm.$destroy(); - }); - - function optionText(requestId) { - return vm.$el.querySelector(`[value='${requestId}']`).innerText.trim(); - } - - it('displays the last component of the path', () => { - expect(optionText(requests[2].id)).toEqual('1.json?serializer=widget'); - }); - - it('keeps the last two components of the path when the last component is numeric', () => { - expect(optionText(requests[1].id)).toEqual('merge_requests/1'); - }); - - it('ignores trailing slashes', () => { - expect(optionText(requests[0].id)).toEqual('gitlab.com'); - }); -}); diff --git a/spec/javascripts/pipelines/graph/graph_component_spec.js b/spec/javascripts/pipelines/graph/graph_component_spec.js index 98e92aff25f..5effbaabcd1 100644 --- a/spec/javascripts/pipelines/graph/graph_component_spec.js +++ b/spec/javascripts/pipelines/graph/graph_component_spec.js @@ -1,10 +1,17 @@ import Vue from 'vue'; import mountComponent from 'spec/helpers/vue_mount_component_helper'; +import PipelineStore from '~/pipelines/stores/pipeline_store'; import graphComponent from '~/pipelines/components/graph/graph_component.vue'; import graphJSON from './mock_data'; +import linkedPipelineJSON from '../linked_pipelines_mock.json'; +import PipelinesMediator from '~/pipelines/pipeline_details_mediator'; describe('graph component', () => { const GraphComponent = Vue.extend(graphComponent); + const store = new PipelineStore(); + store.storePipeline(linkedPipelineJSON); + const mediator = new PipelinesMediator({ endpoint: '' }); + let component; beforeEach(() => { @@ -22,6 +29,7 @@ describe('graph component', () => { component = mountComponent(GraphComponent, { isLoading: true, pipeline: {}, + mediator, }); expect(component.$el.querySelector('.loading-icon')).toBeDefined(); @@ -33,6 +41,7 @@ describe('graph component', () => { component = mountComponent(GraphComponent, { isLoading: false, pipeline: graphJSON, + mediator, }); expect(component.$el.classList.contains('js-pipeline-graph')).toEqual(true); @@ -57,11 +66,205 @@ describe('graph component', () => { }); }); + describe('when linked pipelines are present', () => { + beforeEach(() => { + component = mountComponent(GraphComponent, { + isLoading: false, + pipeline: store.state.pipeline, + mediator, + }); + }); + + describe('rendered output', () => { + it('should include the pipelines graph', () => { + expect(component.$el.classList.contains('js-pipeline-graph')).toEqual(true); + }); + + it('should not include the loading icon', () => { + expect(component.$el.querySelector('.fa-spinner')).toBeNull(); + }); + + it('should include the stage column list', () => { + expect(component.$el.querySelector('.stage-column-list')).not.toBeNull(); + }); + + it('should include the no-margin class on the first child', () => { + const firstStageColumnElement = component.$el.querySelector( + '.stage-column-list .stage-column', + ); + + expect(firstStageColumnElement.classList.contains('no-margin')).toEqual(true); + }); + + it('should include the has-only-one-job class on the first child', () => { + const firstStageColumnElement = component.$el.querySelector( + '.stage-column-list .stage-column', + ); + + expect(firstStageColumnElement.classList.contains('has-only-one-job')).toEqual(true); + }); + + it('should include the left-margin class on the second child', () => { + const firstStageColumnElement = component.$el.querySelector( + '.stage-column-list .stage-column:last-child', + ); + + expect(firstStageColumnElement.classList.contains('left-margin')).toEqual(true); + }); + + it('should include the js-has-linked-pipelines flag', () => { + expect(component.$el.querySelector('.js-has-linked-pipelines')).not.toBeNull(); + }); + }); + + describe('computeds and methods', () => { + describe('capitalizeStageName', () => { + it('it capitalizes the stage name', () => { + expect(component.capitalizeStageName('mystage')).toBe('Mystage'); + }); + }); + + describe('stageConnectorClass', () => { + it('it returns left-margin when there is a triggerer', () => { + expect(component.stageConnectorClass(0, { groups: ['job'] })).toBe('no-margin'); + }); + }); + }); + + describe('linked pipelines components', () => { + beforeEach(() => { + component = mountComponent(GraphComponent, { + isLoading: false, + pipeline: store.state.pipeline, + mediator, + }); + }); + + it('should render an upstream pipelines column', () => { + expect(component.$el.querySelector('.linked-pipelines-column')).not.toBeNull(); + expect(component.$el.innerHTML).toContain('Upstream'); + }); + + it('should render a downstream pipelines column', () => { + expect(component.$el.querySelector('.linked-pipelines-column')).not.toBeNull(); + expect(component.$el.innerHTML).toContain('Downstream'); + }); + + describe('triggered by', () => { + describe('on click', () => { + it('should emit `onClickTriggeredBy` when triggered by linked pipeline is clicked', () => { + spyOn(component, '$emit'); + + component.$el.querySelector('#js-linked-pipeline-12').click(); + + expect(component.$emit).toHaveBeenCalledWith( + 'onClickTriggeredBy', + component.pipeline, + component.pipeline.triggered_by[0], + ); + }); + }); + + describe('with expanded pipeline', () => { + it('should render expanded pipeline', done => { + // expand the pipeline + store.state.pipeline.triggered_by[0].isExpanded = true; + + component = mountComponent(GraphComponent, { + isLoading: false, + pipeline: store.state.pipeline, + mediator, + }); + + Vue.nextTick() + .then(() => { + expect(component.$el.querySelector('.js-upstream-pipeline-12')).not.toBeNull(); + }) + .then(done) + .catch(done.fail); + }); + }); + }); + + describe('triggered', () => { + describe('on click', () => { + it('should emit `onClickTriggered`', () => { + spyOn(component, '$emit'); + + component.$el.querySelector('#js-linked-pipeline-34993051').click(); + + expect(component.$emit).toHaveBeenCalledWith( + 'onClickTriggered', + component.pipeline, + component.pipeline.triggered[0], + ); + }); + }); + + describe('with expanded pipeline', () => { + it('should render expanded pipeline', done => { + // expand the pipeline + store.state.pipeline.triggered[0].isExpanded = true; + + component = mountComponent(GraphComponent, { + isLoading: false, + pipeline: store.state.pipeline, + mediator, + }); + + Vue.nextTick() + .then(() => { + expect( + component.$el.querySelector('.js-downstream-pipeline-34993051'), + ).not.toBeNull(); + }) + .then(done) + .catch(done.fail); + }); + }); + }); + }); + }); + + describe('when linked pipelines are not present', () => { + beforeEach(() => { + const pipeline = Object.assign(linkedPipelineJSON, { triggered: null, triggered_by: null }); + component = mountComponent(GraphComponent, { + isLoading: false, + pipeline, + mediator, + }); + }); + + describe('rendered output', () => { + it('should include the first column with a no margin', () => { + const firstColumn = component.$el.querySelector('.stage-column:first-child'); + + expect(firstColumn.classList.contains('no-margin')).toEqual(true); + }); + + it('should not render a linked pipelines column', () => { + expect(component.$el.querySelector('.linked-pipelines-column')).toBeNull(); + }); + }); + + describe('stageConnectorClass', () => { + it('it returns left-margin when no triggerer and there is one job', () => { + expect(component.stageConnectorClass(0, { groups: ['job'] })).toBe('no-margin'); + }); + + it('it returns left-margin when no triggerer and not the first stage', () => { + expect(component.stageConnectorClass(99, { groups: ['job'] })).toBe('left-margin'); + }); + }); + }); + describe('capitalizeStageName', () => { it('capitalizes and escapes stage name', () => { component = mountComponent(GraphComponent, { isLoading: false, pipeline: graphJSON, + mediator, }); expect( diff --git a/spec/javascripts/pipelines/graph/linked_pipeline_spec.js b/spec/javascripts/pipelines/graph/linked_pipeline_spec.js new file mode 100644 index 00000000000..8d3abf094b6 --- /dev/null +++ b/spec/javascripts/pipelines/graph/linked_pipeline_spec.js @@ -0,0 +1,116 @@ +import Vue from 'vue'; +import LinkedPipelineComponent from '~/pipelines/components/graph/linked_pipeline.vue'; +import mountComponent from 'spec/helpers/vue_mount_component_helper'; +import mockData from './linked_pipelines_mock_data'; + +const mockPipeline = mockData.triggered[0]; + +describe('Linked pipeline', () => { + const Component = Vue.extend(LinkedPipelineComponent); + let vm; + + afterEach(() => { + vm.$destroy(); + }); + + describe('rendered output', () => { + const props = { + pipeline: mockPipeline, + }; + + beforeEach(() => { + vm = mountComponent(Component, props); + }); + + it('should render a list item as the containing element', () => { + expect(vm.$el.tagName).toBe('LI'); + }); + + it('should render a button', () => { + const linkElement = vm.$el.querySelector('.js-linked-pipeline-content'); + + expect(linkElement).not.toBeNull(); + }); + + it('should render the project name', () => { + expect(vm.$el.innerText).toContain(props.pipeline.project.name); + }); + + it('should render an svg within the status container', () => { + const pipelineStatusElement = vm.$el.querySelector('.js-linked-pipeline-status'); + + expect(pipelineStatusElement.querySelector('svg')).not.toBeNull(); + }); + + it('should render the pipeline status icon svg', () => { + expect(vm.$el.querySelector('.js-ci-status-icon-running')).not.toBeNull(); + expect(vm.$el.querySelector('.js-ci-status-icon-running').innerHTML).toContain('<svg'); + }); + + it('should have a ci-status child component', () => { + expect(vm.$el.querySelector('.js-linked-pipeline-status')).not.toBeNull(); + }); + + it('should render the pipeline id', () => { + expect(vm.$el.innerText).toContain(`#${props.pipeline.id}`); + }); + + it('should correctly compute the tooltip text', () => { + expect(vm.tooltipText).toContain(mockPipeline.project.name); + expect(vm.tooltipText).toContain(mockPipeline.details.status.label); + }); + + it('should render the tooltip text as the title attribute', () => { + const tooltipRef = vm.$el.querySelector('.js-linked-pipeline-content'); + const titleAttr = tooltipRef.getAttribute('data-original-title'); + + expect(titleAttr).toContain(mockPipeline.project.name); + expect(titleAttr).toContain(mockPipeline.details.status.label); + }); + + it('does not render the loading icon when isLoading is false', () => { + expect(vm.$el.querySelector('.js-linked-pipeline-loading')).toBeNull(); + }); + }); + + describe('when isLoading is true', () => { + const props = { + pipeline: { ...mockPipeline, isLoading: true }, + }; + + beforeEach(() => { + vm = mountComponent(Component, props); + }); + + it('renders a loading icon', () => { + expect(vm.$el.querySelector('.js-linked-pipeline-loading')).not.toBeNull(); + }); + }); + + describe('on click', () => { + const props = { + pipeline: mockPipeline, + }; + + beforeEach(() => { + vm = mountComponent(Component, props); + }); + + it('emits `pipelineClicked` event', () => { + spyOn(vm, '$emit'); + vm.$el.querySelector('button').click(); + + expect(vm.$emit).toHaveBeenCalledWith('pipelineClicked'); + }); + + it('should emit `bv::hide::tooltip` to close the tooltip', () => { + spyOn(vm.$root, '$emit'); + vm.$el.querySelector('button').click(); + + expect(vm.$root.$emit.calls.argsFor(0)).toEqual([ + 'bv::hide::tooltip', + 'js-linked-pipeline-132', + ]); + }); + }); +}); diff --git a/spec/javascripts/pipelines/graph/linked_pipelines_column_spec.js b/spec/javascripts/pipelines/graph/linked_pipelines_column_spec.js new file mode 100644 index 00000000000..1f835dc4dee --- /dev/null +++ b/spec/javascripts/pipelines/graph/linked_pipelines_column_spec.js @@ -0,0 +1,38 @@ +import Vue from 'vue'; +import LinkedPipelinesColumn from '~/pipelines/components/graph/linked_pipelines_column.vue'; +import mountComponent from 'spec/helpers/vue_mount_component_helper'; +import mockData from './linked_pipelines_mock_data'; + +describe('Linked Pipelines Column', () => { + const Component = Vue.extend(LinkedPipelinesColumn); + const props = { + columnTitle: 'Upstream', + linkedPipelines: mockData.triggered, + graphPosition: 'right', + }; + let vm; + + beforeEach(() => { + vm = mountComponent(Component, props); + }); + + afterEach(() => { + vm.$destroy(); + }); + + it('renders the pipeline orientation', () => { + const titleElement = vm.$el.querySelector('.linked-pipelines-column-title'); + + expect(titleElement.innerText).toContain(props.columnTitle); + }); + + it('has the correct number of linked pipeline child components', () => { + expect(vm.$children.length).toBe(props.linkedPipelines.length); + }); + + it('renders the correct number of linked pipelines', () => { + const linkedPipelineElements = vm.$el.querySelectorAll('.linked-pipeline'); + + expect(linkedPipelineElements.length).toBe(props.linkedPipelines.length); + }); +}); diff --git a/spec/javascripts/pipelines/graph/linked_pipelines_mock_data.js b/spec/javascripts/pipelines/graph/linked_pipelines_mock_data.js new file mode 100644 index 00000000000..f794b8484a7 --- /dev/null +++ b/spec/javascripts/pipelines/graph/linked_pipelines_mock_data.js @@ -0,0 +1,407 @@ +export default { + triggered_by: { + id: 129, + active: true, + path: '/gitlab-org/gitlab-foss/pipelines/129', + project: { + name: 'GitLabCE', + }, + details: { + status: { + icon: 'status_running', + text: 'running', + label: 'running', + group: 'running', + has_details: true, + details_path: '/gitlab-org/gitlab-foss/pipelines/129', + favicon: + '/assets/ci_favicons/dev/favicon_status_running-c3ad2fc53ea6079c174e5b6c1351ff349e99ec3af5a5622fb77b0fe53ea279c1.ico', + }, + }, + flags: { + latest: false, + triggered: false, + stuck: false, + yaml_errors: false, + retryable: true, + cancelable: true, + }, + ref: { + name: '7-5-stable', + path: '/gitlab-org/gitlab-foss/commits/7-5-stable', + tag: false, + branch: true, + }, + commit: { + id: '23433d4d8b20d7e45c103d0b6048faad38a130ab', + short_id: '23433d4d', + title: 'Version 7.5.0.rc1', + created_at: '2014-11-17T15:44:14.000+01:00', + parent_ids: ['30ac909f30f58d319b42ed1537664483894b18cd'], + message: 'Version 7.5.0.rc1\n', + author_name: 'Jacob Vosmaer', + author_email: 'contact@jacobvosmaer.nl', + authored_date: '2014-11-17T15:44:14.000+01:00', + committer_name: 'Jacob Vosmaer', + committer_email: 'contact@jacobvosmaer.nl', + committed_date: '2014-11-17T15:44:14.000+01:00', + author_gravatar_url: + 'http://www.gravatar.com/avatar/e66d11c0eedf8c07b3b18fca46599807?s=80&d=identicon', + commit_url: + 'http://localhost:3000/gitlab-org/gitlab-foss/commit/23433d4d8b20d7e45c103d0b6048faad38a130ab', + commit_path: '/gitlab-org/gitlab-foss/commit/23433d4d8b20d7e45c103d0b6048faad38a130ab', + }, + retry_path: '/gitlab-org/gitlab-foss/pipelines/129/retry', + cancel_path: '/gitlab-org/gitlab-foss/pipelines/129/cancel', + created_at: '2017-05-24T14:46:20.090Z', + updated_at: '2017-05-24T14:46:29.906Z', + }, + triggered: [ + { + id: 132, + active: true, + path: '/gitlab-org/gitlab-foss/pipelines/132', + project: { + name: 'GitLabCE', + }, + details: { + status: { + icon: 'status_running', + text: 'running', + label: 'running', + group: 'running', + has_details: true, + details_path: '/gitlab-org/gitlab-foss/pipelines/132', + favicon: + '/assets/ci_favicons/dev/favicon_status_running-c3ad2fc53ea6079c174e5b6c1351ff349e99ec3af5a5622fb77b0fe53ea279c1.ico', + }, + }, + flags: { + latest: false, + triggered: false, + stuck: false, + yaml_errors: false, + retryable: true, + cancelable: true, + }, + ref: { + name: 'crowd', + path: '/gitlab-org/gitlab-foss/commits/crowd', + tag: false, + branch: true, + }, + commit: { + id: 'b9d58c4cecd06be74c3cc32ccfb522b31544ab2e', + short_id: 'b9d58c4c', + title: 'getting user keys publically through http without any authentication, the github…', + created_at: '2013-10-03T12:50:33.000+05:30', + parent_ids: ['e219cf7246c6a0495e4507deaffeba11e79f13b8'], + message: + 'getting user keys publically through http without any authentication, the github way. E.g: http://github.com/devaroop.keys\n\nchangelog updated to include ssh key retrieval feature update\n', + author_name: 'devaroop', + author_email: 'devaroop123@yahoo.co.in', + authored_date: '2013-10-02T20:39:29.000+05:30', + committer_name: 'devaroop', + committer_email: 'devaroop123@yahoo.co.in', + committed_date: '2013-10-03T12:50:33.000+05:30', + author_gravatar_url: + 'http://www.gravatar.com/avatar/35df4b155ec66a3127d53459941cf8a2?s=80&d=identicon', + commit_url: + 'http://localhost:3000/gitlab-org/gitlab-foss/commit/b9d58c4cecd06be74c3cc32ccfb522b31544ab2e', + commit_path: '/gitlab-org/gitlab-foss/commit/b9d58c4cecd06be74c3cc32ccfb522b31544ab2e', + }, + retry_path: '/gitlab-org/gitlab-foss/pipelines/132/retry', + cancel_path: '/gitlab-org/gitlab-foss/pipelines/132/cancel', + created_at: '2017-05-24T14:46:24.644Z', + updated_at: '2017-05-24T14:48:55.226Z', + }, + { + id: 133, + active: true, + path: '/gitlab-org/gitlab-foss/pipelines/133', + project: { + name: 'GitLabCE', + }, + details: { + status: { + icon: 'status_running', + text: 'running', + label: 'running', + group: 'running', + has_details: true, + details_path: '/gitlab-org/gitlab-foss/pipelines/133', + favicon: + '/assets/ci_favicons/dev/favicon_status_running-c3ad2fc53ea6079c174e5b6c1351ff349e99ec3af5a5622fb77b0fe53ea279c1.ico', + }, + }, + flags: { + latest: false, + triggered: false, + stuck: false, + yaml_errors: false, + retryable: true, + cancelable: true, + }, + ref: { + name: 'crowd', + path: '/gitlab-org/gitlab-foss/commits/crowd', + tag: false, + branch: true, + }, + commit: { + id: 'b6bd4856a33df3d144be66c4ed1f1396009bb08b', + short_id: 'b6bd4856', + title: 'getting user keys publically through http without any authentication, the github…', + created_at: '2013-10-02T20:39:29.000+05:30', + parent_ids: ['e219cf7246c6a0495e4507deaffeba11e79f13b8'], + message: + 'getting user keys publically through http without any authentication, the github way. E.g: http://github.com/devaroop.keys\n', + author_name: 'devaroop', + author_email: 'devaroop123@yahoo.co.in', + authored_date: '2013-10-02T20:39:29.000+05:30', + committer_name: 'devaroop', + committer_email: 'devaroop123@yahoo.co.in', + committed_date: '2013-10-02T20:39:29.000+05:30', + author_gravatar_url: + 'http://www.gravatar.com/avatar/35df4b155ec66a3127d53459941cf8a2?s=80&d=identicon', + commit_url: + 'http://localhost:3000/gitlab-org/gitlab-foss/commit/b6bd4856a33df3d144be66c4ed1f1396009bb08b', + commit_path: '/gitlab-org/gitlab-foss/commit/b6bd4856a33df3d144be66c4ed1f1396009bb08b', + }, + retry_path: '/gitlab-org/gitlab-foss/pipelines/133/retry', + cancel_path: '/gitlab-org/gitlab-foss/pipelines/133/cancel', + created_at: '2017-05-24T14:46:24.648Z', + updated_at: '2017-05-24T14:48:59.673Z', + }, + { + id: 130, + active: true, + path: '/gitlab-org/gitlab-foss/pipelines/130', + project: { + name: 'GitLabCE', + }, + details: { + status: { + icon: 'status_running', + text: 'running', + label: 'running', + group: 'running', + has_details: true, + details_path: '/gitlab-org/gitlab-foss/pipelines/130', + favicon: + '/assets/ci_favicons/dev/favicon_status_running-c3ad2fc53ea6079c174e5b6c1351ff349e99ec3af5a5622fb77b0fe53ea279c1.ico', + }, + }, + flags: { + latest: false, + triggered: false, + stuck: false, + yaml_errors: false, + retryable: true, + cancelable: true, + }, + ref: { + name: 'crowd', + path: '/gitlab-org/gitlab-foss/commits/crowd', + tag: false, + branch: true, + }, + commit: { + id: '6d7ced4a2311eeff037c5575cca1868a6d3f586f', + short_id: '6d7ced4a', + title: 'Whitespace fixes to patch', + created_at: '2013-10-08T13:53:22.000-05:00', + parent_ids: ['1875141a963a4238bda29011d8f7105839485253'], + message: 'Whitespace fixes to patch\n', + author_name: 'Dale Hamel', + author_email: 'dale.hamel@srvthe.net', + authored_date: '2013-10-08T13:53:22.000-05:00', + committer_name: 'Dale Hamel', + committer_email: 'dale.hamel@invenia.ca', + committed_date: '2013-10-08T13:53:22.000-05:00', + author_gravatar_url: + 'http://www.gravatar.com/avatar/cd08930e69fa5ad1a669206e7bafe476?s=80&d=identicon', + commit_url: + 'http://localhost:3000/gitlab-org/gitlab-foss/commit/6d7ced4a2311eeff037c5575cca1868a6d3f586f', + commit_path: '/gitlab-org/gitlab-foss/commit/6d7ced4a2311eeff037c5575cca1868a6d3f586f', + }, + retry_path: '/gitlab-org/gitlab-foss/pipelines/130/retry', + cancel_path: '/gitlab-org/gitlab-foss/pipelines/130/cancel', + created_at: '2017-05-24T14:46:24.630Z', + updated_at: '2017-05-24T14:49:45.091Z', + }, + { + id: 131, + active: true, + path: '/gitlab-org/gitlab-foss/pipelines/132', + project: { + name: 'GitLabCE', + }, + details: { + status: { + icon: 'status_running', + text: 'running', + label: 'running', + group: 'running', + has_details: true, + details_path: '/gitlab-org/gitlab-foss/pipelines/132', + favicon: + '/assets/ci_favicons/dev/favicon_status_running-c3ad2fc53ea6079c174e5b6c1351ff349e99ec3af5a5622fb77b0fe53ea279c1.ico', + }, + }, + flags: { + latest: false, + triggered: false, + stuck: false, + yaml_errors: false, + retryable: true, + cancelable: true, + }, + ref: { + name: 'crowd', + path: '/gitlab-org/gitlab-foss/commits/crowd', + tag: false, + branch: true, + }, + commit: { + id: 'b9d58c4cecd06be74c3cc32ccfb522b31544ab2e', + short_id: 'b9d58c4c', + title: 'getting user keys publically through http without any authentication, the github…', + created_at: '2013-10-03T12:50:33.000+05:30', + parent_ids: ['e219cf7246c6a0495e4507deaffeba11e79f13b8'], + message: + 'getting user keys publically through http without any authentication, the github way. E.g: http://github.com/devaroop.keys\n\nchangelog updated to include ssh key retrieval feature update\n', + author_name: 'devaroop', + author_email: 'devaroop123@yahoo.co.in', + authored_date: '2013-10-02T20:39:29.000+05:30', + committer_name: 'devaroop', + committer_email: 'devaroop123@yahoo.co.in', + committed_date: '2013-10-03T12:50:33.000+05:30', + author_gravatar_url: + 'http://www.gravatar.com/avatar/35df4b155ec66a3127d53459941cf8a2?s=80&d=identicon', + commit_url: + 'http://localhost:3000/gitlab-org/gitlab-foss/commit/b9d58c4cecd06be74c3cc32ccfb522b31544ab2e', + commit_path: '/gitlab-org/gitlab-foss/commit/b9d58c4cecd06be74c3cc32ccfb522b31544ab2e', + }, + retry_path: '/gitlab-org/gitlab-foss/pipelines/132/retry', + cancel_path: '/gitlab-org/gitlab-foss/pipelines/132/cancel', + created_at: '2017-05-24T14:46:24.644Z', + updated_at: '2017-05-24T14:48:55.226Z', + }, + { + id: 134, + active: true, + path: '/gitlab-org/gitlab-foss/pipelines/133', + project: { + name: 'GitLabCE', + }, + details: { + status: { + icon: 'status_running', + text: 'running', + label: 'running', + group: 'running', + has_details: true, + details_path: '/gitlab-org/gitlab-foss/pipelines/133', + favicon: + '/assets/ci_favicons/dev/favicon_status_running-c3ad2fc53ea6079c174e5b6c1351ff349e99ec3af5a5622fb77b0fe53ea279c1.ico', + }, + }, + flags: { + latest: false, + triggered: false, + stuck: false, + yaml_errors: false, + retryable: true, + cancelable: true, + }, + ref: { + name: 'crowd', + path: '/gitlab-org/gitlab-foss/commits/crowd', + tag: false, + branch: true, + }, + commit: { + id: 'b6bd4856a33df3d144be66c4ed1f1396009bb08b', + short_id: 'b6bd4856', + title: 'getting user keys publically through http without any authentication, the github…', + created_at: '2013-10-02T20:39:29.000+05:30', + parent_ids: ['e219cf7246c6a0495e4507deaffeba11e79f13b8'], + message: + 'getting user keys publically through http without any authentication, the github way. E.g: http://github.com/devaroop.keys\n', + author_name: 'devaroop', + author_email: 'devaroop123@yahoo.co.in', + authored_date: '2013-10-02T20:39:29.000+05:30', + committer_name: 'devaroop', + committer_email: 'devaroop123@yahoo.co.in', + committed_date: '2013-10-02T20:39:29.000+05:30', + author_gravatar_url: + 'http://www.gravatar.com/avatar/35df4b155ec66a3127d53459941cf8a2?s=80&d=identicon', + commit_url: + 'http://localhost:3000/gitlab-org/gitlab-foss/commit/b6bd4856a33df3d144be66c4ed1f1396009bb08b', + commit_path: '/gitlab-org/gitlab-foss/commit/b6bd4856a33df3d144be66c4ed1f1396009bb08b', + }, + retry_path: '/gitlab-org/gitlab-foss/pipelines/133/retry', + cancel_path: '/gitlab-org/gitlab-foss/pipelines/133/cancel', + created_at: '2017-05-24T14:46:24.648Z', + updated_at: '2017-05-24T14:48:59.673Z', + }, + { + id: 135, + active: true, + path: '/gitlab-org/gitlab-foss/pipelines/130', + project: { + name: 'GitLabCE', + }, + details: { + status: { + icon: 'status_running', + text: 'running', + label: 'running', + group: 'running', + has_details: true, + details_path: '/gitlab-org/gitlab-foss/pipelines/130', + favicon: + '/assets/ci_favicons/dev/favicon_status_running-c3ad2fc53ea6079c174e5b6c1351ff349e99ec3af5a5622fb77b0fe53ea279c1.ico', + }, + }, + flags: { + latest: false, + triggered: false, + stuck: false, + yaml_errors: false, + retryable: true, + cancelable: true, + }, + ref: { + name: 'crowd', + path: '/gitlab-org/gitlab-foss/commits/crowd', + tag: false, + branch: true, + }, + commit: { + id: '6d7ced4a2311eeff037c5575cca1868a6d3f586f', + short_id: '6d7ced4a', + title: 'Whitespace fixes to patch', + created_at: '2013-10-08T13:53:22.000-05:00', + parent_ids: ['1875141a963a4238bda29011d8f7105839485253'], + message: 'Whitespace fixes to patch\n', + author_name: 'Dale Hamel', + author_email: 'dale.hamel@srvthe.net', + authored_date: '2013-10-08T13:53:22.000-05:00', + committer_name: 'Dale Hamel', + committer_email: 'dale.hamel@invenia.ca', + committed_date: '2013-10-08T13:53:22.000-05:00', + author_gravatar_url: + 'http://www.gravatar.com/avatar/cd08930e69fa5ad1a669206e7bafe476?s=80&d=identicon', + commit_url: + 'http://localhost:3000/gitlab-org/gitlab-foss/commit/6d7ced4a2311eeff037c5575cca1868a6d3f586f', + commit_path: '/gitlab-org/gitlab-foss/commit/6d7ced4a2311eeff037c5575cca1868a6d3f586f', + }, + retry_path: '/gitlab-org/gitlab-foss/pipelines/130/retry', + cancel_path: '/gitlab-org/gitlab-foss/pipelines/130/cancel', + created_at: '2017-05-24T14:46:24.630Z', + updated_at: '2017-05-24T14:49:45.091Z', + }, + ], +}; diff --git a/spec/javascripts/pipelines/linked_pipelines_mock.json b/spec/javascripts/pipelines/linked_pipelines_mock.json new file mode 100644 index 00000000000..b498903f804 --- /dev/null +++ b/spec/javascripts/pipelines/linked_pipelines_mock.json @@ -0,0 +1,3532 @@ +{ + "id": 23211253, + "user": { + "id": 3585, + "name": "Achilleas Pipinellis", + "username": "axil", + "state": "active", + "avatar_url": "https://assets.gitlab-static.net/uploads/-/system/user/avatar/3585/avatar.png", + "web_url": "https://gitlab.com/axil", + "status_tooltip_html": "\u003cspan class=\"user-status-emoji has-tooltip\" title=\"I like pizza\" data-html=\"true\" data-placement=\"top\"\u003e\u003cgl-emoji title=\"slice of pizza\" data-name=\"pizza\" data-unicode-version=\"6.0\"\u003e🍕\u003c/gl-emoji\u003e\u003c/span\u003e", + "path": "/axil" + }, + "active": false, + "coverage": null, + "source": "push", + "created_at": "2018-06-05T11:31:30.452Z", + "updated_at": "2018-10-31T16:35:31.305Z", + "path": "/gitlab-org/gitlab-runner/pipelines/23211253", + "flags": { + "latest": false, + "stuck": false, + "auto_devops": false, + "merge_request": false, + "yaml_errors": false, + "retryable": false, + "cancelable": false, + "failure_reason": false + }, + "details": { + "status": { + "icon": "status_success", + "text": "passed", + "label": "passed", + "group": "success", + "tooltip": "passed", + "has_details": true, + "details_path": "/gitlab-org/gitlab-runner/pipelines/23211253", + "illustration": null, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png" + }, + "duration": 53, + "finished_at": "2018-10-31T16:35:31.299Z", + "stages": [ + { + "name": "prebuild", + "title": "prebuild: passed", + "groups": [ + { + "name": "review-docs-deploy", + "size": 1, + "status": { + "icon": "status_success", + "text": "passed", + "label": "manual play action", + "group": "success", + "tooltip": "passed", + "has_details": true, + "details_path": "/gitlab-org/gitlab-runner/-/jobs/72469032", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.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": "https://gitlab.com/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png", + "action": { + "icon": "play", + "title": "Play", + "path": "/gitlab-org/gitlab-runner/-/jobs/72469032/play", + "method": "post", + "button_title": "Trigger this manual action" + } + }, + "jobs": [ + { + "id": 72469032, + "name": "review-docs-deploy", + "started": "2018-10-31T16:34:58.778Z", + "archived": false, + "build_path": "/gitlab-org/gitlab-runner/-/jobs/72469032", + "retry_path": "/gitlab-org/gitlab-runner/-/jobs/72469032/retry", + "play_path": "/gitlab-org/gitlab-runner/-/jobs/72469032/play", + "playable": true, + "scheduled": false, + "created_at": "2018-06-05T11:31:30.495Z", + "updated_at": "2018-10-31T16:35:31.251Z", + "status": { + "icon": "status_success", + "text": "passed", + "label": "manual play action", + "group": "success", + "tooltip": "passed", + "has_details": true, + "details_path": "/gitlab-org/gitlab-runner/-/jobs/72469032", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.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": "https://gitlab.com/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png", + "action": { + "icon": "play", + "title": "Play", + "path": "/gitlab-org/gitlab-runner/-/jobs/72469032/play", + "method": "post", + "button_title": "Trigger this manual action" + } + } + } + ] + } + ], + "status": { + "icon": "status_success", + "text": "passed", + "label": "passed", + "group": "success", + "tooltip": "passed", + "has_details": true, + "details_path": "/gitlab-org/gitlab-runner/pipelines/23211253#prebuild", + "illustration": null, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png" + }, + "path": "/gitlab-org/gitlab-runner/pipelines/23211253#prebuild", + "dropdown_path": "/gitlab-org/gitlab-runner/pipelines/23211253/stage.json?stage=prebuild" + }, + { + "name": "test", + "title": "test: passed", + "groups": [ + { + "name": "docs check links", + "size": 1, + "status": { + "icon": "status_success", + "text": "passed", + "label": "passed", + "group": "success", + "tooltip": "passed", + "has_details": true, + "details_path": "/gitlab-org/gitlab-runner/-/jobs/72469033", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg", + "size": "svg-430", + "title": "This job does not have a trace." + }, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png", + "action": { + "icon": "retry", + "title": "Retry", + "path": "/gitlab-org/gitlab-runner/-/jobs/72469033/retry", + "method": "post", + "button_title": "Retry this job" + } + }, + "jobs": [ + { + "id": 72469033, + "name": "docs check links", + "started": "2018-06-05T11:31:33.240Z", + "archived": false, + "build_path": "/gitlab-org/gitlab-runner/-/jobs/72469033", + "retry_path": "/gitlab-org/gitlab-runner/-/jobs/72469033/retry", + "playable": false, + "scheduled": false, + "created_at": "2018-06-05T11:31:30.627Z", + "updated_at": "2018-06-05T11:31:54.363Z", + "status": { + "icon": "status_success", + "text": "passed", + "label": "passed", + "group": "success", + "tooltip": "passed", + "has_details": true, + "details_path": "/gitlab-org/gitlab-runner/-/jobs/72469033", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg", + "size": "svg-430", + "title": "This job does not have a trace." + }, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png", + "action": { + "icon": "retry", + "title": "Retry", + "path": "/gitlab-org/gitlab-runner/-/jobs/72469033/retry", + "method": "post", + "button_title": "Retry this job" + } + } + } + ] + } + ], + "status": { + "icon": "status_success", + "text": "passed", + "label": "passed", + "group": "success", + "tooltip": "passed", + "has_details": true, + "details_path": "/gitlab-org/gitlab-runner/pipelines/23211253#test", + "illustration": null, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png" + }, + "path": "/gitlab-org/gitlab-runner/pipelines/23211253#test", + "dropdown_path": "/gitlab-org/gitlab-runner/pipelines/23211253/stage.json?stage=test" + }, + { + "name": "cleanup", + "title": "cleanup: skipped", + "groups": [ + { + "name": "review-docs-cleanup", + "size": 1, + "status": { + "icon": "status_manual", + "text": "manual", + "label": "manual stop action", + "group": "manual", + "tooltip": "manual action", + "has_details": true, + "details_path": "/gitlab-org/gitlab-runner/-/jobs/72469034", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.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": "https://gitlab.com/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png", + "action": { + "icon": "stop", + "title": "Stop", + "path": "/gitlab-org/gitlab-runner/-/jobs/72469034/play", + "method": "post", + "button_title": "Stop this environment" + } + }, + "jobs": [ + { + "id": 72469034, + "name": "review-docs-cleanup", + "started": null, + "archived": false, + "build_path": "/gitlab-org/gitlab-runner/-/jobs/72469034", + "play_path": "/gitlab-org/gitlab-runner/-/jobs/72469034/play", + "playable": true, + "scheduled": false, + "created_at": "2018-06-05T11:31:30.760Z", + "updated_at": "2018-06-05T11:31:56.037Z", + "status": { + "icon": "status_manual", + "text": "manual", + "label": "manual stop action", + "group": "manual", + "tooltip": "manual action", + "has_details": true, + "details_path": "/gitlab-org/gitlab-runner/-/jobs/72469034", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.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": "https://gitlab.com/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png", + "action": { + "icon": "stop", + "title": "Stop", + "path": "/gitlab-org/gitlab-runner/-/jobs/72469034/play", + "method": "post", + "button_title": "Stop this environment" + } + } + } + ] + } + ], + "status": { + "icon": "status_skipped", + "text": "skipped", + "label": "skipped", + "group": "skipped", + "tooltip": "skipped", + "has_details": true, + "details_path": "/gitlab-org/gitlab-runner/pipelines/23211253#cleanup", + "illustration": null, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_skipped-0b9c5e543588945e8c4ca57786bbf2d0c56631959c9f853300392d0315be829b.png" + }, + "path": "/gitlab-org/gitlab-runner/pipelines/23211253#cleanup", + "dropdown_path": "/gitlab-org/gitlab-runner/pipelines/23211253/stage.json?stage=cleanup" + } + ], + "artifacts": [], + "manual_actions": [ + { + "name": "review-docs-cleanup", + "path": "/gitlab-org/gitlab-runner/-/jobs/72469034/play", + "playable": true, + "scheduled": false + }, + { + "name": "review-docs-deploy", + "path": "/gitlab-org/gitlab-runner/-/jobs/72469032/play", + "playable": true, + "scheduled": false + } + ], + "scheduled_actions": [] + }, + "ref": { + "name": "docs/add-development-guide-to-readme", + "path": "/gitlab-org/gitlab-runner/commits/docs/add-development-guide-to-readme", + "tag": false, + "branch": true, + "merge_request": false + }, + "commit": { + "id": "8083eb0a920572214d0dccedd7981f05d535ad46", + "short_id": "8083eb0a", + "title": "Add link to development guide in readme", + "created_at": "2018-06-05T11:30:48.000Z", + "parent_ids": ["1d7cf79b5a1a2121b9474ac20d61c1b8f621289d"], + "message": "Add link to development guide in readme\n\nCloses https://gitlab.com/gitlab-org/gitlab-runner/issues/3122\n", + "author_name": "Achilleas Pipinellis", + "author_email": "axil@gitlab.com", + "authored_date": "2018-06-05T11:30:48.000Z", + "committer_name": "Achilleas Pipinellis", + "committer_email": "axil@gitlab.com", + "committed_date": "2018-06-05T11:30:48.000Z", + "author": { + "id": 3585, + "name": "Achilleas Pipinellis", + "username": "axil", + "state": "active", + "avatar_url": "https://assets.gitlab-static.net/uploads/-/system/user/avatar/3585/avatar.png", + "web_url": "https://gitlab.com/axil", + "status_tooltip_html": null, + "path": "/axil" + }, + "author_gravatar_url": "https://secure.gravatar.com/avatar/1d37af00eec153a8333a4ce18e9aea41?s=80\u0026d=identicon", + "commit_url": "https://gitlab.com/gitlab-org/gitlab-runner/commit/8083eb0a920572214d0dccedd7981f05d535ad46", + "commit_path": "/gitlab-org/gitlab-runner/commit/8083eb0a920572214d0dccedd7981f05d535ad46" + }, + "triggered_by": { + "id": 12, + "user": { + "id": 376774, + "name": "Alessio Caiazza", + "username": "nolith", + "state": "active", + "avatar_url": "https://assets.gitlab-static.net/uploads/-/system/user/avatar/376774/avatar.png", + "web_url": "https://gitlab.com/nolith", + "status_tooltip_html": null, + "path": "/nolith" + }, + "active": false, + "coverage": null, + "source": "pipeline", + "path": "/gitlab-com/gitlab-docs/pipelines/34993051", + "details": { + "status": { + "icon": "status_failed", + "text": "failed", + "label": "failed", + "group": "failed", + "tooltip": "failed", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/pipelines/34993051", + "illustration": null, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_failed-41304d7f7e3828808b0c26771f0309e55296819a9beea3ea9fbf6689d9857c12.png" + }, + "duration": 118, + "finished_at": "2018-10-31T16:41:40.615Z", + "stages": [ + { + "name": "build-images", + "title": "build-images: skipped", + "groups": [ + { + "name": "image:bootstrap", + "size": 1, + "status": { + "icon": "status_manual", + "text": "manual", + "label": "manual play action", + "group": "manual", + "tooltip": "manual action", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982853", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.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": "https://gitlab.com/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png", + "action": { + "icon": "play", + "title": "Play", + "path": "/gitlab-com/gitlab-docs/-/jobs/114982853/play", + "method": "post", + "button_title": "Trigger this manual action" + } + }, + "jobs": [ + { + "id": 11421321982853, + "name": "image:bootstrap", + "started": null, + "archived": false, + "build_path": "/gitlab-com/gitlab-docs/-/jobs/114982853", + "play_path": "/gitlab-com/gitlab-docs/-/jobs/114982853/play", + "playable": true, + "scheduled": false, + "created_at": "2018-10-31T16:35:23.704Z", + "updated_at": "2018-10-31T16:35:24.118Z", + "status": { + "icon": "status_manual", + "text": "manual", + "label": "manual play action", + "group": "manual", + "tooltip": "manual action", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982853", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.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": "https://gitlab.com/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png", + "action": { + "icon": "play", + "title": "Play", + "path": "/gitlab-com/gitlab-docs/-/jobs/114982853/play", + "method": "post", + "button_title": "Trigger this manual action" + } + } + } + ] + }, + { + "name": "image:builder-onbuild", + "size": 1, + "status": { + "icon": "status_manual", + "text": "manual", + "label": "manual play action", + "group": "manual", + "tooltip": "manual action", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982854", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.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": "https://gitlab.com/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png", + "action": { + "icon": "play", + "title": "Play", + "path": "/gitlab-com/gitlab-docs/-/jobs/114982854/play", + "method": "post", + "button_title": "Trigger this manual action" + } + }, + "jobs": [ + { + "id": 1149822131854, + "name": "image:builder-onbuild", + "started": null, + "archived": false, + "build_path": "/gitlab-com/gitlab-docs/-/jobs/114982854", + "play_path": "/gitlab-com/gitlab-docs/-/jobs/114982854/play", + "playable": true, + "scheduled": false, + "created_at": "2018-10-31T16:35:23.728Z", + "updated_at": "2018-10-31T16:35:24.070Z", + "status": { + "icon": "status_manual", + "text": "manual", + "label": "manual play action", + "group": "manual", + "tooltip": "manual action", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982854", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.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": "https://gitlab.com/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png", + "action": { + "icon": "play", + "title": "Play", + "path": "/gitlab-com/gitlab-docs/-/jobs/114982854/play", + "method": "post", + "button_title": "Trigger this manual action" + } + } + } + ] + }, + { + "name": "image:nginx-onbuild", + "size": 1, + "status": { + "icon": "status_manual", + "text": "manual", + "label": "manual play action", + "group": "manual", + "tooltip": "manual action", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982855", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.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": "https://gitlab.com/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png", + "action": { + "icon": "play", + "title": "Play", + "path": "/gitlab-com/gitlab-docs/-/jobs/114982855/play", + "method": "post", + "button_title": "Trigger this manual action" + } + }, + "jobs": [ + { + "id": 11498285523424, + "name": "image:nginx-onbuild", + "started": null, + "archived": false, + "build_path": "/gitlab-com/gitlab-docs/-/jobs/114982855", + "play_path": "/gitlab-com/gitlab-docs/-/jobs/114982855/play", + "playable": true, + "scheduled": false, + "created_at": "2018-10-31T16:35:23.753Z", + "updated_at": "2018-10-31T16:35:24.033Z", + "status": { + "icon": "status_manual", + "text": "manual", + "label": "manual play action", + "group": "manual", + "tooltip": "manual action", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982855", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.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": "https://gitlab.com/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png", + "action": { + "icon": "play", + "title": "Play", + "path": "/gitlab-com/gitlab-docs/-/jobs/114982855/play", + "method": "post", + "button_title": "Trigger this manual action" + } + } + } + ] + } + ], + "status": { + "icon": "status_skipped", + "text": "skipped", + "label": "skipped", + "group": "skipped", + "tooltip": "skipped", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/pipelines/34993051#build-images", + "illustration": null, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_skipped-0b9c5e543588945e8c4ca57786bbf2d0c56631959c9f853300392d0315be829b.png" + }, + "path": "/gitlab-com/gitlab-docs/pipelines/34993051#build-images", + "dropdown_path": "/gitlab-com/gitlab-docs/pipelines/34993051/stage.json?stage=build-images" + }, + { + "name": "build", + "title": "build: failed", + "groups": [ + { + "name": "compile_dev", + "size": 1, + "status": { + "icon": "status_failed", + "text": "failed", + "label": "failed", + "group": "failed", + "tooltip": "failed - (script failure)", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/-/jobs/114984694", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg", + "size": "svg-430", + "title": "This job does not have a trace." + }, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_failed-41304d7f7e3828808b0c26771f0309e55296819a9beea3ea9fbf6689d9857c12.png", + "action": { + "icon": "retry", + "title": "Retry", + "path": "/h5bp/html5-boilerplate/-/jobs/528/retry", + "method": "post", + "button_title": "Retry this job" + } + }, + "jobs": [ + { + "id": 1149846949786, + "name": "compile_dev", + "started": "2018-10-31T16:39:41.598Z", + "archived": false, + "build_path": "/gitlab-com/gitlab-docs/-/jobs/114984694", + "retry_path": "/gitlab-com/gitlab-docs/-/jobs/114984694/retry", + "playable": false, + "scheduled": false, + "created_at": "2018-10-31T16:39:41.138Z", + "updated_at": "2018-10-31T16:41:40.072Z", + "status": { + "icon": "status_failed", + "text": "failed", + "label": "failed", + "group": "failed", + "tooltip": "failed - (script failure)", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/-/jobs/114984694", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg", + "size": "svg-430", + "title": "This job does not have a trace." + }, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_failed-41304d7f7e3828808b0c26771f0309e55296819a9beea3ea9fbf6689d9857c12.png", + "action": { + "icon": "retry", + "title": "Retry", + "path": "/h5bp/html5-boilerplate/-/jobs/528/retry", + "method": "post", + "button_title": "Retry this job" + } + }, + "recoverable": false + } + ] + } + ], + "status": { + "icon": "status_failed", + "text": "failed", + "label": "failed", + "group": "failed", + "tooltip": "failed", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/pipelines/34993051#build", + "illustration": null, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_failed-41304d7f7e3828808b0c26771f0309e55296819a9beea3ea9fbf6689d9857c12.png" + }, + "path": "/gitlab-com/gitlab-docs/pipelines/34993051#build", + "dropdown_path": "/gitlab-com/gitlab-docs/pipelines/34993051/stage.json?stage=build" + }, + { + "name": "deploy", + "title": "deploy: skipped", + "groups": [ + { + "name": "review", + "size": 1, + "status": { + "icon": "status_skipped", + "text": "skipped", + "label": "skipped", + "group": "skipped", + "tooltip": "skipped", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982857", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg", + "size": "svg-430", + "title": "This job has been skipped" + }, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_skipped-0b9c5e543588945e8c4ca57786bbf2d0c56631959c9f853300392d0315be829b.png" + }, + "jobs": [ + { + "id": 11498282342357, + "name": "review", + "started": null, + "archived": false, + "build_path": "/gitlab-com/gitlab-docs/-/jobs/114982857", + "playable": false, + "scheduled": false, + "created_at": "2018-10-31T16:35:23.805Z", + "updated_at": "2018-10-31T16:41:40.569Z", + "status": { + "icon": "status_skipped", + "text": "skipped", + "label": "skipped", + "group": "skipped", + "tooltip": "skipped", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982857", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg", + "size": "svg-430", + "title": "This job has been skipped" + }, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_skipped-0b9c5e543588945e8c4ca57786bbf2d0c56631959c9f853300392d0315be829b.png" + } + } + ] + }, + { + "name": "review_stop", + "size": 1, + "status": { + "icon": "status_skipped", + "text": "skipped", + "label": "skipped", + "group": "skipped", + "tooltip": "skipped", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982858", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg", + "size": "svg-430", + "title": "This job has been skipped" + }, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_skipped-0b9c5e543588945e8c4ca57786bbf2d0c56631959c9f853300392d0315be829b.png" + }, + "jobs": [ + { + "id": 114982858, + "name": "review_stop", + "started": null, + "archived": false, + "build_path": "/gitlab-com/gitlab-docs/-/jobs/114982858", + "playable": false, + "scheduled": false, + "created_at": "2018-10-31T16:35:23.840Z", + "updated_at": "2018-10-31T16:41:40.480Z", + "status": { + "icon": "status_skipped", + "text": "skipped", + "label": "skipped", + "group": "skipped", + "tooltip": "skipped", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982858", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg", + "size": "svg-430", + "title": "This job has been skipped" + }, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_skipped-0b9c5e543588945e8c4ca57786bbf2d0c56631959c9f853300392d0315be829b.png" + } + } + ] + } + ], + "status": { + "icon": "status_skipped", + "text": "skipped", + "label": "skipped", + "group": "skipped", + "tooltip": "skipped", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/pipelines/34993051#deploy", + "illustration": null, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_skipped-0b9c5e543588945e8c4ca57786bbf2d0c56631959c9f853300392d0315be829b.png" + }, + "path": "/gitlab-com/gitlab-docs/pipelines/34993051#deploy", + "dropdown_path": "/gitlab-com/gitlab-docs/pipelines/34993051/stage.json?stage=deploy" + } + ], + "artifacts": [], + "manual_actions": [ + { + "name": "image:bootstrap", + "path": "/gitlab-com/gitlab-docs/-/jobs/114982853/play", + "playable": true, + "scheduled": false + }, + { + "name": "image:builder-onbuild", + "path": "/gitlab-com/gitlab-docs/-/jobs/114982854/play", + "playable": true, + "scheduled": false + }, + { + "name": "image:nginx-onbuild", + "path": "/gitlab-com/gitlab-docs/-/jobs/114982855/play", + "playable": true, + "scheduled": false + }, + { + "name": "review_stop", + "path": "/gitlab-com/gitlab-docs/-/jobs/114982858/play", + "playable": false, + "scheduled": false + } + ], + "scheduled_actions": [] + }, + "project": { + "id": 1794617, + "name": "Test", + "full_path": "/gitlab-com/gitlab-docs", + "full_name": "GitLab.com / GitLab Docs" + }, + "triggered_by": { + "id": 349932310342451, + "user": { + "id": 376774, + "name": "Alessio Caiazza", + "username": "nolith", + "state": "active", + "avatar_url": "https://assets.gitlab-static.net/uploads/-/system/user/avatar/376774/avatar.png", + "web_url": "https://gitlab.com/nolith", + "status_tooltip_html": null, + "path": "/nolith" + }, + "active": false, + "coverage": null, + "source": "pipeline", + "path": "/gitlab-com/gitlab-docs/pipelines/34993051", + "details": { + "status": { + "icon": "status_failed", + "text": "failed", + "label": "failed", + "group": "failed", + "tooltip": "failed", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/pipelines/34993051", + "illustration": null, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_failed-41304d7f7e3828808b0c26771f0309e55296819a9beea3ea9fbf6689d9857c12.png" + }, + "duration": 118, + "finished_at": "2018-10-31T16:41:40.615Z", + "stages": [ + { + "name": "build-images", + "title": "build-images: skipped", + "groups": [ + { + "name": "image:bootstrap", + "size": 1, + "status": { + "icon": "status_manual", + "text": "manual", + "label": "manual play action", + "group": "manual", + "tooltip": "manual action", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982853", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.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": "https://gitlab.com/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png", + "action": { + "icon": "play", + "title": "Play", + "path": "/gitlab-com/gitlab-docs/-/jobs/114982853/play", + "method": "post", + "button_title": "Trigger this manual action" + } + }, + "jobs": [ + { + "id": 11421321982853, + "name": "image:bootstrap", + "started": null, + "archived": false, + "build_path": "/gitlab-com/gitlab-docs/-/jobs/114982853", + "play_path": "/gitlab-com/gitlab-docs/-/jobs/114982853/play", + "playable": true, + "scheduled": false, + "created_at": "2018-10-31T16:35:23.704Z", + "updated_at": "2018-10-31T16:35:24.118Z", + "status": { + "icon": "status_manual", + "text": "manual", + "label": "manual play action", + "group": "manual", + "tooltip": "manual action", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982853", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.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": "https://gitlab.com/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png", + "action": { + "icon": "play", + "title": "Play", + "path": "/gitlab-com/gitlab-docs/-/jobs/114982853/play", + "method": "post", + "button_title": "Trigger this manual action" + } + } + } + ] + }, + { + "name": "image:builder-onbuild", + "size": 1, + "status": { + "icon": "status_manual", + "text": "manual", + "label": "manual play action", + "group": "manual", + "tooltip": "manual action", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982854", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.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": "https://gitlab.com/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png", + "action": { + "icon": "play", + "title": "Play", + "path": "/gitlab-com/gitlab-docs/-/jobs/114982854/play", + "method": "post", + "button_title": "Trigger this manual action" + } + }, + "jobs": [ + { + "id": 1149822131854, + "name": "image:builder-onbuild", + "started": null, + "archived": false, + "build_path": "/gitlab-com/gitlab-docs/-/jobs/114982854", + "play_path": "/gitlab-com/gitlab-docs/-/jobs/114982854/play", + "playable": true, + "scheduled": false, + "created_at": "2018-10-31T16:35:23.728Z", + "updated_at": "2018-10-31T16:35:24.070Z", + "status": { + "icon": "status_manual", + "text": "manual", + "label": "manual play action", + "group": "manual", + "tooltip": "manual action", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982854", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.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": "https://gitlab.com/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png", + "action": { + "icon": "play", + "title": "Play", + "path": "/gitlab-com/gitlab-docs/-/jobs/114982854/play", + "method": "post", + "button_title": "Trigger this manual action" + } + } + } + ] + }, + { + "name": "image:nginx-onbuild", + "size": 1, + "status": { + "icon": "status_manual", + "text": "manual", + "label": "manual play action", + "group": "manual", + "tooltip": "manual action", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982855", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.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": "https://gitlab.com/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png", + "action": { + "icon": "play", + "title": "Play", + "path": "/gitlab-com/gitlab-docs/-/jobs/114982855/play", + "method": "post", + "button_title": "Trigger this manual action" + } + }, + "jobs": [ + { + "id": 11498285523424, + "name": "image:nginx-onbuild", + "started": null, + "archived": false, + "build_path": "/gitlab-com/gitlab-docs/-/jobs/114982855", + "play_path": "/gitlab-com/gitlab-docs/-/jobs/114982855/play", + "playable": true, + "scheduled": false, + "created_at": "2018-10-31T16:35:23.753Z", + "updated_at": "2018-10-31T16:35:24.033Z", + "status": { + "icon": "status_manual", + "text": "manual", + "label": "manual play action", + "group": "manual", + "tooltip": "manual action", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982855", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.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": "https://gitlab.com/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png", + "action": { + "icon": "play", + "title": "Play", + "path": "/gitlab-com/gitlab-docs/-/jobs/114982855/play", + "method": "post", + "button_title": "Trigger this manual action" + } + } + } + ] + } + ], + "status": { + "icon": "status_skipped", + "text": "skipped", + "label": "skipped", + "group": "skipped", + "tooltip": "skipped", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/pipelines/34993051#build-images", + "illustration": null, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_skipped-0b9c5e543588945e8c4ca57786bbf2d0c56631959c9f853300392d0315be829b.png" + }, + "path": "/gitlab-com/gitlab-docs/pipelines/34993051#build-images", + "dropdown_path": "/gitlab-com/gitlab-docs/pipelines/34993051/stage.json?stage=build-images" + }, + { + "name": "build", + "title": "build: failed", + "groups": [ + { + "name": "compile_dev", + "size": 1, + "status": { + "icon": "status_failed", + "text": "failed", + "label": "failed", + "group": "failed", + "tooltip": "failed - (script failure)", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/-/jobs/114984694", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg", + "size": "svg-430", + "title": "This job does not have a trace." + }, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_failed-41304d7f7e3828808b0c26771f0309e55296819a9beea3ea9fbf6689d9857c12.png", + "action": { + "icon": "retry", + "title": "Retry", + "path": "/gitlab-com/gitlab-docs/-/jobs/114984694/retry", + "method": "post", + "button_title": "Retry this job" + } + }, + "jobs": [ + { + "id": 1149846949786, + "name": "compile_dev", + "started": "2018-10-31T16:39:41.598Z", + "archived": false, + "build_path": "/gitlab-com/gitlab-docs/-/jobs/114984694", + "retry_path": "/gitlab-com/gitlab-docs/-/jobs/114984694/retry", + "playable": false, + "scheduled": false, + "created_at": "2018-10-31T16:39:41.138Z", + "updated_at": "2018-10-31T16:41:40.072Z", + "status": { + "icon": "status_failed", + "text": "failed", + "label": "failed", + "group": "failed", + "tooltip": "failed - (script failure)", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/-/jobs/114984694", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg", + "size": "svg-430", + "title": "This job does not have a trace." + }, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_failed-41304d7f7e3828808b0c26771f0309e55296819a9beea3ea9fbf6689d9857c12.png", + "action": { + "icon": "retry", + "title": "Retry", + "path": "/gitlab-com/gitlab-docs/-/jobs/114984694/retry", + "method": "post", + "button_title": "Retry this job" + } + }, + "recoverable": false + } + ] + } + ], + "status": { + "icon": "status_failed", + "text": "failed", + "label": "failed", + "group": "failed", + "tooltip": "failed", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/pipelines/34993051#build", + "illustration": null, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_failed-41304d7f7e3828808b0c26771f0309e55296819a9beea3ea9fbf6689d9857c12.png" + }, + "path": "/gitlab-com/gitlab-docs/pipelines/34993051#build", + "dropdown_path": "/gitlab-com/gitlab-docs/pipelines/34993051/stage.json?stage=build" + }, + { + "name": "deploy", + "title": "deploy: skipped", + "groups": [ + { + "name": "review", + "size": 1, + "status": { + "icon": "status_skipped", + "text": "skipped", + "label": "skipped", + "group": "skipped", + "tooltip": "skipped", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982857", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg", + "size": "svg-430", + "title": "This job has been skipped" + }, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_skipped-0b9c5e543588945e8c4ca57786bbf2d0c56631959c9f853300392d0315be829b.png" + }, + "jobs": [ + { + "id": 11498282342357, + "name": "review", + "started": null, + "archived": false, + "build_path": "/gitlab-com/gitlab-docs/-/jobs/114982857", + "playable": false, + "scheduled": false, + "created_at": "2018-10-31T16:35:23.805Z", + "updated_at": "2018-10-31T16:41:40.569Z", + "status": { + "icon": "status_skipped", + "text": "skipped", + "label": "skipped", + "group": "skipped", + "tooltip": "skipped", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982857", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg", + "size": "svg-430", + "title": "This job has been skipped" + }, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_skipped-0b9c5e543588945e8c4ca57786bbf2d0c56631959c9f853300392d0315be829b.png" + } + } + ] + }, + { + "name": "review_stop", + "size": 1, + "status": { + "icon": "status_skipped", + "text": "skipped", + "label": "skipped", + "group": "skipped", + "tooltip": "skipped", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982858", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg", + "size": "svg-430", + "title": "This job has been skipped" + }, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_skipped-0b9c5e543588945e8c4ca57786bbf2d0c56631959c9f853300392d0315be829b.png" + }, + "jobs": [ + { + "id": 114982858, + "name": "review_stop", + "started": null, + "archived": false, + "build_path": "/gitlab-com/gitlab-docs/-/jobs/114982858", + "playable": false, + "scheduled": false, + "created_at": "2018-10-31T16:35:23.840Z", + "updated_at": "2018-10-31T16:41:40.480Z", + "status": { + "icon": "status_skipped", + "text": "skipped", + "label": "skipped", + "group": "skipped", + "tooltip": "skipped", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982858", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg", + "size": "svg-430", + "title": "This job has been skipped" + }, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_skipped-0b9c5e543588945e8c4ca57786bbf2d0c56631959c9f853300392d0315be829b.png" + } + } + ] + } + ], + "status": { + "icon": "status_skipped", + "text": "skipped", + "label": "skipped", + "group": "skipped", + "tooltip": "skipped", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/pipelines/34993051#deploy", + "illustration": null, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_skipped-0b9c5e543588945e8c4ca57786bbf2d0c56631959c9f853300392d0315be829b.png" + }, + "path": "/gitlab-com/gitlab-docs/pipelines/34993051#deploy", + "dropdown_path": "/gitlab-com/gitlab-docs/pipelines/34993051/stage.json?stage=deploy" + } + ], + "artifacts": [], + "manual_actions": [ + { + "name": "image:bootstrap", + "path": "/gitlab-com/gitlab-docs/-/jobs/114982853/play", + "playable": true, + "scheduled": false + }, + { + "name": "image:builder-onbuild", + "path": "/gitlab-com/gitlab-docs/-/jobs/114982854/play", + "playable": true, + "scheduled": false + }, + { + "name": "image:nginx-onbuild", + "path": "/gitlab-com/gitlab-docs/-/jobs/114982855/play", + "playable": true, + "scheduled": false + }, + { + "name": "review_stop", + "path": "/gitlab-com/gitlab-docs/-/jobs/114982858/play", + "playable": false, + "scheduled": false + } + ], + "scheduled_actions": [] + }, + "project": { + "id": 1794617, + "name": "GitLab Docs", + "full_path": "/gitlab-com/gitlab-docs", + "full_name": "GitLab.com / GitLab Docs" + } + }, + "triggered": [] + }, + "triggered": [ + { + "id": 34993051, + "user": { + "id": 376774, + "name": "Alessio Caiazza", + "username": "nolith", + "state": "active", + "avatar_url": "https://assets.gitlab-static.net/uploads/-/system/user/avatar/376774/avatar.png", + "web_url": "https://gitlab.com/nolith", + "status_tooltip_html": null, + "path": "/nolith" + }, + "active": false, + "coverage": null, + "source": "pipeline", + "path": "/gitlab-com/gitlab-docs/pipelines/34993051", + "details": { + "status": { + "icon": "status_failed", + "text": "failed", + "label": "failed", + "group": "failed", + "tooltip": "failed", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/pipelines/34993051", + "illustration": null, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_failed-41304d7f7e3828808b0c26771f0309e55296819a9beea3ea9fbf6689d9857c12.png" + }, + "duration": 118, + "finished_at": "2018-10-31T16:41:40.615Z", + "stages": [ + { + "name": "build-images", + "title": "build-images: skipped", + "groups": [ + { + "name": "image:bootstrap", + "size": 1, + "status": { + "icon": "status_manual", + "text": "manual", + "label": "manual play action", + "group": "manual", + "tooltip": "manual action", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982853", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.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": "https://gitlab.com/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png", + "action": { + "icon": "play", + "title": "Play", + "path": "/gitlab-com/gitlab-docs/-/jobs/114982853/play", + "method": "post", + "button_title": "Trigger this manual action" + } + }, + "jobs": [ + { + "id": 114982853, + "name": "image:bootstrap", + "started": null, + "archived": false, + "build_path": "/gitlab-com/gitlab-docs/-/jobs/114982853", + "play_path": "/gitlab-com/gitlab-docs/-/jobs/114982853/play", + "playable": true, + "scheduled": false, + "created_at": "2018-10-31T16:35:23.704Z", + "updated_at": "2018-10-31T16:35:24.118Z", + "status": { + "icon": "status_manual", + "text": "manual", + "label": "manual play action", + "group": "manual", + "tooltip": "manual action", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982853", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.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": "https://gitlab.com/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png", + "action": { + "icon": "play", + "title": "Play", + "path": "/gitlab-com/gitlab-docs/-/jobs/114982853/play", + "method": "post", + "button_title": "Trigger this manual action" + } + } + } + ] + }, + { + "name": "image:builder-onbuild", + "size": 1, + "status": { + "icon": "status_manual", + "text": "manual", + "label": "manual play action", + "group": "manual", + "tooltip": "manual action", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982854", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.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": "https://gitlab.com/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png", + "action": { + "icon": "play", + "title": "Play", + "path": "/gitlab-com/gitlab-docs/-/jobs/114982854/play", + "method": "post", + "button_title": "Trigger this manual action" + } + }, + "jobs": [ + { + "id": 114982854, + "name": "image:builder-onbuild", + "started": null, + "archived": false, + "build_path": "/gitlab-com/gitlab-docs/-/jobs/114982854", + "play_path": "/gitlab-com/gitlab-docs/-/jobs/114982854/play", + "playable": true, + "scheduled": false, + "created_at": "2018-10-31T16:35:23.728Z", + "updated_at": "2018-10-31T16:35:24.070Z", + "status": { + "icon": "status_manual", + "text": "manual", + "label": "manual play action", + "group": "manual", + "tooltip": "manual action", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982854", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.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": "https://gitlab.com/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png", + "action": { + "icon": "play", + "title": "Play", + "path": "/gitlab-com/gitlab-docs/-/jobs/114982854/play", + "method": "post", + "button_title": "Trigger this manual action" + } + } + } + ] + }, + { + "name": "image:nginx-onbuild", + "size": 1, + "status": { + "icon": "status_manual", + "text": "manual", + "label": "manual play action", + "group": "manual", + "tooltip": "manual action", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982855", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.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": "https://gitlab.com/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png", + "action": { + "icon": "play", + "title": "Play", + "path": "/gitlab-com/gitlab-docs/-/jobs/114982855/play", + "method": "post", + "button_title": "Trigger this manual action" + } + }, + "jobs": [ + { + "id": 114982855, + "name": "image:nginx-onbuild", + "started": null, + "archived": false, + "build_path": "/gitlab-com/gitlab-docs/-/jobs/114982855", + "play_path": "/gitlab-com/gitlab-docs/-/jobs/114982855/play", + "playable": true, + "scheduled": false, + "created_at": "2018-10-31T16:35:23.753Z", + "updated_at": "2018-10-31T16:35:24.033Z", + "status": { + "icon": "status_manual", + "text": "manual", + "label": "manual play action", + "group": "manual", + "tooltip": "manual action", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982855", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.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": "https://gitlab.com/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png", + "action": { + "icon": "play", + "title": "Play", + "path": "/gitlab-com/gitlab-docs/-/jobs/114982855/play", + "method": "post", + "button_title": "Trigger this manual action" + } + } + } + ] + } + ], + "status": { + "icon": "status_skipped", + "text": "skipped", + "label": "skipped", + "group": "skipped", + "tooltip": "skipped", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/pipelines/34993051#build-images", + "illustration": null, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_skipped-0b9c5e543588945e8c4ca57786bbf2d0c56631959c9f853300392d0315be829b.png" + }, + "path": "/gitlab-com/gitlab-docs/pipelines/34993051#build-images", + "dropdown_path": "/gitlab-com/gitlab-docs/pipelines/34993051/stage.json?stage=build-images" + }, + { + "name": "build", + "title": "build: failed", + "groups": [ + { + "name": "compile_dev", + "size": 1, + "status": { + "icon": "status_failed", + "text": "failed", + "label": "failed", + "group": "failed", + "tooltip": "failed - (script failure)", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/-/jobs/114984694", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg", + "size": "svg-430", + "title": "This job does not have a trace." + }, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_failed-41304d7f7e3828808b0c26771f0309e55296819a9beea3ea9fbf6689d9857c12.png", + "action": { + "icon": "retry", + "title": "Retry", + "path": "/h5bp/html5-boilerplate/-/jobs/528/retry", + "method": "post", + "button_title": "Retry this job" + } + }, + "jobs": [ + { + "id": 114984694, + "name": "compile_dev", + "started": "2018-10-31T16:39:41.598Z", + "archived": false, + "build_path": "/gitlab-com/gitlab-docs/-/jobs/114984694", + "retry_path": "/gitlab-com/gitlab-docs/-/jobs/114984694/retry", + "playable": false, + "scheduled": false, + "created_at": "2018-10-31T16:39:41.138Z", + "updated_at": "2018-10-31T16:41:40.072Z", + "status": { + "icon": "status_failed", + "text": "failed", + "label": "failed", + "group": "failed", + "tooltip": "failed - (script failure)", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/-/jobs/114984694", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg", + "size": "svg-430", + "title": "This job does not have a trace." + }, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_failed-41304d7f7e3828808b0c26771f0309e55296819a9beea3ea9fbf6689d9857c12.png", + "action": { + "icon": "retry", + "title": "Retry", + "path": "/h5bp/html5-boilerplate/-/jobs/528/retry", + "method": "post", + "button_title": "Retry this job" + } + }, + "recoverable": false + } + ] + } + ], + "status": { + "icon": "status_failed", + "text": "failed", + "label": "failed", + "group": "failed", + "tooltip": "failed", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/pipelines/34993051#build", + "illustration": null, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_failed-41304d7f7e3828808b0c26771f0309e55296819a9beea3ea9fbf6689d9857c12.png" + }, + "path": "/gitlab-com/gitlab-docs/pipelines/34993051#build", + "dropdown_path": "/gitlab-com/gitlab-docs/pipelines/34993051/stage.json?stage=build" + }, + { + "name": "deploy", + "title": "deploy: skipped", + "groups": [ + { + "name": "review", + "size": 1, + "status": { + "icon": "status_skipped", + "text": "skipped", + "label": "skipped", + "group": "skipped", + "tooltip": "skipped", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982857", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg", + "size": "svg-430", + "title": "This job has been skipped" + }, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_skipped-0b9c5e543588945e8c4ca57786bbf2d0c56631959c9f853300392d0315be829b.png" + }, + "jobs": [ + { + "id": 114982857, + "name": "review", + "started": null, + "archived": false, + "build_path": "/gitlab-com/gitlab-docs/-/jobs/114982857", + "playable": false, + "scheduled": false, + "created_at": "2018-10-31T16:35:23.805Z", + "updated_at": "2018-10-31T16:41:40.569Z", + "status": { + "icon": "status_skipped", + "text": "skipped", + "label": "skipped", + "group": "skipped", + "tooltip": "skipped", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982857", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg", + "size": "svg-430", + "title": "This job has been skipped" + }, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_skipped-0b9c5e543588945e8c4ca57786bbf2d0c56631959c9f853300392d0315be829b.png" + } + } + ] + }, + { + "name": "review_stop", + "size": 1, + "status": { + "icon": "status_skipped", + "text": "skipped", + "label": "skipped", + "group": "skipped", + "tooltip": "skipped", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982858", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg", + "size": "svg-430", + "title": "This job has been skipped" + }, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_skipped-0b9c5e543588945e8c4ca57786bbf2d0c56631959c9f853300392d0315be829b.png" + }, + "jobs": [ + { + "id": 114982858, + "name": "review_stop", + "started": null, + "archived": false, + "build_path": "/gitlab-com/gitlab-docs/-/jobs/114982858", + "playable": false, + "scheduled": false, + "created_at": "2018-10-31T16:35:23.840Z", + "updated_at": "2018-10-31T16:41:40.480Z", + "status": { + "icon": "status_skipped", + "text": "skipped", + "label": "skipped", + "group": "skipped", + "tooltip": "skipped", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982858", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg", + "size": "svg-430", + "title": "This job has been skipped" + }, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_skipped-0b9c5e543588945e8c4ca57786bbf2d0c56631959c9f853300392d0315be829b.png" + } + } + ] + } + ], + "status": { + "icon": "status_skipped", + "text": "skipped", + "label": "skipped", + "group": "skipped", + "tooltip": "skipped", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/pipelines/34993051#deploy", + "illustration": null, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_skipped-0b9c5e543588945e8c4ca57786bbf2d0c56631959c9f853300392d0315be829b.png" + }, + "path": "/gitlab-com/gitlab-docs/pipelines/34993051#deploy", + "dropdown_path": "/gitlab-com/gitlab-docs/pipelines/34993051/stage.json?stage=deploy" + } + ], + "artifacts": [], + "manual_actions": [ + { + "name": "image:bootstrap", + "path": "/gitlab-com/gitlab-docs/-/jobs/114982853/play", + "playable": true, + "scheduled": false + }, + { + "name": "image:builder-onbuild", + "path": "/gitlab-com/gitlab-docs/-/jobs/114982854/play", + "playable": true, + "scheduled": false + }, + { + "name": "image:nginx-onbuild", + "path": "/gitlab-com/gitlab-docs/-/jobs/114982855/play", + "playable": true, + "scheduled": false + }, + { + "name": "review_stop", + "path": "/gitlab-com/gitlab-docs/-/jobs/114982858/play", + "playable": false, + "scheduled": false + } + ], + "scheduled_actions": [] + }, + "project": { + "id": 1794617, + "name": "GitLab Docs", + "full_path": "/gitlab-com/gitlab-docs", + "full_name": "GitLab.com / GitLab Docs" + } + }, + { + "id": 34993052, + "user": { + "id": 376774, + "name": "Alessio Caiazza", + "username": "nolith", + "state": "active", + "avatar_url": "https://assets.gitlab-static.net/uploads/-/system/user/avatar/376774/avatar.png", + "web_url": "https://gitlab.com/nolith", + "status_tooltip_html": null, + "path": "/nolith" + }, + "active": false, + "coverage": null, + "source": "pipeline", + "path": "/gitlab-com/gitlab-docs/pipelines/34993051", + "details": { + "status": { + "icon": "status_failed", + "text": "failed", + "label": "failed", + "group": "failed", + "tooltip": "failed", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/pipelines/34993051", + "illustration": null, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_failed-41304d7f7e3828808b0c26771f0309e55296819a9beea3ea9fbf6689d9857c12.png" + }, + "duration": 118, + "finished_at": "2018-10-31T16:41:40.615Z", + "stages": [ + { + "name": "build-images", + "title": "build-images: skipped", + "groups": [ + { + "name": "image:bootstrap", + "size": 1, + "status": { + "icon": "status_manual", + "text": "manual", + "label": "manual play action", + "group": "manual", + "tooltip": "manual action", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982853", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.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": "https://gitlab.com/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png", + "action": { + "icon": "play", + "title": "Play", + "path": "/gitlab-com/gitlab-docs/-/jobs/114982853/play", + "method": "post", + "button_title": "Trigger this manual action" + } + }, + "jobs": [ + { + "id": 114982853, + "name": "image:bootstrap", + "started": null, + "archived": false, + "build_path": "/gitlab-com/gitlab-docs/-/jobs/114982853", + "play_path": "/gitlab-com/gitlab-docs/-/jobs/114982853/play", + "playable": true, + "scheduled": false, + "created_at": "2018-10-31T16:35:23.704Z", + "updated_at": "2018-10-31T16:35:24.118Z", + "status": { + "icon": "status_manual", + "text": "manual", + "label": "manual play action", + "group": "manual", + "tooltip": "manual action", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982853", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.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": "https://gitlab.com/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png", + "action": { + "icon": "play", + "title": "Play", + "path": "/gitlab-com/gitlab-docs/-/jobs/114982853/play", + "method": "post", + "button_title": "Trigger this manual action" + } + } + } + ] + }, + { + "name": "image:builder-onbuild", + "size": 1, + "status": { + "icon": "status_manual", + "text": "manual", + "label": "manual play action", + "group": "manual", + "tooltip": "manual action", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982854", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.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": "https://gitlab.com/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png", + "action": { + "icon": "play", + "title": "Play", + "path": "/gitlab-com/gitlab-docs/-/jobs/114982854/play", + "method": "post", + "button_title": "Trigger this manual action" + } + }, + "jobs": [ + { + "id": 114982854, + "name": "image:builder-onbuild", + "started": null, + "archived": false, + "build_path": "/gitlab-com/gitlab-docs/-/jobs/114982854", + "play_path": "/gitlab-com/gitlab-docs/-/jobs/114982854/play", + "playable": true, + "scheduled": false, + "created_at": "2018-10-31T16:35:23.728Z", + "updated_at": "2018-10-31T16:35:24.070Z", + "status": { + "icon": "status_manual", + "text": "manual", + "label": "manual play action", + "group": "manual", + "tooltip": "manual action", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982854", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.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": "https://gitlab.com/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png", + "action": { + "icon": "play", + "title": "Play", + "path": "/gitlab-com/gitlab-docs/-/jobs/114982854/play", + "method": "post", + "button_title": "Trigger this manual action" + } + } + } + ] + }, + { + "name": "image:nginx-onbuild", + "size": 1, + "status": { + "icon": "status_manual", + "text": "manual", + "label": "manual play action", + "group": "manual", + "tooltip": "manual action", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982855", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.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": "https://gitlab.com/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png", + "action": { + "icon": "play", + "title": "Play", + "path": "/gitlab-com/gitlab-docs/-/jobs/114982855/play", + "method": "post", + "button_title": "Trigger this manual action" + } + }, + "jobs": [ + { + "id": 1224982855, + "name": "image:nginx-onbuild", + "started": null, + "archived": false, + "build_path": "/gitlab-com/gitlab-docs/-/jobs/114982855", + "play_path": "/gitlab-com/gitlab-docs/-/jobs/114982855/play", + "playable": true, + "scheduled": false, + "created_at": "2018-10-31T16:35:23.753Z", + "updated_at": "2018-10-31T16:35:24.033Z", + "status": { + "icon": "status_manual", + "text": "manual", + "label": "manual play action", + "group": "manual", + "tooltip": "manual action", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982855", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.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": "https://gitlab.com/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png", + "action": { + "icon": "play", + "title": "Play", + "path": "/gitlab-com/gitlab-docs/-/jobs/114982855/play", + "method": "post", + "button_title": "Trigger this manual action" + } + } + } + ] + } + ], + "status": { + "icon": "status_skipped", + "text": "skipped", + "label": "skipped", + "group": "skipped", + "tooltip": "skipped", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/pipelines/34993051#build-images", + "illustration": null, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_skipped-0b9c5e543588945e8c4ca57786bbf2d0c56631959c9f853300392d0315be829b.png" + }, + "path": "/gitlab-com/gitlab-docs/pipelines/34993051#build-images", + "dropdown_path": "/gitlab-com/gitlab-docs/pipelines/34993051/stage.json?stage=build-images" + }, + { + "name": "build", + "title": "build: failed", + "groups": [ + { + "name": "compile_dev", + "size": 1, + "status": { + "icon": "status_failed", + "text": "failed", + "label": "failed", + "group": "failed", + "tooltip": "failed - (script failure)", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/-/jobs/114984694", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg", + "size": "svg-430", + "title": "This job does not have a trace." + }, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_failed-41304d7f7e3828808b0c26771f0309e55296819a9beea3ea9fbf6689d9857c12.png", + "action": { + "icon": "retry", + "title": "Retry", + "path": "/gitlab-com/gitlab-docs/-/jobs/114984694/retry", + "method": "post", + "button_title": "Retry this job" + } + }, + "jobs": [ + { + "id": 1123984694, + "name": "compile_dev", + "started": "2018-10-31T16:39:41.598Z", + "archived": false, + "build_path": "/gitlab-com/gitlab-docs/-/jobs/114984694", + "retry_path": "/gitlab-com/gitlab-docs/-/jobs/114984694/retry", + "playable": false, + "scheduled": false, + "created_at": "2018-10-31T16:39:41.138Z", + "updated_at": "2018-10-31T16:41:40.072Z", + "status": { + "icon": "status_failed", + "text": "failed", + "label": "failed", + "group": "failed", + "tooltip": "failed - (script failure)", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/-/jobs/114984694", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg", + "size": "svg-430", + "title": "This job does not have a trace." + }, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_failed-41304d7f7e3828808b0c26771f0309e55296819a9beea3ea9fbf6689d9857c12.png", + "action": { + "icon": "retry", + "title": "Retry", + "path": "/gitlab-com/gitlab-docs/-/jobs/114984694/retry", + "method": "post", + "button_title": "Retry this job" + } + }, + "recoverable": false + } + ] + } + ], + "status": { + "icon": "status_failed", + "text": "failed", + "label": "failed", + "group": "failed", + "tooltip": "failed", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/pipelines/34993051#build", + "illustration": null, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_failed-41304d7f7e3828808b0c26771f0309e55296819a9beea3ea9fbf6689d9857c12.png" + }, + "path": "/gitlab-com/gitlab-docs/pipelines/34993051#build", + "dropdown_path": "/gitlab-com/gitlab-docs/pipelines/34993051/stage.json?stage=build" + }, + { + "name": "deploy", + "title": "deploy: skipped", + "groups": [ + { + "name": "review", + "size": 1, + "status": { + "icon": "status_skipped", + "text": "skipped", + "label": "skipped", + "group": "skipped", + "tooltip": "skipped", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982857", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg", + "size": "svg-430", + "title": "This job has been skipped" + }, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_skipped-0b9c5e543588945e8c4ca57786bbf2d0c56631959c9f853300392d0315be829b.png" + }, + "jobs": [ + { + "id": 1143232982857, + "name": "review", + "started": null, + "archived": false, + "build_path": "/gitlab-com/gitlab-docs/-/jobs/114982857", + "playable": false, + "scheduled": false, + "created_at": "2018-10-31T16:35:23.805Z", + "updated_at": "2018-10-31T16:41:40.569Z", + "status": { + "icon": "status_skipped", + "text": "skipped", + "label": "skipped", + "group": "skipped", + "tooltip": "skipped", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982857", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg", + "size": "svg-430", + "title": "This job has been skipped" + }, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_skipped-0b9c5e543588945e8c4ca57786bbf2d0c56631959c9f853300392d0315be829b.png" + } + } + ] + }, + { + "name": "review_stop", + "size": 1, + "status": { + "icon": "status_skipped", + "text": "skipped", + "label": "skipped", + "group": "skipped", + "tooltip": "skipped", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982858", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg", + "size": "svg-430", + "title": "This job has been skipped" + }, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_skipped-0b9c5e543588945e8c4ca57786bbf2d0c56631959c9f853300392d0315be829b.png" + }, + "jobs": [ + { + "id": 114921313182858, + "name": "review_stop", + "started": null, + "archived": false, + "build_path": "/gitlab-com/gitlab-docs/-/jobs/114982858", + "playable": false, + "scheduled": false, + "created_at": "2018-10-31T16:35:23.840Z", + "updated_at": "2018-10-31T16:41:40.480Z", + "status": { + "icon": "status_skipped", + "text": "skipped", + "label": "skipped", + "group": "skipped", + "tooltip": "skipped", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982858", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg", + "size": "svg-430", + "title": "This job has been skipped" + }, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_skipped-0b9c5e543588945e8c4ca57786bbf2d0c56631959c9f853300392d0315be829b.png" + } + } + ] + } + ], + "status": { + "icon": "status_skipped", + "text": "skipped", + "label": "skipped", + "group": "skipped", + "tooltip": "skipped", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/pipelines/34993051#deploy", + "illustration": null, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_skipped-0b9c5e543588945e8c4ca57786bbf2d0c56631959c9f853300392d0315be829b.png" + }, + "path": "/gitlab-com/gitlab-docs/pipelines/34993051#deploy", + "dropdown_path": "/gitlab-com/gitlab-docs/pipelines/34993051/stage.json?stage=deploy" + } + ], + "artifacts": [], + "manual_actions": [ + { + "name": "image:bootstrap", + "path": "/gitlab-com/gitlab-docs/-/jobs/114982853/play", + "playable": true, + "scheduled": false + }, + { + "name": "image:builder-onbuild", + "path": "/gitlab-com/gitlab-docs/-/jobs/114982854/play", + "playable": true, + "scheduled": false + }, + { + "name": "image:nginx-onbuild", + "path": "/gitlab-com/gitlab-docs/-/jobs/114982855/play", + "playable": true, + "scheduled": false + }, + { + "name": "review_stop", + "path": "/gitlab-com/gitlab-docs/-/jobs/114982858/play", + "playable": false, + "scheduled": false + } + ], + "scheduled_actions": [] + }, + "project": { + "id": 1794617, + "name": "GitLab Docs", + "full_path": "/gitlab-com/gitlab-docs", + "full_name": "GitLab.com / GitLab Docs" + }, + "triggered": [ + { + "id": 26, + "user": null, + "active": false, + "coverage": null, + "source": "push", + "created_at": "2019-01-06T17:48:37.599Z", + "updated_at": "2019-01-06T17:48:38.371Z", + "path": "/h5bp/html5-boilerplate/pipelines/26", + "flags": { + "latest": true, + "stuck": false, + "auto_devops": false, + "merge_request": false, + "yaml_errors": false, + "retryable": true, + "cancelable": false, + "failure_reason": false + }, + "details": { + "status": { + "icon": "status_warning", + "text": "passed", + "label": "passed with warnings", + "group": "success-with-warnings", + "tooltip": "passed", + "has_details": true, + "details_path": "/h5bp/html5-boilerplate/pipelines/26", + "illustration": null, + "favicon": "/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png" + }, + "duration": null, + "finished_at": "2019-01-06T17:48:38.370Z", + "stages": [ + { + "name": "build", + "title": "build: passed", + "groups": [ + { + "name": "build:linux", + "size": 1, + "status": { + "icon": "status_success", + "text": "passed", + "label": "passed", + "group": "success", + "tooltip": "passed", + "has_details": true, + "details_path": "/h5bp/html5-boilerplate/-/jobs/526", + "illustration": { + "image": "/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.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": "/h5bp/html5-boilerplate/-/jobs/526/retry", + "method": "post", + "button_title": "Retry this job" + } + }, + "jobs": [ + { + "id": 526, + "name": "build:linux", + "started": "2019-01-06T08:48:20.236Z", + "archived": false, + "build_path": "/h5bp/html5-boilerplate/-/jobs/526", + "retry_path": "/h5bp/html5-boilerplate/-/jobs/526/retry", + "playable": false, + "scheduled": false, + "created_at": "2019-01-06T17:48:37.806Z", + "updated_at": "2019-01-06T17:48:37.806Z", + "status": { + "icon": "status_success", + "text": "passed", + "label": "passed", + "group": "success", + "tooltip": "passed", + "has_details": true, + "details_path": "/h5bp/html5-boilerplate/-/jobs/526", + "illustration": { + "image": "/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.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": "/h5bp/html5-boilerplate/-/jobs/526/retry", + "method": "post", + "button_title": "Retry this job" + } + } + } + ] + }, + { + "name": "build:osx", + "size": 1, + "status": { + "icon": "status_success", + "text": "passed", + "label": "passed", + "group": "success", + "tooltip": "passed", + "has_details": true, + "details_path": "/h5bp/html5-boilerplate/-/jobs/527", + "illustration": { + "image": "/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.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": "/h5bp/html5-boilerplate/-/jobs/527/retry", + "method": "post", + "button_title": "Retry this job" + } + }, + "jobs": [ + { + "id": 527, + "name": "build:osx", + "started": "2019-01-06T07:48:20.237Z", + "archived": false, + "build_path": "/h5bp/html5-boilerplate/-/jobs/527", + "retry_path": "/h5bp/html5-boilerplate/-/jobs/527/retry", + "playable": false, + "scheduled": false, + "created_at": "2019-01-06T17:48:37.846Z", + "updated_at": "2019-01-06T17:48:37.846Z", + "status": { + "icon": "status_success", + "text": "passed", + "label": "passed", + "group": "success", + "tooltip": "passed", + "has_details": true, + "details_path": "/h5bp/html5-boilerplate/-/jobs/527", + "illustration": { + "image": "/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.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": "/h5bp/html5-boilerplate/-/jobs/527/retry", + "method": "post", + "button_title": "Retry this job" + } + } + } + ] + } + ], + "status": { + "icon": "status_success", + "text": "passed", + "label": "passed", + "group": "success", + "tooltip": "passed", + "has_details": true, + "details_path": "/h5bp/html5-boilerplate/pipelines/26#build", + "illustration": null, + "favicon": "/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png" + }, + "path": "/h5bp/html5-boilerplate/pipelines/26#build", + "dropdown_path": "/h5bp/html5-boilerplate/pipelines/26/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": 546, + "name": "jenkins", + "started": "2019-01-06T11:48:20.237Z", + "archived": false, + "build_path": "/h5bp/html5-boilerplate/-/jobs/546", + "playable": false, + "scheduled": false, + "created_at": "2019-01-06T17:48:38.359Z", + "updated_at": "2019-01-06T17:48:38.359Z", + "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": 528, + "name": "rspec:linux 0 3", + "started": "2019-01-06T09:48:20.237Z", + "archived": false, + "build_path": "/h5bp/html5-boilerplate/-/jobs/528", + "retry_path": "/h5bp/html5-boilerplate/-/jobs/528/retry", + "playable": false, + "scheduled": false, + "created_at": "2019-01-06T17:48:37.885Z", + "updated_at": "2019-01-06T17:48:37.885Z", + "status": { + "icon": "status_success", + "text": "passed", + "label": "passed", + "group": "success", + "tooltip": "passed", + "has_details": true, + "details_path": "/h5bp/html5-boilerplate/-/jobs/528", + "illustration": { + "image": "/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.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": "/h5bp/html5-boilerplate/-/jobs/528/retry", + "method": "post", + "button_title": "Retry this job" + } + } + }, + { + "id": 529, + "name": "rspec:linux 1 3", + "started": "2019-01-06T09:48:20.237Z", + "archived": false, + "build_path": "/h5bp/html5-boilerplate/-/jobs/529", + "retry_path": "/h5bp/html5-boilerplate/-/jobs/529/retry", + "playable": false, + "scheduled": false, + "created_at": "2019-01-06T17:48:37.907Z", + "updated_at": "2019-01-06T17:48:37.907Z", + "status": { + "icon": "status_success", + "text": "passed", + "label": "passed", + "group": "success", + "tooltip": "passed", + "has_details": true, + "details_path": "/h5bp/html5-boilerplate/-/jobs/529", + "illustration": { + "image": "/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.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": "/h5bp/html5-boilerplate/-/jobs/529/retry", + "method": "post", + "button_title": "Retry this job" + } + } + }, + { + "id": 530, + "name": "rspec:linux 2 3", + "started": "2019-01-06T09:48:20.237Z", + "archived": false, + "build_path": "/h5bp/html5-boilerplate/-/jobs/530", + "retry_path": "/h5bp/html5-boilerplate/-/jobs/530/retry", + "playable": false, + "scheduled": false, + "created_at": "2019-01-06T17:48:37.927Z", + "updated_at": "2019-01-06T17:48:37.927Z", + "status": { + "icon": "status_success", + "text": "passed", + "label": "passed", + "group": "success", + "tooltip": "passed", + "has_details": true, + "details_path": "/h5bp/html5-boilerplate/-/jobs/530", + "illustration": { + "image": "/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.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": "/h5bp/html5-boilerplate/-/jobs/530/retry", + "method": "post", + "button_title": "Retry this job" + } + } + } + ] + }, + { + "name": "rspec:osx", + "size": 1, + "status": { + "icon": "status_success", + "text": "passed", + "label": "passed", + "group": "success", + "tooltip": "passed", + "has_details": true, + "details_path": "/h5bp/html5-boilerplate/-/jobs/535", + "illustration": { + "image": "/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.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": "/h5bp/html5-boilerplate/-/jobs/535/retry", + "method": "post", + "button_title": "Retry this job" + } + }, + "jobs": [ + { + "id": 535, + "name": "rspec:osx", + "started": "2019-01-06T09:48:20.237Z", + "archived": false, + "build_path": "/h5bp/html5-boilerplate/-/jobs/535", + "retry_path": "/h5bp/html5-boilerplate/-/jobs/535/retry", + "playable": false, + "scheduled": false, + "created_at": "2019-01-06T17:48:38.018Z", + "updated_at": "2019-01-06T17:48:38.018Z", + "status": { + "icon": "status_success", + "text": "passed", + "label": "passed", + "group": "success", + "tooltip": "passed", + "has_details": true, + "details_path": "/h5bp/html5-boilerplate/-/jobs/535", + "illustration": { + "image": "/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.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": "/h5bp/html5-boilerplate/-/jobs/535/retry", + "method": "post", + "button_title": "Retry this job" + } + } + } + ] + }, + { + "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": 531, + "name": "rspec:windows 0 3", + "started": "2019-01-06T09:48:20.237Z", + "archived": false, + "build_path": "/h5bp/html5-boilerplate/-/jobs/531", + "retry_path": "/h5bp/html5-boilerplate/-/jobs/531/retry", + "playable": false, + "scheduled": false, + "created_at": "2019-01-06T17:48:37.944Z", + "updated_at": "2019-01-06T17:48:37.944Z", + "status": { + "icon": "status_success", + "text": "passed", + "label": "passed", + "group": "success", + "tooltip": "passed", + "has_details": true, + "details_path": "/h5bp/html5-boilerplate/-/jobs/531", + "illustration": { + "image": "/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.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": "/h5bp/html5-boilerplate/-/jobs/531/retry", + "method": "post", + "button_title": "Retry this job" + } + } + }, + { + "id": 532, + "name": "rspec:windows 1 3", + "started": "2019-01-06T09:48:20.237Z", + "archived": false, + "build_path": "/h5bp/html5-boilerplate/-/jobs/532", + "retry_path": "/h5bp/html5-boilerplate/-/jobs/532/retry", + "playable": false, + "scheduled": false, + "created_at": "2019-01-06T17:48:37.962Z", + "updated_at": "2019-01-06T17:48:37.962Z", + "status": { + "icon": "status_success", + "text": "passed", + "label": "passed", + "group": "success", + "tooltip": "passed", + "has_details": true, + "details_path": "/h5bp/html5-boilerplate/-/jobs/532", + "illustration": { + "image": "/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.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": "/h5bp/html5-boilerplate/-/jobs/532/retry", + "method": "post", + "button_title": "Retry this job" + } + } + }, + { + "id": 534, + "name": "rspec:windows 2 3", + "started": "2019-01-06T09:48:20.237Z", + "archived": false, + "build_path": "/h5bp/html5-boilerplate/-/jobs/534", + "retry_path": "/h5bp/html5-boilerplate/-/jobs/534/retry", + "playable": false, + "scheduled": false, + "created_at": "2019-01-06T17:48:37.999Z", + "updated_at": "2019-01-06T17:48:37.999Z", + "status": { + "icon": "status_success", + "text": "passed", + "label": "passed", + "group": "success", + "tooltip": "passed", + "has_details": true, + "details_path": "/h5bp/html5-boilerplate/-/jobs/534", + "illustration": { + "image": "/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.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": "/h5bp/html5-boilerplate/-/jobs/534/retry", + "method": "post", + "button_title": "Retry this job" + } + } + } + ] + }, + { + "name": "spinach:linux", + "size": 1, + "status": { + "icon": "status_success", + "text": "passed", + "label": "passed", + "group": "success", + "tooltip": "passed", + "has_details": true, + "details_path": "/h5bp/html5-boilerplate/-/jobs/536", + "illustration": { + "image": "/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.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": "/h5bp/html5-boilerplate/-/jobs/536/retry", + "method": "post", + "button_title": "Retry this job" + } + }, + "jobs": [ + { + "id": 536, + "name": "spinach:linux", + "started": "2019-01-06T09:48:20.237Z", + "archived": false, + "build_path": "/h5bp/html5-boilerplate/-/jobs/536", + "retry_path": "/h5bp/html5-boilerplate/-/jobs/536/retry", + "playable": false, + "scheduled": false, + "created_at": "2019-01-06T17:48:38.050Z", + "updated_at": "2019-01-06T17:48:38.050Z", + "status": { + "icon": "status_success", + "text": "passed", + "label": "passed", + "group": "success", + "tooltip": "passed", + "has_details": true, + "details_path": "/h5bp/html5-boilerplate/-/jobs/536", + "illustration": { + "image": "/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.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": "/h5bp/html5-boilerplate/-/jobs/536/retry", + "method": "post", + "button_title": "Retry this job" + } + } + } + ] + }, + { + "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": "/h5bp/html5-boilerplate/-/jobs/537", + "illustration": { + "image": "/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.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": "/h5bp/html5-boilerplate/-/jobs/537/retry", + "method": "post", + "button_title": "Retry this job" + } + }, + "jobs": [ + { + "id": 537, + "name": "spinach:osx", + "started": "2019-01-06T09:48:20.237Z", + "archived": false, + "build_path": "/h5bp/html5-boilerplate/-/jobs/537", + "retry_path": "/h5bp/html5-boilerplate/-/jobs/537/retry", + "playable": false, + "scheduled": false, + "created_at": "2019-01-06T17:48:38.069Z", + "updated_at": "2019-01-06T17:48:38.069Z", + "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": "/h5bp/html5-boilerplate/-/jobs/537", + "illustration": { + "image": "/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.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": "/h5bp/html5-boilerplate/-/jobs/537/retry", + "method": "post", + "button_title": "Retry this job" + } + }, + "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": "/h5bp/html5-boilerplate/pipelines/26#test", + "illustration": null, + "favicon": "/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png" + }, + "path": "/h5bp/html5-boilerplate/pipelines/26#test", + "dropdown_path": "/h5bp/html5-boilerplate/pipelines/26/stage.json?stage=test" + }, + { + "name": "security", + "title": "security: passed", + "groups": [ + { + "name": "container_scanning", + "size": 1, + "status": { + "icon": "status_success", + "text": "passed", + "label": "passed", + "group": "success", + "tooltip": "passed", + "has_details": true, + "details_path": "/h5bp/html5-boilerplate/-/jobs/541", + "illustration": { + "image": "/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.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": "/h5bp/html5-boilerplate/-/jobs/541/retry", + "method": "post", + "button_title": "Retry this job" + } + }, + "jobs": [ + { + "id": 541, + "name": "container_scanning", + "started": "2019-01-06T09:48:20.237Z", + "archived": false, + "build_path": "/h5bp/html5-boilerplate/-/jobs/541", + "retry_path": "/h5bp/html5-boilerplate/-/jobs/541/retry", + "playable": false, + "scheduled": false, + "created_at": "2019-01-06T17:48:38.186Z", + "updated_at": "2019-01-06T17:48:38.186Z", + "status": { + "icon": "status_success", + "text": "passed", + "label": "passed", + "group": "success", + "tooltip": "passed", + "has_details": true, + "details_path": "/h5bp/html5-boilerplate/-/jobs/541", + "illustration": { + "image": "/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.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": "/h5bp/html5-boilerplate/-/jobs/541/retry", + "method": "post", + "button_title": "Retry this job" + } + } + } + ] + }, + { + "name": "dast", + "size": 1, + "status": { + "icon": "status_success", + "text": "passed", + "label": "passed", + "group": "success", + "tooltip": "passed", + "has_details": true, + "details_path": "/h5bp/html5-boilerplate/-/jobs/538", + "illustration": { + "image": "/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.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": "/h5bp/html5-boilerplate/-/jobs/538/retry", + "method": "post", + "button_title": "Retry this job" + } + }, + "jobs": [ + { + "id": 538, + "name": "dast", + "started": "2019-01-06T09:48:20.237Z", + "archived": false, + "build_path": "/h5bp/html5-boilerplate/-/jobs/538", + "retry_path": "/h5bp/html5-boilerplate/-/jobs/538/retry", + "playable": false, + "scheduled": false, + "created_at": "2019-01-06T17:48:38.087Z", + "updated_at": "2019-01-06T17:48:38.087Z", + "status": { + "icon": "status_success", + "text": "passed", + "label": "passed", + "group": "success", + "tooltip": "passed", + "has_details": true, + "details_path": "/h5bp/html5-boilerplate/-/jobs/538", + "illustration": { + "image": "/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.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": "/h5bp/html5-boilerplate/-/jobs/538/retry", + "method": "post", + "button_title": "Retry this job" + } + } + } + ] + }, + { + "name": "dependency_scanning", + "size": 1, + "status": { + "icon": "status_success", + "text": "passed", + "label": "passed", + "group": "success", + "tooltip": "passed", + "has_details": true, + "details_path": "/h5bp/html5-boilerplate/-/jobs/540", + "illustration": { + "image": "/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.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": "/h5bp/html5-boilerplate/-/jobs/540/retry", + "method": "post", + "button_title": "Retry this job" + } + }, + "jobs": [ + { + "id": 540, + "name": "dependency_scanning", + "started": "2019-01-06T09:48:20.237Z", + "archived": false, + "build_path": "/h5bp/html5-boilerplate/-/jobs/540", + "retry_path": "/h5bp/html5-boilerplate/-/jobs/540/retry", + "playable": false, + "scheduled": false, + "created_at": "2019-01-06T17:48:38.153Z", + "updated_at": "2019-01-06T17:48:38.153Z", + "status": { + "icon": "status_success", + "text": "passed", + "label": "passed", + "group": "success", + "tooltip": "passed", + "has_details": true, + "details_path": "/h5bp/html5-boilerplate/-/jobs/540", + "illustration": { + "image": "/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.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": "/h5bp/html5-boilerplate/-/jobs/540/retry", + "method": "post", + "button_title": "Retry this job" + } + } + } + ] + }, + { + "name": "sast", + "size": 1, + "status": { + "icon": "status_success", + "text": "passed", + "label": "passed", + "group": "success", + "tooltip": "passed", + "has_details": true, + "details_path": "/h5bp/html5-boilerplate/-/jobs/539", + "illustration": { + "image": "/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.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": "/h5bp/html5-boilerplate/-/jobs/539/retry", + "method": "post", + "button_title": "Retry this job" + } + }, + "jobs": [ + { + "id": 539, + "name": "sast", + "started": "2019-01-06T09:48:20.237Z", + "archived": false, + "build_path": "/h5bp/html5-boilerplate/-/jobs/539", + "retry_path": "/h5bp/html5-boilerplate/-/jobs/539/retry", + "playable": false, + "scheduled": false, + "created_at": "2019-01-06T17:48:38.121Z", + "updated_at": "2019-01-06T17:48:38.121Z", + "status": { + "icon": "status_success", + "text": "passed", + "label": "passed", + "group": "success", + "tooltip": "passed", + "has_details": true, + "details_path": "/h5bp/html5-boilerplate/-/jobs/539", + "illustration": { + "image": "/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.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": "/h5bp/html5-boilerplate/-/jobs/539/retry", + "method": "post", + "button_title": "Retry this job" + } + } + } + ] + } + ], + "status": { + "icon": "status_success", + "text": "passed", + "label": "passed", + "group": "success", + "tooltip": "passed", + "has_details": true, + "details_path": "/h5bp/html5-boilerplate/pipelines/26#security", + "illustration": null, + "favicon": "/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png" + }, + "path": "/h5bp/html5-boilerplate/pipelines/26#security", + "dropdown_path": "/h5bp/html5-boilerplate/pipelines/26/stage.json?stage=security" + }, + { + "name": "deploy", + "title": "deploy: passed", + "groups": [ + { + "name": "production", + "size": 1, + "status": { + "icon": "status_skipped", + "text": "skipped", + "label": "skipped", + "group": "skipped", + "tooltip": "skipped", + "has_details": true, + "details_path": "/h5bp/html5-boilerplate/-/jobs/544", + "illustration": { + "image": "/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg", + "size": "svg-430", + "title": "This job has been skipped" + }, + "favicon": "/assets/ci_favicons/favicon_status_skipped-0b9c5e543588945e8c4ca57786bbf2d0c56631959c9f853300392d0315be829b.png" + }, + "jobs": [ + { + "id": 544, + "name": "production", + "started": null, + "archived": false, + "build_path": "/h5bp/html5-boilerplate/-/jobs/544", + "playable": false, + "scheduled": false, + "created_at": "2019-01-06T17:48:38.313Z", + "updated_at": "2019-01-06T17:48:38.313Z", + "status": { + "icon": "status_skipped", + "text": "skipped", + "label": "skipped", + "group": "skipped", + "tooltip": "skipped", + "has_details": true, + "details_path": "/h5bp/html5-boilerplate/-/jobs/544", + "illustration": { + "image": "/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg", + "size": "svg-430", + "title": "This job has been skipped" + }, + "favicon": "/assets/ci_favicons/favicon_status_skipped-0b9c5e543588945e8c4ca57786bbf2d0c56631959c9f853300392d0315be829b.png" + } + } + ] + }, + { + "name": "staging", + "size": 1, + "status": { + "icon": "status_success", + "text": "passed", + "label": "passed", + "group": "success", + "tooltip": "passed", + "has_details": true, + "details_path": "/h5bp/html5-boilerplate/-/jobs/542", + "illustration": { + "image": "/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.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": "/h5bp/html5-boilerplate/-/jobs/542/retry", + "method": "post", + "button_title": "Retry this job" + } + }, + "jobs": [ + { + "id": 542, + "name": "staging", + "started": "2019-01-06T11:48:20.237Z", + "archived": false, + "build_path": "/h5bp/html5-boilerplate/-/jobs/542", + "retry_path": "/h5bp/html5-boilerplate/-/jobs/542/retry", + "playable": false, + "scheduled": false, + "created_at": "2019-01-06T17:48:38.219Z", + "updated_at": "2019-01-06T17:48:38.219Z", + "status": { + "icon": "status_success", + "text": "passed", + "label": "passed", + "group": "success", + "tooltip": "passed", + "has_details": true, + "details_path": "/h5bp/html5-boilerplate/-/jobs/542", + "illustration": { + "image": "/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.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": "/h5bp/html5-boilerplate/-/jobs/542/retry", + "method": "post", + "button_title": "Retry this job" + } + } + } + ] + }, + { + "name": "stop staging", + "size": 1, + "status": { + "icon": "status_skipped", + "text": "skipped", + "label": "skipped", + "group": "skipped", + "tooltip": "skipped", + "has_details": true, + "details_path": "/h5bp/html5-boilerplate/-/jobs/543", + "illustration": { + "image": "/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg", + "size": "svg-430", + "title": "This job has been skipped" + }, + "favicon": "/assets/ci_favicons/favicon_status_skipped-0b9c5e543588945e8c4ca57786bbf2d0c56631959c9f853300392d0315be829b.png" + }, + "jobs": [ + { + "id": 543, + "name": "stop staging", + "started": null, + "archived": false, + "build_path": "/h5bp/html5-boilerplate/-/jobs/543", + "playable": false, + "scheduled": false, + "created_at": "2019-01-06T17:48:38.283Z", + "updated_at": "2019-01-06T17:48:38.283Z", + "status": { + "icon": "status_skipped", + "text": "skipped", + "label": "skipped", + "group": "skipped", + "tooltip": "skipped", + "has_details": true, + "details_path": "/h5bp/html5-boilerplate/-/jobs/543", + "illustration": { + "image": "/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg", + "size": "svg-430", + "title": "This job has been skipped" + }, + "favicon": "/assets/ci_favicons/favicon_status_skipped-0b9c5e543588945e8c4ca57786bbf2d0c56631959c9f853300392d0315be829b.png" + } + } + ] + } + ], + "status": { + "icon": "status_success", + "text": "passed", + "label": "passed", + "group": "success", + "tooltip": "passed", + "has_details": true, + "details_path": "/h5bp/html5-boilerplate/pipelines/26#deploy", + "illustration": null, + "favicon": "/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png" + }, + "path": "/h5bp/html5-boilerplate/pipelines/26#deploy", + "dropdown_path": "/h5bp/html5-boilerplate/pipelines/26/stage.json?stage=deploy" + }, + { + "name": "notify", + "title": "notify: passed", + "groups": [ + { + "name": "slack", + "size": 1, + "status": { + "icon": "status_success", + "text": "passed", + "label": "manual play action", + "group": "success", + "tooltip": "passed", + "has_details": true, + "details_path": "/h5bp/html5-boilerplate/-/jobs/545", + "illustration": { + "image": "/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.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_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png", + "action": { + "icon": "play", + "title": "Play", + "path": "/h5bp/html5-boilerplate/-/jobs/545/play", + "method": "post", + "button_title": "Trigger this manual action" + } + }, + "jobs": [ + { + "id": 545, + "name": "slack", + "started": null, + "archived": false, + "build_path": "/h5bp/html5-boilerplate/-/jobs/545", + "retry_path": "/h5bp/html5-boilerplate/-/jobs/545/retry", + "play_path": "/h5bp/html5-boilerplate/-/jobs/545/play", + "playable": true, + "scheduled": false, + "created_at": "2019-01-06T17:48:38.341Z", + "updated_at": "2019-01-06T17:48:38.341Z", + "status": { + "icon": "status_success", + "text": "passed", + "label": "manual play action", + "group": "success", + "tooltip": "passed", + "has_details": true, + "details_path": "/h5bp/html5-boilerplate/-/jobs/545", + "illustration": { + "image": "/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.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_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png", + "action": { + "icon": "play", + "title": "Play", + "path": "/h5bp/html5-boilerplate/-/jobs/545/play", + "method": "post", + "button_title": "Trigger this manual action" + } + } + } + ] + } + ], + "status": { + "icon": "status_success", + "text": "passed", + "label": "passed", + "group": "success", + "tooltip": "passed", + "has_details": true, + "details_path": "/h5bp/html5-boilerplate/pipelines/26#notify", + "illustration": null, + "favicon": "/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png" + }, + "path": "/h5bp/html5-boilerplate/pipelines/26#notify", + "dropdown_path": "/h5bp/html5-boilerplate/pipelines/26/stage.json?stage=notify" + } + ], + "artifacts": [ + { + "name": "build:linux", + "expired": null, + "expire_at": null, + "path": "/h5bp/html5-boilerplate/-/jobs/526/artifacts/download", + "browse_path": "/h5bp/html5-boilerplate/-/jobs/526/artifacts/browse" + }, + { + "name": "build:osx", + "expired": null, + "expire_at": null, + "path": "/h5bp/html5-boilerplate/-/jobs/527/artifacts/download", + "browse_path": "/h5bp/html5-boilerplate/-/jobs/527/artifacts/browse" + } + ], + "manual_actions": [ + { + "name": "stop staging", + "path": "/h5bp/html5-boilerplate/-/jobs/543/play", + "playable": false, + "scheduled": false + }, + { + "name": "production", + "path": "/h5bp/html5-boilerplate/-/jobs/544/play", + "playable": false, + "scheduled": false + }, + { + "name": "slack", + "path": "/h5bp/html5-boilerplate/-/jobs/545/play", + "playable": true, + "scheduled": false + } + ], + "scheduled_actions": [] + }, + "ref": { + "name": "master", + "path": "/h5bp/html5-boilerplate/commits/master", + "tag": false, + "branch": true, + "merge_request": false + }, + "commit": { + "id": "bad98c453eab56d20057f3929989251d45cd1a8b", + "short_id": "bad98c45", + "title": "remove instances of shrink-to-fit=no (#2103)", + "created_at": "2018-12-17T20:52:18.000Z", + "parent_ids": ["49130f6cfe9ff1f749015d735649a2bc6f66cf3a"], + "message": "remove instances of shrink-to-fit=no (#2103)\n\ncloses #2102\r\n\r\nPer my findings, the need for it as a default was rectified with the release of iOS 9.3, where the viewport no longer shrunk to accommodate overflow, as was introduced in iOS 9.", + "author_name": "Scott O'Hara", + "author_email": "scottaohara@users.noreply.github.com", + "authored_date": "2018-12-17T20:52:18.000Z", + "committer_name": "Rob Larsen", + "committer_email": "rob@drunkenfist.com", + "committed_date": "2018-12-17T20:52:18.000Z", + "author": null, + "author_gravatar_url": "https://www.gravatar.com/avatar/6d597df7cf998d16cbe00ccac063b31e?s=80\u0026d=identicon", + "commit_url": "http://localhost:3001/h5bp/html5-boilerplate/commit/bad98c453eab56d20057f3929989251d45cd1a8b", + "commit_path": "/h5bp/html5-boilerplate/commit/bad98c453eab56d20057f3929989251d45cd1a8b" + }, + "retry_path": "/h5bp/html5-boilerplate/pipelines/26/retry", + "triggered_by": { + "id": 4, + "user": null, + "active": false, + "coverage": null, + "source": "push", + "path": "/gitlab-org/gitlab-test/pipelines/4", + "details": { + "status": { + "icon": "status_warning", + "text": "passed", + "label": "passed with warnings", + "group": "success-with-warnings", + "tooltip": "passed", + "has_details": true, + "details_path": "/gitlab-org/gitlab-test/pipelines/4", + "illustration": null, + "favicon": "/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png" + } + }, + "project": { + "id": 1, + "name": "Gitlab Test", + "full_path": "/gitlab-org/gitlab-test", + "full_name": "Gitlab Org / Gitlab Test" + } + }, + "triggered": [], + "project": { + "id": 1794617, + "name": "GitLab Docs", + "full_path": "/gitlab-com/gitlab-docs", + "full_name": "GitLab.com / GitLab Docs" + } + } + ] + } + ] +} diff --git a/spec/javascripts/pipelines/stores/pipeline.json b/spec/javascripts/pipelines/stores/pipeline.json new file mode 100644 index 00000000000..7d5891d3d52 --- /dev/null +++ b/spec/javascripts/pipelines/stores/pipeline.json @@ -0,0 +1,167 @@ +{ + "id": 37232567, + "user": { + "id": 113870, + "name": "Phil Hughes", + "username": "iamphill", + "state": "active", + "avatar_url": "https://secure.gravatar.com/avatar/533a51534470a11062df393543eab649?s=80\u0026d=identicon", + "web_url": "https://gitlab.com/iamphill", + "status_tooltip_html": null, + "path": "/iamphill" + }, + "active": false, + "coverage": null, + "source": "push", + "created_at": "2018-11-20T10:22:52.617Z", + "updated_at": "2018-11-20T10:24:09.511Z", + "path": "/gitlab-org/gl-vue-cli/pipelines/37232567", + "flags": { + "latest": true, + "stuck": false, + "auto_devops": false, + "yaml_errors": false, + "retryable": false, + "cancelable": false, + "failure_reason": false + }, + "details": { + "status": { + "icon": "status_success", + "text": "passed", + "label": "passed", + "group": "success", + "tooltip": "passed", + "has_details": true, + "details_path": "/gitlab-org/gl-vue-cli/pipelines/37232567", + "illustration": null, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png" + }, + "duration": 65, + "finished_at": "2018-11-20T10:24:09.483Z", + "stages": [ + { + "name": "test", + "title": "test: passed", + "groups": [ + { + "name": "eslint", + "size": 1, + "status": { + "icon": "status_success", + "text": "passed", + "label": "passed", + "group": "success", + "tooltip": "passed", + "has_details": true, + "details_path": "/gitlab-org/gl-vue-cli/-/jobs/122845352", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg", + "size": "svg-430", + "title": "This job does not have a trace." + }, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png", + "action": { + "icon": "retry", + "title": "Retry", + "path": "/gitlab-org/gl-vue-cli/-/jobs/122845352/retry", + "method": "post", + "button_title": "Retry this job" + } + }, + "jobs": [ + { + "id": 122845352, + "name": "eslint", + "started": "2018-11-20T10:22:53.369Z", + "archived": false, + "build_path": "/gitlab-org/gl-vue-cli/-/jobs/122845352", + "retry_path": "/gitlab-org/gl-vue-cli/-/jobs/122845352/retry", + "playable": false, + "scheduled": false, + "created_at": "2018-11-20T10:22:52.630Z", + "updated_at": "2018-11-20T10:23:58.948Z", + "status": { + "icon": "status_success", + "text": "passed", + "label": "passed", + "group": "success", + "tooltip": "passed", + "has_details": true, + "details_path": "/gitlab-org/gl-vue-cli/-/jobs/122845352", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg", + "size": "svg-430", + "title": "This job does not have a trace." + }, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png", + "action": { + "icon": "retry", + "title": "Retry", + "path": "/gitlab-org/gl-vue-cli/-/jobs/122845352/retry", + "method": "post", + "button_title": "Retry this job" + } + } + } + ] + } + ], + "status": { + "icon": "status_success", + "text": "passed", + "label": "passed", + "group": "success", + "tooltip": "passed", + "has_details": true, + "details_path": "/gitlab-org/gl-vue-cli/pipelines/37232567#test", + "illustration": null, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png" + }, + "path": "/gitlab-org/gl-vue-cli/pipelines/37232567#test", + "dropdown_path": "/gitlab-org/gl-vue-cli/pipelines/37232567/stage.json?stage=test" + } + ], + "artifacts": [], + "manual_actions": [], + "scheduled_actions": [] + }, + "ref": { + "name": "master", + "path": "/gitlab-org/gl-vue-cli/commits/master", + "tag": false, + "branch": true + }, + "commit": { + "id": "8f179601d481950bcb67032caeb33d1c24dde6bd", + "short_id": "8f179601", + "title": "Merge branch 'gl-cli-startt' into 'master'", + "created_at": "2018-11-20T10:22:51.000Z", + "parent_ids": [ + "781d78fcd3d6c17ccf208f0cf0ab47c3e5397118", + "d227a0bb858c48eeee7393fcade1a33748f35183" + ], + "message": "Merge branch 'gl-cli-startt' into 'master'\n\nFirst iteration of the CLI\n\nCloses gitlab-foss#53657\n\nSee merge request gitlab-org/gl-vue-cli!2", + "author_name": "Phil Hughes", + "author_email": "me@iamphill.com", + "authored_date": "2018-11-20T10:22:51.000Z", + "committer_name": "Phil Hughes", + "committer_email": "me@iamphill.com", + "committed_date": "2018-11-20T10:22:51.000Z", + "author": { + "id": 113870, + "name": "Phil Hughes", + "username": "iamphill", + "state": "active", + "avatar_url": "https://secure.gravatar.com/avatar/533a51534470a11062df393543eab649?s=80\u0026d=identicon", + "web_url": "https://gitlab.com/iamphill", + "status_tooltip_html": null, + "path": "/iamphill" + }, + "author_gravatar_url": "https://secure.gravatar.com/avatar/533a51534470a11062df393543eab649?s=80\u0026d=identicon", + "commit_url": "https://gitlab.com/gitlab-org/gl-vue-cli/commit/8f179601d481950bcb67032caeb33d1c24dde6bd", + "commit_path": "/gitlab-org/gl-vue-cli/commit/8f179601d481950bcb67032caeb33d1c24dde6bd" + }, + "triggered_by": null, + "triggered": [] +} diff --git a/spec/javascripts/pipelines/stores/pipeline_store.js b/spec/javascripts/pipelines/stores/pipeline_store.js new file mode 100644 index 00000000000..4a0b3bf4c02 --- /dev/null +++ b/spec/javascripts/pipelines/stores/pipeline_store.js @@ -0,0 +1,165 @@ +import PipelineStore from '~/pipelines/stores/pipeline_store'; +import LinkedPipelines from '../linked_pipelines_mock.json'; + +describe('EE Pipeline store', () => { + let store; + let data; + + beforeEach(() => { + store = new PipelineStore(); + data = Object.assign({}, LinkedPipelines); + }); + + describe('storePipeline', () => { + beforeAll(() => { + store.storePipeline(data); + }); + + describe('triggered_by', () => { + it('sets triggered_by as an array', () => { + expect(store.state.pipeline.triggered_by.length).toEqual(1); + }); + + it('adds isExpanding & isLoading keys set to false', () => { + expect(store.state.pipeline.triggered_by[0].isExpanded).toEqual(false); + expect(store.state.pipeline.triggered_by[0].isLoading).toEqual(false); + }); + + it('parses nested triggered_by', () => { + expect(store.state.pipeline.triggered_by[0].triggered_by.length).toEqual(1); + expect(store.state.pipeline.triggered_by[0].triggered_by[0].isExpanded).toEqual(false); + expect(store.state.pipeline.triggered_by[0].triggered_by[0].isLoading).toEqual(false); + }); + }); + + describe('triggered', () => { + it('adds isExpanding & isLoading keys set to false for each triggered pipeline', () => { + store.state.pipeline.triggered.forEach(pipeline => { + expect(pipeline.isExpanded).toEqual(false); + expect(pipeline.isLoading).toEqual(false); + }); + }); + + it('parses nested triggered pipelines', () => { + store.state.pipeline.triggered[1].triggered.forEach(pipeline => { + expect(pipeline.isExpanded).toEqual(false); + expect(pipeline.isLoading).toEqual(false); + }); + }); + }); + }); + + describe('resetTriggeredByPipeline', () => { + beforeEach(() => { + store.storePipeline(data); + }); + + it('closes the pipeline & nested ones', () => { + store.state.pipeline.triggered_by[0].isExpanded = true; + store.state.pipeline.triggered_by[0].triggered_by[0].isExpanded = true; + + store.resetTriggeredByPipeline(store.state.pipeline, store.state.pipeline.triggered_by[0]); + + expect(store.state.pipeline.triggered_by[0].isExpanded).toEqual(false); + expect(store.state.pipeline.triggered_by[0].triggered_by[0].isExpanded).toEqual(false); + }); + }); + + describe('openTriggeredByPipeline', () => { + beforeEach(() => { + store.storePipeline(data); + }); + + it('opens the given pipeline', () => { + store.openTriggeredByPipeline(store.state.pipeline, store.state.pipeline.triggered_by[0]); + + expect(store.state.pipeline.triggered_by[0].isExpanded).toEqual(true); + }); + }); + + describe('closeTriggeredByPipeline', () => { + beforeEach(() => { + store.storePipeline(data); + }); + + it('closes the given pipeline', () => { + // open it first + store.openTriggeredByPipeline(store.state.pipeline, store.state.pipeline.triggered_by[0]); + + store.closeTriggeredByPipeline(store.state.pipeline, store.state.pipeline.triggered_by[0]); + + expect(store.state.pipeline.triggered_by[0].isExpanded).toEqual(false); + }); + }); + + describe('resetTriggeredPipelines', () => { + beforeEach(() => { + store.storePipeline(data); + }); + + it('closes the pipeline & nested ones', () => { + store.state.pipeline.triggered[0].isExpanded = true; + store.state.pipeline.triggered[0].triggered[0].isExpanded = true; + + store.resetTriggeredPipeline(store.state.pipeline, store.state.pipeline.triggered[0]); + + expect(store.state.pipeline.triggered[0].isExpanded).toEqual(false); + expect(store.state.pipeline.triggered[0].triggered[0].isExpanded).toEqual(false); + }); + }); + + describe('openTriggeredPipeline', () => { + beforeEach(() => { + store.storePipeline(data); + }); + + it('opens the given pipeline', () => { + store.openTriggeredPipeline(store.state.pipeline, store.state.pipeline.triggered[0]); + + expect(store.state.pipeline.triggered[0].isExpanded).toEqual(true); + }); + }); + + describe('closeTriggeredPipeline', () => { + beforeEach(() => { + store.storePipeline(data); + }); + + it('closes the given pipeline', () => { + // open it first + store.openTriggeredPipeline(store.state.pipeline, store.state.pipeline.triggered[0]); + + store.closeTriggeredPipeline(store.state.pipeline, store.state.pipeline.triggered[0]); + + expect(store.state.pipeline.triggered[0].isExpanded).toEqual(false); + }); + }); + + describe('toggleLoading', () => { + beforeEach(() => { + store.storePipeline(data); + }); + + it('toggles the isLoading property for the given pipeline', () => { + store.togglePipeline(store.state.pipeline.triggered[0]); + + expect(store.state.pipeline.triggered[0].isLoading).toEqual(true); + }); + }); + + describe('addExpandedPipelineToRequestData', () => { + it('pushes the given id to expandedPipelines array', () => { + store.addExpandedPipelineToRequestData('213231'); + + expect(store.state.expandedPipelines).toEqual(['213231']); + }); + }); + + describe('removeExpandedPipelineToRequestData', () => { + it('pushes the given id to expandedPipelines array', () => { + store.removeExpandedPipelineToRequestData('213231'); + + expect(store.state.expandedPipelines).toEqual([]); + }); + }); +}); diff --git a/spec/javascripts/pipelines/stores/pipeline_with_triggered.json b/spec/javascripts/pipelines/stores/pipeline_with_triggered.json new file mode 100644 index 00000000000..1fa15e45792 --- /dev/null +++ b/spec/javascripts/pipelines/stores/pipeline_with_triggered.json @@ -0,0 +1,381 @@ +{ + "id": 23211253, + "user": { + "id": 3585, + "name": "Achilleas Pipinellis", + "username": "axil", + "state": "active", + "avatar_url": "https://assets.gitlab-static.net/uploads/-/system/user/avatar/3585/avatar.png", + "web_url": "https://gitlab.com/axil", + "status_tooltip_html": "\u003cspan class=\"user-status-emoji has-tooltip\" title=\"\" data-html=\"true\" data-placement=\"top\"\u003e\u003cgl-emoji title=\"trumpet\" data-name=\"trumpet\" data-unicode-version=\"6.0\"\u003e🎺\u003c/gl-emoji\u003e\u003c/span\u003e", + "path": "/axil" + }, + "active": false, + "coverage": null, + "source": "push", + "created_at": "2018-06-05T11:31:30.452Z", + "updated_at": "2018-10-31T16:35:31.305Z", + "path": "/gitlab-org/gitlab-runner/pipelines/23211253", + "flags": { + "latest": false, + "stuck": false, + "auto_devops": false, + "yaml_errors": false, + "retryable": false, + "cancelable": false, + "failure_reason": false + }, + "details": { + "status": { + "icon": "status_success", + "text": "passed", + "label": "passed", + "group": "success", + "tooltip": "passed", + "has_details": true, + "details_path": "/gitlab-org/gitlab-runner/pipelines/23211253", + "illustration": null, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png" + }, + "duration": 53, + "finished_at": "2018-10-31T16:35:31.299Z", + "stages": [ + { + "name": "prebuild", + "title": "prebuild: passed", + "groups": [ + { + "name": "review-docs-deploy", + "size": 1, + "status": { + "icon": "status_success", + "text": "passed", + "label": "manual play action", + "group": "success", + "tooltip": "passed", + "has_details": true, + "details_path": "/gitlab-org/gitlab-runner/-/jobs/72469032", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.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": "https://gitlab.com/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png", + "action": { + "icon": "play", + "title": "Play", + "path": "/gitlab-org/gitlab-runner/-/jobs/72469032/play", + "method": "post", + "button_title": "Trigger this manual action" + } + }, + "jobs": [ + { + "id": 72469032, + "name": "review-docs-deploy", + "started": "2018-10-31T16:34:58.778Z", + "archived": false, + "build_path": "/gitlab-org/gitlab-runner/-/jobs/72469032", + "retry_path": "/gitlab-org/gitlab-runner/-/jobs/72469032/retry", + "play_path": "/gitlab-org/gitlab-runner/-/jobs/72469032/play", + "playable": true, + "scheduled": false, + "created_at": "2018-06-05T11:31:30.495Z", + "updated_at": "2018-10-31T16:35:31.251Z", + "status": { + "icon": "status_success", + "text": "passed", + "label": "manual play action", + "group": "success", + "tooltip": "passed", + "has_details": true, + "details_path": "/gitlab-org/gitlab-runner/-/jobs/72469032", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.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": "https://gitlab.com/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png", + "action": { + "icon": "play", + "title": "Play", + "path": "/gitlab-org/gitlab-runner/-/jobs/72469032/play", + "method": "post", + "button_title": "Trigger this manual action" + } + } + } + ] + } + ], + "status": { + "icon": "status_success", + "text": "passed", + "label": "passed", + "group": "success", + "tooltip": "passed", + "has_details": true, + "details_path": "/gitlab-org/gitlab-runner/pipelines/23211253#prebuild", + "illustration": null, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png" + }, + "path": "/gitlab-org/gitlab-runner/pipelines/23211253#prebuild", + "dropdown_path": "/gitlab-org/gitlab-runner/pipelines/23211253/stage.json?stage=prebuild" + }, + { + "name": "test", + "title": "test: passed", + "groups": [ + { + "name": "docs check links", + "size": 1, + "status": { + "icon": "status_success", + "text": "passed", + "label": "passed", + "group": "success", + "tooltip": "passed", + "has_details": true, + "details_path": "/gitlab-org/gitlab-runner/-/jobs/72469033", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg", + "size": "svg-430", + "title": "This job does not have a trace." + }, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png", + "action": { + "icon": "retry", + "title": "Retry", + "path": "/gitlab-org/gitlab-runner/-/jobs/72469033/retry", + "method": "post", + "button_title": "Retry this job" + } + }, + "jobs": [ + { + "id": 72469033, + "name": "docs check links", + "started": "2018-06-05T11:31:33.240Z", + "archived": false, + "build_path": "/gitlab-org/gitlab-runner/-/jobs/72469033", + "retry_path": "/gitlab-org/gitlab-runner/-/jobs/72469033/retry", + "playable": false, + "scheduled": false, + "created_at": "2018-06-05T11:31:30.627Z", + "updated_at": "2018-06-05T11:31:54.363Z", + "status": { + "icon": "status_success", + "text": "passed", + "label": "passed", + "group": "success", + "tooltip": "passed", + "has_details": true, + "details_path": "/gitlab-org/gitlab-runner/-/jobs/72469033", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg", + "size": "svg-430", + "title": "This job does not have a trace." + }, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png", + "action": { + "icon": "retry", + "title": "Retry", + "path": "/gitlab-org/gitlab-runner/-/jobs/72469033/retry", + "method": "post", + "button_title": "Retry this job" + } + } + } + ] + } + ], + "status": { + "icon": "status_success", + "text": "passed", + "label": "passed", + "group": "success", + "tooltip": "passed", + "has_details": true, + "details_path": "/gitlab-org/gitlab-runner/pipelines/23211253#test", + "illustration": null, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png" + }, + "path": "/gitlab-org/gitlab-runner/pipelines/23211253#test", + "dropdown_path": "/gitlab-org/gitlab-runner/pipelines/23211253/stage.json?stage=test" + }, + { + "name": "cleanup", + "title": "cleanup: skipped", + "groups": [ + { + "name": "review-docs-cleanup", + "size": 1, + "status": { + "icon": "status_manual", + "text": "manual", + "label": "manual stop action", + "group": "manual", + "tooltip": "manual action", + "has_details": true, + "details_path": "/gitlab-org/gitlab-runner/-/jobs/72469034", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.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": "https://gitlab.com/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png", + "action": { + "icon": "stop", + "title": "Stop", + "path": "/gitlab-org/gitlab-runner/-/jobs/72469034/play", + "method": "post", + "button_title": "Stop this environment" + } + }, + "jobs": [ + { + "id": 72469034, + "name": "review-docs-cleanup", + "started": null, + "archived": false, + "build_path": "/gitlab-org/gitlab-runner/-/jobs/72469034", + "play_path": "/gitlab-org/gitlab-runner/-/jobs/72469034/play", + "playable": true, + "scheduled": false, + "created_at": "2018-06-05T11:31:30.760Z", + "updated_at": "2018-06-05T11:31:56.037Z", + "status": { + "icon": "status_manual", + "text": "manual", + "label": "manual stop action", + "group": "manual", + "tooltip": "manual action", + "has_details": true, + "details_path": "/gitlab-org/gitlab-runner/-/jobs/72469034", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.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": "https://gitlab.com/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png", + "action": { + "icon": "stop", + "title": "Stop", + "path": "/gitlab-org/gitlab-runner/-/jobs/72469034/play", + "method": "post", + "button_title": "Stop this environment" + } + } + } + ] + } + ], + "status": { + "icon": "status_skipped", + "text": "skipped", + "label": "skipped", + "group": "skipped", + "tooltip": "skipped", + "has_details": true, + "details_path": "/gitlab-org/gitlab-runner/pipelines/23211253#cleanup", + "illustration": null, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_skipped-0b9c5e543588945e8c4ca57786bbf2d0c56631959c9f853300392d0315be829b.png" + }, + "path": "/gitlab-org/gitlab-runner/pipelines/23211253#cleanup", + "dropdown_path": "/gitlab-org/gitlab-runner/pipelines/23211253/stage.json?stage=cleanup" + } + ], + "artifacts": [], + "manual_actions": [ + { + "name": "review-docs-cleanup", + "path": "/gitlab-org/gitlab-runner/-/jobs/72469034/play", + "playable": true, + "scheduled": false + }, + { + "name": "review-docs-deploy", + "path": "/gitlab-org/gitlab-runner/-/jobs/72469032/play", + "playable": true, + "scheduled": false + } + ], + "scheduled_actions": [] + }, + "ref": { + "name": "docs/add-development-guide-to-readme", + "path": "/gitlab-org/gitlab-runner/commits/docs/add-development-guide-to-readme", + "tag": false, + "branch": true + }, + "commit": { + "id": "8083eb0a920572214d0dccedd7981f05d535ad46", + "short_id": "8083eb0a", + "title": "Add link to development guide in readme", + "created_at": "2018-06-05T11:30:48.000Z", + "parent_ids": ["1d7cf79b5a1a2121b9474ac20d61c1b8f621289d"], + "message": "Add link to development guide in readme\n\nCloses https://gitlab.com/gitlab-org/gitlab-runner/issues/3122\n", + "author_name": "Achilleas Pipinellis", + "author_email": "axil@gitlab.com", + "authored_date": "2018-06-05T11:30:48.000Z", + "committer_name": "Achilleas Pipinellis", + "committer_email": "axil@gitlab.com", + "committed_date": "2018-06-05T11:30:48.000Z", + "author": { + "id": 3585, + "name": "Achilleas Pipinellis", + "username": "axil", + "state": "active", + "avatar_url": "https://assets.gitlab-static.net/uploads/-/system/user/avatar/3585/avatar.png", + "web_url": "https://gitlab.com/axil", + "status_tooltip_html": null, + "path": "/axil" + }, + "author_gravatar_url": "https://secure.gravatar.com/avatar/1d37af00eec153a8333a4ce18e9aea41?s=80\u0026d=identicon", + "commit_url": "https://gitlab.com/gitlab-org/gitlab-runner/commit/8083eb0a920572214d0dccedd7981f05d535ad46", + "commit_path": "/gitlab-org/gitlab-runner/commit/8083eb0a920572214d0dccedd7981f05d535ad46" + }, + "triggered_by": null, + "triggered": [ + { + "id": 34993051, + "user": { + "id": 376774, + "name": "Alessio Caiazza", + "username": "nolith", + "state": "active", + "avatar_url": "https://assets.gitlab-static.net/uploads/-/system/user/avatar/376774/avatar.png", + "web_url": "https://gitlab.com/nolith", + "status_tooltip_html": null, + "path": "/nolith" + }, + "active": false, + "coverage": null, + "source": "pipeline", + "path": "/gitlab-com/gitlab-docs/pipelines/34993051", + "details": { + "status": { + "icon": "status_failed", + "text": "failed", + "label": "failed", + "group": "failed", + "tooltip": "failed", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/pipelines/34993051", + "illustration": null, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_failed-41304d7f7e3828808b0c26771f0309e55296819a9beea3ea9fbf6689d9857c12.png" + } + }, + "project": { + "id": 1794617, + "name": "GitLab Docs", + "full_path": "/gitlab-com/gitlab-docs", + "full_name": "GitLab.com / GitLab Docs" + } + } + ] +} diff --git a/spec/javascripts/pipelines/stores/pipeline_with_triggered_by.json b/spec/javascripts/pipelines/stores/pipeline_with_triggered_by.json new file mode 100644 index 00000000000..7aeea6f3ebb --- /dev/null +++ b/spec/javascripts/pipelines/stores/pipeline_with_triggered_by.json @@ -0,0 +1,379 @@ +{ + "id": 23211253, + "user": { + "id": 3585, + "name": "Achilleas Pipinellis", + "username": "axil", + "state": "active", + "avatar_url": "https://assets.gitlab-static.net/uploads/-/system/user/avatar/3585/avatar.png", + "web_url": "https://gitlab.com/axil", + "status_tooltip_html": "\u003cspan class=\"user-status-emoji has-tooltip\" title=\"\" data-html=\"true\" data-placement=\"top\"\u003e\u003cgl-emoji title=\"trumpet\" data-name=\"trumpet\" data-unicode-version=\"6.0\"\u003e🎺\u003c/gl-emoji\u003e\u003c/span\u003e", + "path": "/axil" + }, + "active": false, + "coverage": null, + "source": "push", + "created_at": "2018-06-05T11:31:30.452Z", + "updated_at": "2018-10-31T16:35:31.305Z", + "path": "/gitlab-org/gitlab-runner/pipelines/23211253", + "flags": { + "latest": false, + "stuck": false, + "auto_devops": false, + "yaml_errors": false, + "retryable": false, + "cancelable": false, + "failure_reason": false + }, + "details": { + "status": { + "icon": "status_success", + "text": "passed", + "label": "passed", + "group": "success", + "tooltip": "passed", + "has_details": true, + "details_path": "/gitlab-org/gitlab-runner/pipelines/23211253", + "illustration": null, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png" + }, + "duration": 53, + "finished_at": "2018-10-31T16:35:31.299Z", + "stages": [ + { + "name": "prebuild", + "title": "prebuild: passed", + "groups": [ + { + "name": "review-docs-deploy", + "size": 1, + "status": { + "icon": "status_success", + "text": "passed", + "label": "manual play action", + "group": "success", + "tooltip": "passed", + "has_details": true, + "details_path": "/gitlab-org/gitlab-runner/-/jobs/72469032", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.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": "https://gitlab.com/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png", + "action": { + "icon": "play", + "title": "Play", + "path": "/gitlab-org/gitlab-runner/-/jobs/72469032/play", + "method": "post", + "button_title": "Trigger this manual action" + } + }, + "jobs": [ + { + "id": 72469032, + "name": "review-docs-deploy", + "started": "2018-10-31T16:34:58.778Z", + "archived": false, + "build_path": "/gitlab-org/gitlab-runner/-/jobs/72469032", + "retry_path": "/gitlab-org/gitlab-runner/-/jobs/72469032/retry", + "play_path": "/gitlab-org/gitlab-runner/-/jobs/72469032/play", + "playable": true, + "scheduled": false, + "created_at": "2018-06-05T11:31:30.495Z", + "updated_at": "2018-10-31T16:35:31.251Z", + "status": { + "icon": "status_success", + "text": "passed", + "label": "manual play action", + "group": "success", + "tooltip": "passed", + "has_details": true, + "details_path": "/gitlab-org/gitlab-runner/-/jobs/72469032", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.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": "https://gitlab.com/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png", + "action": { + "icon": "play", + "title": "Play", + "path": "/gitlab-org/gitlab-runner/-/jobs/72469032/play", + "method": "post", + "button_title": "Trigger this manual action" + } + } + } + ] + } + ], + "status": { + "icon": "status_success", + "text": "passed", + "label": "passed", + "group": "success", + "tooltip": "passed", + "has_details": true, + "details_path": "/gitlab-org/gitlab-runner/pipelines/23211253#prebuild", + "illustration": null, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png" + }, + "path": "/gitlab-org/gitlab-runner/pipelines/23211253#prebuild", + "dropdown_path": "/gitlab-org/gitlab-runner/pipelines/23211253/stage.json?stage=prebuild" + }, + { + "name": "test", + "title": "test: passed", + "groups": [ + { + "name": "docs check links", + "size": 1, + "status": { + "icon": "status_success", + "text": "passed", + "label": "passed", + "group": "success", + "tooltip": "passed", + "has_details": true, + "details_path": "/gitlab-org/gitlab-runner/-/jobs/72469033", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg", + "size": "svg-430", + "title": "This job does not have a trace." + }, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png", + "action": { + "icon": "retry", + "title": "Retry", + "path": "/gitlab-org/gitlab-runner/-/jobs/72469033/retry", + "method": "post", + "button_title": "Retry this job" + } + }, + "jobs": [ + { + "id": 72469033, + "name": "docs check links", + "started": "2018-06-05T11:31:33.240Z", + "archived": false, + "build_path": "/gitlab-org/gitlab-runner/-/jobs/72469033", + "retry_path": "/gitlab-org/gitlab-runner/-/jobs/72469033/retry", + "playable": false, + "scheduled": false, + "created_at": "2018-06-05T11:31:30.627Z", + "updated_at": "2018-06-05T11:31:54.363Z", + "status": { + "icon": "status_success", + "text": "passed", + "label": "passed", + "group": "success", + "tooltip": "passed", + "has_details": true, + "details_path": "/gitlab-org/gitlab-runner/-/jobs/72469033", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg", + "size": "svg-430", + "title": "This job does not have a trace." + }, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png", + "action": { + "icon": "retry", + "title": "Retry", + "path": "/gitlab-org/gitlab-runner/-/jobs/72469033/retry", + "method": "post", + "button_title": "Retry this job" + } + } + } + ] + } + ], + "status": { + "icon": "status_success", + "text": "passed", + "label": "passed", + "group": "success", + "tooltip": "passed", + "has_details": true, + "details_path": "/gitlab-org/gitlab-runner/pipelines/23211253#test", + "illustration": null, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png" + }, + "path": "/gitlab-org/gitlab-runner/pipelines/23211253#test", + "dropdown_path": "/gitlab-org/gitlab-runner/pipelines/23211253/stage.json?stage=test" + }, + { + "name": "cleanup", + "title": "cleanup: skipped", + "groups": [ + { + "name": "review-docs-cleanup", + "size": 1, + "status": { + "icon": "status_manual", + "text": "manual", + "label": "manual stop action", + "group": "manual", + "tooltip": "manual action", + "has_details": true, + "details_path": "/gitlab-org/gitlab-runner/-/jobs/72469034", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.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": "https://gitlab.com/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png", + "action": { + "icon": "stop", + "title": "Stop", + "path": "/gitlab-org/gitlab-runner/-/jobs/72469034/play", + "method": "post", + "button_title": "Stop this environment" + } + }, + "jobs": [ + { + "id": 72469034, + "name": "review-docs-cleanup", + "started": null, + "archived": false, + "build_path": "/gitlab-org/gitlab-runner/-/jobs/72469034", + "play_path": "/gitlab-org/gitlab-runner/-/jobs/72469034/play", + "playable": true, + "scheduled": false, + "created_at": "2018-06-05T11:31:30.760Z", + "updated_at": "2018-06-05T11:31:56.037Z", + "status": { + "icon": "status_manual", + "text": "manual", + "label": "manual stop action", + "group": "manual", + "tooltip": "manual action", + "has_details": true, + "details_path": "/gitlab-org/gitlab-runner/-/jobs/72469034", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.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": "https://gitlab.com/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png", + "action": { + "icon": "stop", + "title": "Stop", + "path": "/gitlab-org/gitlab-runner/-/jobs/72469034/play", + "method": "post", + "button_title": "Stop this environment" + } + } + } + ] + } + ], + "status": { + "icon": "status_skipped", + "text": "skipped", + "label": "skipped", + "group": "skipped", + "tooltip": "skipped", + "has_details": true, + "details_path": "/gitlab-org/gitlab-runner/pipelines/23211253#cleanup", + "illustration": null, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_skipped-0b9c5e543588945e8c4ca57786bbf2d0c56631959c9f853300392d0315be829b.png" + }, + "path": "/gitlab-org/gitlab-runner/pipelines/23211253#cleanup", + "dropdown_path": "/gitlab-org/gitlab-runner/pipelines/23211253/stage.json?stage=cleanup" + } + ], + "artifacts": [], + "manual_actions": [ + { + "name": "review-docs-cleanup", + "path": "/gitlab-org/gitlab-runner/-/jobs/72469034/play", + "playable": true, + "scheduled": false + }, + { + "name": "review-docs-deploy", + "path": "/gitlab-org/gitlab-runner/-/jobs/72469032/play", + "playable": true, + "scheduled": false + } + ], + "scheduled_actions": [] + }, + "ref": { + "name": "docs/add-development-guide-to-readme", + "path": "/gitlab-org/gitlab-runner/commits/docs/add-development-guide-to-readme", + "tag": false, + "branch": true + }, + "commit": { + "id": "8083eb0a920572214d0dccedd7981f05d535ad46", + "short_id": "8083eb0a", + "title": "Add link to development guide in readme", + "created_at": "2018-06-05T11:30:48.000Z", + "parent_ids": ["1d7cf79b5a1a2121b9474ac20d61c1b8f621289d"], + "message": "Add link to development guide in readme\n\nCloses https://gitlab.com/gitlab-org/gitlab-runner/issues/3122\n", + "author_name": "Achilleas Pipinellis", + "author_email": "axil@gitlab.com", + "authored_date": "2018-06-05T11:30:48.000Z", + "committer_name": "Achilleas Pipinellis", + "committer_email": "axil@gitlab.com", + "committed_date": "2018-06-05T11:30:48.000Z", + "author": { + "id": 3585, + "name": "Achilleas Pipinellis", + "username": "axil", + "state": "active", + "avatar_url": "https://assets.gitlab-static.net/uploads/-/system/user/avatar/3585/avatar.png", + "web_url": "https://gitlab.com/axil", + "status_tooltip_html": null, + "path": "/axil" + }, + "author_gravatar_url": "https://secure.gravatar.com/avatar/1d37af00eec153a8333a4ce18e9aea41?s=80\u0026d=identicon", + "commit_url": "https://gitlab.com/gitlab-org/gitlab-runner/commit/8083eb0a920572214d0dccedd7981f05d535ad46", + "commit_path": "/gitlab-org/gitlab-runner/commit/8083eb0a920572214d0dccedd7981f05d535ad46" + }, + "triggered_by": { + "id": 34993051, + "user": { + "id": 376774, + "name": "Alessio Caiazza", + "username": "nolith", + "state": "active", + "avatar_url": "https://assets.gitlab-static.net/uploads/-/system/user/avatar/376774/avatar.png", + "web_url": "https://gitlab.com/nolith", + "status_tooltip_html": null, + "path": "/nolith" + }, + "active": false, + "coverage": null, + "source": "pipeline", + "path": "/gitlab-com/gitlab-docs/pipelines/34993051", + "details": { + "status": { + "icon": "status_failed", + "text": "failed", + "label": "failed", + "group": "failed", + "tooltip": "failed", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/pipelines/34993051", + "illustration": null, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_failed-41304d7f7e3828808b0c26771f0309e55296819a9beea3ea9fbf6689d9857c12.png" + } + }, + "project": { + "id": 1794617, + "name": "GitLab Docs", + "full_path": "/gitlab-com/gitlab-docs", + "full_name": "GitLab.com / GitLab Docs" + } + }, + "triggered": [] +} diff --git a/spec/javascripts/pipelines/stores/pipeline_with_triggered_triggered_by.json b/spec/javascripts/pipelines/stores/pipeline_with_triggered_triggered_by.json new file mode 100644 index 00000000000..2402cbae6c8 --- /dev/null +++ b/spec/javascripts/pipelines/stores/pipeline_with_triggered_triggered_by.json @@ -0,0 +1,452 @@ +{ + "id": 23211253, + "user": { + "id": 3585, + "name": "Achilleas Pipinellis", + "username": "axil", + "state": "active", + "avatar_url": "https://assets.gitlab-static.net/uploads/-/system/user/avatar/3585/avatar.png", + "web_url": "https://gitlab.com/axil", + "status_tooltip_html": "\u003cspan class=\"user-status-emoji has-tooltip\" title=\"\" data-html=\"true\" data-placement=\"top\"\u003e\u003cgl-emoji title=\"trumpet\" data-name=\"trumpet\" data-unicode-version=\"6.0\"\u003e🎺\u003c/gl-emoji\u003e\u003c/span\u003e", + "path": "/axil" + }, + "active": false, + "coverage": null, + "source": "push", + "created_at": "2018-06-05T11:31:30.452Z", + "updated_at": "2018-10-31T16:35:31.305Z", + "path": "/gitlab-org/gitlab-runner/pipelines/23211253", + "flags": { + "latest": false, + "stuck": false, + "auto_devops": false, + "yaml_errors": false, + "retryable": false, + "cancelable": false, + "failure_reason": false + }, + "details": { + "status": { + "icon": "status_success", + "text": "passed", + "label": "passed", + "group": "success", + "tooltip": "passed", + "has_details": true, + "details_path": "/gitlab-org/gitlab-runner/pipelines/23211253", + "illustration": null, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png" + }, + "duration": 53, + "finished_at": "2018-10-31T16:35:31.299Z", + "stages": [ + { + "name": "prebuild", + "title": "prebuild: passed", + "groups": [ + { + "name": "review-docs-deploy", + "size": 1, + "status": { + "icon": "status_success", + "text": "passed", + "label": "manual play action", + "group": "success", + "tooltip": "passed", + "has_details": true, + "details_path": "/gitlab-org/gitlab-runner/-/jobs/72469032", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.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": "https://gitlab.com/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png", + "action": { + "icon": "play", + "title": "Play", + "path": "/gitlab-org/gitlab-runner/-/jobs/72469032/play", + "method": "post", + "button_title": "Trigger this manual action" + } + }, + "jobs": [ + { + "id": 72469032, + "name": "review-docs-deploy", + "started": "2018-10-31T16:34:58.778Z", + "archived": false, + "build_path": "/gitlab-org/gitlab-runner/-/jobs/72469032", + "retry_path": "/gitlab-org/gitlab-runner/-/jobs/72469032/retry", + "play_path": "/gitlab-org/gitlab-runner/-/jobs/72469032/play", + "playable": true, + "scheduled": false, + "created_at": "2018-06-05T11:31:30.495Z", + "updated_at": "2018-10-31T16:35:31.251Z", + "status": { + "icon": "status_success", + "text": "passed", + "label": "manual play action", + "group": "success", + "tooltip": "passed", + "has_details": true, + "details_path": "/gitlab-org/gitlab-runner/-/jobs/72469032", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.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": "https://gitlab.com/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png", + "action": { + "icon": "play", + "title": "Play", + "path": "/gitlab-org/gitlab-runner/-/jobs/72469032/play", + "method": "post", + "button_title": "Trigger this manual action" + } + } + } + ] + } + ], + "status": { + "icon": "status_success", + "text": "passed", + "label": "passed", + "group": "success", + "tooltip": "passed", + "has_details": true, + "details_path": "/gitlab-org/gitlab-runner/pipelines/23211253#prebuild", + "illustration": null, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png" + }, + "path": "/gitlab-org/gitlab-runner/pipelines/23211253#prebuild", + "dropdown_path": "/gitlab-org/gitlab-runner/pipelines/23211253/stage.json?stage=prebuild" + }, + { + "name": "test", + "title": "test: passed", + "groups": [ + { + "name": "docs check links", + "size": 1, + "status": { + "icon": "status_success", + "text": "passed", + "label": "passed", + "group": "success", + "tooltip": "passed", + "has_details": true, + "details_path": "/gitlab-org/gitlab-runner/-/jobs/72469033", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg", + "size": "svg-430", + "title": "This job does not have a trace." + }, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png", + "action": { + "icon": "retry", + "title": "Retry", + "path": "/gitlab-org/gitlab-runner/-/jobs/72469033/retry", + "method": "post", + "button_title": "Retry this job" + } + }, + "jobs": [ + { + "id": 72469033, + "name": "docs check links", + "started": "2018-06-05T11:31:33.240Z", + "archived": false, + "build_path": "/gitlab-org/gitlab-runner/-/jobs/72469033", + "retry_path": "/gitlab-org/gitlab-runner/-/jobs/72469033/retry", + "playable": false, + "scheduled": false, + "created_at": "2018-06-05T11:31:30.627Z", + "updated_at": "2018-06-05T11:31:54.363Z", + "status": { + "icon": "status_success", + "text": "passed", + "label": "passed", + "group": "success", + "tooltip": "passed", + "has_details": true, + "details_path": "/gitlab-org/gitlab-runner/-/jobs/72469033", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg", + "size": "svg-430", + "title": "This job does not have a trace." + }, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png", + "action": { + "icon": "retry", + "title": "Retry", + "path": "/gitlab-org/gitlab-runner/-/jobs/72469033/retry", + "method": "post", + "button_title": "Retry this job" + } + } + } + ] + } + ], + "status": { + "icon": "status_success", + "text": "passed", + "label": "passed", + "group": "success", + "tooltip": "passed", + "has_details": true, + "details_path": "/gitlab-org/gitlab-runner/pipelines/23211253#test", + "illustration": null, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png" + }, + "path": "/gitlab-org/gitlab-runner/pipelines/23211253#test", + "dropdown_path": "/gitlab-org/gitlab-runner/pipelines/23211253/stage.json?stage=test" + }, + { + "name": "cleanup", + "title": "cleanup: skipped", + "groups": [ + { + "name": "review-docs-cleanup", + "size": 1, + "status": { + "icon": "status_manual", + "text": "manual", + "label": "manual stop action", + "group": "manual", + "tooltip": "manual action", + "has_details": true, + "details_path": "/gitlab-org/gitlab-runner/-/jobs/72469034", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.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": "https://gitlab.com/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png", + "action": { + "icon": "stop", + "title": "Stop", + "path": "/gitlab-org/gitlab-runner/-/jobs/72469034/play", + "method": "post", + "button_title": "Stop this environment" + } + }, + "jobs": [ + { + "id": 72469034, + "name": "review-docs-cleanup", + "started": null, + "archived": false, + "build_path": "/gitlab-org/gitlab-runner/-/jobs/72469034", + "play_path": "/gitlab-org/gitlab-runner/-/jobs/72469034/play", + "playable": true, + "scheduled": false, + "created_at": "2018-06-05T11:31:30.760Z", + "updated_at": "2018-06-05T11:31:56.037Z", + "status": { + "icon": "status_manual", + "text": "manual", + "label": "manual stop action", + "group": "manual", + "tooltip": "manual action", + "has_details": true, + "details_path": "/gitlab-org/gitlab-runner/-/jobs/72469034", + "illustration": { + "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.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": "https://gitlab.com/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png", + "action": { + "icon": "stop", + "title": "Stop", + "path": "/gitlab-org/gitlab-runner/-/jobs/72469034/play", + "method": "post", + "button_title": "Stop this environment" + } + } + } + ] + } + ], + "status": { + "icon": "status_skipped", + "text": "skipped", + "label": "skipped", + "group": "skipped", + "tooltip": "skipped", + "has_details": true, + "details_path": "/gitlab-org/gitlab-runner/pipelines/23211253#cleanup", + "illustration": null, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_skipped-0b9c5e543588945e8c4ca57786bbf2d0c56631959c9f853300392d0315be829b.png" + }, + "path": "/gitlab-org/gitlab-runner/pipelines/23211253#cleanup", + "dropdown_path": "/gitlab-org/gitlab-runner/pipelines/23211253/stage.json?stage=cleanup" + } + ], + "artifacts": [], + "manual_actions": [ + { + "name": "review-docs-cleanup", + "path": "/gitlab-org/gitlab-runner/-/jobs/72469034/play", + "playable": true, + "scheduled": false + }, + { + "name": "review-docs-deploy", + "path": "/gitlab-org/gitlab-runner/-/jobs/72469032/play", + "playable": true, + "scheduled": false + } + ], + "scheduled_actions": [] + }, + "ref": { + "name": "docs/add-development-guide-to-readme", + "path": "/gitlab-org/gitlab-runner/commits/docs/add-development-guide-to-readme", + "tag": false, + "branch": true + }, + "commit": { + "id": "8083eb0a920572214d0dccedd7981f05d535ad46", + "short_id": "8083eb0a", + "title": "Add link to development guide in readme", + "created_at": "2018-06-05T11:30:48.000Z", + "parent_ids": ["1d7cf79b5a1a2121b9474ac20d61c1b8f621289d"], + "message": "Add link to development guide in readme\n\nCloses https://gitlab.com/gitlab-org/gitlab-runner/issues/3122\n", + "author_name": "Achilleas Pipinellis", + "author_email": "axil@gitlab.com", + "authored_date": "2018-06-05T11:30:48.000Z", + "committer_name": "Achilleas Pipinellis", + "committer_email": "axil@gitlab.com", + "committed_date": "2018-06-05T11:30:48.000Z", + "author": { + "id": 3585, + "name": "Achilleas Pipinellis", + "username": "axil", + "state": "active", + "avatar_url": "https://assets.gitlab-static.net/uploads/-/system/user/avatar/3585/avatar.png", + "web_url": "https://gitlab.com/axil", + "status_tooltip_html": null, + "path": "/axil" + }, + "author_gravatar_url": "https://secure.gravatar.com/avatar/1d37af00eec153a8333a4ce18e9aea41?s=80\u0026d=identicon", + "commit_url": "https://gitlab.com/gitlab-org/gitlab-runner/commit/8083eb0a920572214d0dccedd7981f05d535ad46", + "commit_path": "/gitlab-org/gitlab-runner/commit/8083eb0a920572214d0dccedd7981f05d535ad46" + }, + "triggered_by": { + "id": 34993051, + "user": { + "id": 376774, + "name": "Alessio Caiazza", + "username": "nolith", + "state": "active", + "avatar_url": "https://assets.gitlab-static.net/uploads/-/system/user/avatar/376774/avatar.png", + "web_url": "https://gitlab.com/nolith", + "status_tooltip_html": null, + "path": "/nolith" + }, + "active": false, + "coverage": null, + "source": "pipeline", + "path": "/gitlab-com/gitlab-docs/pipelines/34993051", + "details": { + "status": { + "icon": "status_failed", + "text": "failed", + "label": "failed", + "group": "failed", + "tooltip": "failed", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/pipelines/34993051", + "illustration": null, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_failed-41304d7f7e3828808b0c26771f0309e55296819a9beea3ea9fbf6689d9857c12.png" + } + }, + "project": { + "id": 1794617, + "name": "GitLab Docs", + "full_path": "/gitlab-com/gitlab-docs", + "full_name": "GitLab.com / GitLab Docs" + } + }, + "triggered": [ + { + "id": 349233051, + "user": { + "id": 376774, + "name": "Alessio Caiazza", + "username": "nolith", + "state": "active", + "avatar_url": "https://assets.gitlab-static.net/uploads/-/system/user/avatar/376774/avatar.png", + "web_url": "https://gitlab.com/nolith", + "status_tooltip_html": null, + "path": "/nolith" + }, + "active": false, + "coverage": null, + "source": "pipeline", + "path": "/gitlab-com/gitlab-docs/pipelines/34993051", + "details": { + "status": { + "icon": "status_failed", + "text": "failed", + "label": "failed", + "group": "failed", + "tooltip": "failed", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/pipelines/349233051", + "illustration": null, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_failed-41304d7f7e3828808b0c26771f0309e55296819a9beea3ea9fbf6689d9857c12.png" + } + }, + "project": { + "id": 1794617, + "name": "GitLab Docs", + "full_path": "/gitlab-com/gitlab-docs", + "full_name": "GitLab.com / GitLab Docs" + } + }, + { + "id": 34993023, + "user": { + "id": 376774, + "name": "Alessio Caiazza", + "username": "nolith", + "state": "active", + "avatar_url": "https://assets.gitlab-static.net/uploads/-/system/user/avatar/376774/avatar.png", + "web_url": "https://gitlab.com/nolith", + "status_tooltip_html": null, + "path": "/nolith" + }, + "active": false, + "coverage": null, + "source": "pipeline", + "path": "/gitlab-com/gitlab-docs/pipelines/34993023", + "details": { + "status": { + "icon": "status_failed", + "text": "failed", + "label": "failed", + "group": "failed", + "tooltip": "failed", + "has_details": true, + "details_path": "/gitlab-com/gitlab-docs/pipelines/34993051", + "illustration": null, + "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_failed-41304d7f7e3828808b0c26771f0309e55296819a9beea3ea9fbf6689d9857c12.png" + } + }, + "project": { + "id": 1794617, + "name": "GitLab Docs", + "full_path": "/gitlab-com/gitlab-docs", + "full_name": "GitLab.com / GitLab Docs" + } + } + ] +} diff --git a/spec/javascripts/registry/components/app_spec.js b/spec/javascripts/registry/components/app_spec.js deleted file mode 100644 index 5ea3f85a247..00000000000 --- a/spec/javascripts/registry/components/app_spec.js +++ /dev/null @@ -1,129 +0,0 @@ -import MockAdapter from 'axios-mock-adapter'; -import axios from '~/lib/utils/axios_utils'; -import Vue from 'vue'; -import registry from '~/registry/components/app.vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; -import { TEST_HOST } from 'spec/test_constants'; -import { reposServerResponse } from '../mock_data'; - -describe('Registry List', () => { - const Component = Vue.extend(registry); - const props = { - endpoint: `${TEST_HOST}/foo`, - helpPagePath: 'foo', - noContainersImage: 'foo', - containersErrorImage: 'foo', - repositoryUrl: 'foo', - }; - let vm; - let mock; - - beforeEach(() => { - mock = new MockAdapter(axios); - }); - - afterEach(() => { - mock.restore(); - vm.$destroy(); - }); - - describe('with data', () => { - beforeEach(() => { - mock.onGet(`${TEST_HOST}/foo`).replyOnce(200, reposServerResponse); - - vm = mountComponent(Component, { ...props }); - }); - - it('should render a list of repos', done => { - setTimeout(() => { - expect(vm.$store.state.repos.length).toEqual(reposServerResponse.length); - - Vue.nextTick(() => { - expect(vm.$el.querySelectorAll('.container-image').length).toEqual( - reposServerResponse.length, - ); - done(); - }); - }, 0); - }); - - describe('delete repository', () => { - it('should be possible to delete a repo', done => { - setTimeout(() => { - Vue.nextTick(() => { - expect(vm.$el.querySelector('.container-image-head .js-remove-repo')).toBeDefined(); - done(); - }); - }, 0); - }); - }); - - describe('toggle repository', () => { - it('should open the container', done => { - setTimeout(() => { - Vue.nextTick(() => { - vm.$el.querySelector('.js-toggle-repo').click(); - Vue.nextTick(() => { - expect( - vm.$el.querySelector('.js-toggle-repo use').getAttribute('xlink:href'), - ).toContain('angle-up'); - done(); - }); - }); - }, 0); - }); - }); - }); - - describe('without data', () => { - beforeEach(() => { - mock.onGet(`${TEST_HOST}/foo`).replyOnce(200, []); - - vm = mountComponent(Component, { ...props }); - }); - - it('should render empty message', done => { - setTimeout(() => { - expect(vm.$el.querySelector('.js-no-container-images-text').textContent).toEqual( - 'With the Container Registry, every project can have its own space to store its Docker images. More Information', - ); - done(); - }, 0); - }); - }); - - describe('while loading data', () => { - beforeEach(() => { - mock.onGet(`${TEST_HOST}/foo`).replyOnce(200, []); - - vm = mountComponent(Component, { ...props }); - }); - - it('should render a loading spinner', done => { - Vue.nextTick(() => { - expect(vm.$el.querySelector('.gl-spinner')).not.toBe(null); - done(); - }); - }); - }); - - describe('invalid characters in path', () => { - beforeEach(() => { - mock.onGet(`${TEST_HOST}/foo`).replyOnce(200, []); - - vm = mountComponent(Component, { - ...props, - characterError: true, - }); - }); - - it('should render invalid characters error message', done => { - setTimeout(() => { - expect(vm.$el.querySelector('p')).not.toContain( - 'We are having trouble connecting to Docker, which could be due to an issue with your project name or path. More information', - ); - done(); - }); - }); - }); -}); diff --git a/spec/javascripts/registry/components/collapsible_container_spec.js b/spec/javascripts/registry/components/collapsible_container_spec.js deleted file mode 100644 index 2a5d8dd11da..00000000000 --- a/spec/javascripts/registry/components/collapsible_container_spec.js +++ /dev/null @@ -1,87 +0,0 @@ -import MockAdapter from 'axios-mock-adapter'; -import axios from '~/lib/utils/axios_utils'; -import Vue from 'vue'; -import collapsibleComponent from '~/registry/components/collapsible_container.vue'; -import store from '~/registry/stores'; -import * as types from '~/registry/stores/mutation_types'; - -import { repoPropsData, registryServerResponse, reposServerResponse } from '../mock_data'; - -describe('collapsible registry container', () => { - let vm; - let mock; - const Component = Vue.extend(collapsibleComponent); - - const findDeleteBtn = () => vm.$el.querySelector('.js-remove-repo'); - - beforeEach(() => { - mock = new MockAdapter(axios); - - mock.onGet(repoPropsData.tagsPath).replyOnce(200, registryServerResponse, {}); - - store.commit(types.SET_REPOS_LIST, reposServerResponse); - - vm = new Component({ - store, - propsData: { - repo: repoPropsData, - }, - }).$mount(); - }); - - afterEach(() => { - mock.restore(); - vm.$destroy(); - }); - - describe('toggle', () => { - it('should be closed by default', () => { - expect(vm.$el.querySelector('.container-image-tags')).toBe(null); - expect(vm.iconName).toEqual('angle-right'); - }); - - it('should be open when user clicks on closed repo', done => { - vm.$el.querySelector('.js-toggle-repo').click(); - - Vue.nextTick(() => { - expect(vm.$el.querySelector('.container-image-tags')).not.toBeNull(); - expect(vm.iconName).toEqual('angle-up'); - - done(); - }); - }); - - it('should be closed when the user clicks on an opened repo', done => { - vm.$el.querySelector('.js-toggle-repo').click(); - - Vue.nextTick(() => { - vm.$el.querySelector('.js-toggle-repo').click(); - setTimeout(() => { - Vue.nextTick(() => { - expect(vm.$el.querySelector('.container-image-tags')).toBe(null); - expect(vm.iconName).toEqual('angle-right'); - done(); - }); - }); - }); - }); - }); - - describe('delete repo', () => { - it('should be possible to delete a repo', () => { - expect(findDeleteBtn()).not.toBeNull(); - }); - - it('should call deleteItem when confirming deletion', done => { - findDeleteBtn().click(); - spyOn(vm, 'deleteItem').and.returnValue(Promise.resolve()); - - Vue.nextTick(() => { - document.querySelector(`#${vm.modalId} .btn-danger`).click(); - - expect(vm.deleteItem).toHaveBeenCalledWith(vm.repo); - done(); - }); - }); - }); -}); diff --git a/spec/javascripts/registry/components/table_registry_spec.js b/spec/javascripts/registry/components/table_registry_spec.js deleted file mode 100644 index 9c7439206ef..00000000000 --- a/spec/javascripts/registry/components/table_registry_spec.js +++ /dev/null @@ -1,189 +0,0 @@ -import Vue from 'vue'; -import tableRegistry from '~/registry/components/table_registry.vue'; -import store from '~/registry/stores'; -import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; -import { repoPropsData } from '../mock_data'; - -const [firstImage, secondImage] = repoPropsData.list; - -describe('table registry', () => { - let vm; - const Component = Vue.extend(tableRegistry); - const bulkDeletePath = 'path'; - - const findDeleteBtn = () => vm.$el.querySelector('.js-delete-registry'); - const findDeleteBtnRow = () => vm.$el.querySelector('.js-delete-registry-row'); - const findSelectAllCheckbox = () => vm.$el.querySelector('.js-select-all-checkbox > input'); - const findAllRowCheckboxes = () => - Array.from(vm.$el.querySelectorAll('.js-select-checkbox input')); - const confirmationModal = (child = '') => document.querySelector(`#${vm.modalId} ${child}`); - - const createComponent = () => { - vm = mountComponentWithStore(Component, { - store, - props: { - repo: repoPropsData, - }, - }); - }; - - const selectAllCheckboxes = () => vm.selectAll(); - const deselectAllCheckboxes = () => vm.deselectAll(); - - beforeEach(() => { - createComponent(); - }); - - afterEach(() => { - vm.$destroy(); - }); - - describe('rendering', () => { - it('should render a table with the registry list', () => { - expect(vm.$el.querySelectorAll('table tbody tr').length).toEqual(repoPropsData.list.length); - }); - - it('should render registry tag', () => { - const textRendered = vm.$el - .querySelector('.table tbody tr') - .textContent.trim() - // replace additional whitespace characters (e.g. new lines) with a single empty space - .replace(/\s\s+/g, ' '); - - expect(textRendered).toContain(repoPropsData.list[0].tag); - expect(textRendered).toContain(repoPropsData.list[0].shortRevision); - expect(textRendered).toContain(repoPropsData.list[0].layers); - expect(textRendered).toContain(repoPropsData.list[0].size); - }); - }); - - describe('multi select', () => { - it('should support multiselect and selecting a row should enable delete button', done => { - findSelectAllCheckbox().click(); - selectAllCheckboxes(); - - expect(findSelectAllCheckbox().checked).toBe(true); - - Vue.nextTick(() => { - expect(findDeleteBtn().disabled).toBe(false); - done(); - }); - }); - - it('selecting all checkbox should select all rows and enable delete button', done => { - selectAllCheckboxes(); - - Vue.nextTick(() => { - const checkedValues = findAllRowCheckboxes().filter(x => x.checked); - - expect(checkedValues.length).toBe(repoPropsData.list.length); - done(); - }); - }); - - it('deselecting select all checkbox should deselect all rows and disable delete button', done => { - selectAllCheckboxes(); - deselectAllCheckboxes(); - - Vue.nextTick(() => { - const checkedValues = findAllRowCheckboxes().filter(x => x.checked); - - expect(checkedValues.length).toBe(0); - done(); - }); - }); - - it('should delete multiple items when multiple items are selected', done => { - selectAllCheckboxes(); - - Vue.nextTick(() => { - expect(vm.itemsToBeDeleted).toEqual([0, 1]); - expect(findDeleteBtn().disabled).toBe(false); - - findDeleteBtn().click(); - spyOn(vm, 'multiDeleteItems').and.returnValue(Promise.resolve()); - - Vue.nextTick(() => { - const modal = confirmationModal(); - confirmationModal('.btn-danger').click(); - - expect(modal).toExist(); - - Vue.nextTick(() => { - expect(vm.itemsToBeDeleted).toEqual([]); - expect(vm.multiDeleteItems).toHaveBeenCalledWith({ - path: bulkDeletePath, - items: [firstImage.tag, secondImage.tag], - }); - done(); - }); - }); - }); - }); - }); - - describe('delete registry', () => { - beforeEach(() => { - vm.itemsToBeDeleted = [0]; - }); - - it('should be possible to delete a registry', done => { - Vue.nextTick(() => { - expect(vm.itemsToBeDeleted).toEqual([0]); - expect(findDeleteBtn()).toBeDefined(); - expect(findDeleteBtn().disabled).toBe(false); - expect(findDeleteBtnRow()).toBeDefined(); - done(); - }); - }); - - it('should call deleteItems and reset itemsToBeDeleted when confirming deletion', done => { - Vue.nextTick(() => { - expect(vm.itemsToBeDeleted).toEqual([0]); - expect(findDeleteBtn().disabled).toBe(false); - findDeleteBtn().click(); - spyOn(vm, 'multiDeleteItems').and.returnValue(Promise.resolve()); - - Vue.nextTick(() => { - confirmationModal('.btn-danger').click(); - - expect(vm.itemsToBeDeleted).toEqual([]); - expect(vm.multiDeleteItems).toHaveBeenCalledWith({ - path: bulkDeletePath, - items: [firstImage.tag], - }); - done(); - }); - }); - }); - }); - - describe('pagination', () => { - it('should be possible to change the page', () => { - expect(vm.$el.querySelector('.gl-pagination')).toBeDefined(); - }); - }); - - describe('modal content', () => { - it('should show the singular title and image name when deleting a single image', done => { - findDeleteBtnRow().click(); - - Vue.nextTick(() => { - expect(vm.modalTitle).toBe('Remove image'); - expect(vm.modalDescription).toContain(firstImage.tag); - done(); - }); - }); - - it('should show the plural title and image count when deleting more than one image', done => { - selectAllCheckboxes(); - vm.setModalDescription(); - - Vue.nextTick(() => { - expect(vm.modalTitle).toBe('Remove images'); - expect(vm.modalDescription).toContain('<b>2</b> images'); - done(); - }); - }); - }); -}); diff --git a/spec/javascripts/registry/mock_data.js b/spec/javascripts/registry/mock_data.js deleted file mode 100644 index 130ab298e89..00000000000 --- a/spec/javascripts/registry/mock_data.js +++ /dev/null @@ -1,134 +0,0 @@ -export const defaultState = { - isLoading: false, - endpoint: '', - repos: [], -}; - -export const reposServerResponse = [ - { - destroy_path: 'path', - id: '123', - location: 'location', - path: 'foo', - tags_path: 'tags_path', - }, - { - destroy_path: 'path_', - id: '456', - location: 'location_', - path: 'bar', - tags_path: 'tags_path_', - }, -]; - -export const registryServerResponse = [ - { - name: 'centos7', - short_revision: 'b118ab5b0', - revision: 'b118ab5b0e90b7cb5127db31d5321ac14961d097516a8e0e72084b6cdc783b43', - total_size: 679, - layers: 19, - location: 'location', - created_at: 1505828744434, - destroy_path: 'path_', - }, - { - name: 'centos6', - short_revision: 'b118ab5b0', - revision: 'b118ab5b0e90b7cb5127db31d5321ac14961d097516a8e0e72084b6cdc783b43', - total_size: 679, - layers: 19, - location: 'location', - created_at: 1505828744434, - }, -]; - -export const parsedReposServerResponse = [ - { - canDelete: true, - destroyPath: reposServerResponse[0].destroy_path, - id: reposServerResponse[0].id, - isLoading: false, - list: [], - location: reposServerResponse[0].location, - name: reposServerResponse[0].path, - tagsPath: reposServerResponse[0].tags_path, - }, - { - canDelete: true, - destroyPath: reposServerResponse[1].destroy_path, - id: reposServerResponse[1].id, - isLoading: false, - list: [], - location: reposServerResponse[1].location, - name: reposServerResponse[1].path, - tagsPath: reposServerResponse[1].tags_path, - }, -]; - -export const parsedRegistryServerResponse = [ - { - tag: registryServerResponse[0].name, - revision: registryServerResponse[0].revision, - shortRevision: registryServerResponse[0].short_revision, - size: registryServerResponse[0].total_size, - layers: registryServerResponse[0].layers, - location: registryServerResponse[0].location, - createdAt: registryServerResponse[0].created_at, - destroyPath: registryServerResponse[0].destroy_path, - canDelete: true, - }, - { - tag: registryServerResponse[1].name, - revision: registryServerResponse[1].revision, - shortRevision: registryServerResponse[1].short_revision, - size: registryServerResponse[1].total_size, - layers: registryServerResponse[1].layers, - location: registryServerResponse[1].location, - createdAt: registryServerResponse[1].created_at, - destroyPath: registryServerResponse[1].destroy_path, - canDelete: false, - }, -]; - -export const repoPropsData = { - canDelete: true, - destroyPath: 'path', - id: '123', - isLoading: false, - list: [ - { - tag: 'centos6', - revision: 'b118ab5b0e90b7cb5127db31d5321ac14961d097516a8e0e72084b6cdc783b43', - shortRevision: 'b118ab5b0', - size: 19, - layers: 10, - location: 'location', - createdAt: 1505828744434, - destroyPath: 'path', - canDelete: true, - }, - { - tag: 'test-image', - revision: 'b969de599faea2b3d9b6605a8b0897261c571acaa36db1bdc7349b5775b4e0b4', - shortRevision: 'b969de599', - size: 19, - layers: 10, - location: 'location-2', - createdAt: 1505828744434, - destroyPath: 'path-2', - canDelete: true, - }, - ], - location: 'location', - name: 'foo', - tagsPath: 'path', - pagination: { - perPage: 5, - page: 1, - total: 13, - totalPages: 1, - nextPage: null, - previousPage: null, - }, -}; diff --git a/spec/javascripts/registry/stores/actions_spec.js b/spec/javascripts/registry/stores/actions_spec.js deleted file mode 100644 index 0613ec8e0f1..00000000000 --- a/spec/javascripts/registry/stores/actions_spec.js +++ /dev/null @@ -1,132 +0,0 @@ -import MockAdapter from 'axios-mock-adapter'; -import axios from '~/lib/utils/axios_utils'; -import * as actions from '~/registry/stores/actions'; -import * as types from '~/registry/stores/mutation_types'; -import state from '~/registry/stores/state'; -import { TEST_HOST } from 'spec/test_constants'; -import testAction from '../../helpers/vuex_action_helper'; -import { - reposServerResponse, - registryServerResponse, - parsedReposServerResponse, -} from '../mock_data'; - -describe('Actions Registry Store', () => { - let mockedState; - let mock; - - beforeEach(() => { - mockedState = state(); - mockedState.endpoint = `${TEST_HOST}/endpoint.json`; - mock = new MockAdapter(axios); - }); - - afterEach(() => { - mock.restore(); - }); - - describe('server requests', () => { - describe('fetchRepos', () => { - beforeEach(() => { - mock.onGet(`${TEST_HOST}/endpoint.json`).replyOnce(200, reposServerResponse, {}); - }); - - it('should set receveived repos', done => { - testAction( - actions.fetchRepos, - null, - mockedState, - [ - { type: types.TOGGLE_MAIN_LOADING }, - { type: types.TOGGLE_MAIN_LOADING }, - { type: types.SET_REPOS_LIST, payload: reposServerResponse }, - ], - [], - done, - ); - }); - }); - - describe('fetchList', () => { - let repo; - beforeEach(() => { - mockedState.repos = parsedReposServerResponse; - [, repo] = mockedState.repos; - - mock.onGet(repo.tagsPath).replyOnce(200, registryServerResponse, {}); - }); - - it('should set received list', done => { - testAction( - actions.fetchList, - { repo }, - mockedState, - [ - { type: types.TOGGLE_REGISTRY_LIST_LOADING, payload: repo }, - { type: types.TOGGLE_REGISTRY_LIST_LOADING, payload: repo }, - { - type: types.SET_REGISTRY_LIST, - payload: { - repo, - resp: registryServerResponse, - headers: jasmine.anything(), - }, - }, - ], - [], - done, - ); - }); - }); - }); - - describe('setMainEndpoint', () => { - it('should commit set main endpoint', done => { - testAction( - actions.setMainEndpoint, - 'endpoint', - mockedState, - [{ type: types.SET_MAIN_ENDPOINT, payload: 'endpoint' }], - [], - done, - ); - }); - }); - - describe('toggleLoading', () => { - it('should commit toggle main loading', done => { - testAction( - actions.toggleLoading, - null, - mockedState, - [{ type: types.TOGGLE_MAIN_LOADING }], - [], - done, - ); - }); - }); - - describe('deleteItem', () => { - it('should perform DELETE request on destroyPath', done => { - const destroyPath = `${TEST_HOST}/mygroup/myproject/container_registry/1.json`; - let deleted = false; - mock.onDelete(destroyPath).replyOnce(() => { - deleted = true; - return [200]; - }); - testAction( - actions.deleteItem, - { - destroyPath, - }, - mockedState, - ) - .then(() => { - expect(mock.history.delete.length).toBe(1); - expect(deleted).toBe(true); - done(); - }) - .catch(done.fail); - }); - }); -}); diff --git a/spec/javascripts/registry/stores/mutations_spec.js b/spec/javascripts/registry/stores/mutations_spec.js deleted file mode 100644 index e19fe7a27cf..00000000000 --- a/spec/javascripts/registry/stores/mutations_spec.js +++ /dev/null @@ -1,85 +0,0 @@ -import mutations from '~/registry/stores/mutations'; -import * as types from '~/registry/stores/mutation_types'; -import { - defaultState, - reposServerResponse, - registryServerResponse, - parsedReposServerResponse, - parsedRegistryServerResponse, -} from '../mock_data'; - -describe('Mutations Registry Store', () => { - let mockState; - beforeEach(() => { - mockState = defaultState; - }); - - describe('SET_MAIN_ENDPOINT', () => { - it('should set the main endpoint', () => { - const expectedState = Object.assign({}, mockState, { endpoint: 'foo' }); - mutations[types.SET_MAIN_ENDPOINT](mockState, 'foo'); - - expect(mockState).toEqual(expectedState); - }); - }); - - describe('SET_REPOS_LIST', () => { - it('should set a parsed repository list', () => { - mutations[types.SET_REPOS_LIST](mockState, reposServerResponse); - - expect(mockState.repos).toEqual(parsedReposServerResponse); - }); - }); - - describe('TOGGLE_MAIN_LOADING', () => { - it('should set a parsed repository list', () => { - mutations[types.TOGGLE_MAIN_LOADING](mockState); - - expect(mockState.isLoading).toEqual(true); - }); - }); - - describe('SET_REGISTRY_LIST', () => { - it('should set a list of registries in a specific repository', () => { - mutations[types.SET_REPOS_LIST](mockState, reposServerResponse); - mutations[types.SET_REGISTRY_LIST](mockState, { - repo: mockState.repos[0], - resp: registryServerResponse, - headers: { - 'x-per-page': 2, - 'x-page': 1, - 'x-total': 10, - }, - }); - - expect(mockState.repos[0].list).toEqual(parsedRegistryServerResponse); - expect(mockState.repos[0].pagination).toEqual({ - perPage: 2, - page: 1, - total: 10, - totalPages: NaN, - nextPage: NaN, - previousPage: NaN, - }); - }); - }); - - describe('TOGGLE_REGISTRY_LIST_LOADING', () => { - it('should toggle isLoading property for a specific repository', () => { - mutations[types.SET_REPOS_LIST](mockState, reposServerResponse); - mutations[types.SET_REGISTRY_LIST](mockState, { - repo: mockState.repos[0], - resp: registryServerResponse, - headers: { - 'x-per-page': 2, - 'x-page': 1, - 'x-total': 10, - }, - }); - - mutations[types.TOGGLE_REGISTRY_LIST_LOADING](mockState, mockState.repos[0]); - - expect(mockState.repos[0].isLoading).toEqual(true); - }); - }); -}); diff --git a/spec/javascripts/releases/components/release_block_spec.js b/spec/javascripts/releases/components/release_block_spec.js deleted file mode 100644 index 11a385fa64d..00000000000 --- a/spec/javascripts/releases/components/release_block_spec.js +++ /dev/null @@ -1,170 +0,0 @@ -import Vue from 'vue'; -import component from '~/releases/components/release_block.vue'; -import timeagoMixin from '~/vue_shared/mixins/timeago'; - -import mountComponent from '../../helpers/vue_mount_component_helper'; - -describe('Release block', () => { - const Component = Vue.extend(component); - - const release = { - name: 'Bionic Beaver', - tag_name: '18.04', - description: '## changelog\n\n* line 1\n* line2', - description_html: '<div><h2>changelog</h2><ul><li>line1</li<li>line 2</li></ul></div>', - author_name: 'Release bot', - author_email: 'release-bot@example.com', - released_at: '2012-05-28T05:00:00-07:00', - author: { - avatar_url: 'uploads/-/system/user/avatar/johndoe/avatar.png', - id: 482476, - name: 'John Doe', - path: '/johndoe', - state: 'active', - status_tooltip_html: null, - username: 'johndoe', - web_url: 'https://gitlab.com/johndoe', - }, - commit: { - id: '2695effb5807a22ff3d138d593fd856244e155e7', - short_id: '2695effb', - title: 'Initial commit', - created_at: '2017-07-26T11:08:53.000+02:00', - parent_ids: ['2a4b78934375d7f53875269ffd4f45fd83a84ebe'], - message: 'Initial commit', - author_name: 'John Smith', - author_email: 'john@example.com', - authored_date: '2012-05-28T04:42:42-07:00', - committer_name: 'Jack Smith', - committer_email: 'jack@example.com', - committed_date: '2012-05-28T04:42:42-07:00', - }, - assets: { - count: 6, - sources: [ - { - format: 'zip', - url: - 'https://gitlab.com/gitlab-org/gitlab-foss/-/archive/v11.3.12/gitlab-ce-v11.3.12.zip', - }, - { - format: 'tar.gz', - url: - 'https://gitlab.com/gitlab-org/gitlab-foss/-/archive/v11.3.12/gitlab-ce-v11.3.12.tar.gz', - }, - { - format: 'tar.bz2', - url: - 'https://gitlab.com/gitlab-org/gitlab-foss/-/archive/v11.3.12/gitlab-ce-v11.3.12.tar.bz2', - }, - { - format: 'tar', - url: - 'https://gitlab.com/gitlab-org/gitlab-foss/-/archive/v11.3.12/gitlab-ce-v11.3.12.tar', - }, - ], - links: [ - { - name: 'release-18.04.dmg', - url: 'https://my-external-hosting.example.com/scrambled-url/', - external: true, - }, - { - name: 'binary-linux-amd64', - url: - 'https://gitlab.com/gitlab-org/gitlab-foss/-/jobs/artifacts/v11.6.0-rc4/download?job=rspec-mysql+41%2F50', - external: false, - }, - ], - }, - }; - let vm; - - const factory = props => mountComponent(Component, { release: props }); - - beforeEach(() => { - vm = factory(release); - }); - - afterEach(() => { - vm.$destroy(); - }); - - it("renders the block with an id equal to the release's tag name", () => { - expect(vm.$el.id).toBe('18.04'); - }); - - it('renders release name', () => { - expect(vm.$el.textContent).toContain(release.name); - }); - - it('renders commit sha', () => { - expect(vm.$el.textContent).toContain(release.commit.short_id); - }); - - it('renders tag name', () => { - expect(vm.$el.textContent).toContain(release.tag_name); - }); - - it('renders release date', () => { - expect(vm.$el.textContent).toContain(timeagoMixin.methods.timeFormated(release.released_at)); - }); - - it('renders number of assets provided', () => { - expect(vm.$el.querySelector('.js-assets-count').textContent).toContain(release.assets.count); - }); - - it('renders dropdown with the sources', () => { - expect(vm.$el.querySelectorAll('.js-sources-dropdown li').length).toEqual( - release.assets.sources.length, - ); - - expect(vm.$el.querySelector('.js-sources-dropdown li a').getAttribute('href')).toEqual( - release.assets.sources[0].url, - ); - - expect(vm.$el.querySelector('.js-sources-dropdown li a').textContent).toContain( - release.assets.sources[0].format, - ); - }); - - it('renders list with the links provided', () => { - expect(vm.$el.querySelectorAll('.js-assets-list li').length).toEqual( - release.assets.links.length, - ); - - expect(vm.$el.querySelector('.js-assets-list li a').getAttribute('href')).toEqual( - release.assets.links[0].url, - ); - - expect(vm.$el.querySelector('.js-assets-list li a').textContent).toContain( - release.assets.links[0].name, - ); - }); - - it('renders author avatar', () => { - expect(vm.$el.querySelector('.user-avatar-link')).not.toBeNull(); - }); - - describe('external label', () => { - it('renders external label when link is external', () => { - expect(vm.$el.querySelector('.js-assets-list li a').textContent).toContain('external source'); - }); - - it('does not render external label when link is not external', () => { - expect(vm.$el.querySelector('.js-assets-list li:nth-child(2) a').textContent).not.toContain( - 'external source', - ); - }); - }); - - describe('with upcoming_release flag', () => { - beforeEach(() => { - vm = factory(Object.assign({}, release, { upcoming_release: true })); - }); - - it('renders upcoming release badge', () => { - expect(vm.$el.textContent).toContain('Upcoming Release'); - }); - }); -}); diff --git a/spec/javascripts/releases/components/app_spec.js b/spec/javascripts/releases/list/components/app_spec.js index f30c7685e34..471c442e497 100644 --- a/spec/javascripts/releases/components/app_spec.js +++ b/spec/javascripts/releases/list/components/app_spec.js @@ -1,10 +1,10 @@ import Vue from 'vue'; -import app from '~/releases/components/app.vue'; -import createStore from '~/releases/store'; +import app from '~/releases/list/components/app.vue'; +import createStore from '~/releases/list/store'; import api from '~/api'; import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; import { resetStore } from '../store/helpers'; -import { releases } from '../mock_data'; +import { releases } from '../../mock_data'; describe('Releases App ', () => { const Component = Vue.extend(app); diff --git a/spec/javascripts/releases/store/actions_spec.js b/spec/javascripts/releases/list/store/actions_spec.js index 6eb8e681be9..8e78a631a5f 100644 --- a/spec/javascripts/releases/store/actions_spec.js +++ b/spec/javascripts/releases/list/store/actions_spec.js @@ -3,12 +3,12 @@ import { fetchReleases, receiveReleasesSuccess, receiveReleasesError, -} from '~/releases/store/actions'; -import state from '~/releases/store/state'; -import * as types from '~/releases/store/mutation_types'; +} from '~/releases/list/store/actions'; +import state from '~/releases/list/store/state'; +import * as types from '~/releases/list/store/mutation_types'; import api from '~/api'; import testAction from 'spec/helpers/vuex_action_helper'; -import { releases } from '../mock_data'; +import { releases } from '../../mock_data'; describe('Releases State actions', () => { let mockedState; diff --git a/spec/javascripts/releases/store/helpers.js b/spec/javascripts/releases/list/store/helpers.js index e962b254377..fbc89ec2148 100644 --- a/spec/javascripts/releases/store/helpers.js +++ b/spec/javascripts/releases/list/store/helpers.js @@ -1,4 +1,4 @@ -import state from '~/releases/store/state'; +import state from '~/releases/list/store/state'; // eslint-disable-next-line import/prefer-default-export export const resetStore = store => { diff --git a/spec/javascripts/releases/store/mutations_spec.js b/spec/javascripts/releases/list/store/mutations_spec.js index 72b98529fe9..d2577891495 100644 --- a/spec/javascripts/releases/store/mutations_spec.js +++ b/spec/javascripts/releases/list/store/mutations_spec.js @@ -1,7 +1,7 @@ -import state from '~/releases/store/state'; -import mutations from '~/releases/store/mutations'; -import * as types from '~/releases/store/mutation_types'; -import { releases } from '../mock_data'; +import state from '~/releases/list/store/state'; +import mutations from '~/releases/list/store/mutations'; +import * as types from '~/releases/list/store/mutation_types'; +import { releases } from '../../mock_data'; describe('Releases Store Mutations', () => { let stateCopy; diff --git a/spec/javascripts/reports/components/grouped_test_reports_app_spec.js b/spec/javascripts/reports/components/grouped_test_reports_app_spec.js index 1f1e626ed33..1b006cdbd4e 100644 --- a/spec/javascripts/reports/components/grouped_test_reports_app_spec.js +++ b/spec/javascripts/reports/components/grouped_test_reports_app_spec.js @@ -83,11 +83,11 @@ describe('Grouped Test Reports App', () => { setTimeout(() => { expect(vm.$el.querySelector('.gl-spinner')).toBeNull(); expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual( - 'Test summary contained 2 failed test results out of 11 total tests', + 'Test summary contained 2 failed/error test results out of 11 total tests', ); expect(vm.$el.textContent).toContain( - 'rspec:pg found 2 failed test results out of 8 total tests', + 'rspec:pg found 2 failed/error test results out of 8 total tests', ); expect(vm.$el.textContent).toContain('New'); @@ -111,16 +111,16 @@ describe('Grouped Test Reports App', () => { setTimeout(() => { expect(vm.$el.querySelector('.gl-spinner')).toBeNull(); expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual( - 'Test summary contained 2 failed test results and 2 fixed test results out of 11 total tests', + 'Test summary contained 2 failed/error test results and 2 fixed test results out of 11 total tests', ); expect(vm.$el.textContent).toContain( - 'rspec:pg found 1 failed test result and 2 fixed test results out of 8 total tests', + 'rspec:pg found 1 failed/error test result and 2 fixed test results out of 8 total tests', ); expect(vm.$el.textContent).toContain('New'); expect(vm.$el.textContent).toContain( - ' java ant found 1 failed test result out of 3 total tests', + ' java ant found 1 failed/error test result out of 3 total tests', ); done(); }, 0); diff --git a/spec/javascripts/sidebar/assignee_title_spec.js b/spec/javascripts/sidebar/assignee_title_spec.js index 7fff7c075d9..6c65a55ff29 100644 --- a/spec/javascripts/sidebar/assignee_title_spec.js +++ b/spec/javascripts/sidebar/assignee_title_spec.js @@ -1,13 +1,12 @@ import Vue from 'vue'; import AssigneeTitle from '~/sidebar/components/assignees/assignee_title.vue'; +import { mockTracking, triggerEvent } from 'spec/helpers/tracking_helper'; describe('AssigneeTitle component', () => { let component; let AssigneeTitleComponent; - let statsSpy; beforeEach(() => { - statsSpy = spyOnDependency(AssigneeTitle, 'trackEvent'); AssigneeTitleComponent = Vue.extend(AssigneeTitle); }); @@ -105,15 +104,20 @@ describe('AssigneeTitle component', () => { expect(component.$el.querySelector('.edit-link')).not.toBeNull(); }); - it('calls trackEvent when edit is clicked', () => { + it('tracks the event when edit is clicked', () => { component = new AssigneeTitleComponent({ propsData: { numberOfAssignees: 0, editable: true, }, }).$mount(); - component.$el.querySelector('.js-sidebar-dropdown-toggle').click(); - expect(statsSpy).toHaveBeenCalled(); + const spy = mockTracking('_category_', component.$el, spyOn); + triggerEvent('.js-sidebar-dropdown-toggle'); + + expect(spy).toHaveBeenCalledWith('_category_', 'click_edit_button', { + label: 'right_sidebar', + property: 'assignee', + }); }); }); diff --git a/spec/javascripts/sidebar/components/time_tracking/time_tracker_spec.js b/spec/javascripts/sidebar/components/time_tracking/time_tracker_spec.js index 2e1863cff86..ab28190ae64 100644 --- a/spec/javascripts/sidebar/components/time_tracking/time_tracker_spec.js +++ b/spec/javascripts/sidebar/components/time_tracking/time_tracker_spec.js @@ -83,8 +83,8 @@ describe('Issuable Time Tracker', () => { initTimeTrackingComponent({ timeEstimate: 100000, // 1d 3h timeSpent: 5000, // 1h 23m - timeEstimateHumanReadable: '', - timeSpentHumanReadable: '', + timeEstimateHumanReadable: '1d 3h', + timeSpentHumanReadable: '1h 23m', }); }); @@ -98,6 +98,16 @@ describe('Issuable Time Tracker', () => { }); }); + it('should show full times when the sidebar is collapsed', done => { + Vue.nextTick(() => { + const timeTrackingText = vm.$el.querySelector('.time-tracking-collapsed-summary span') + .innerText; + + expect(timeTrackingText).toBe('1h 23m / 1d 3h'); + done(); + }); + }); + describe('Remaining meter', () => { it('should display the remaining meter with the correct width', done => { Vue.nextTick(() => { diff --git a/spec/javascripts/sidebar/confidential_issue_sidebar_spec.js b/spec/javascripts/sidebar/confidential_issue_sidebar_spec.js deleted file mode 100644 index ea9e5677bc5..00000000000 --- a/spec/javascripts/sidebar/confidential_issue_sidebar_spec.js +++ /dev/null @@ -1,78 +0,0 @@ -import Vue from 'vue'; -import confidentialIssueSidebar from '~/sidebar/components/confidential/confidential_issue_sidebar.vue'; - -describe('Confidential Issue Sidebar Block', () => { - let vm1; - let vm2; - let statsSpy; - - beforeEach(() => { - statsSpy = spyOnDependency(confidentialIssueSidebar, 'trackEvent'); - const Component = Vue.extend(confidentialIssueSidebar); - const service = { - update: () => Promise.resolve(true), - }; - - vm1 = new Component({ - propsData: { - isConfidential: true, - isEditable: true, - service, - }, - }).$mount(); - - vm2 = new Component({ - propsData: { - isConfidential: false, - isEditable: false, - service, - }, - }).$mount(); - }); - - it('shows if confidential and/or editable', () => { - expect(vm1.$el.innerHTML.includes('Edit')).toBe(true); - - expect(vm1.$el.innerHTML.includes('This issue is confidential')).toBe(true); - - expect(vm2.$el.innerHTML.includes('Not confidential')).toBe(true); - }); - - it('displays the edit form when editable', done => { - expect(vm1.edit).toBe(false); - - vm1.$el.querySelector('.confidential-edit').click(); - - expect(vm1.edit).toBe(true); - - setTimeout(() => { - expect(vm1.$el.innerHTML.includes('You are going to turn off the confidentiality.')).toBe( - true, - ); - - done(); - }); - }); - - it('displays the edit form when opened from collapsed state', done => { - expect(vm1.edit).toBe(false); - - vm1.$el.querySelector('.sidebar-collapsed-icon').click(); - - expect(vm1.edit).toBe(true); - - setTimeout(() => { - expect(vm1.$el.innerHTML.includes('You are going to turn off the confidentiality.')).toBe( - true, - ); - - done(); - }); - }); - - it('calls trackEvent when "Edit" is clicked', () => { - vm1.$el.querySelector('.confidential-edit').click(); - - expect(statsSpy).toHaveBeenCalled(); - }); -}); diff --git a/spec/javascripts/sidebar/lock/lock_issue_sidebar_spec.js b/spec/javascripts/sidebar/lock/lock_issue_sidebar_spec.js index 2d930428230..decccbb8964 100644 --- a/spec/javascripts/sidebar/lock/lock_issue_sidebar_spec.js +++ b/spec/javascripts/sidebar/lock/lock_issue_sidebar_spec.js @@ -1,13 +1,12 @@ import Vue from 'vue'; import lockIssueSidebar from '~/sidebar/components/lock/lock_issue_sidebar.vue'; +import { mockTracking, triggerEvent } from 'spec/helpers/tracking_helper'; describe('LockIssueSidebar', () => { let vm1; let vm2; - let statsSpy; beforeEach(() => { - statsSpy = spyOnDependency(lockIssueSidebar, 'trackEvent'); const Component = Vue.extend(lockIssueSidebar); const mediator = { @@ -61,10 +60,14 @@ describe('LockIssueSidebar', () => { }); }); - it('calls trackEvent when "Edit" is clicked', () => { - vm1.$el.querySelector('.lock-edit').click(); + it('tracks an event when "Edit" is clicked', () => { + const spy = mockTracking('_category_', vm1.$el, spyOn); + triggerEvent('.lock-edit'); - expect(statsSpy).toHaveBeenCalled(); + expect(spy).toHaveBeenCalledWith('_category_', 'click_edit_button', { + label: 'right_sidebar', + property: 'lock_issue', + }); }); it('displays the edit form when opened from collapsed state', done => { diff --git a/spec/javascripts/sidebar/subscriptions_spec.js b/spec/javascripts/sidebar/subscriptions_spec.js index 2efa13f3fe8..a97608d6b8a 100644 --- a/spec/javascripts/sidebar/subscriptions_spec.js +++ b/spec/javascripts/sidebar/subscriptions_spec.js @@ -2,14 +2,13 @@ import Vue from 'vue'; import subscriptions from '~/sidebar/components/subscriptions/subscriptions.vue'; import eventHub from '~/sidebar/event_hub'; import mountComponent from 'spec/helpers/vue_mount_component_helper'; +import { mockTracking } from 'spec/helpers/tracking_helper'; describe('Subscriptions', function() { let vm; let Subscriptions; - let statsSpy; beforeEach(() => { - statsSpy = spyOnDependency(subscriptions, 'trackEvent'); Subscriptions = Vue.extend(subscriptions); }); @@ -53,6 +52,7 @@ describe('Subscriptions', function() { vm = mountComponent(Subscriptions, { subscribed: true }); spyOn(eventHub, '$emit'); spyOn(vm, '$emit'); + spyOn(vm, 'track'); vm.toggleSubscription(); @@ -60,11 +60,12 @@ describe('Subscriptions', function() { expect(vm.$emit).toHaveBeenCalledWith('toggleSubscription', jasmine.any(Object)); }); - it('calls trackEvent when toggled', () => { + it('tracks the event when toggled', () => { vm = mountComponent(Subscriptions, { subscribed: true }); + const spy = mockTracking('_category_', vm.$el, spyOn); vm.toggleSubscription(); - expect(statsSpy).toHaveBeenCalled(); + expect(spy).toHaveBeenCalled(); }); it('onClickCollapsedIcon method emits `toggleSidebar` event on component', () => { diff --git a/spec/javascripts/sidebar/todo_spec.js b/spec/javascripts/sidebar/todo_spec.js deleted file mode 100644 index e7abd19c865..00000000000 --- a/spec/javascripts/sidebar/todo_spec.js +++ /dev/null @@ -1,171 +0,0 @@ -import Vue from 'vue'; - -import SidebarTodos from '~/sidebar/components/todo_toggle/todo.vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; - -const createComponent = ({ - issuableId = 1, - issuableType = 'epic', - isTodo, - isActionActive, - collapsed, -}) => { - const Component = Vue.extend(SidebarTodos); - - return mountComponent(Component, { - issuableId, - issuableType, - isTodo, - isActionActive, - collapsed, - }); -}; - -describe('SidebarTodo', () => { - let vm; - - beforeEach(() => { - vm = createComponent({}); - }); - - afterEach(() => { - vm.$destroy(); - }); - - describe('computed', () => { - describe('buttonClasses', () => { - it('returns todo button classes for when `collapsed` prop is `false`', () => { - expect(vm.buttonClasses).toBe('btn btn-default btn-todo issuable-header-btn float-right'); - }); - - it('returns todo button classes for when `collapsed` prop is `true`', done => { - vm.collapsed = true; - Vue.nextTick() - .then(() => { - expect(vm.buttonClasses).toBe( - 'btn-blank btn-todo sidebar-collapsed-icon dont-change-state', - ); - }) - .then(done) - .catch(done.fail); - }); - }); - - describe('buttonLabel', () => { - it('returns todo button text for marking todo as done when `isTodo` prop is `true`', () => { - expect(vm.buttonLabel).toBe('Mark as done'); - }); - - it('returns todo button text for add todo when `isTodo` prop is `false`', done => { - vm.isTodo = false; - Vue.nextTick() - .then(() => { - expect(vm.buttonLabel).toBe('Add a To Do'); - }) - .then(done) - .catch(done.fail); - }); - }); - - describe('collapsedButtonIconClasses', () => { - it('returns collapsed button icon class when `isTodo` prop is `true`', () => { - expect(vm.collapsedButtonIconClasses).toBe('todo-undone'); - }); - - it('returns empty string when `isTodo` prop is `false`', done => { - vm.isTodo = false; - Vue.nextTick() - .then(() => { - expect(vm.collapsedButtonIconClasses).toBe(''); - }) - .then(done) - .catch(done.fail); - }); - }); - - describe('collapsedButtonIcon', () => { - it('returns button icon name when `isTodo` prop is `true`', () => { - expect(vm.collapsedButtonIcon).toBe('todo-done'); - }); - - it('returns button icon name when `isTodo` prop is `false`', done => { - vm.isTodo = false; - Vue.nextTick() - .then(() => { - expect(vm.collapsedButtonIcon).toBe('todo-add'); - }) - .then(done) - .catch(done.fail); - }); - }); - }); - - describe('methods', () => { - describe('handleButtonClick', () => { - it('emits `toggleTodo` event on component', () => { - spyOn(vm, '$emit'); - vm.handleButtonClick(); - - expect(vm.$emit).toHaveBeenCalledWith('toggleTodo'); - }); - }); - }); - - describe('template', () => { - it('renders component container element', () => { - const dataAttributes = { - issuableId: '1', - issuableType: 'epic', - originalTitle: '', - placement: 'left', - container: 'body', - boundary: 'viewport', - }; - - expect(vm.$el.nodeName).toBe('BUTTON'); - - const elDataAttrs = vm.$el.dataset; - Object.keys(elDataAttrs).forEach(attr => { - expect(elDataAttrs[attr]).toBe(dataAttributes[attr]); - }); - }); - - it('check button label computed property', () => { - expect(vm.buttonLabel).toEqual('Mark as done'); - }); - - it('renders button label element when `collapsed` prop is `false`', () => { - const buttonLabelEl = vm.$el.querySelector('span.issuable-todo-inner'); - - expect(buttonLabelEl).not.toBeNull(); - expect(buttonLabelEl.innerText.trim()).toBe('Mark as done'); - }); - - it('renders button icon when `collapsed` prop is `true`', done => { - vm.collapsed = true; - Vue.nextTick() - .then(() => { - const buttonIconEl = vm.$el.querySelector('svg'); - - expect(buttonIconEl).not.toBeNull(); - expect(buttonIconEl.querySelector('use').getAttribute('xlink:href')).toContain( - 'todo-done', - ); - }) - .then(done) - .catch(done.fail); - }); - - it('renders loading icon when `isActionActive` prop is true', done => { - vm.isActionActive = true; - Vue.nextTick() - .then(() => { - const loadingEl = vm.$el.querySelector('span.loading-container'); - - expect(loadingEl).not.toBeNull(); - }) - .then(done) - .catch(done.fail); - }); - }); -}); diff --git a/spec/javascripts/test_bundle.js b/spec/javascripts/test_bundle.js index c0a999cfaa6..cb6b158f01c 100644 --- a/spec/javascripts/test_bundle.js +++ b/spec/javascripts/test_bundle.js @@ -7,7 +7,6 @@ import 'core-js/features/set-immediate'; import 'vendor/jasmine-jquery'; import '~/commons'; import Vue from 'vue'; -import VueResource from 'vue-resource'; import Translate from '~/vue_shared/translate'; import jasmineDiff from 'jasmine-diff'; import { config as testUtilsConfig } from '@vue/test-utils'; @@ -46,7 +45,6 @@ Vue.config.errorHandler = function(err) { fail(err); }; -Vue.use(VueResource); Vue.use(Translate); // enable test fixtures @@ -72,7 +70,7 @@ window.gl = window.gl || {}; window.gl.TEST_HOST = TEST_HOST; window.gon = window.gon || {}; window.gon.test_env = true; -window.gon.ee = process.env.IS_GITLAB_EE; +window.gon.ee = process.env.IS_EE; gon.relative_url_root = ''; let hasUnhandledPromiseRejections = false; @@ -102,13 +100,6 @@ afterEach(__rewire_reset_all__); // eslint-disable-line // to run our unit tests. beforeEach(done => done()); -const builtinVueHttpInterceptors = Vue.http.interceptors.slice(); - -beforeEach(() => { - // restore interceptors so we have no remaining ones from previous tests - Vue.http.interceptors = builtinVueHttpInterceptors.slice(); -}); - let longRunningTestTimeoutHandle; beforeEach(done => { @@ -127,7 +118,7 @@ const axiosDefaultAdapter = getDefaultAdapter(); // render all of our tests const testContexts = [require.context('spec', true, /_spec$/)]; -if (process.env.IS_GITLAB_EE) { +if (process.env.IS_EE) { testContexts.push(require.context('ee_spec', true, /_spec$/)); } @@ -216,7 +207,7 @@ if (process.env.BABEL_ENV === 'coverage') { describe('Uncovered files', function() { const sourceFilesContexts = [require.context('~', true, /\.(js|vue)$/)]; - if (process.env.IS_GITLAB_EE) { + if (process.env.IS_EE) { sourceFilesContexts.push(require.context('ee', true, /\.(js|vue)$/)); } diff --git a/spec/javascripts/test_constants.js b/spec/javascripts/test_constants.js index c97d47a6406..51c0716b99d 100644 --- a/spec/javascripts/test_constants.js +++ b/spec/javascripts/test_constants.js @@ -1,7 +1 @@ -export const FIXTURES_PATH = `/fixtures`; -export const TEST_HOST = 'http://test.host'; - -export const DUMMY_IMAGE_URL = `${FIXTURES_PATH}/static/images/one_white_pixel.png`; - -export const GREEN_BOX_IMAGE_URL = `${FIXTURES_PATH}/static/images/green_box.png`; -export const RED_BOX_IMAGE_URL = `${FIXTURES_PATH}/static/images/red_box.png`; +export * from '../frontend/helpers/test_constants'; diff --git a/spec/javascripts/todos_spec.js b/spec/javascripts/todos_spec.js index 802f54f6a7e..dc3c547c632 100644 --- a/spec/javascripts/todos_spec.js +++ b/spec/javascripts/todos_spec.js @@ -1,18 +1,31 @@ import $ from 'jquery'; +import MockAdapter from 'axios-mock-adapter'; import Todos from '~/pages/dashboard/todos/index/todos'; import '~/lib/utils/common_utils'; +import '~/gl_dropdown'; +import axios from '~/lib/utils/axios_utils'; +import { addDelimiter } from '~/lib/utils/text_utility'; + +const TEST_COUNT_BIG = 2000; +const TEST_DONE_COUNT_BIG = 7300; describe('Todos', () => { preloadFixtures('todos/todos.html'); let todoItem; + let mock; beforeEach(() => { loadFixtures('todos/todos.html'); todoItem = document.querySelector('.todos-list .todo'); + mock = new MockAdapter(axios); return new Todos(); }); + afterEach(() => { + mock.restore(); + }); + describe('goToTodoUrl', () => { it('opens the todo url', done => { const todoLink = todoItem.dataset.url; @@ -53,5 +66,43 @@ describe('Todos', () => { expect(windowOpenSpy).not.toHaveBeenCalled(); }); }); + + describe('on done todo click', () => { + let onToggleSpy; + + beforeEach(done => { + const el = document.querySelector('.js-done-todo'); + const path = el.dataset.href; + + // Arrange + mock + .onDelete(path) + .replyOnce(200, { count: TEST_COUNT_BIG, done_count: TEST_DONE_COUNT_BIG }); + onToggleSpy = jasmine.createSpy('onToggle'); + $(document).on('todo:toggle', onToggleSpy); + + // Act + el.click(); + + // Wait for axios and HTML to udpate + setImmediate(done); + }); + + it('dispatches todo:toggle', () => { + expect(onToggleSpy).toHaveBeenCalledWith(jasmine.anything(), TEST_COUNT_BIG); + }); + + it('updates pending text', () => { + expect(document.querySelector('.todos-pending .badge').innerHTML).toEqual( + addDelimiter(TEST_COUNT_BIG), + ); + }); + + it('updates done text', () => { + expect(document.querySelector('.todos-done .badge').innerHTML).toEqual( + addDelimiter(TEST_DONE_COUNT_BIG), + ); + }); + }); }); }); diff --git a/spec/javascripts/vue_mr_widget/components/deployment_spec.js b/spec/javascripts/vue_mr_widget/components/deployment_spec.js index 42bf3b7df09..1949bee1406 100644 --- a/spec/javascripts/vue_mr_widget/components/deployment_spec.js +++ b/spec/javascripts/vue_mr_widget/components/deployment_spec.js @@ -207,7 +207,7 @@ describe('Deployment component', () => { it('renders the link to the review app without dropdown', () => { expect(vm.$el.querySelector('.js-mr-wigdet-deployment-dropdown')).toBeNull(); - expect(vm.$el.querySelector('.js-deploy-url-feature-flag')).not.toBeNull(); + expect(vm.$el.querySelector('.js-deploy-url')).not.toBeNull(); }); }); @@ -223,12 +223,12 @@ describe('Deployment component', () => { it('renders the link to the review app without dropdown', () => { expect(vm.$el.querySelector('.js-mr-wigdet-deployment-dropdown')).toBeNull(); - expect(vm.$el.querySelector('.js-deploy-url-feature-flag')).not.toBeNull(); + expect(vm.$el.querySelector('.js-deploy-url')).not.toBeNull(); }); it('renders the link to the review app linked to to the first change', () => { const expectedUrl = deploymentMockData.changes[0].external_url; - const deployUrl = vm.$el.querySelector('.js-deploy-url-feature-flag'); + const deployUrl = vm.$el.querySelector('.js-deploy-url'); expect(vm.$el.querySelector('.js-mr-wigdet-deployment-dropdown')).toBeNull(); expect(deployUrl).not.toBeNull(); diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_container_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_container_spec.js index dfbc68c48b9..6cdf60f3535 100644 --- a/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_container_spec.js +++ b/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_container_spec.js @@ -1,6 +1,7 @@ import { mount, createLocalVue } from '@vue/test-utils'; import MrWidgetPipelineContainer from '~/vue_merge_request_widget/components/mr_widget_pipeline_container.vue'; import MrWidgetPipeline from '~/vue_merge_request_widget/components/mr_widget_pipeline.vue'; +import ArtifactsApp from '~/vue_merge_request_widget/components/artifacts_list_app.vue'; import { mockStore } from '../mock_data'; describe('MrWidgetPipelineContainer', () => { @@ -87,4 +88,10 @@ describe('MrWidgetPipelineContainer', () => { expect(deployments.wrappers.map(x => x.props())).toEqual(expectedProps); }); }); + + describe('with artifacts path', () => { + it('renders the artifacts app', () => { + expect(wrapper.find(ArtifactsApp).isVisible()).toBe(true); + }); + }); }); diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_wip_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_wip_spec.js index 7b1d589dcf8..5844dad42ff 100644 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_wip_spec.js +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_wip_spec.js @@ -47,7 +47,7 @@ describe('Wip', () => { it('should make a request to service and handle response', done => { const vm = createComponent(); - spyOn(window, 'Flash').and.returnValue(true); + const flashSpy = spyOnDependency(WorkInProgress, 'createFlash').and.returnValue(true); spyOn(eventHub, '$emit'); spyOn(vm.service, 'removeWIP').and.returnValue( new Promise(resolve => { @@ -61,10 +61,7 @@ describe('Wip', () => { setTimeout(() => { expect(vm.isMakingRequest).toBeTruthy(); expect(eventHub.$emit).toHaveBeenCalledWith('UpdateWidgetData', mrObj); - expect(window.Flash).toHaveBeenCalledWith( - 'The merge request can now be merged.', - 'notice', - ); + expect(flashSpy).toHaveBeenCalledWith('The merge request can now be merged.', 'notice'); done(); }, 333); }); diff --git a/spec/javascripts/vue_mr_widget/mock_data.js b/spec/javascripts/vue_mr_widget/mock_data.js index 2f79806652b..089ec08fbf9 100644 --- a/spec/javascripts/vue_mr_widget/mock_data.js +++ b/spec/javascripts/vue_mr_widget/mock_data.js @@ -289,4 +289,5 @@ export const mockStore = { troubleshootingDocsPath: 'troubleshooting-docs-path', ciStatus: 'ci-status', hasCI: true, + exposedArtifactsPath: 'exposed_artifacts.json', }; diff --git a/spec/javascripts/vue_mr_widget/stores/artifacts_list/actions_spec.js b/spec/javascripts/vue_mr_widget/stores/artifacts_list/actions_spec.js new file mode 100644 index 00000000000..4c4bebcb4cd --- /dev/null +++ b/spec/javascripts/vue_mr_widget/stores/artifacts_list/actions_spec.js @@ -0,0 +1,165 @@ +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; +import { + setEndpoint, + requestArtifacts, + clearEtagPoll, + stopPolling, + fetchArtifacts, + receiveArtifactsSuccess, + receiveArtifactsError, +} from '~/vue_merge_request_widget/stores/artifacts_list/actions'; +import state from '~/vue_merge_request_widget/stores/artifacts_list/state'; +import * as types from '~/vue_merge_request_widget/stores/artifacts_list/mutation_types'; +import testAction from 'spec/helpers/vuex_action_helper'; +import { TEST_HOST } from 'spec/test_constants'; + +describe('Artifacts App Store Actions', () => { + let mockedState; + + beforeEach(() => { + mockedState = state(); + }); + + describe('setEndpoint', () => { + it('should commit SET_ENDPOINT mutation', done => { + testAction( + setEndpoint, + 'endpoint.json', + mockedState, + [{ type: types.SET_ENDPOINT, payload: 'endpoint.json' }], + [], + done, + ); + }); + }); + + describe('requestArtifacts', () => { + it('should commit REQUEST_ARTIFACTS mutation', done => { + testAction( + requestArtifacts, + null, + mockedState, + [{ type: types.REQUEST_ARTIFACTS }], + [], + done, + ); + }); + }); + + describe('fetchArtifacts', () => { + let mock; + + beforeEach(() => { + mockedState.endpoint = `${TEST_HOST}/endpoint.json`; + mock = new MockAdapter(axios); + }); + + afterEach(() => { + mock.restore(); + stopPolling(); + clearEtagPoll(); + }); + + describe('success', () => { + it('dispatches requestArtifacts and receiveArtifactsSuccess ', done => { + mock.onGet(`${TEST_HOST}/endpoint.json`).replyOnce(200, [ + { + text: 'result.txt', + url: 'asda', + job_name: 'generate-artifact', + job_path: 'asda', + }, + ]); + + testAction( + fetchArtifacts, + null, + mockedState, + [], + [ + { + type: 'requestArtifacts', + }, + { + payload: { + data: [ + { + text: 'result.txt', + url: 'asda', + job_name: 'generate-artifact', + job_path: 'asda', + }, + ], + status: 200, + }, + type: 'receiveArtifactsSuccess', + }, + ], + done, + ); + }); + }); + + describe('error', () => { + beforeEach(() => { + mock.onGet(`${TEST_HOST}/endpoint.json`).reply(500); + }); + + it('dispatches requestArtifacts and receiveArtifactsError ', done => { + testAction( + fetchArtifacts, + null, + mockedState, + [], + [ + { + type: 'requestArtifacts', + }, + { + type: 'receiveArtifactsError', + }, + ], + done, + ); + }); + }); + }); + + describe('receiveArtifactsSuccess', () => { + it('should commit RECEIVE_ARTIFACTS_SUCCESS mutation with 200', done => { + testAction( + receiveArtifactsSuccess, + { data: { summary: {} }, status: 200 }, + mockedState, + [{ type: types.RECEIVE_ARTIFACTS_SUCCESS, payload: { summary: {} } }], + [], + done, + ); + }); + + it('should not commit RECEIVE_ARTIFACTS_SUCCESS mutation with 204', done => { + testAction( + receiveArtifactsSuccess, + { data: { summary: {} }, status: 204 }, + mockedState, + [], + [], + done, + ); + }); + }); + + describe('receiveArtifactsError', () => { + it('should commit RECEIVE_ARTIFACTS_ERROR mutation', done => { + testAction( + receiveArtifactsError, + null, + mockedState, + [{ type: types.RECEIVE_ARTIFACTS_ERROR }], + [], + done, + ); + }); + }); +}); diff --git a/spec/javascripts/vue_shared/components/clipboard_button_spec.js b/spec/javascripts/vue_shared/components/clipboard_button_spec.js index fd17349d48f..29a76574b89 100644 --- a/spec/javascripts/vue_shared/components/clipboard_button_spec.js +++ b/spec/javascripts/vue_shared/components/clipboard_button_spec.js @@ -14,7 +14,7 @@ describe('clipboard button', () => { beforeEach(() => { vm = mountComponent(Component, { text: 'copy me', - title: 'Copy this value into Clipboard!', + title: 'Copy this value', cssClass: 'btn-danger', }); }); @@ -26,7 +26,7 @@ describe('clipboard button', () => { }); it('should have a tooltip with default values', () => { - expect(vm.$el.getAttribute('data-original-title')).toEqual('Copy this value into Clipboard!'); + expect(vm.$el.getAttribute('data-original-title')).toEqual('Copy this value'); }); it('should render provided classname', () => { @@ -39,7 +39,7 @@ describe('clipboard button', () => { vm = mountComponent(Component, { text: 'copy me', gfm: '`path/to/file`', - title: 'Copy this value into Clipboard!', + title: 'Copy this value', cssClass: 'btn-danger', }); diff --git a/spec/javascripts/vue_shared/components/gl_modal_spec.js b/spec/javascripts/vue_shared/components/deprecated_modal_2_spec.js index 19af8b5d2f7..64fb984d9fc 100644 --- a/spec/javascripts/vue_shared/components/gl_modal_spec.js +++ b/spec/javascripts/vue_shared/components/deprecated_modal_2_spec.js @@ -1,11 +1,11 @@ import $ from 'jquery'; import Vue from 'vue'; -import GlModal from '~/vue_shared/components/gl_modal.vue'; +import DeprecatedModal2 from '~/vue_shared/components/deprecated_modal_2.vue'; import mountComponent from 'spec/helpers/vue_mount_component_helper'; -const modalComponent = Vue.extend(GlModal); +const modalComponent = Vue.extend(DeprecatedModal2); -describe('GlModal', () => { +describe('DeprecatedModal2', () => { let vm; afterEach(() => { @@ -153,17 +153,17 @@ describe('GlModal', () => { let template; if (slotName) { template = ` - <gl-modal> + <deprecated-modal-2> <template slot="${slotName}">${slotContent}</template> - </gl-modal> + </deprecated-modal-2> `; } else { - template = `<gl-modal>${slotContent}</gl-modal>`; + template = `<deprecated-modal-2>${slotContent}</deprecated-modal-2>`; } return Vue.extend({ components: { - GlModal, + DeprecatedModal2, }, template, }); diff --git a/spec/javascripts/vue_shared/components/file_row_spec.js b/spec/javascripts/vue_shared/components/file_row_spec.js index 6abcac5c0ff..7da69e3fa84 100644 --- a/spec/javascripts/vue_shared/components/file_row_spec.js +++ b/spec/javascripts/vue_shared/components/file_row_spec.js @@ -90,19 +90,6 @@ describe('File row component', () => { expect(vm.$el.querySelector('.js-file-row-header')).not.toBe(null); }); - it('is not rendered for `moved` entries in subfolders', () => { - createComponent({ - file: { - path: 't5', - moved: true, - tree: [], - }, - level: 2, - }); - - expect(vm.$el.nodeType).not.toEqual(1); - }); - describe('new dropdown', () => { beforeEach(() => { createComponent({ diff --git a/spec/javascripts/vue_shared/components/icon_spec.js b/spec/javascripts/vue_shared/components/icon_spec.js index 45eef2ad737..7390798afa8 100644 --- a/spec/javascripts/vue_shared/components/icon_spec.js +++ b/spec/javascripts/vue_shared/components/icon_spec.js @@ -12,8 +12,6 @@ describe('Sprite Icon Component', function() { icon = mountComponent(IconComponent, { name: 'commit', size: 32, - cssClasses: 'extraclasses', - tabIndex: '0', }); }); @@ -47,10 +45,8 @@ describe('Sprite Icon Component', function() { it('should properly render img css', function() { const { classList } = icon.$el; const containsSizeClass = classList.contains('s32'); - const containsCustomClass = classList.contains('extraclasses'); expect(containsSizeClass).toBe(true); - expect(containsCustomClass).toBe(true); }); it('`name` validator should return false for non existing icons', () => { @@ -60,9 +56,5 @@ describe('Sprite Icon Component', function() { it('`name` validator should return false for existing icons', () => { expect(Icon.props.name.validator('commit')).toBe(true); }); - - it('should contain `tabindex` attribute on svg element when `tabIndex` prop is defined', () => { - expect(icon.$el.getAttribute('tabindex')).toBe('0'); - }); }); }); diff --git a/spec/javascripts/vue_shared/components/project_selector/project_selector_spec.js b/spec/javascripts/vue_shared/components/project_selector/project_selector_spec.js index f8271866ca1..9c2deca585b 100644 --- a/spec/javascripts/vue_shared/components/project_selector/project_selector_spec.js +++ b/spec/javascripts/vue_shared/components/project_selector/project_selector_spec.js @@ -4,9 +4,11 @@ import ProjectSelector from '~/vue_shared/components/project_selector/project_se import ProjectListItem from '~/vue_shared/components/project_selector/project_list_item.vue'; import { GlSearchBoxByType } from '@gitlab/ui'; -import { mount } from '@vue/test-utils'; +import { mount, createLocalVue } from '@vue/test-utils'; import { trimText } from 'spec/helpers/text_helper'; +const localVue = createLocalVue(); + describe('ProjectSelector component', () => { let wrapper; let vm; @@ -22,6 +24,7 @@ describe('ProjectSelector component', () => { jasmine.clock().install(); wrapper = mount(Vue.extend(ProjectSelector), { + localVue, propsData: { projectSearchResults: searchResults, selectedProjects: selected, diff --git a/spec/javascripts/zen_mode_spec.js b/spec/javascripts/zen_mode_spec.js index 8f662c71c7a..5dee11b3810 100644 --- a/spec/javascripts/zen_mode_spec.js +++ b/spec/javascripts/zen_mode_spec.js @@ -2,6 +2,7 @@ import $ from 'jquery'; import Dropzone from 'dropzone'; import Mousetrap from 'mousetrap'; import ZenMode from '~/zen_mode'; +import initNotes from '~/init_notes'; describe('ZenMode', () => { let zen; @@ -28,6 +29,7 @@ describe('ZenMode', () => { beforeEach(() => { loadFixtures(fixtureName); + initNotes(); dropzoneForElementSpy = spyOn(Dropzone, 'forElement').and.callFake(() => ({ enable: () => true, |