From 580622bdb3c762a8e89facd8a3946881ee480442 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Tue, 31 Mar 2020 18:07:42 +0000 Subject: Add latest changes from gitlab-org/gitlab@master --- CHANGELOG.md | 1 - .../blob/pipeline_tour_success_modal.vue | 17 + .../pages/groups/registry/repositories/index.js | 11 +- .../javascripts/pages/projects/blob/show/index.js | 3 +- .../pages/projects/registry/repositories/index.js | 3 - .../javascripts/registry/list/components/app.vue | 153 --------- .../list/components/collapsible_container.vue | 155 --------- .../registry/list/components/group_empty_state.vue | 46 --- .../list/components/project_empty_state.vue | 133 -------- .../registry/list/components/table_registry.vue | 289 ---------------- app/assets/javascripts/registry/list/constants.js | 8 - app/assets/javascripts/registry/list/index.js | 44 --- .../javascripts/registry/list/stores/actions.js | 46 --- .../javascripts/registry/list/stores/getters.js | 6 - .../javascripts/registry/list/stores/index.js | 15 - .../registry/list/stores/mutation_types.js | 8 - .../javascripts/registry/list/stores/mutations.js | 57 ---- .../javascripts/registry/list/stores/state.js | 27 -- .../sidebar/components/lock/lock_issue_sidebar.vue | 6 +- .../components/participants/participants.vue | 2 +- app/assets/stylesheets/framework/buttons.scss | 1 + app/assets/stylesheets/pages/issuable.scss | 13 +- app/controllers/boards/issues_controller.rb | 2 +- app/controllers/concerns/snippets_actions.rb | 8 + .../groups/registry/repositories_controller.rb | 13 +- .../projects/registry/repositories_controller.rb | 6 +- app/helpers/groups_helper.rb | 3 +- app/models/concerns/has_repository.rb | 4 + app/services/award_emojis/add_service.rb | 8 +- app/services/award_emojis/destroy_service.rb | 8 + app/services/boards/issues/list_service.rb | 2 +- app/services/issues/close_service.rb | 2 - .../groups/registry/repositories/index.html.haml | 31 +- .../projects/blob/_pipeline_tour_success.html.haml | 4 +- .../projects/registry/repositories/index.html.haml | 39 +-- .../unreleased/212662-edit-snippet-images.yml | 5 + .../unreleased/23315-remove-feature-flag.yml | 5 + .../35947-board-issues-search-optmization-2.yml | 5 + doc/user/application_security/dast/index.md | 91 ++++- doc/user/group/saml_sso/index.md | 47 +++ lib/gitlab/repository_size_checker.rb | 44 +++ lib/gitlab/repository_size_error_message.rb | 48 +++ lib/gitlab/url_blocker.rb | 4 +- lib/sentry/client.rb | 1 - lib/sentry/client/issue.rb | 1 + locale/gitlab.pot | 24 +- .../registry/repositories_controller_spec.rb | 34 +- .../registry/repositories_controller_spec.rb | 16 - .../projects/snippets_controller_spec.rb | 40 ++- spec/controllers/snippets_controller_spec.rb | 21 +- spec/features/projects/container_registry_spec.rb | 192 ++++------- spec/frontend/__mocks__/sortablejs/index.js | 5 + .../blob/pipeline_tour_success_mock_data.js | 7 + .../blob/pipeline_tour_success_modal_spec.js | 51 +++ spec/frontend/blob/pipeline_tour_success_spec.js | 40 --- spec/frontend/boards/board_card_spec.js | 213 ++++++++++++ spec/frontend/boards/board_list_spec.js | 274 +++++++++++++++ spec/frontend/boards/list_spec.js | 232 +++++++++++++ .../__snapshots__/group_empty_state_spec.js.snap | 61 ---- .../__snapshots__/project_empty_state_spec.js.snap | 186 ---------- spec/frontend/registry/list/components/app_spec.js | 149 -------- .../list/components/collapsible_container_spec.js | 176 ---------- .../list/components/group_empty_state_spec.js | 23 -- .../list/components/project_empty_state_spec.js | 27 -- .../list/components/table_registry_spec.js | 373 --------------------- spec/frontend/registry/list/mock_data.js | 134 -------- spec/frontend/registry/list/stores/actions_spec.js | 203 ----------- spec/frontend/registry/list/stores/getters_spec.js | 52 --- .../registry/list/stores/mutations_spec.js | 94 ------ spec/javascripts/boards/board_card_spec.js | 215 ------------ spec/javascripts/boards/board_list_spec.js | 262 --------------- spec/javascripts/boards/list_spec.js | 225 ------------- spec/lib/gitlab/repository_size_checker_spec.rb | 93 +++++ .../gitlab/repository_size_error_message_spec.rb | 55 +++ spec/services/issues/close_service_spec.rb | 13 - .../controllers/binary_blob_shared_examples.rb | 60 ++++ 76 files changed, 1450 insertions(+), 3525 deletions(-) delete mode 100644 app/assets/javascripts/registry/list/components/app.vue delete mode 100644 app/assets/javascripts/registry/list/components/collapsible_container.vue delete mode 100644 app/assets/javascripts/registry/list/components/group_empty_state.vue delete mode 100644 app/assets/javascripts/registry/list/components/project_empty_state.vue delete mode 100644 app/assets/javascripts/registry/list/components/table_registry.vue delete mode 100644 app/assets/javascripts/registry/list/constants.js delete mode 100644 app/assets/javascripts/registry/list/index.js delete mode 100644 app/assets/javascripts/registry/list/stores/actions.js delete mode 100644 app/assets/javascripts/registry/list/stores/getters.js delete mode 100644 app/assets/javascripts/registry/list/stores/index.js delete mode 100644 app/assets/javascripts/registry/list/stores/mutation_types.js delete mode 100644 app/assets/javascripts/registry/list/stores/mutations.js delete mode 100644 app/assets/javascripts/registry/list/stores/state.js create mode 100644 changelogs/unreleased/212662-edit-snippet-images.yml create mode 100644 changelogs/unreleased/23315-remove-feature-flag.yml create mode 100644 changelogs/unreleased/35947-board-issues-search-optmization-2.yml create mode 100644 lib/gitlab/repository_size_checker.rb create mode 100644 lib/gitlab/repository_size_error_message.rb create mode 100644 spec/frontend/__mocks__/sortablejs/index.js create mode 100644 spec/frontend/blob/pipeline_tour_success_mock_data.js create mode 100644 spec/frontend/blob/pipeline_tour_success_modal_spec.js delete mode 100644 spec/frontend/blob/pipeline_tour_success_spec.js create mode 100644 spec/frontend/boards/board_card_spec.js create mode 100644 spec/frontend/boards/board_list_spec.js create mode 100644 spec/frontend/boards/list_spec.js delete mode 100644 spec/frontend/registry/list/components/__snapshots__/group_empty_state_spec.js.snap delete mode 100644 spec/frontend/registry/list/components/__snapshots__/project_empty_state_spec.js.snap delete mode 100644 spec/frontend/registry/list/components/app_spec.js delete mode 100644 spec/frontend/registry/list/components/collapsible_container_spec.js delete mode 100644 spec/frontend/registry/list/components/group_empty_state_spec.js delete mode 100644 spec/frontend/registry/list/components/project_empty_state_spec.js delete mode 100644 spec/frontend/registry/list/components/table_registry_spec.js delete mode 100644 spec/frontend/registry/list/mock_data.js delete mode 100644 spec/frontend/registry/list/stores/actions_spec.js delete mode 100644 spec/frontend/registry/list/stores/getters_spec.js delete mode 100644 spec/frontend/registry/list/stores/mutations_spec.js delete mode 100644 spec/javascripts/boards/board_card_spec.js delete mode 100644 spec/javascripts/boards/board_list_spec.js delete mode 100644 spec/javascripts/boards/list_spec.js create mode 100644 spec/lib/gitlab/repository_size_checker_spec.rb create mode 100644 spec/lib/gitlab/repository_size_error_message_spec.rb create mode 100644 spec/support/shared_examples/controllers/binary_blob_shared_examples.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 197596031b8..d11761e0607 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,6 @@ entry. ## 12.9.2 (2020-03-31) -- No changes. ### Fixed (5 changes) - Ensure import by URL works after a failed import. !27546 diff --git a/app/assets/javascripts/blob/pipeline_tour_success_modal.vue b/app/assets/javascripts/blob/pipeline_tour_success_modal.vue index 0739b4d5e39..179a515e5ca 100644 --- a/app/assets/javascripts/blob/pipeline_tour_success_modal.vue +++ b/app/assets/javascripts/blob/pipeline_tour_success_modal.vue @@ -3,6 +3,9 @@ import { GlModal, GlSprintf, GlLink } from '@gitlab/ui'; import { sprintf, s__, __ } from '~/locale'; import Cookies from 'js-cookie'; import { glEmojiTag } from '~/emoji'; +import Tracking from '~/tracking'; + +const trackingMixin = Tracking.mixin(); export default { beginnerLink: @@ -23,6 +26,7 @@ export default { GlSprintf, GlLink, }, + mixins: [trackingMixin], props: { goToPipelinesPath: { type: String, @@ -32,8 +36,21 @@ export default { type: String, required: true, }, + humanAccess: { + type: String, + required: true, + }, + }, + data() { + return { + tracking: { + label: 'congratulate_first_pipeline', + property: this.humanAccess, + }, + }; }, mounted() { + this.track(); this.disableModalFromRenderingAgain(); }, methods: { diff --git a/app/assets/javascripts/pages/groups/registry/repositories/index.js b/app/assets/javascripts/pages/groups/registry/repositories/index.js index 47fea2be189..cdafe838994 100644 --- a/app/assets/javascripts/pages/groups/registry/repositories/index.js +++ b/app/assets/javascripts/pages/groups/registry/repositories/index.js @@ -1,9 +1,10 @@ -import initRegistryImages from '~/registry/list/index'; import registryExplorer from '~/registry/explorer/index'; document.addEventListener('DOMContentLoaded', () => { - initRegistryImages(); - const { attachMainComponent, attachBreadcrumb } = registryExplorer(); - attachBreadcrumb(); - attachMainComponent(); + const explorer = registryExplorer(); + + if (explorer) { + explorer.attachBreadcrumb(); + explorer.attachMainComponent(); + } }); diff --git a/app/assets/javascripts/pages/projects/blob/show/index.js b/app/assets/javascripts/pages/projects/blob/show/index.js index 4d308d6b07a..9ff68a88f95 100644 --- a/app/assets/javascripts/pages/projects/blob/show/index.js +++ b/app/assets/javascripts/pages/projects/blob/show/index.js @@ -45,12 +45,13 @@ document.addEventListener('DOMContentLoaded', () => { new Vue({ el: successPipelineEl, render(createElement) { - const { commitCookie, pipelinesPath: goToPipelinesPath } = this.$el.dataset; + const { commitCookie, goToPipelinesPath, humanAccess } = this.$el.dataset; return createElement(PipelineTourSuccessModal, { props: { goToPipelinesPath, commitCookie, + humanAccess, }, }); }, diff --git a/app/assets/javascripts/pages/projects/registry/repositories/index.js b/app/assets/javascripts/pages/projects/registry/repositories/index.js index 73469e287ed..cdafe838994 100644 --- a/app/assets/javascripts/pages/projects/registry/repositories/index.js +++ b/app/assets/javascripts/pages/projects/registry/repositories/index.js @@ -1,9 +1,6 @@ -import initRegistryImages from '~/registry/list/index'; import registryExplorer from '~/registry/explorer/index'; document.addEventListener('DOMContentLoaded', () => { - initRegistryImages(); - const explorer = registryExplorer(); if (explorer) { diff --git a/app/assets/javascripts/registry/list/components/app.vue b/app/assets/javascripts/registry/list/components/app.vue deleted file mode 100644 index c555c2b04d1..00000000000 --- a/app/assets/javascripts/registry/list/components/app.vue +++ /dev/null @@ -1,153 +0,0 @@ - - diff --git a/app/assets/javascripts/registry/list/components/collapsible_container.vue b/app/assets/javascripts/registry/list/components/collapsible_container.vue deleted file mode 100644 index 9786a1a3f75..00000000000 --- a/app/assets/javascripts/registry/list/components/collapsible_container.vue +++ /dev/null @@ -1,155 +0,0 @@ - - - diff --git a/app/assets/javascripts/registry/list/components/group_empty_state.vue b/app/assets/javascripts/registry/list/components/group_empty_state.vue deleted file mode 100644 index 7885fd2146d..00000000000 --- a/app/assets/javascripts/registry/list/components/group_empty_state.vue +++ /dev/null @@ -1,46 +0,0 @@ - - diff --git a/app/assets/javascripts/registry/list/components/project_empty_state.vue b/app/assets/javascripts/registry/list/components/project_empty_state.vue deleted file mode 100644 index 900498ed03d..00000000000 --- a/app/assets/javascripts/registry/list/components/project_empty_state.vue +++ /dev/null @@ -1,133 +0,0 @@ - - diff --git a/app/assets/javascripts/registry/list/components/table_registry.vue b/app/assets/javascripts/registry/list/components/table_registry.vue deleted file mode 100644 index 4e14db7f578..00000000000 --- a/app/assets/javascripts/registry/list/components/table_registry.vue +++ /dev/null @@ -1,289 +0,0 @@ - - diff --git a/app/assets/javascripts/registry/list/constants.js b/app/assets/javascripts/registry/list/constants.js deleted file mode 100644 index e55ea9cc9d9..00000000000 --- a/app/assets/javascripts/registry/list/constants.js +++ /dev/null @@ -1,8 +0,0 @@ -import { __ } from '~/locale'; - -export const FETCH_REGISTRY_ERROR_MESSAGE = __( - 'Something went wrong while fetching the registry list.', -); -export const FETCH_REPOS_ERROR_MESSAGE = __('Something went wrong while fetching the projects.'); -export const DELETE_REPO_ERROR_MESSAGE = __('Something went wrong on our end.'); -export const DELETE_REGISTRY_ERROR_MESSAGE = __('Something went wrong on our end.'); diff --git a/app/assets/javascripts/registry/list/index.js b/app/assets/javascripts/registry/list/index.js deleted file mode 100644 index e8e54fda169..00000000000 --- a/app/assets/javascripts/registry/list/index.js +++ /dev/null @@ -1,44 +0,0 @@ -import Vue from 'vue'; -import registryApp from './components/app.vue'; -import Translate from '~/vue_shared/translate'; - -Vue.use(Translate); - -export default () => { - const el = document.getElementById('js-vue-registry-images'); - - if (!el) { - return null; - } - - return new Vue({ - el, - components: { - registryApp, - }, - data() { - const { dataset } = el; - return { - registryData: { - endpoint: dataset.endpoint, - characterError: Boolean(dataset.characterError), - helpPagePath: dataset.helpPagePath, - noContainersImage: dataset.noContainersImage, - containersErrorImage: dataset.containersErrorImage, - repositoryUrl: dataset.repositoryUrl, - isGroupPage: dataset.isGroupPage, - personalAccessTokensHelpLink: dataset.personalAccessTokensHelpLink, - registryHostUrlWithPort: dataset.registryHostUrlWithPort, - twoFactorAuthHelpLink: dataset.twoFactorAuthHelpLink, - }, - }; - }, - render(createElement) { - return createElement('registry-app', { - props: { - ...this.registryData, - }, - }); - }, - }); -}; diff --git a/app/assets/javascripts/registry/list/stores/actions.js b/app/assets/javascripts/registry/list/stores/actions.js deleted file mode 100644 index 6afba618486..00000000000 --- a/app/assets/javascripts/registry/list/stores/actions.js +++ /dev/null @@ -1,46 +0,0 @@ -import axios from '~/lib/utils/axios_utils'; -import createFlash from '~/flash'; -import * as types from './mutation_types'; -import { FETCH_REPOS_ERROR_MESSAGE, FETCH_REGISTRY_ERROR_MESSAGE } from '../constants'; - -export const fetchRepos = ({ commit, state }) => { - commit(types.TOGGLE_MAIN_LOADING); - - return axios - .get(state.endpoint) - .then(({ data }) => { - commit(types.TOGGLE_MAIN_LOADING); - commit(types.SET_REPOS_LIST, data); - }) - .catch(() => { - commit(types.TOGGLE_MAIN_LOADING); - createFlash(FETCH_REPOS_ERROR_MESSAGE); - }); -}; - -export const fetchList = ({ commit }, { repo, page }) => { - commit(types.TOGGLE_REGISTRY_LIST_LOADING, repo); - return axios - .get(repo.tagsPath, { params: { page } }) - .then(response => { - const { headers, data } = response; - - commit(types.TOGGLE_REGISTRY_LIST_LOADING, repo); - commit(types.SET_REGISTRY_LIST, { repo, resp: data, headers }); - }) - .catch(() => { - commit(types.TOGGLE_REGISTRY_LIST_LOADING, repo); - createFlash(FETCH_REGISTRY_ERROR_MESSAGE); - }); -}; - -export const deleteItem = (_, item) => axios.delete(item.destroyPath); -export const multiDeleteItems = (_, { path, items }) => - axios.delete(path, { params: { ids: items } }); - -export const setMainEndpoint = ({ commit }, data) => commit(types.SET_MAIN_ENDPOINT, data); -export const setIsDeleteDisabled = ({ commit }, data) => commit(types.SET_IS_DELETE_DISABLED, data); -export const toggleLoading = ({ commit }) => commit(types.TOGGLE_MAIN_LOADING); - -// prevent babel-plugin-rewire from generating an invalid default during karma tests -export default () => {}; diff --git a/app/assets/javascripts/registry/list/stores/getters.js b/app/assets/javascripts/registry/list/stores/getters.js deleted file mode 100644 index ac90bde1b2a..00000000000 --- a/app/assets/javascripts/registry/list/stores/getters.js +++ /dev/null @@ -1,6 +0,0 @@ -export const isLoading = state => state.isLoading; -export const repos = state => state.repos; -export const isDeleteDisabled = state => state.isDeleteDisabled; - -// prevent babel-plugin-rewire from generating an invalid default during karma tests -export default () => {}; diff --git a/app/assets/javascripts/registry/list/stores/index.js b/app/assets/javascripts/registry/list/stores/index.js deleted file mode 100644 index 1bb06bd6e81..00000000000 --- a/app/assets/javascripts/registry/list/stores/index.js +++ /dev/null @@ -1,15 +0,0 @@ -import Vue from 'vue'; -import Vuex from 'vuex'; -import * as actions from './actions'; -import * as getters from './getters'; -import mutations from './mutations'; -import createState from './state'; - -Vue.use(Vuex); - -export default new Vuex.Store({ - state: createState(), - actions, - getters, - mutations, -}); diff --git a/app/assets/javascripts/registry/list/stores/mutation_types.js b/app/assets/javascripts/registry/list/stores/mutation_types.js deleted file mode 100644 index 6740bfede1a..00000000000 --- a/app/assets/javascripts/registry/list/stores/mutation_types.js +++ /dev/null @@ -1,8 +0,0 @@ -export const SET_MAIN_ENDPOINT = 'SET_MAIN_ENDPOINT'; -export const SET_IS_DELETE_DISABLED = 'SET_IS_DELETE_DISABLED'; - -export const SET_REPOS_LIST = 'SET_REPOS_LIST'; -export const TOGGLE_MAIN_LOADING = 'TOGGLE_MAIN_LOADING'; - -export const SET_REGISTRY_LIST = 'SET_REGISTRY_LIST'; -export const TOGGLE_REGISTRY_LIST_LOADING = 'TOGGLE_REGISTRY_LIST_LOADING'; diff --git a/app/assets/javascripts/registry/list/stores/mutations.js b/app/assets/javascripts/registry/list/stores/mutations.js deleted file mode 100644 index 419de848883..00000000000 --- a/app/assets/javascripts/registry/list/stores/mutations.js +++ /dev/null @@ -1,57 +0,0 @@ -import * as types from './mutation_types'; -import { parseIntPagination, normalizeHeaders } from '~/lib/utils/common_utils'; - -export default { - [types.SET_MAIN_ENDPOINT](state, endpoint) { - state.endpoint = endpoint; - }, - - [types.SET_IS_DELETE_DISABLED](state, isDeleteDisabled) { - state.isDeleteDisabled = isDeleteDisabled; - }, - - [types.SET_REPOS_LIST](state, list) { - state.repos = list.map(el => ({ - canDelete: Boolean(el.destroy_path), - destroyPath: el.destroy_path, - id: el.id, - isLoading: false, - list: [], - location: el.location, - name: el.path, - tagsPath: el.tags_path, - projectId: el.project_id, - })); - }, - - [types.TOGGLE_MAIN_LOADING](state) { - state.isLoading = !state.isLoading; - }, - - [types.SET_REGISTRY_LIST](state, { repo, resp, headers }) { - const listToUpdate = state.repos.find(el => el.id === repo.id); - - const normalizedHeaders = normalizeHeaders(headers); - const pagination = parseIntPagination(normalizedHeaders); - - listToUpdate.pagination = pagination; - - listToUpdate.list = resp.map(element => ({ - tag: element.name, - revision: element.revision, - shortRevision: element.short_revision, - size: element.total_size, - layers: element.layers, - location: element.location, - createdAt: element.created_at, - destroyPath: element.destroy_path, - canDelete: Boolean(element.destroy_path), - })); - }, - - [types.TOGGLE_REGISTRY_LIST_LOADING](state, list) { - const listToUpdate = state.repos.find(el => el.id === list.id); - - listToUpdate.isLoading = !listToUpdate.isLoading; - }, -}; diff --git a/app/assets/javascripts/registry/list/stores/state.js b/app/assets/javascripts/registry/list/stores/state.js deleted file mode 100644 index 724c64b4994..00000000000 --- a/app/assets/javascripts/registry/list/stores/state.js +++ /dev/null @@ -1,27 +0,0 @@ -export default () => ({ - isLoading: false, - endpoint: '', // initial endpoint to fetch the repos list - isDeleteDisabled: false, // controls the delete buttons in the registry - /** - * Each object in `repos` has the following strucure: - * { - * name: String, - * isLoading: Boolean, - * tagsPath: String // endpoint to request the list - * destroyPath: String // endpoit to delete the repo - * list: Array // List of the registry images - * } - * - * Each registry image inside `list` has the following structure: - * { - * tag: String, - * revision: String - * shortRevision: String - * size: Number - * layers: Number - * createdAt: String - * destroyPath: String // endpoit to delete each image - * } - */ - repos: [], -}); diff --git a/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue b/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue index 012a4f4ad89..728f655d33d 100644 --- a/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue +++ b/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue @@ -106,17 +106,17 @@ export default {
{{ sprintf(__('Lock %{issuableDisplayName}'), { issuableDisplayName: issuableDisplayName }) }} - +
- - -`; diff --git a/spec/frontend/registry/list/components/app_spec.js b/spec/frontend/registry/list/components/app_spec.js deleted file mode 100644 index c2c220b2cd2..00000000000 --- a/spec/frontend/registry/list/components/app_spec.js +++ /dev/null @@ -1,149 +0,0 @@ -import { mount } from '@vue/test-utils'; -import { TEST_HOST } from 'helpers/test_constants'; -import registry from '~/registry/list/components/app.vue'; -import { reposServerResponse, parsedReposServerResponse } from '../mock_data'; - -describe('Registry List', () => { - let wrapper; - - const findCollapsibleContainer = () => wrapper.findAll({ name: 'CollapsibeContainerRegisty' }); - const findProjectEmptyState = () => wrapper.find({ name: 'ProjectEmptyState' }); - const findGroupEmptyState = () => wrapper.find({ name: 'GroupEmptyState' }); - const findSpinner = () => wrapper.find('.gl-spinner'); - const findCharacterErrorText = () => wrapper.find('.js-character-error-text'); - - const propsData = { - endpoint: `${TEST_HOST}/foo`, - helpPagePath: 'foo', - noContainersImage: 'foo', - containersErrorImage: 'foo', - repositoryUrl: 'foo', - registryHostUrlWithPort: 'foo', - personalAccessTokensHelpLink: 'foo', - twoFactorAuthHelpLink: 'foo', - }; - - const setMainEndpoint = jest.fn(); - const fetchRepos = jest.fn(); - const setIsDeleteDisabled = jest.fn(); - - const methods = { - setMainEndpoint, - fetchRepos, - setIsDeleteDisabled, - }; - - beforeEach(() => { - wrapper = mount(registry, { - propsData, - computed: { - repos() { - return parsedReposServerResponse; - }, - }, - methods, - }); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - describe('with data', () => { - it('should render a list of CollapsibeContainerRegisty', () => { - const containers = findCollapsibleContainer(); - expect(wrapper.vm.repos.length).toEqual(reposServerResponse.length); - expect(containers.length).toEqual(reposServerResponse.length); - }); - }); - - describe('without data', () => { - beforeEach(() => { - wrapper = mount(registry, { - propsData, - computed: { - repos() { - return []; - }, - }, - methods, - }); - }); - - it('should render project empty message', () => { - const projectEmptyState = findProjectEmptyState(); - expect(projectEmptyState.exists()).toBe(true); - }); - }); - - describe('while loading data', () => { - beforeEach(() => { - wrapper = mount(registry, { - propsData, - computed: { - repos() { - return []; - }, - isLoading() { - return true; - }, - }, - methods, - }); - }); - - it('should render a loading spinner', () => { - const spinner = findSpinner(); - expect(spinner.exists()).toBe(true); - }); - }); - - describe('invalid characters in path', () => { - beforeEach(() => { - wrapper = mount(registry, { - propsData: { - ...propsData, - characterError: true, - }, - computed: { - repos() { - return []; - }, - }, - methods, - }); - }); - - it('should render invalid characters error message', () => { - const characterErrorText = findCharacterErrorText(); - expect(characterErrorText.text()).toEqual( - 'We are having trouble connecting to Docker, which could be due to an issue with your project name or path. More Information', - ); - }); - }); - - describe('with groupId set', () => { - const isGroupPage = true; - - beforeEach(() => { - wrapper = mount(registry, { - propsData: { - ...propsData, - endpoint: '', - isGroupPage, - }, - methods, - }); - }); - - it('call the right vuex setters', () => { - expect(methods.setMainEndpoint).toHaveBeenLastCalledWith(''); - expect(methods.setIsDeleteDisabled).toHaveBeenLastCalledWith(true); - }); - - it('should render groups empty message', () => { - const groupEmptyState = findGroupEmptyState(wrapper); - expect(groupEmptyState.exists()).toBe(true); - }); - }); -}); diff --git a/spec/frontend/registry/list/components/collapsible_container_spec.js b/spec/frontend/registry/list/components/collapsible_container_spec.js deleted file mode 100644 index f969f0ba9ba..00000000000 --- a/spec/frontend/registry/list/components/collapsible_container_spec.js +++ /dev/null @@ -1,176 +0,0 @@ -import Vuex from 'vuex'; -import { mount, createLocalVue } from '@vue/test-utils'; -import createFlash from '~/flash'; -import Tracking from '~/tracking'; -import collapsibleComponent from '~/registry/list/components/collapsible_container.vue'; -import * as getters from '~/registry/list/stores/getters'; -import { repoPropsData } from '../mock_data'; - -jest.mock('~/flash.js'); - -const localVue = createLocalVue(); - -localVue.use(Vuex); - -describe('collapsible registry container', () => { - let wrapper; - let store; - - const findDeleteBtn = () => wrapper.find('.js-remove-repo'); - const findContainerImageTags = () => wrapper.find('.container-image-tags'); - const findToggleRepos = () => wrapper.findAll('.js-toggle-repo'); - const findDeleteModal = () => wrapper.find({ ref: 'deleteModal' }); - - const mountWithStore = config => - mount(collapsibleComponent, { - ...config, - store, - localVue, - }); - - beforeEach(() => { - createFlash.mockClear(); - store = new Vuex.Store({ - state: { - isDeleteDisabled: false, - }, - getters, - }); - - wrapper = mountWithStore({ - propsData: { - repo: repoPropsData, - }, - }); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - describe('toggle', () => { - beforeEach(() => { - const fetchList = jest.fn(); - wrapper.setMethods({ fetchList }); - return wrapper.vm.$nextTick(); - }); - - const expectIsClosed = () => { - const container = findContainerImageTags(); - expect(container.exists()).toBe(false); - expect(wrapper.vm.iconName).toEqual('angle-right'); - }; - - it('should be closed by default', () => { - expectIsClosed(); - }); - - it('should be open when user clicks on closed repo', () => { - const toggleRepos = findToggleRepos(); - toggleRepos.at(0).trigger('click'); - return wrapper.vm.$nextTick().then(() => { - const container = findContainerImageTags(); - expect(container.exists()).toBe(true); - expect(wrapper.vm.fetchList).toHaveBeenCalled(); - }); - }); - - it('should be closed when the user clicks on an opened repo', () => { - const toggleRepos = findToggleRepos(); - toggleRepos.at(0).trigger('click'); - return wrapper.vm.$nextTick().then(() => { - toggleRepos.at(0).trigger('click'); - wrapper.vm.$nextTick(() => { - expectIsClosed(); - }); - }); - }); - }); - - describe('delete repo', () => { - beforeEach(() => { - const deleteItem = jest.fn().mockResolvedValue(); - const fetchRepos = jest.fn().mockResolvedValue(); - wrapper.setMethods({ deleteItem, fetchRepos }); - }); - - it('should be possible to delete a repo', () => { - const deleteBtn = findDeleteBtn(); - expect(deleteBtn.exists()).toBe(true); - }); - - it('should call deleteItem when confirming deletion', () => { - wrapper.vm.handleDeleteRepository(); - expect(wrapper.vm.deleteItem).toHaveBeenCalledWith(wrapper.vm.repo); - }); - - it('should show a flash with a success notice', () => - wrapper.vm.handleDeleteRepository().then(() => { - expect(wrapper.vm.deleteImageConfirmationMessage).toContain(wrapper.vm.repo.name); - expect(createFlash).toHaveBeenCalledWith( - wrapper.vm.deleteImageConfirmationMessage, - 'notice', - ); - })); - - it('should show an error when there is API error', () => { - const deleteItem = jest.fn().mockRejectedValue('error'); - wrapper.setMethods({ deleteItem }); - return wrapper.vm.handleDeleteRepository().then(() => { - expect(createFlash).toHaveBeenCalled(); - }); - }); - }); - - describe('disabled delete', () => { - beforeEach(() => { - store = new Vuex.Store({ - state: { - isDeleteDisabled: true, - }, - getters, - }); - wrapper = mountWithStore({ - propsData: { - repo: repoPropsData, - }, - }); - }); - - it('should not render delete button', () => { - const deleteBtn = findDeleteBtn(); - expect(deleteBtn.exists()).toBe(false); - }); - }); - - describe('tracking', () => { - const testTrackingCall = action => { - expect(Tracking.event).toHaveBeenCalledWith(undefined, action, { - label: 'registry_repository_delete', - }); - }; - - beforeEach(() => { - jest.spyOn(Tracking, 'event'); - wrapper.vm.deleteItem = jest.fn().mockResolvedValue(); - wrapper.vm.fetchRepos = jest.fn(); - }); - - it('send an event when delete button is clicked', () => { - const deleteBtn = findDeleteBtn(); - deleteBtn.trigger('click'); - testTrackingCall('click_button'); - }); - it('send an event when cancel is pressed on modal', () => { - const deleteModal = findDeleteModal(); - deleteModal.vm.$emit('cancel'); - testTrackingCall('cancel_delete'); - }); - it('send an event when confirm is clicked on modal', () => { - const deleteModal = findDeleteModal(); - deleteModal.vm.$emit('ok'); - - testTrackingCall('confirm_delete'); - }); - }); -}); diff --git a/spec/frontend/registry/list/components/group_empty_state_spec.js b/spec/frontend/registry/list/components/group_empty_state_spec.js deleted file mode 100644 index 7541c3d459c..00000000000 --- a/spec/frontend/registry/list/components/group_empty_state_spec.js +++ /dev/null @@ -1,23 +0,0 @@ -import { mount } from '@vue/test-utils'; -import groupEmptyState from '~/registry/list/components/group_empty_state.vue'; - -describe('Registry Group Empty state', () => { - let wrapper; - - beforeEach(() => { - wrapper = mount(groupEmptyState, { - propsData: { - noContainersImage: 'imageUrl', - helpPagePath: 'help', - }, - }); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - it('to match the default snapshot', () => { - expect(wrapper.element).toMatchSnapshot(); - }); -}); diff --git a/spec/frontend/registry/list/components/project_empty_state_spec.js b/spec/frontend/registry/list/components/project_empty_state_spec.js deleted file mode 100644 index d29b9e47233..00000000000 --- a/spec/frontend/registry/list/components/project_empty_state_spec.js +++ /dev/null @@ -1,27 +0,0 @@ -import { mount } from '@vue/test-utils'; -import projectEmptyState from '~/registry/list/components/project_empty_state.vue'; - -describe('Registry Project Empty state', () => { - let wrapper; - - beforeEach(() => { - wrapper = mount(projectEmptyState, { - propsData: { - noContainersImage: 'imageUrl', - helpPagePath: 'help', - repositoryUrl: 'url', - twoFactorAuthHelpLink: 'help_link', - personalAccessTokensHelpLink: 'personal_token', - registryHostUrlWithPort: 'host', - }, - }); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - it('to match the default snapshot', () => { - expect(wrapper.element).toMatchSnapshot(); - }); -}); diff --git a/spec/frontend/registry/list/components/table_registry_spec.js b/spec/frontend/registry/list/components/table_registry_spec.js deleted file mode 100644 index b13797929dd..00000000000 --- a/spec/frontend/registry/list/components/table_registry_spec.js +++ /dev/null @@ -1,373 +0,0 @@ -import Vuex from 'vuex'; -import { mount, createLocalVue } from '@vue/test-utils'; -import createFlash from '~/flash'; -import Tracking from '~/tracking'; -import tableRegistry from '~/registry/list/components/table_registry.vue'; -import { repoPropsData } from '../mock_data'; -import * as getters from '~/registry/list/stores/getters'; - -jest.mock('~/flash'); - -const [firstImage, secondImage] = repoPropsData.list; - -const localVue = createLocalVue(); - -localVue.use(Vuex); - -describe('table registry', () => { - let wrapper; - let store; - - const findSelectAllCheckbox = () => wrapper.find('.js-select-all-checkbox > input'); - const findSelectCheckboxes = () => wrapper.findAll('.js-select-checkbox > input'); - const findDeleteButton = () => wrapper.find({ ref: 'bulkDeleteButton' }); - const findDeleteButtonsRow = () => wrapper.findAll('.js-delete-registry-row'); - const findPagination = () => wrapper.find('.js-registry-pagination'); - const findDeleteModal = () => wrapper.find({ ref: 'deleteModal' }); - const findImageId = () => wrapper.find({ ref: 'imageId' }); - const bulkDeletePath = 'path'; - - const mountWithStore = config => - mount(tableRegistry, { - ...config, - store, - localVue, - }); - - beforeEach(() => { - store = new Vuex.Store({ - state: { - isDeleteDisabled: false, - }, - getters, - }); - - wrapper = mountWithStore({ - propsData: { - repo: repoPropsData, - canDeleteRepo: true, - }, - }); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - describe('rendering', () => { - it('should render a table with the registry list', () => { - expect(wrapper.findAll('.registry-image-row').length).toEqual(repoPropsData.list.length); - }); - - it('should render registry tag', () => { - const tds = wrapper.findAll('.registry-image-row td'); - expect(tds.at(0).classes()).toContain('check'); - expect(tds.at(1).html()).toContain(repoPropsData.list[0].tag); - expect(tds.at(2).html()).toContain(repoPropsData.list[0].shortRevision); - expect(tds.at(3).html()).toContain(repoPropsData.list[0].layers); - expect(tds.at(3).html()).toContain(repoPropsData.list[0].size); - expect(tds.at(4).html()).toContain(wrapper.vm.timeFormatted(repoPropsData.list[0].createdAt)); - }); - - it('should have a label called Image ID', () => { - const label = findImageId(); - expect(label.element).toMatchInlineSnapshot(` - - Image ID - - `); - }); - }); - - describe('multi select', () => { - it('selecting a row should enable delete button', () => { - const deleteBtn = findDeleteButton(); - const checkboxes = findSelectCheckboxes(); - - expect(deleteBtn.attributes('disabled')).toBe('disabled'); - - checkboxes.at(0).trigger('click'); - return wrapper.vm.$nextTick().then(() => { - expect(deleteBtn.attributes('disabled')).toEqual(undefined); - }); - }); - - it('selecting all checkbox should select all rows and enable delete button', () => { - const selectAll = findSelectAllCheckbox(); - selectAll.trigger('click'); - - return wrapper.vm.$nextTick().then(() => { - const checkboxes = findSelectCheckboxes(); - const checked = checkboxes.filter(w => w.element.checked); - expect(checked.length).toBe(checkboxes.length); - }); - }); - - it('deselecting select all checkbox should deselect all rows and disable delete button', () => { - const checkboxes = findSelectCheckboxes(); - const selectAll = findSelectAllCheckbox(); - selectAll.trigger('click'); - selectAll.trigger('click'); - - return wrapper.vm.$nextTick().then(() => { - const checked = checkboxes.filter(w => !w.element.checked); - expect(checked.length).toBe(checkboxes.length); - }); - }); - - it('should delete multiple items when multiple items are selected', () => { - const multiDeleteItems = jest.fn().mockResolvedValue(); - wrapper.setMethods({ multiDeleteItems }); - - return wrapper.vm - .$nextTick() - .then(() => { - const selectAll = findSelectAllCheckbox(); - selectAll.trigger('click'); - return wrapper.vm.$nextTick(); - }) - .then(() => { - const deleteBtn = findDeleteButton(); - expect(wrapper.vm.selectedItems).toEqual([0, 1]); - expect(deleteBtn.attributes('disabled')).toEqual(undefined); - wrapper.setData({ itemsToBeDeleted: [...wrapper.vm.selectedItems] }); - wrapper.vm.handleMultipleDelete(); - expect(wrapper.vm.selectedItems).toEqual([]); - expect(wrapper.vm.itemsToBeDeleted).toEqual([]); - expect(wrapper.vm.multiDeleteItems).toHaveBeenCalledWith({ - path: bulkDeletePath, - items: [firstImage.tag, secondImage.tag], - }); - }); - }); - - it('should show an error message if bulkDeletePath is not set', () => { - const showError = jest.fn(); - wrapper.setMethods({ showError }); - wrapper.setProps({ - repo: { - ...repoPropsData, - tagsPath: null, - }, - }); - wrapper.vm.handleMultipleDelete(); - expect(createFlash).toHaveBeenCalled(); - }); - }); - - describe('delete registry', () => { - beforeEach(() => { - wrapper.setData({ selectedItems: [0] }); - return wrapper.vm.$nextTick(); - }); - - it('should be possible to delete a registry', () => { - const deleteBtn = findDeleteButton(); - const deleteBtns = findDeleteButtonsRow(); - expect(wrapper.vm.selectedItems).toEqual([0]); - expect(deleteBtn).toBeDefined(); - expect(deleteBtn.attributes('disable')).toBe(undefined); - expect(deleteBtns.is('button')).toBe(true); - }); - - it('should allow deletion row by row', () => { - const deleteBtns = findDeleteButtonsRow(); - const deleteSingleItem = jest.fn(); - const deleteItem = jest.fn().mockResolvedValue(); - wrapper.setMethods({ deleteSingleItem, deleteItem }); - return wrapper.vm.$nextTick().then(() => { - deleteBtns.at(0).trigger('click'); - expect(wrapper.vm.deleteSingleItem).toHaveBeenCalledWith(0); - wrapper.vm.handleSingleDelete(1); - expect(wrapper.vm.deleteItem).toHaveBeenCalledWith(1); - }); - }); - }); - - describe('modal event handlers', () => { - beforeEach(() => { - wrapper.vm.handleSingleDelete = jest.fn(); - wrapper.vm.handleMultipleDelete = jest.fn(); - }); - it('on ok when one item is selected should call singleDelete', () => { - wrapper.setData({ itemsToBeDeleted: [0] }); - wrapper.vm.onDeletionConfirmed(); - - expect(wrapper.vm.handleSingleDelete).toHaveBeenCalledWith(repoPropsData.list[0]); - expect(wrapper.vm.handleMultipleDelete).not.toHaveBeenCalled(); - }); - it('on ok when multiple items are selected should call multiDelete', () => { - wrapper.setData({ itemsToBeDeleted: [0, 1, 2] }); - wrapper.vm.onDeletionConfirmed(); - - expect(wrapper.vm.handleMultipleDelete).toHaveBeenCalled(); - expect(wrapper.vm.handleSingleDelete).not.toHaveBeenCalled(); - }); - }); - - describe('pagination', () => { - const repo = { - repoPropsData, - pagination: { - total: 20, - perPage: 2, - nextPage: 2, - }, - }; - - beforeEach(() => { - wrapper = mount(tableRegistry, { - propsData: { - repo, - }, - }); - }); - - it('should exist', () => { - const pagination = findPagination(); - expect(pagination.exists()).toBe(true); - }); - it('should be visible when pagination is needed', () => { - const pagination = findPagination(); - expect(pagination.isVisible()).toBe(true); - wrapper.setProps({ - repo: { - pagination: { - total: 0, - perPage: 10, - }, - }, - }); - expect(wrapper.vm.shouldRenderPagination).toBe(false); - }); - it('should have a change function that update the list when run', () => { - const fetchList = jest.fn().mockResolvedValue(); - wrapper.setMethods({ fetchList }); - wrapper.vm.onPageChange(1); - expect(wrapper.vm.fetchList).toHaveBeenCalledWith({ repo, page: 1 }); - }); - }); - - describe('modal content', () => { - it('should show the singular title and image name when deleting a single image', () => { - wrapper.setData({ selectedItems: [1, 2, 3] }); - wrapper.vm.deleteSingleItem(0); - expect(wrapper.vm.modalAction).toBe('Remove tag'); - expect(wrapper.vm.modalDescription).toContain(firstImage.tag); - }); - - it('should show the plural title and image count when deleting more than one image', () => { - wrapper.setData({ selectedItems: [1, 2] }); - wrapper.vm.deleteMultipleItems(); - - expect(wrapper.vm.modalAction).toBe('Remove tags'); - expect(wrapper.vm.modalDescription).toContain('2 tags'); - }); - }); - - describe('disabled delete', () => { - beforeEach(() => { - store = new Vuex.Store({ - state: { - isDeleteDisabled: true, - }, - getters, - }); - wrapper = mountWithStore({ - propsData: { - repo: repoPropsData, - canDeleteRepo: false, - }, - }); - }); - - it('should not render select all', () => { - const selectAll = findSelectAllCheckbox(); - expect(selectAll.exists()).toBe(false); - }); - - it('should not render any select checkbox', () => { - const selects = findSelectCheckboxes(); - expect(selects.length).toBe(0); - }); - - it('should not render delete registry button', () => { - const deleteBtn = findDeleteButton(); - expect(deleteBtn.exists()).toBe(false); - }); - - it('should not render delete row button', () => { - const deleteBtns = findDeleteButtonsRow(); - expect(deleteBtns.length).toBe(0); - }); - }); - - describe('event tracking', () => { - const testTrackingCall = (action, label = 'registry_tag_delete') => { - expect(Tracking.event).toHaveBeenCalledWith(undefined, action, { label, property: 'foo' }); - }; - - beforeEach(() => { - jest.spyOn(Tracking, 'event'); - wrapper.vm.handleSingleDelete = jest.fn(); - wrapper.vm.handleMultipleDelete = jest.fn(); - }); - - describe('single tag delete', () => { - beforeEach(() => { - wrapper.setData({ itemsToBeDeleted: [0] }); - return wrapper.vm.$nextTick(); - }); - - it('send an event when delete button is clicked', () => { - const deleteBtn = findDeleteButtonsRow(); - deleteBtn.at(0).trigger('click'); - - testTrackingCall('click_button'); - }); - - it('send an event when cancel is pressed on modal', () => { - const deleteModal = findDeleteModal(); - deleteModal.vm.$emit('cancel'); - - testTrackingCall('cancel_delete'); - }); - - it('send an event when confirm is clicked on modal', () => { - const deleteModal = findDeleteModal(); - deleteModal.vm.$emit('ok'); - - testTrackingCall('confirm_delete'); - }); - }); - - describe('bulk tag delete', () => { - beforeEach(() => { - const items = [0, 1, 2]; - wrapper.setData({ itemsToBeDeleted: items, selectedItems: items }); - return wrapper.vm.$nextTick(); - }); - - it('send an event when delete button is clicked', () => { - const deleteBtn = findDeleteButton(); - deleteBtn.vm.$emit('click'); - - testTrackingCall('click_button', 'bulk_registry_tag_delete'); - }); - - it('send an event when cancel is pressed on modal', () => { - const deleteModal = findDeleteModal(); - deleteModal.vm.$emit('cancel'); - - testTrackingCall('cancel_delete', 'bulk_registry_tag_delete'); - }); - - it('send an event when confirm is clicked on modal', () => { - const deleteModal = findDeleteModal(); - deleteModal.vm.$emit('ok'); - - testTrackingCall('confirm_delete', 'bulk_registry_tag_delete'); - }); - }); - }); -}); diff --git a/spec/frontend/registry/list/mock_data.js b/spec/frontend/registry/list/mock_data.js deleted file mode 100644 index 130ab298e89..00000000000 --- a/spec/frontend/registry/list/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/frontend/registry/list/stores/actions_spec.js b/spec/frontend/registry/list/stores/actions_spec.js deleted file mode 100644 index 2fc363e9a4f..00000000000 --- a/spec/frontend/registry/list/stores/actions_spec.js +++ /dev/null @@ -1,203 +0,0 @@ -import MockAdapter from 'axios-mock-adapter'; -import { TEST_HOST } from 'helpers/test_constants'; -import testAction from 'helpers/vuex_action_helper'; -import axios from '~/lib/utils/axios_utils'; -import * as actions from '~/registry/list/stores/actions'; -import * as types from '~/registry/list/stores/mutation_types'; -import createFlash from '~/flash'; - -import { - reposServerResponse, - registryServerResponse, - parsedReposServerResponse, -} from '../mock_data'; - -jest.mock('~/flash.js'); - -describe('Actions Registry Store', () => { - let mock; - let state; - - beforeEach(() => { - mock = new MockAdapter(axios); - state = { - endpoint: `${TEST_HOST}/endpoint.json`, - }; - }); - - afterEach(() => { - mock.restore(); - }); - - describe('fetchRepos', () => { - beforeEach(() => { - mock.onGet(`${TEST_HOST}/endpoint.json`).replyOnce(200, reposServerResponse, {}); - }); - - it('should set received repos', done => { - testAction( - actions.fetchRepos, - null, - state, - [ - { type: types.TOGGLE_MAIN_LOADING }, - { type: types.TOGGLE_MAIN_LOADING }, - { type: types.SET_REPOS_LIST, payload: reposServerResponse }, - ], - [], - done, - ); - }); - - it('should create flash on API error', done => { - testAction( - actions.fetchRepos, - null, - { - endpoint: null, - }, - [{ type: types.TOGGLE_MAIN_LOADING }, { type: types.TOGGLE_MAIN_LOADING }], - [], - () => { - expect(createFlash).toHaveBeenCalled(); - done(); - }, - ); - }); - }); - - describe('fetchList', () => { - let repo; - beforeEach(() => { - state.repos = parsedReposServerResponse; - [, repo] = state.repos; - }); - - it('should set received list', done => { - mock.onGet(repo.tagsPath).replyOnce(200, registryServerResponse, {}); - testAction( - actions.fetchList, - { repo }, - state, - [ - { 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: expect.anything(), - }, - }, - ], - [], - done, - ); - }); - - it('should create flash on API error', done => { - mock.onGet(repo.tagsPath).replyOnce(400); - const updatedRepo = { - ...repo, - tagsPath: null, - }; - testAction( - actions.fetchList, - { - repo: updatedRepo, - }, - state, - [ - { type: types.TOGGLE_REGISTRY_LIST_LOADING, payload: updatedRepo }, - { type: types.TOGGLE_REGISTRY_LIST_LOADING, payload: updatedRepo }, - ], - [], - () => { - expect(createFlash).toHaveBeenCalled(); - done(); - }, - ); - }); - }); - - describe('setMainEndpoint', () => { - it('should commit set main endpoint', done => { - testAction( - actions.setMainEndpoint, - 'endpoint', - state, - [{ type: types.SET_MAIN_ENDPOINT, payload: 'endpoint' }], - [], - done, - ); - }); - }); - - describe('setIsDeleteDisabled', () => { - it('should commit set is delete disabled', done => { - testAction( - actions.setIsDeleteDisabled, - true, - state, - [{ type: types.SET_IS_DELETE_DISABLED, payload: true }], - [], - done, - ); - }); - }); - - describe('toggleLoading', () => { - it('should commit toggle main loading', done => { - testAction( - actions.toggleLoading, - null, - state, - [{ type: types.TOGGLE_MAIN_LOADING }], - [], - done, - ); - }); - }); - - describe('deleteItem and multiDeleteItems', () => { - let deleted; - const destroyPath = `${TEST_HOST}/mygroup/myproject/container_registry/1.json`; - - const expectDelete = done => { - expect(mock.history.delete.length).toBe(1); - expect(deleted).toBe(true); - done(); - }; - - beforeEach(() => { - deleted = false; - mock.onDelete(destroyPath).replyOnce(() => { - deleted = true; - return [200]; - }); - }); - - it('deleteItem should perform DELETE request on destroyPath', done => { - testAction( - actions.deleteItem, - { - destroyPath, - }, - state, - ) - .then(() => { - expectDelete(done); - }) - .catch(done.fail); - }); - - it('multiDeleteItems should perform DELETE request on path', done => { - testAction(actions.multiDeleteItems, { path: destroyPath, items: [1] }, state) - .then(() => { - expectDelete(done); - }) - .catch(done.fail); - }); - }); -}); diff --git a/spec/frontend/registry/list/stores/getters_spec.js b/spec/frontend/registry/list/stores/getters_spec.js deleted file mode 100644 index c8d054b226b..00000000000 --- a/spec/frontend/registry/list/stores/getters_spec.js +++ /dev/null @@ -1,52 +0,0 @@ -import * as getters from '~/registry/list/stores/getters'; - -describe('Getters Registry Store', () => { - let state; - - beforeEach(() => { - state = { - isLoading: false, - endpoint: '/root/empty-project/container_registry.json', - isDeleteDisabled: false, - repos: [ - { - canDelete: true, - destroyPath: 'bar', - id: '134', - isLoading: false, - list: [], - location: 'foo', - name: 'gitlab-org/omnibus-gitlab/foo', - tagsPath: 'foo', - }, - { - canDelete: true, - destroyPath: 'bar', - id: '123', - isLoading: false, - list: [], - location: 'foo', - name: 'gitlab-org/omnibus-gitlab', - tagsPath: 'foo', - }, - ], - }; - }); - - describe('isLoading', () => { - it('should return the isLoading property', () => { - expect(getters.isLoading(state)).toEqual(state.isLoading); - }); - }); - - describe('repos', () => { - it('should return the repos', () => { - expect(getters.repos(state)).toEqual(state.repos); - }); - }); - describe('isDeleteDisabled', () => { - it('should return isDeleteDisabled', () => { - expect(getters.isDeleteDisabled(state)).toEqual(state.isDeleteDisabled); - }); - }); -}); diff --git a/spec/frontend/registry/list/stores/mutations_spec.js b/spec/frontend/registry/list/stores/mutations_spec.js deleted file mode 100644 index f894f688c1f..00000000000 --- a/spec/frontend/registry/list/stores/mutations_spec.js +++ /dev/null @@ -1,94 +0,0 @@ -import mutations from '~/registry/list/stores/mutations'; -import * as types from '~/registry/list/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.endpoint).toEqual(expectedState.endpoint); - }); - }); - - describe('SET_IS_DELETE_DISABLED', () => { - it('should set the is delete disabled', () => { - const expectedState = Object.assign({}, mockState, { isDeleteDisabled: true }); - mutations[types.SET_IS_DELETE_DISABLED](mockState, true); - - expect(mockState.isDeleteDisabled).toEqual(expectedState.isDeleteDisabled); - }); - }); - - 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/boards/board_card_spec.js b/spec/javascripts/boards/board_card_spec.js deleted file mode 100644 index 2b0eee8b95d..00000000000 --- a/spec/javascripts/boards/board_card_spec.js +++ /dev/null @@ -1,215 +0,0 @@ -/* global List */ -/* global ListAssignee */ -/* global ListLabel */ - -import Vue from 'vue'; -import MockAdapter from 'axios-mock-adapter'; -import axios from '~/lib/utils/axios_utils'; - -import eventHub from '~/boards/eventhub'; -import '~/boards/models/label'; -import '~/boards/models/assignee'; -import '~/boards/models/list'; -import store from '~/boards/stores'; -import boardsStore from '~/boards/stores/boards_store'; -import boardCard from '~/boards/components/board_card.vue'; -import { listObj, boardsMockInterceptor, setMockEndpoints } from './mock_data'; - -describe('Board card', () => { - let vm; - let mock; - - beforeEach(done => { - mock = new MockAdapter(axios); - mock.onAny().reply(boardsMockInterceptor); - setMockEndpoints(); - - boardsStore.create(); - boardsStore.detail.issue = {}; - - const BoardCardComp = Vue.extend(boardCard); - const list = new List(listObj); - const label1 = new ListLabel({ - id: 3, - title: 'testing 123', - color: '#000cff', - text_color: 'white', - description: 'test', - }); - - setTimeout(() => { - list.issues[0].labels.push(label1); - - vm = new BoardCardComp({ - store, - propsData: { - list, - issue: list.issues[0], - issueLinkBase: '/', - disabled: false, - index: 0, - rootPath: '/', - }, - }).$mount(); - done(); - }, 0); - }); - - afterEach(() => { - mock.restore(); - }); - - it('returns false when detailIssue is empty', () => { - expect(vm.issueDetailVisible).toBe(false); - }); - - it('returns true when detailIssue is equal to card issue', () => { - boardsStore.detail.issue = vm.issue; - - 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); - }); - - it('does not add user-can-drag class disabled', done => { - vm.disabled = true; - - setTimeout(() => { - expect(vm.$el.classList.contains('user-can-drag')).toBe(false); - done(); - }, 0); - }); - - it('does not add disabled class', () => { - expect(vm.$el.classList.contains('is-disabled')).toBe(false); - }); - - it('adds disabled class is disabled is true', done => { - vm.disabled = true; - - setTimeout(() => { - expect(vm.$el.classList.contains('is-disabled')).toBe(true); - done(); - }, 0); - }); - - describe('mouse events', () => { - const triggerEvent = (eventName, el = vm.$el) => { - const event = document.createEvent('MouseEvents'); - event.initMouseEvent( - eventName, - true, - true, - window, - 1, - 0, - 0, - 0, - 0, - false, - false, - false, - false, - 0, - null, - ); - - el.dispatchEvent(event); - }; - - it('sets showDetail to true on mousedown', () => { - triggerEvent('mousedown'); - - expect(vm.showDetail).toBe(true); - }); - - it('sets showDetail to false on mousemove', () => { - triggerEvent('mousedown'); - - expect(vm.showDetail).toBe(true); - - triggerEvent('mousemove'); - - expect(vm.showDetail).toBe(false); - }); - - it('does not set detail issue if showDetail is false', () => { - expect(boardsStore.detail.issue).toEqual({}); - }); - - it('does not set detail issue if link is clicked', () => { - triggerEvent('mouseup', vm.$el.querySelector('a')); - - expect(boardsStore.detail.issue).toEqual({}); - }); - - it('does not set detail issue if img is clicked', done => { - vm.issue.assignees = [ - new ListAssignee({ - id: 1, - name: 'testing 123', - username: 'test', - avatar: 'test_image', - }), - ]; - - Vue.nextTick(() => { - triggerEvent('mouseup', vm.$el.querySelector('img')); - - expect(boardsStore.detail.issue).toEqual({}); - - done(); - }); - }); - - it('does not set detail issue if showDetail is false after mouseup', () => { - triggerEvent('mouseup'); - - expect(boardsStore.detail.issue).toEqual({}); - }); - - it('sets detail issue to card issue on mouse up', () => { - spyOn(eventHub, '$emit'); - - triggerEvent('mousedown'); - triggerEvent('mouseup'); - - expect(eventHub.$emit).toHaveBeenCalledWith('newDetailIssue', vm.issue, undefined); - expect(boardsStore.detail.list).toEqual(vm.list); - }); - - it('adds active class if detail issue is set', done => { - vm.detailIssue.issue = vm.issue; - - Vue.nextTick() - .then(() => { - expect(vm.$el.classList.contains('is-active')).toBe(true); - }) - .then(done) - .catch(done.fail); - }); - - it('resets detail issue to empty if already set', () => { - spyOn(eventHub, '$emit'); - - boardsStore.detail.issue = vm.issue; - - triggerEvent('mousedown'); - triggerEvent('mouseup'); - - expect(eventHub.$emit).toHaveBeenCalledWith('clearDetailIssue', undefined); - }); - }); -}); diff --git a/spec/javascripts/boards/board_list_spec.js b/spec/javascripts/boards/board_list_spec.js deleted file mode 100644 index b4e1d3b97b1..00000000000 --- a/spec/javascripts/boards/board_list_spec.js +++ /dev/null @@ -1,262 +0,0 @@ -/* global List */ - -import Vue from 'vue'; -import eventHub from '~/boards/eventhub'; -import createComponent from './board_list_common_spec'; -import waitForPromises from '../helpers/wait_for_promises'; - -import '~/boards/models/list'; - -describe('Board list component', () => { - let mock; - let component; - let getIssues; - function generateIssues(compWrapper) { - for (let i = 1; i < 20; i += 1) { - const issue = Object.assign({}, compWrapper.list.issues[0]); - issue.id += i; - compWrapper.list.issues.push(issue); - } - } - - describe('When Expanded', () => { - beforeEach(done => { - getIssues = spyOn(List.prototype, 'getIssues').and.returnValue(new Promise(() => {})); - ({ mock, component } = createComponent({ done })); - }); - - afterEach(() => { - mock.restore(); - component.$destroy(); - }); - - it('loads first page of issues', done => { - waitForPromises() - .then(() => { - expect(getIssues).toHaveBeenCalled(); - }) - .then(done) - .catch(done.fail); - }); - - it('renders component', () => { - expect(component.$el.classList.contains('board-list-component')).toBe(true); - }); - - it('renders loading icon', done => { - component.loading = true; - - Vue.nextTick(() => { - expect(component.$el.querySelector('.board-list-loading')).not.toBeNull(); - - done(); - }); - }); - - it('renders issues', () => { - expect(component.$el.querySelectorAll('.board-card').length).toBe(1); - }); - - it('sets data attribute with issue id', () => { - expect(component.$el.querySelector('.board-card').getAttribute('data-issue-id')).toBe('1'); - }); - - it('shows new issue form', done => { - component.toggleForm(); - - Vue.nextTick(() => { - expect(component.$el.querySelector('.board-new-issue-form')).not.toBeNull(); - - expect(component.$el.querySelector('.is-smaller')).not.toBeNull(); - - done(); - }); - }); - - it('shows new issue form after eventhub event', done => { - eventHub.$emit(`hide-issue-form-${component.list.id}`); - - Vue.nextTick(() => { - expect(component.$el.querySelector('.board-new-issue-form')).not.toBeNull(); - - expect(component.$el.querySelector('.is-smaller')).not.toBeNull(); - - done(); - }); - }); - - it('does not show new issue form for closed list', done => { - component.list.type = 'closed'; - component.toggleForm(); - - Vue.nextTick(() => { - expect(component.$el.querySelector('.board-new-issue-form')).toBeNull(); - - done(); - }); - }); - - it('shows count list item', done => { - component.showCount = true; - - Vue.nextTick(() => { - expect(component.$el.querySelector('.board-list-count')).not.toBeNull(); - - expect(component.$el.querySelector('.board-list-count').textContent.trim()).toBe( - 'Showing all issues', - ); - - done(); - }); - }); - - it('sets data attribute with invalid id', done => { - component.showCount = true; - - Vue.nextTick(() => { - expect(component.$el.querySelector('.board-list-count').getAttribute('data-issue-id')).toBe( - '-1', - ); - - done(); - }); - }); - - it('shows how many more issues to load', done => { - component.showCount = true; - component.list.issuesSize = 20; - - Vue.nextTick(() => { - expect(component.$el.querySelector('.board-list-count').textContent.trim()).toBe( - 'Showing 1 of 20 issues', - ); - - done(); - }); - }); - - it('loads more issues after scrolling', done => { - spyOn(component.list, 'nextPage'); - component.$refs.list.style.height = '100px'; - component.$refs.list.style.overflow = 'scroll'; - generateIssues(component); - - Vue.nextTick(() => { - component.$refs.list.scrollTop = 20000; - - waitForPromises() - .then(() => { - expect(component.list.nextPage).toHaveBeenCalled(); - }) - .then(done) - .catch(done.fail); - }); - }); - - it('does not load issues if already loading', done => { - component.list.nextPage = spyOn(component.list, 'nextPage').and.returnValue( - new Promise(() => {}), - ); - - component.onScroll(); - component.onScroll(); - - waitForPromises() - .then(() => { - expect(component.list.nextPage).toHaveBeenCalledTimes(1); - }) - .then(done) - .catch(done.fail); - }); - - it('shows loading more spinner', done => { - component.showCount = true; - component.list.loadingMore = true; - - Vue.nextTick(() => { - expect(component.$el.querySelector('.board-list-count .gl-spinner')).not.toBeNull(); - - done(); - }); - }); - }); - - describe('When Collapsed', () => { - beforeEach(done => { - getIssues = spyOn(List.prototype, 'getIssues').and.returnValue(new Promise(() => {})); - ({ mock, component } = createComponent({ - done, - listProps: { type: 'closed', collapsed: true, issuesSize: 50 }, - })); - generateIssues(component); - component.scrollHeight = spyOn(component, 'scrollHeight').and.returnValue(0); - }); - - afterEach(() => { - mock.restore(); - component.$destroy(); - }); - - it('does not load all issues', done => { - waitForPromises() - .then(() => { - // Initial getIssues from list constructor - expect(getIssues).toHaveBeenCalledTimes(1); - }) - .then(done) - .catch(done.fail); - }); - }); - - describe('max issue count warning', () => { - beforeEach(done => { - ({ mock, component } = createComponent({ - done, - listProps: { type: 'closed', collapsed: true, issuesSize: 50 }, - })); - }); - - afterEach(() => { - mock.restore(); - component.$destroy(); - }); - - describe('when issue count exceeds max issue count', () => { - it('sets background to bg-danger-100', done => { - component.list.issuesSize = 4; - component.list.maxIssueCount = 3; - - Vue.nextTick(() => { - expect(component.$el.querySelector('.bg-danger-100')).not.toBeNull(); - - done(); - }); - }); - }); - - describe('when list issue count does NOT exceed list max issue count', () => { - it('does not sets background to bg-danger-100', done => { - component.list.issuesSize = 2; - component.list.maxIssueCount = 3; - - Vue.nextTick(() => { - expect(component.$el.querySelector('.bg-danger-100')).toBeNull(); - - done(); - }); - }); - }); - - describe('when list max issue count is 0', () => { - it('does not sets background to bg-danger-100', done => { - component.list.maxIssueCount = 0; - - Vue.nextTick(() => { - expect(component.$el.querySelector('.bg-danger-100')).toBeNull(); - - done(); - }); - }); - }); - }); -}); diff --git a/spec/javascripts/boards/list_spec.js b/spec/javascripts/boards/list_spec.js deleted file mode 100644 index 7385bfb0e5f..00000000000 --- a/spec/javascripts/boards/list_spec.js +++ /dev/null @@ -1,225 +0,0 @@ -/* global List */ -/* global ListAssignee */ -/* global ListIssue */ -/* global ListLabel */ - -import MockAdapter from 'axios-mock-adapter'; -import axios from '~/lib/utils/axios_utils'; -import '~/boards/models/label'; -import '~/boards/models/assignee'; -import '~/boards/models/issue'; -import '~/boards/models/list'; -import boardsStore from '~/boards/stores/boards_store'; -import { listObj, listObjDuplicate, boardsMockInterceptor } from './mock_data'; - -describe('List model', () => { - let list; - let mock; - - beforeEach(() => { - mock = new MockAdapter(axios); - mock.onAny().reply(boardsMockInterceptor); - boardsStore.create(); - - list = new List(listObj); - }); - - afterEach(() => { - mock.restore(); - }); - - it('gets issues when created', done => { - setTimeout(() => { - expect(list.issues.length).toBe(1); - done(); - }, 0); - }); - - it('saves list and returns ID', done => { - list = new List({ - title: 'test', - label: { - id: 1, - title: 'test', - color: 'red', - text_color: 'white', - }, - }); - list.save(); - - setTimeout(() => { - expect(list.id).toBe(listObj.id); - expect(list.type).toBe('label'); - expect(list.position).toBe(0); - expect(list.label.color).toBe('red'); - expect(list.label.textColor).toBe('white'); - done(); - }, 0); - }); - - it('destroys the list', done => { - boardsStore.addList(listObj); - list = boardsStore.findList('id', listObj.id); - - expect(boardsStore.state.lists.length).toBe(1); - list.destroy(); - - setTimeout(() => { - expect(boardsStore.state.lists.length).toBe(0); - done(); - }, 0); - }); - - it('gets issue from list', done => { - setTimeout(() => { - const issue = list.findIssue(1); - - expect(issue).toBeDefined(); - done(); - }, 0); - }); - - it('removes issue', done => { - setTimeout(() => { - const issue = list.findIssue(1); - - expect(list.issues.length).toBe(1); - list.removeIssue(issue); - - expect(list.issues.length).toBe(0); - done(); - }, 0); - }); - - it('sends service request to update issue label', () => { - const listDup = new List(listObjDuplicate); - const issue = new ListIssue({ - title: 'Testing', - id: 1, - iid: 1, - confidential: false, - labels: [list.label, listDup.label], - assignees: [], - }); - - list.issues.push(issue); - listDup.issues.push(issue); - - spyOn(boardsStore, 'moveIssue').and.callThrough(); - - listDup.updateIssueLabel(issue, list); - - expect(boardsStore.moveIssue).toHaveBeenCalledWith( - issue.id, - list.id, - listDup.id, - undefined, - undefined, - ); - }); - - describe('page number', () => { - beforeEach(() => { - spyOn(list, 'getIssues'); - }); - - it('increase page number if current issue count is more than the page size', () => { - for (let i = 0; i < 30; i += 1) { - list.issues.push( - new ListIssue({ - title: 'Testing', - id: i, - iid: i, - confidential: false, - labels: [list.label], - assignees: [], - }), - ); - } - list.issuesSize = 50; - - expect(list.issues.length).toBe(30); - - list.nextPage(); - - expect(list.page).toBe(2); - expect(list.getIssues).toHaveBeenCalled(); - }); - - it('does not increase page number if issue count is less than the page size', () => { - list.issues.push( - new ListIssue({ - title: 'Testing', - id: 1, - confidential: false, - labels: [list.label], - assignees: [], - }), - ); - list.issuesSize = 2; - - list.nextPage(); - - expect(list.page).toBe(1); - expect(list.getIssues).toHaveBeenCalled(); - }); - }); - - describe('newIssue', () => { - beforeEach(() => { - spyOn(boardsStore, 'newIssue').and.returnValue( - Promise.resolve({ - data: { - id: 42, - subscribed: false, - assignable_labels_endpoint: '/issue/42/labels', - toggle_subscription_endpoint: '/issue/42/subscriptions', - issue_sidebar_endpoint: '/issue/42/sidebar_info', - }, - }), - ); - }); - - it('adds new issue to top of list', done => { - const user = new ListAssignee({ - id: 1, - name: 'testing 123', - username: 'test', - avatar: 'test_image', - }); - - list.issues.push( - new ListIssue({ - title: 'Testing', - id: 1, - confidential: false, - labels: [new ListLabel(list.label)], - assignees: [], - }), - ); - const dummyIssue = new ListIssue({ - title: 'new issue', - id: 2, - confidential: false, - labels: [new ListLabel(list.label)], - assignees: [user], - subscribed: false, - }); - - list - .newIssue(dummyIssue) - .then(() => { - expect(list.issues.length).toBe(2); - expect(list.issues[0]).toBe(dummyIssue); - expect(list.issues[0].subscribed).toBe(false); - expect(list.issues[0].assignableLabelsEndpoint).toBe('/issue/42/labels'); - expect(list.issues[0].toggleSubscriptionEndpoint).toBe('/issue/42/subscriptions'); - expect(list.issues[0].sidebarInfoEndpoint).toBe('/issue/42/sidebar_info'); - expect(list.issues[0].labels).toBe(dummyIssue.labels); - expect(list.issues[0].assignees).toBe(dummyIssue.assignees); - }) - .then(done) - .catch(done.fail); - }); - }); -}); diff --git a/spec/lib/gitlab/repository_size_checker_spec.rb b/spec/lib/gitlab/repository_size_checker_spec.rb new file mode 100644 index 00000000000..61f76d716e5 --- /dev/null +++ b/spec/lib/gitlab/repository_size_checker_spec.rb @@ -0,0 +1,93 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::RepositorySizeChecker do + let(:current_size) { 0 } + let(:limit) { 50 } + let(:enabled) { true } + + subject do + described_class.new( + current_size_proc: -> { current_size }, + limit: limit, + enabled: enabled + ) + end + + describe '#enabled?' do + context 'when enabled' do + it 'returns true' do + expect(subject.enabled?).to be_truthy + end + end + + context 'when limit is zero' do + let(:limit) { 0 } + + it 'returns false' do + expect(subject.enabled?).to be_falsey + end + end + end + + describe '#changes_will_exceed_size_limit?' do + let(:current_size) { 49 } + + it 'returns true when changes go over' do + expect(subject.changes_will_exceed_size_limit?(2)).to be_truthy + end + + it 'returns false when changes do not go over' do + expect(subject.changes_will_exceed_size_limit?(1)).to be_falsey + end + end + + describe '#above_size_limit?' do + context 'when size is above the limit' do + let(:current_size) { 100 } + + it 'returns true' do + expect(subject.above_size_limit?).to be_truthy + end + end + + it 'returns false when not over the limit' do + expect(subject.above_size_limit?).to be_falsey + end + end + + describe '#exceeded_size' do + context 'when current size is below or equal to the limit' do + let(:current_size) { 50 } + + it 'returns zero' do + expect(subject.exceeded_size).to eq(0) + end + end + + context 'when current size is over the limit' do + let(:current_size) { 51 } + + it 'returns zero' do + expect(subject.exceeded_size).to eq(1) + end + end + + context 'when change size will be over the limit' do + let(:current_size) { 50 } + + it 'returns zero' do + expect(subject.exceeded_size(1)).to eq(1) + end + end + + context 'when change size will not be over the limit' do + let(:current_size) { 49 } + + it 'returns zero' do + expect(subject.exceeded_size(1)).to eq(0) + end + end + end +end diff --git a/spec/lib/gitlab/repository_size_error_message_spec.rb b/spec/lib/gitlab/repository_size_error_message_spec.rb new file mode 100644 index 00000000000..9e4d19cc572 --- /dev/null +++ b/spec/lib/gitlab/repository_size_error_message_spec.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::RepositorySizeErrorMessage do + let(:checker) do + Gitlab::RepositorySizeChecker.new( + current_size_proc: -> { 15.megabytes }, + limit: 10.megabytes + ) + end + + let(:message) { checker.error_message } + let(:base_message) { 'because this repository has exceeded its size limit of 10 MB by 5 MB' } + + describe 'error messages' do + describe '#commit_error' do + it 'returns the correct message' do + expect(message.commit_error).to eq("Your changes could not be committed, #{base_message}") + end + end + + describe '#merge_error' do + it 'returns the correct message' do + expect(message.merge_error).to eq("This merge request cannot be merged, #{base_message}") + end + end + + describe '#push_error' do + context 'with exceeded_limit value' do + let(:rejection_message) do + 'because this repository has exceeded its size limit of 10 MB by 15 MB' + end + + it 'returns the correct message' do + expect(message.push_error(10.megabytes)) + .to eq("Your push has been rejected, #{rejection_message}. #{message.more_info_message}") + end + end + + context 'without exceeded_limit value' do + it 'returns the correct message' do + expect(message.push_error) + .to eq("Your push has been rejected, #{base_message}. #{message.more_info_message}") + end + end + end + + describe '#new_changes_error' do + it 'returns the correct message' do + expect(message.new_changes_error).to eq("Your push to this repository would cause it to exceed the size limit of 10 MB so it has been rejected. #{message.more_info_message}") + end + end + end +end diff --git a/spec/services/issues/close_service_spec.rb b/spec/services/issues/close_service_spec.rb index d8bcd8008ce..86377e054c1 100644 --- a/spec/services/issues/close_service_spec.rb +++ b/spec/services/issues/close_service_spec.rb @@ -163,19 +163,6 @@ describe Issues::CloseService do expect(issue.metrics.first_mentioned_in_commit_at).to be_nil end end - - context 'when `store_first_mentioned_in_commit_on_issue_close` feature flag is off' do - before do - stub_feature_flags(store_first_mentioned_in_commit_on_issue_close: { enabled: false, thing: issue.project }) - end - - it 'does not update the metrics' do - subject - - expect(described_class).not_to receive(:store_first_mentioned_in_commit_at) - expect(issue.metrics.first_mentioned_in_commit_at).to be_nil - end - end end end diff --git a/spec/support/shared_examples/controllers/binary_blob_shared_examples.rb b/spec/support/shared_examples/controllers/binary_blob_shared_examples.rb new file mode 100644 index 00000000000..c1ec515f1fe --- /dev/null +++ b/spec/support/shared_examples/controllers/binary_blob_shared_examples.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'editing snippet checks blob is binary' do + before do + sign_in(user) + + allow_next_instance_of(Blob) do |blob| + allow(blob).to receive(:binary?).and_return(binary) + end + + subject + end + + context 'when blob is text' do + let(:binary) { false } + + it 'responds with status 200' do + expect(response).to have_gitlab_http_status(:ok) + expect(response).to render_template(:edit) + end + end + + context 'when blob is binary' do + let(:binary) { true } + + it 'redirects away' do + expect(response).to redirect_to(gitlab_snippet_path(snippet)) + end + end +end + +RSpec.shared_examples 'updating snippet checks blob is binary' do + before do + sign_in(user) + + allow_next_instance_of(Blob) do |blob| + allow(blob).to receive(:binary?).and_return(binary) + end + + subject + end + + context 'when blob is text' do + let(:binary) { false } + + it 'updates successfully' do + expect(snippet.reload.title).to eq title + expect(response).to redirect_to(gitlab_snippet_path(snippet)) + end + end + + context 'when blob is binary' do + let(:binary) { true } + + it 'redirects away without updating' do + expect(response).to redirect_to(gitlab_snippet_path(snippet)) + expect(snippet.reload.title).not_to eq title + end + end +end -- cgit v1.2.3