Commit 8254a1d2 authored by Nicolò Maria Mezzopera's avatar Nicolò Maria Mezzopera Committed by Natalia Tepluhina

Remove pathGenerator and add docker commands utils

- source
- tests
parent de6d6c5a
<script> <script>
import { GlDropdown } from '@gitlab/ui'; import { GlDropdown } from '@gitlab/ui';
import { mapGetters } from 'vuex';
import Tracking from '~/tracking'; import Tracking from '~/tracking';
import CodeInstruction from '~/vue_shared/components/registry/code_instruction.vue'; import CodeInstruction from '~/vue_shared/components/registry/code_instruction.vue';
import { import {
...@@ -20,6 +19,7 @@ export default { ...@@ -20,6 +19,7 @@ export default {
GlDropdown, GlDropdown,
CodeInstruction, CodeInstruction,
}, },
inject: ['config', 'dockerBuildCommand', 'dockerPushCommand', 'dockerLoginCommand'],
mixins: [Tracking.mixin({ label: trackingLabel })], mixins: [Tracking.mixin({ label: trackingLabel })],
trackingLabel, trackingLabel,
i18n: { i18n: {
...@@ -31,9 +31,6 @@ export default { ...@@ -31,9 +31,6 @@ export default {
PUSH_COMMAND_LABEL, PUSH_COMMAND_LABEL,
COPY_PUSH_TITLE, COPY_PUSH_TITLE,
}, },
computed: {
...mapGetters(['dockerBuildCommand', 'dockerPushCommand', 'dockerLoginCommand']),
},
}; };
</script> </script>
<template> <template>
......
<script> <script>
import { GlEmptyState, GlSprintf, GlLink } from '@gitlab/ui'; import { GlEmptyState, GlSprintf, GlLink } from '@gitlab/ui';
import { mapState } from 'vuex';
export default { export default {
name: 'GroupEmptyState', name: 'GroupEmptyState',
inject: ['config'],
components: { components: {
GlEmptyState, GlEmptyState,
GlSprintf, GlSprintf,
GlLink, GlLink,
}, },
computed: {
...mapState(['config']),
},
}; };
</script> </script>
<template> <template>
......
<script> <script>
import { GlEmptyState, GlSprintf, GlLink, GlFormInputGroup, GlFormInput } from '@gitlab/ui'; import { GlEmptyState, GlSprintf, GlLink, GlFormInputGroup, GlFormInput } from '@gitlab/ui';
import { mapState, mapGetters } from 'vuex';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import { import {
...@@ -20,6 +19,7 @@ export default { ...@@ -20,6 +19,7 @@ export default {
GlFormInputGroup, GlFormInputGroup,
GlFormInput, GlFormInput,
}, },
inject: ['config', 'dockerBuildCommand', 'dockerPushCommand', 'dockerLoginCommand'],
i18n: { i18n: {
quickStart: QUICK_START, quickStart: QUICK_START,
copyLoginTitle: COPY_LOGIN_TITLE, copyLoginTitle: COPY_LOGIN_TITLE,
...@@ -35,10 +35,6 @@ export default { ...@@ -35,10 +35,6 @@ export default {
'ContainerRegistry|You can add an image to this registry with the following commands:', 'ContainerRegistry|You can add an image to this registry with the following commands:',
), ),
}, },
computed: {
...mapState(['config']),
...mapGetters(['dockerBuildCommand', 'dockerPushCommand', 'dockerLoginCommand']),
},
}; };
</script> </script>
<template> <template>
......
import Vue from 'vue'; import Vue from 'vue';
import { GlToast } from '@gitlab/ui'; import { GlToast } from '@gitlab/ui';
import Translate from '~/vue_shared/translate'; import Translate from '~/vue_shared/translate';
import { parseBoolean } from '~/lib/utils/common_utils';
import RegistryExplorer from './pages/index.vue'; import RegistryExplorer from './pages/index.vue';
import RegistryBreadcrumb from './components/registry_breadcrumb.vue'; import RegistryBreadcrumb from './components/registry_breadcrumb.vue';
import { createStore } from './stores';
import createRouter from './router'; import createRouter from './router';
import { apolloProvider } from './graphql/index'; import { apolloProvider } from './graphql/index';
...@@ -17,7 +17,7 @@ export default () => { ...@@ -17,7 +17,7 @@ export default () => {
return null; return null;
} }
const { endpoint } = el.dataset; const { endpoint, expirationPolicy, isGroupPage, isAdmin, ...config } = el.dataset;
// This is a mini state to help the breadcrumb have the correct name in the details page // This is a mini state to help the breadcrumb have the correct name in the details page
const breadCrumbState = Vue.observable({ const breadCrumbState = Vue.observable({
...@@ -27,21 +27,31 @@ export default () => { ...@@ -27,21 +27,31 @@ export default () => {
}, },
}); });
const store = createStore();
const router = createRouter(endpoint, breadCrumbState); const router = createRouter(endpoint, breadCrumbState);
store.dispatch('setInitialState', el.dataset);
const attachMainComponent = () => const attachMainComponent = () =>
new Vue({ new Vue({
el, el,
store,
router, router,
apolloProvider, apolloProvider,
components: { components: {
RegistryExplorer, RegistryExplorer,
}, },
provide() { provide() {
return { breadCrumbState }; return {
breadCrumbState,
config: {
...config,
expirationPolicy: expirationPolicy ? JSON.parse(expirationPolicy) : undefined,
isGroupPage: parseBoolean(isGroupPage),
isAdmin: parseBoolean(isAdmin),
},
/* eslint-disable @gitlab/require-i18n-strings */
dockerBuildCommand: `docker build -t ${config.repositoryUrl} .`,
dockerPushCommand: `docker push ${config.repositoryUrl}`,
dockerLoginCommand: `docker login ${config.registryHostUrlWithPort}`,
/* eslint-enable @gitlab/require-i18n-strings */
};
}, },
render(createElement) { render(createElement) {
return createElement('registry-explorer'); return createElement('registry-explorer');
......
<script> <script>
import { mapState } from 'vuex';
import { GlKeysetPagination, GlResizeObserverDirective } from '@gitlab/ui'; import { GlKeysetPagination, GlResizeObserverDirective } from '@gitlab/ui';
import { GlBreakpointInstance } from '@gitlab/ui/dist/utils'; import { GlBreakpointInstance } from '@gitlab/ui/dist/utils';
import createFlash from '~/flash'; import createFlash from '~/flash';
...@@ -36,7 +35,7 @@ export default { ...@@ -36,7 +35,7 @@ export default {
TagsLoader, TagsLoader,
EmptyTagsState, EmptyTagsState,
}, },
inject: ['breadCrumbState'], inject: ['breadCrumbState', 'config'],
directives: { directives: {
GlResizeObserver: GlResizeObserverDirective, GlResizeObserver: GlResizeObserverDirective,
}, },
...@@ -71,7 +70,6 @@ export default { ...@@ -71,7 +70,6 @@ export default {
}; };
}, },
computed: { computed: {
...mapState(['config']),
queryVariables() { queryVariables() {
return { return {
id: joinPaths(this.config.gidPrefix, `${this.$route.params.id}`), id: joinPaths(this.config.gidPrefix, `${this.$route.params.id}`),
......
<script> <script>
import { mapState } from 'vuex';
import { import {
GlEmptyState, GlEmptyState,
GlTooltipDirective, GlTooltipDirective,
...@@ -54,6 +53,7 @@ export default { ...@@ -54,6 +53,7 @@ export default {
RegistryHeader, RegistryHeader,
CliCommands, CliCommands,
}, },
inject: ['config'],
directives: { directives: {
GlTooltip: GlTooltipDirective, GlTooltip: GlTooltipDirective,
}, },
...@@ -106,7 +106,6 @@ export default { ...@@ -106,7 +106,6 @@ export default {
}; };
}, },
computed: { computed: {
...mapState(['config']),
graphqlResource() { graphqlResource() {
return this.config.isGroupPage ? 'group' : 'project'; return this.config.isGroupPage ? 'group' : 'project';
}, },
......
import axios from '~/lib/utils/axios_utils';
import createFlash from '~/flash';
import Api from '~/api';
import * as types from './mutation_types';
import {
FETCH_IMAGES_LIST_ERROR_MESSAGE,
DEFAULT_PAGE,
DEFAULT_PAGE_SIZE,
FETCH_TAGS_LIST_ERROR_MESSAGE,
FETCH_IMAGE_DETAILS_ERROR_MESSAGE,
} from '../constants/index';
import { pathGenerator } from '../utils';
export const setInitialState = ({ commit }, data) => commit(types.SET_INITIAL_STATE, data);
export const setShowGarbageCollectionTip = ({ commit }, data) =>
commit(types.SET_SHOW_GARBAGE_COLLECTION_TIP, data);
export const receiveImagesListSuccess = ({ commit }, { data, headers }) => {
commit(types.SET_IMAGES_LIST_SUCCESS, data);
commit(types.SET_PAGINATION, headers);
};
export const receiveTagsListSuccess = ({ commit }, { data, headers }) => {
commit(types.SET_TAGS_LIST_SUCCESS, data);
commit(types.SET_TAGS_PAGINATION, headers);
};
export const requestImagesList = (
{ commit, dispatch, state },
{ pagination = {}, name = null } = {},
) => {
commit(types.SET_MAIN_LOADING, true);
const { page = DEFAULT_PAGE, perPage = DEFAULT_PAGE_SIZE } = pagination;
return axios
.get(state.config.endpoint, { params: { page, per_page: perPage, name } })
.then(({ data, headers }) => {
dispatch('receiveImagesListSuccess', { data, headers });
})
.catch(() => {
createFlash({ message: FETCH_IMAGES_LIST_ERROR_MESSAGE });
})
.finally(() => {
commit(types.SET_MAIN_LOADING, false);
});
};
export const requestTagsList = ({ commit, dispatch, state: { imageDetails } }, pagination = {}) => {
commit(types.SET_MAIN_LOADING, true);
const tagsPath = pathGenerator(imageDetails);
const { page = DEFAULT_PAGE, perPage = DEFAULT_PAGE_SIZE } = pagination;
return axios
.get(tagsPath, { params: { page, per_page: perPage } })
.then(({ data, headers }) => {
dispatch('receiveTagsListSuccess', { data, headers });
})
.catch(() => {
createFlash({ message: FETCH_TAGS_LIST_ERROR_MESSAGE });
})
.finally(() => {
commit(types.SET_MAIN_LOADING, false);
});
};
export const requestImageDetailsAndTagsList = ({ dispatch, commit }, id) => {
commit(types.SET_MAIN_LOADING, true);
return Api.containerRegistryDetails(id)
.then(({ data }) => {
commit(types.SET_IMAGE_DETAILS, data);
dispatch('requestTagsList');
})
.catch(() => {
createFlash({ message: FETCH_IMAGE_DETAILS_ERROR_MESSAGE });
commit(types.SET_MAIN_LOADING, false);
});
};
export const requestDeleteTag = ({ commit, dispatch, state }, { tag }) => {
commit(types.SET_MAIN_LOADING, true);
return axios
.delete(tag.destroy_path)
.then(() => {
dispatch('setShowGarbageCollectionTip', true);
return dispatch('requestTagsList', state.tagsPagination);
})
.finally(() => {
commit(types.SET_MAIN_LOADING, false);
});
};
export const requestDeleteTags = ({ commit, dispatch, state }, { ids }) => {
commit(types.SET_MAIN_LOADING, true);
const tagsPath = pathGenerator(state.imageDetails, '/bulk_destroy');
return axios
.delete(tagsPath, { params: { ids } })
.then(() => {
dispatch('setShowGarbageCollectionTip', true);
return dispatch('requestTagsList', state.tagsPagination);
})
.finally(() => {
commit(types.SET_MAIN_LOADING, false);
});
};
export const requestDeleteImage = ({ commit }, image) => {
commit(types.SET_MAIN_LOADING, true);
return axios
.delete(image.destroy_path)
.then(() => {
commit(types.UPDATE_IMAGE, { ...image, deleting: true });
})
.finally(() => {
commit(types.SET_MAIN_LOADING, false);
});
};
export const dockerBuildCommand = state => {
/* eslint-disable @gitlab/require-i18n-strings */
return `docker build -t ${state.config.repositoryUrl} .`;
};
export const dockerPushCommand = state => {
/* eslint-disable @gitlab/require-i18n-strings */
return `docker push ${state.config.repositoryUrl}`;
};
export const dockerLoginCommand = state => {
/* eslint-disable @gitlab/require-i18n-strings */
return `docker login ${state.config.registryHostUrlWithPort}`;
};
export const showGarbageCollection = state => {
return state.showGarbageCollectionTip && state.config.isAdmin;
};
import Vue from 'vue';
import Vuex from 'vuex';
import * as actions from './actions';
import * as getters from './getters';
import mutations from './mutations';
import state from './state';
Vue.use(Vuex);
export const createStore = () =>
new Vuex.Store({
state,
getters,
actions,
mutations,
});
export const SET_INITIAL_STATE = 'SET_INITIAL_STATE';
export const SET_IMAGES_LIST_SUCCESS = 'SET_PACKAGE_LIST_SUCCESS';
export const UPDATE_IMAGE = 'UPDATE_IMAGE';
export const SET_PAGINATION = 'SET_PAGINATION';
export const SET_MAIN_LOADING = 'SET_MAIN_LOADING';
export const SET_TAGS_PAGINATION = 'SET_TAGS_PAGINATION';
export const SET_TAGS_LIST_SUCCESS = 'SET_TAGS_LIST_SUCCESS';
export const SET_SHOW_GARBAGE_COLLECTION_TIP = 'SET_SHOW_GARBAGE_COLLECTION_TIP';
export const SET_IMAGE_DETAILS = 'SET_IMAGE_DETAILS';
import * as types from './mutation_types';
import { parseIntPagination, normalizeHeaders, parseBoolean } from '~/lib/utils/common_utils';
import { IMAGE_DELETE_SCHEDULED_STATUS, IMAGE_FAILED_DELETED_STATUS } from '../constants/index';
export default {
[types.SET_INITIAL_STATE](state, config) {
state.config = {
...config,
expirationPolicy: config.expirationPolicy ? JSON.parse(config.expirationPolicy) : undefined,
isGroupPage: parseBoolean(config.isGroupPage),
isAdmin: parseBoolean(config.isAdmin),
};
},
[types.SET_IMAGES_LIST_SUCCESS](state, images) {
state.images = images.map(i => ({
...i,
status: undefined,
deleting: i.status === IMAGE_DELETE_SCHEDULED_STATUS,
failedDelete: i.status === IMAGE_FAILED_DELETED_STATUS,
}));
},
[types.UPDATE_IMAGE](state, image) {
const index = state.images.findIndex(i => i.id === image.id);
state.images.splice(index, 1, { ...image });
},
[types.SET_TAGS_LIST_SUCCESS](state, tags) {
state.tags = tags;
},
[types.SET_MAIN_LOADING](state, isLoading) {
state.isLoading = isLoading;
},
[types.SET_SHOW_GARBAGE_COLLECTION_TIP](state, showGarbageCollectionTip) {
state.showGarbageCollectionTip = showGarbageCollectionTip;
},
[types.SET_PAGINATION](state, headers) {
const normalizedHeaders = normalizeHeaders(headers);
state.pagination = parseIntPagination(normalizedHeaders);
},
[types.SET_TAGS_PAGINATION](state, headers) {
const normalizedHeaders = normalizeHeaders(headers);
state.tagsPagination = parseIntPagination(normalizedHeaders);
},
[types.SET_IMAGE_DETAILS](state, details) {
state.imageDetails = details;
},
};
export default () => ({
isLoading: false,
showGarbageCollectionTip: false,
config: {},
images: [],
imageDetails: {},
tags: [],
pagination: {},
tagsPagination: {},
});
import { joinPaths } from '~/lib/utils/url_utility';
export const pathGenerator = (imageDetails, ending = '?format=json') => {
// this method is a temporary workaround, to be removed with graphql implementation
// https://gitlab.com/gitlab-org/gitlab/-/issues/276432
const splitPath = imageDetails.path.split('/').reverse();
const splitName = imageDetails.name ? imageDetails.name.split('/').reverse() : [];
const basePath = splitPath
.reduce((acc, curr, index) => {
if (splitPath[index] !== splitName[index]) {
acc.unshift(curr);
}
return acc;
}, [])
.join('/');
return joinPaths(
window.gon.relative_url_root,
`/${basePath}`,
'/registry/repository/',
`${imageDetails.id}`,
`tags${ending}`,
);
};
---
title: Refactor container registry to use GraphQL API
merge_request: 49584
author:
type: changed
...@@ -15,12 +15,12 @@ import { ...@@ -15,12 +15,12 @@ import {
NOT_AVAILABLE_SIZE, NOT_AVAILABLE_SIZE,
} from '~/registry/explorer/constants/index'; } from '~/registry/explorer/constants/index';
import { tagsListResponse } from '../../mock_data'; import { tagsMock } from '../../mock_data';
import { ListItem } from '../../stubs'; import { ListItem } from '../../stubs';
describe('tags list row', () => { describe('tags list row', () => {
let wrapper; let wrapper;
const [tag] = [...tagsListResponse]; const [tag] = [...tagsMock];
const defaultProps = { tag, isMobile: false, index: 0 }; const defaultProps = { tag, isMobile: false, index: 0 };
...@@ -172,19 +172,19 @@ describe('tags list row', () => { ...@@ -172,19 +172,19 @@ describe('tags list row', () => {
}); });
it('contains the totalSize and layers', () => { it('contains the totalSize and layers', () => {
mountComponent({ ...defaultProps, tag: { ...tag, totalSize: 1024 } }); mountComponent({ ...defaultProps, tag: { ...tag, totalSize: 1024, layers: 10 } });
expect(findSize().text()).toMatchInterpolatedText('1.00 KiB · 10 layers'); expect(findSize().text()).toMatchInterpolatedText('1.00 KiB · 10 layers');
}); });
it('when totalSize is missing', () => { it('when totalSize is missing', () => {
mountComponent({ ...defaultProps, tag: { ...tag, totalSize: 0 } }); mountComponent({ ...defaultProps, tag: { ...tag, totalSize: 0, layers: 10 } });
expect(findSize().text()).toMatchInterpolatedText(`${NOT_AVAILABLE_SIZE} · 10 layers`); expect(findSize().text()).toMatchInterpolatedText(`${NOT_AVAILABLE_SIZE} · 10 layers`);
}); });
it('when layers are missing', () => { it('when layers are missing', () => {
mountComponent({ ...defaultProps, tag: { ...tag, totalSize: 1024, layers: null } }); mountComponent({ ...defaultProps, tag: { ...tag, totalSize: 1024 } });
expect(findSize().text()).toMatchInterpolatedText('1.00 KiB'); expect(findSize().text()).toMatchInterpolatedText('1.00 KiB');
}); });
...@@ -232,7 +232,7 @@ describe('tags list row', () => { ...@@ -232,7 +232,7 @@ describe('tags list row', () => {
it('has the correct text', () => { it('has the correct text', () => {
mountComponent(); mountComponent();
expect(findShortRevision().text()).toMatchInterpolatedText('Digest: 9d72ae1'); expect(findShortRevision().text()).toMatchInterpolatedText('Digest: 2cf3d2f');
}); });
it(`displays ${NOT_AVAILABLE_TEXT} when digest is missing`, () => { it(`displays ${NOT_AVAILABLE_TEXT} when digest is missing`, () => {
...@@ -294,8 +294,8 @@ describe('tags list row', () => { ...@@ -294,8 +294,8 @@ describe('tags list row', () => {
describe.each` describe.each`
name | finderFunction | text | icon | clipboard name | finderFunction | text | icon | clipboard
${'published date detail'} | ${findPublishedDateDetail} | ${'Published to the gitlab-org/gitlab-test/rails-12009 image repository at 01:29 GMT+0000 on 2020-11-03'} | ${'clock'} | ${false} ${'published date detail'} | ${findPublishedDateDetail} | ${'Published to the gitlab-org/gitlab-test/rails-12009 image repository at 01:29 GMT+0000 on 2020-11-03'} | ${'clock'} | ${false}
${'manifest detail'} | ${findManifestDetail} | ${'Manifest digest: sha256:9d72ae1db47404e44e1760eb1ca4cb427b84be8c511f05dfe2089e1b9f741dd7'} | ${'log'} | ${true} ${'manifest detail'} | ${findManifestDetail} | ${'Manifest digest: sha256:2cf3d2fdac1b04a14301d47d51cb88dcd26714c74f91440eeee99ce399089062'} | ${'log'} | ${true}
${'configuration detail'} | ${findConfigurationDetail} | ${'Configuration digest: sha256:5183b5d133fa864dca2de602f874b0d1bffe0f204ad894e3660432a487935139'} | ${'cloud-gear'} | ${true} ${'configuration detail'} | ${findConfigurationDetail} | ${'Configuration digest: sha256:c2613843ab33aabf847965442b13a8b55a56ae28837ce182627c0716eb08c02b'} | ${'cloud-gear'} | ${true}
`('$name details row', ({ finderFunction, text, icon, clipboard }) => { `('$name details row', ({ finderFunction, text, icon, clipboard }) => {
it(`has ${text} as text`, () => { it(`has ${text} as text`, () => {
expect(finderFunction().text()).toMatchInterpolatedText(text); expect(finderFunction().text()).toMatchInterpolatedText(text);
......
...@@ -3,11 +3,11 @@ import { GlButton } from '@gitlab/ui'; ...@@ -3,11 +3,11 @@ import { GlButton } from '@gitlab/ui';
import component from '~/registry/explorer/components/details_page/tags_list.vue'; import component from '~/registry/explorer/components/details_page/tags_list.vue';
import TagsListRow from '~/registry/explorer/components/details_page/tags_list_row.vue'; import TagsListRow from '~/registry/explorer/components/details_page/tags_list_row.vue';
import { TAGS_LIST_TITLE, REMOVE_TAGS_BUTTON_TITLE } from '~/registry/explorer/constants/index'; import { TAGS_LIST_TITLE, REMOVE_TAGS_BUTTON_TITLE } from '~/registry/explorer/constants/index';
import { tagsListResponse } from '../../mock_data'; import { tagsMock } from '../../mock_data';
describe('Tags List', () => { describe('Tags List', () => {
let wrapper; let wrapper;
const tags = [...tagsListResponse]; const tags = [...tagsMock];
const readOnlyTags = tags.map(t => ({ ...t, canDelete: false })); const readOnlyTags = tags.map(t => ({ ...t, canDelete: false }));
const findTagsListRow = () => wrapper.findAll(TagsListRow); const findTagsListRow = () => wrapper.findAll(TagsListRow);
...@@ -92,7 +92,7 @@ describe('Tags List', () => { ...@@ -92,7 +92,7 @@ describe('Tags List', () => {
.vm.$emit('select'); .vm.$emit('select');
findDeleteButton().vm.$emit('click'); findDeleteButton().vm.$emit('click');
expect(wrapper.emitted('delete')).toEqual([[{ 'alpha-11821': true }]]); expect(wrapper.emitted('delete')).toEqual([[{ 'beta-24753': true }]]);
}); });
}); });
...@@ -132,7 +132,7 @@ describe('Tags List', () => { ...@@ -132,7 +132,7 @@ describe('Tags List', () => {
findTagsListRow() findTagsListRow()
.at(0) .at(0)
.vm.$emit('delete'); .vm.$emit('delete');
expect(wrapper.emitted('delete')).toEqual([[{ 'alpha-11821': true }]]); expect(wrapper.emitted('delete')).toEqual([[{ 'beta-24753': true }]]);
}); });
}); });
}); });
......
...@@ -46,7 +46,7 @@ exports[`Registry Project Empty state to match the default snapshot 1`] = ` ...@@ -46,7 +46,7 @@ exports[`Registry Project Empty state to match the default snapshot 1`] = `
class="gl-font-monospace!" class="gl-font-monospace!"
readonly="" readonly=""
type="text" type="text"
value="docker login bar" value="bazbaz"
/> />
</gl-form-input-group-stub> </gl-form-input-group-stub>
...@@ -67,7 +67,7 @@ exports[`Registry Project Empty state to match the default snapshot 1`] = ` ...@@ -67,7 +67,7 @@ exports[`Registry Project Empty state to match the default snapshot 1`] = `
class="gl-font-monospace!" class="gl-font-monospace!"
readonly="" readonly=""
type="text" type="text"
value="docker build -t foo ." value="foofoo"
/> />
</gl-form-input-group-stub> </gl-form-input-group-stub>
...@@ -79,7 +79,7 @@ exports[`Registry Project Empty state to match the default snapshot 1`] = ` ...@@ -79,7 +79,7 @@ exports[`Registry Project Empty state to match the default snapshot 1`] = `
class="gl-font-monospace!" class="gl-font-monospace!"
readonly="" readonly=""
type="text" type="text"
value="docker push foo" value="barbar"
/> />
</gl-form-input-group-stub> </gl-form-input-group-stub>
</div> </div>
......
...@@ -2,10 +2,8 @@ import Vuex from 'vuex'; ...@@ -2,10 +2,8 @@ import Vuex from 'vuex';
import { mount, createLocalVue } from '@vue/test-utils'; import { mount, createLocalVue } from '@vue/test-utils';
import { GlDropdown } from '@gitlab/ui'; import { GlDropdown } from '@gitlab/ui';
import Tracking from '~/tracking'; import Tracking from '~/tracking';
import * as getters from '~/registry/explorer/stores/getters';
import QuickstartDropdown from '~/registry/explorer/components/list_page/cli_commands.vue'; import QuickstartDropdown from '~/registry/explorer/components/list_page/cli_commands.vue';
import CodeInstruction from '~/vue_shared/components/registry/code_instruction.vue'; import CodeInstruction from '~/vue_shared/components/registry/code_instruction.vue';
import { import {
QUICK_START, QUICK_START,
LOGIN_COMMAND_LABEL, LOGIN_COMMAND_LABEL,
...@@ -14,31 +12,33 @@ import { ...@@ -14,31 +12,33 @@ import {
COPY_BUILD_TITLE, COPY_BUILD_TITLE,
PUSH_COMMAND_LABEL, PUSH_COMMAND_LABEL,
COPY_PUSH_TITLE, COPY_PUSH_TITLE,
} from '~/registry/explorer//constants'; } from '~/registry/explorer/constants';
import { dockerCommands } from '../../mock_data';
const localVue = createLocalVue(); const localVue = createLocalVue();
localVue.use(Vuex); localVue.use(Vuex);
describe('cli_commands', () => { describe('cli_commands', () => {
let wrapper; let wrapper;
let store;
const config = {
repositoryUrl: 'foo',
registryHostUrlWithPort: 'bar',
};
const findDropdownButton = () => wrapper.find(GlDropdown); const findDropdownButton = () => wrapper.find(GlDropdown);
const findCodeInstruction = () => wrapper.findAll(CodeInstruction); const findCodeInstruction = () => wrapper.findAll(CodeInstruction);
const mountComponent = () => { const mountComponent = () => {
store = new Vuex.Store({
state: {
config: {
repositoryUrl: 'foo',
registryHostUrlWithPort: 'bar',
},
},
getters,
});
wrapper = mount(QuickstartDropdown, { wrapper = mount(QuickstartDropdown, {
localVue, localVue,
store, provide() {
return {
config,
...dockerCommands,
};
},
}); });
}; };
...@@ -50,7 +50,6 @@ describe('cli_commands', () => { ...@@ -50,7 +50,6 @@ describe('cli_commands', () => {
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
wrapper = null; wrapper = null;
store = null;
}); });
it('shows the correct text on the button', () => { it('shows the correct text on the button', () => {
...@@ -67,11 +66,11 @@ describe('cli_commands', () => { ...@@ -67,11 +66,11 @@ describe('cli_commands', () => {
}); });
describe.each` describe.each`
index | labelText | titleText | getter | trackedEvent index | labelText | titleText | command | trackedEvent
${0} | ${LOGIN_COMMAND_LABEL} | ${COPY_LOGIN_TITLE} | ${'dockerLoginCommand'} | ${'click_copy_login'} ${0} | ${LOGIN_COMMAND_LABEL} | ${COPY_LOGIN_TITLE} | ${dockerCommands.dockerLoginCommand} | ${'click_copy_login'}
${1} | ${BUILD_COMMAND_LABEL} | ${COPY_BUILD_TITLE} | ${'dockerBuildCommand'} | ${'click_copy_build'} ${1} | ${BUILD_COMMAND_LABEL} | ${COPY_BUILD_TITLE} | ${dockerCommands.dockerBuildCommand} | ${'click_copy_build'}
${2} | ${PUSH_COMMAND_LABEL} | ${COPY_PUSH_TITLE} | ${'dockerPushCommand'} | ${'click_copy_push'} ${2} | ${PUSH_COMMAND_LABEL} | ${COPY_PUSH_TITLE} | ${dockerCommands.dockerPushCommand} | ${'click_copy_push'}
`('code instructions at $index', ({ index, labelText, titleText, getter, trackedEvent }) => { `('code instructions at $index', ({ index, labelText, titleText, command, trackedEvent }) => {
let codeInstruction; let codeInstruction;
beforeEach(() => { beforeEach(() => {
...@@ -85,7 +84,7 @@ describe('cli_commands', () => { ...@@ -85,7 +84,7 @@ describe('cli_commands', () => {
it(`has the correct props`, () => { it(`has the correct props`, () => {
expect(codeInstruction.props()).toMatchObject({ expect(codeInstruction.props()).toMatchObject({
label: labelText, label: labelText,
instruction: store.getters[getter], instruction: command,
copyText: titleText, copyText: titleText,
trackingAction: trackedEvent, trackingAction: trackedEvent,
trackingLabel: 'quickstart_dropdown', trackingLabel: 'quickstart_dropdown',
......
...@@ -9,24 +9,21 @@ localVue.use(Vuex); ...@@ -9,24 +9,21 @@ localVue.use(Vuex);
describe('Registry Group Empty state', () => { describe('Registry Group Empty state', () => {
let wrapper; let wrapper;
let store; const config = {
beforeEach(() => {
store = new Vuex.Store({
state: {
config: {
noContainersImage: 'foo', noContainersImage: 'foo',
helpPagePath: 'baz', helpPagePath: 'baz',
}, };
},
}); beforeEach(() => {
wrapper = shallowMount(groupEmptyState, { wrapper = shallowMount(groupEmptyState, {
localVue, localVue,
store,
stubs: { stubs: {
GlEmptyState, GlEmptyState,
GlSprintf, GlSprintf,
}, },
provide() {
return { config };
},
}); });
}); });
......
...@@ -3,36 +3,35 @@ import { shallowMount, createLocalVue } from '@vue/test-utils'; ...@@ -3,36 +3,35 @@ import { shallowMount, createLocalVue } from '@vue/test-utils';
import { GlSprintf } from '@gitlab/ui'; import { GlSprintf } from '@gitlab/ui';
import { GlEmptyState } from '../../stubs'; import { GlEmptyState } from '../../stubs';
import projectEmptyState from '~/registry/explorer/components/list_page/project_empty_state.vue'; import projectEmptyState from '~/registry/explorer/components/list_page/project_empty_state.vue';
import * as getters from '~/registry/explorer/stores/getters'; import { dockerCommands } from '../../mock_data';
const localVue = createLocalVue(); const localVue = createLocalVue();
localVue.use(Vuex); localVue.use(Vuex);
describe('Registry Project Empty state', () => { describe('Registry Project Empty state', () => {
let wrapper; let wrapper;
let store; const config = {
beforeEach(() => {
store = new Vuex.Store({
state: {
config: {
repositoryUrl: 'foo', repositoryUrl: 'foo',
registryHostUrlWithPort: 'bar', registryHostUrlWithPort: 'bar',
helpPagePath: 'baz', helpPagePath: 'baz',
twoFactorAuthHelpLink: 'barBaz', twoFactorAuthHelpLink: 'barBaz',
personalAccessTokensHelpLink: 'fooBaz', personalAccessTokensHelpLink: 'fooBaz',
noContainersImage: 'bazFoo', noContainersImage: 'bazFoo',
}, };
},
getters, beforeEach(() => {
});
wrapper = shallowMount(projectEmptyState, { wrapper = shallowMount(projectEmptyState, {
localVue, localVue,
store,
stubs: { stubs: {
GlEmptyState, GlEmptyState,
GlSprintf, GlSprintf,
}, },
provide() {
return {
config,
...dockerCommands,
};
},
}); });
}); });
......
export const headers = {
'X-PER-PAGE': 5,
'X-PAGE': 1,
'X-TOTAL': 13,
'X-TOTAL_PAGES': 1,
'X-NEXT-PAGE': null,
'X-PREVIOUS-PAGE': null,
};
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 imagesListResponse = [ export const imagesListResponse = [
{ {
__typename: 'ContainerRepository', __typename: 'ContainerRepository',
...@@ -72,35 +25,6 @@ export const imagesListResponse = [ ...@@ -72,35 +25,6 @@ export const imagesListResponse = [
}, },
]; ];
export const tagsListResponse = [
{
canDelete: true,
createdAt: '2020-11-03T13:29:49+00:00',
digest: 'sha256:9d72ae1db47404e44e1760eb1ca4cb427b84be8c511f05dfe2089e1b9f741dd7',
location: 'host.docker.internal:5000/gitlab-org/gitlab-test/rails-12009:alpha-11821',
name: 'alpha-11821',
path: 'gitlab-org/gitlab-test/rails-12009:alpha-11821',
revision: '5183b5d133fa864dca2de602f874b0d1bffe0f204ad894e3660432a487935139',
shortRevision: '5183b5d13',
totalSize: 104,
layers: 10,
__typename: 'ContainerRepositoryTag',
},
{
canDelete: true,
createdAt: '2020-11-03T13:29:48+00:00',
digest: 'sha256:64f61282a71659f72066f9decd30b9038a465859b277a5e20da8681eb83e72f7',
location: 'host.docker.internal:5000/gitlab-org/gitlab-test/rails-12009:alpha-20825',
name: 'alpha-20825',
path: 'gitlab-org/gitlab-test/rails-12009:alpha-20825',
revision: 'e4212f1b73c6f9def2c37fa7df6c8d35c345fb1402860ff9a56404821aacf16f',
shortRevision: 'e4212f1b7',
totalSize: 105,
layers: 10,
__typename: 'ContainerRepositoryTag',
},
];
export const pageInfo = { export const pageInfo = {
hasNextPage: true, hasNextPage: true,
hasPreviousPage: true, hasPreviousPage: true,
...@@ -109,17 +33,6 @@ export const pageInfo = { ...@@ -109,17 +33,6 @@ export const pageInfo = {
__typename: 'ContainerRepositoryConnection', __typename: 'ContainerRepositoryConnection',
}; };
export const imageDetailsMock = {
canDelete: true,
createdAt: '2020-11-03T13:29:21Z',
expirationPolicyStartedAt: null,
id: 'gid://gitlab/ContainerRepository/26',
location: 'host.docker.internal:5000/gitlab-org/gitlab-test/rails-12009',
name: 'rails-12009',
path: 'gitlab-org/gitlab-test/rails-12009',
status: null,
};
export const graphQLImageListMock = { export const graphQLImageListMock = {
data: { data: {
project: { project: {
...@@ -285,3 +198,9 @@ export const graphQLDeleteImageRepositoryTagsMock = { ...@@ -285,3 +198,9 @@ export const graphQLDeleteImageRepositoryTagsMock = {
}, },
}, },
}; };
export const dockerCommands = {
dockerBuildCommand: 'foofoo',
dockerPushCommand: 'barbar',
dockerLoginCommand: 'bazbaz',
};
...@@ -11,7 +11,6 @@ import DetailsHeader from '~/registry/explorer/components/details_page/details_h ...@@ -11,7 +11,6 @@ import DetailsHeader from '~/registry/explorer/components/details_page/details_h
import TagsLoader from '~/registry/explorer/components/details_page/tags_loader.vue'; import TagsLoader from '~/registry/explorer/components/details_page/tags_loader.vue';
import TagsList from '~/registry/explorer/components/details_page/tags_list.vue'; import TagsList from '~/registry/explorer/components/details_page/tags_list.vue';
import EmptyTagsState from '~/registry/explorer/components/details_page/empty_tags_state.vue'; import EmptyTagsState from '~/registry/explorer/components/details_page/empty_tags_state.vue';
import { createStore } from '~/registry/explorer/stores/';
import getContainerRepositoryDetailsQuery from '~/registry/explorer/graphql/queries/get_container_repository_details.graphql'; import getContainerRepositoryDetailsQuery from '~/registry/explorer/graphql/queries/get_container_repository_details.graphql';
import deleteContainerRepositoryTagsMutation from '~/registry/explorer/graphql/mutations/delete_container_repository_tags.graphql'; import deleteContainerRepositoryTagsMutation from '~/registry/explorer/graphql/mutations/delete_container_repository_tags.graphql';
...@@ -30,7 +29,6 @@ const localVue = createLocalVue(); ...@@ -30,7 +29,6 @@ const localVue = createLocalVue();
describe('Details Page', () => { describe('Details Page', () => {
let wrapper; let wrapper;
let store;
let apolloProvider; let apolloProvider;
const findDeleteModal = () => wrapper.find(DeleteModal); const findDeleteModal = () => wrapper.find(DeleteModal);
...@@ -70,6 +68,7 @@ describe('Details Page', () => { ...@@ -70,6 +68,7 @@ describe('Details Page', () => {
resolver = jest.fn().mockResolvedValue(graphQLImageDetailsMock()), resolver = jest.fn().mockResolvedValue(graphQLImageDetailsMock()),
mutationResolver = jest.fn().mockResolvedValue(graphQLDeleteImageRepositoryTagsMock), mutationResolver = jest.fn().mockResolvedValue(graphQLDeleteImageRepositoryTagsMock),
options, options,
config = {},
} = {}) => { } = {}) => {
localVue.use(VueApollo); localVue.use(VueApollo);
...@@ -81,7 +80,6 @@ describe('Details Page', () => { ...@@ -81,7 +80,6 @@ describe('Details Page', () => {
apolloProvider = createMockApollo(requestHandlers); apolloProvider = createMockApollo(requestHandlers);
wrapper = shallowMount(component, { wrapper = shallowMount(component, {
store,
localVue, localVue,
apolloProvider, apolloProvider,
stubs: { stubs: {
...@@ -97,6 +95,7 @@ describe('Details Page', () => { ...@@ -97,6 +95,7 @@ describe('Details Page', () => {
provide() { provide() {
return { return {
breadCrumbState, breadCrumbState,
config,
}; };
}, },
...options, ...options,
...@@ -104,7 +103,6 @@ describe('Details Page', () => { ...@@ -104,7 +103,6 @@ describe('Details Page', () => {
}; };
beforeEach(() => { beforeEach(() => {
store = createStore();
jest.spyOn(Tracking, 'event'); jest.spyOn(Tracking, 'event');
}); });
...@@ -374,13 +372,13 @@ describe('Details Page', () => { ...@@ -374,13 +372,13 @@ describe('Details Page', () => {
}); });
it('has the correct props', async () => { it('has the correct props', async () => {
store.commit('SET_INITIAL_STATE', { ...config });
mountComponent({ mountComponent({
options: { options: {
data: () => ({ data: () => ({
deleteAlertType, deleteAlertType,
}), }),
}, },
config,
}); });
await waitForApolloRequestRender(); await waitForApolloRequestRender();
...@@ -414,9 +412,7 @@ describe('Details Page', () => { ...@@ -414,9 +412,7 @@ describe('Details Page', () => {
}); });
it('has the correct props', async () => { it('has the correct props', async () => {
store.commit('SET_INITIAL_STATE', { ...config }); mountComponent({ resolver, config });
mountComponent({ resolver });
await waitForApolloRequestRender(); await waitForApolloRequestRender();
......
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import component from '~/registry/explorer/pages/index.vue'; import component from '~/registry/explorer/pages/index.vue';
import { createStore } from '~/registry/explorer/stores/';
describe('List Page', () => { describe('List Page', () => {
let wrapper; let wrapper;
let store;
const findRouterView = () => wrapper.find({ ref: 'router-view' }); const findRouterView = () => wrapper.find({ ref: 'router-view' });
const mountComponent = () => { const mountComponent = () => {
wrapper = shallowMount(component, { wrapper = shallowMount(component, {
store,
stubs: { stubs: {
RouterView: true, RouterView: true,
}, },
...@@ -18,7 +15,6 @@ describe('List Page', () => { ...@@ -18,7 +15,6 @@ describe('List Page', () => {
}; };
beforeEach(() => { beforeEach(() => {
store = createStore();
mountComponent(); mountComponent();
}); });
......
...@@ -11,8 +11,7 @@ import ProjectEmptyState from '~/registry/explorer/components/list_page/project_ ...@@ -11,8 +11,7 @@ import ProjectEmptyState from '~/registry/explorer/components/list_page/project_
import RegistryHeader from '~/registry/explorer/components/list_page/registry_header.vue'; import RegistryHeader from '~/registry/explorer/components/list_page/registry_header.vue';
import ImageList from '~/registry/explorer/components/list_page/image_list.vue'; import ImageList from '~/registry/explorer/components/list_page/image_list.vue';
import TitleArea from '~/vue_shared/components/registry/title_area.vue'; import TitleArea from '~/vue_shared/components/registry/title_area.vue';
import { createStore } from '~/registry/explorer/stores/';
import { SET_INITIAL_STATE } from '~/registry/explorer/stores/mutation_types';
import { import {
DELETE_IMAGE_SUCCESS_MESSAGE, DELETE_IMAGE_SUCCESS_MESSAGE,
DELETE_IMAGE_ERROR_MESSAGE, DELETE_IMAGE_ERROR_MESSAGE,
...@@ -40,7 +39,6 @@ const localVue = createLocalVue(); ...@@ -40,7 +39,6 @@ const localVue = createLocalVue();
describe('List Page', () => { describe('List Page', () => {
let wrapper; let wrapper;
let store;
let apolloProvider; let apolloProvider;
const findDeleteModal = () => wrapper.find(GlModal); const findDeleteModal = () => wrapper.find(GlModal);
...@@ -69,6 +67,7 @@ describe('List Page', () => { ...@@ -69,6 +67,7 @@ describe('List Page', () => {
resolver = jest.fn().mockResolvedValue(graphQLImageListMock), resolver = jest.fn().mockResolvedValue(graphQLImageListMock),
groupResolver = jest.fn().mockResolvedValue(graphQLImageListMock), groupResolver = jest.fn().mockResolvedValue(graphQLImageListMock),
mutationResolver = jest.fn().mockResolvedValue(graphQLImageDeleteMock), mutationResolver = jest.fn().mockResolvedValue(graphQLImageDeleteMock),
config = {},
} = {}) => { } = {}) => {
localVue.use(VueApollo); localVue.use(VueApollo);
...@@ -83,7 +82,6 @@ describe('List Page', () => { ...@@ -83,7 +82,6 @@ describe('List Page', () => {
wrapper = shallowMount(component, { wrapper = shallowMount(component, {
localVue, localVue,
apolloProvider, apolloProvider,
store,
stubs: { stubs: {
GlModal, GlModal,
GlEmptyState, GlEmptyState,
...@@ -98,12 +96,13 @@ describe('List Page', () => { ...@@ -98,12 +96,13 @@ describe('List Page', () => {
}, },
...mocks, ...mocks,
}, },
}); provide() {
return {
config,
}; };
},
beforeEach(() => {
store = createStore();
}); });
};
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
...@@ -127,34 +126,26 @@ describe('List Page', () => { ...@@ -127,34 +126,26 @@ describe('List Page', () => {
helpPagePath: 'bar', helpPagePath: 'bar',
}; };
beforeEach(() => {
store.commit(SET_INITIAL_STATE, config);
});
afterEach(() => {
store.commit(SET_INITIAL_STATE, {});
});
it('should show an empty state', () => { it('should show an empty state', () => {
mountComponent(); mountComponent({ config });
expect(findEmptyState().exists()).toBe(true); expect(findEmptyState().exists()).toBe(true);
}); });
it('empty state should have an svg-path', () => { it('empty state should have an svg-path', () => {
mountComponent(); mountComponent({ config });
expect(findEmptyState().attributes('svg-path')).toBe(config.containersErrorImage); expect(findEmptyState().attributes('svg-path')).toBe(config.containersErrorImage);
}); });
it('empty state should have a description', () => { it('empty state should have a description', () => {
mountComponent(); mountComponent({ config });
expect(findEmptyState().html()).toContain('connection error'); expect(findEmptyState().html()).toContain('connection error');
}); });
it('should not show the loading or default state', () => { it('should not show the loading or default state', () => {
mountComponent(); mountComponent({ config });
expect(findSkeletonLoader().exists()).toBe(false); expect(findSkeletonLoader().exists()).toBe(false);
expect(findImageList().exists()).toBe(false); expect(findImageList().exists()).toBe(false);
...@@ -204,16 +195,12 @@ describe('List Page', () => { ...@@ -204,16 +195,12 @@ describe('List Page', () => {
describe('group page', () => { describe('group page', () => {
const groupResolver = jest.fn().mockResolvedValue(graphQLEmptyGroupImageListMock); const groupResolver = jest.fn().mockResolvedValue(graphQLEmptyGroupImageListMock);
beforeEach(() => { const config = {
store.commit(SET_INITIAL_STATE, { isGroupPage: true }); isGroupPage: true,
}); };
afterEach(() => {
store.commit(SET_INITIAL_STATE, { isGroupPage: undefined });
});
it('group empty state is visible', async () => { it('group empty state is visible', async () => {
mountComponent({ groupResolver }); mountComponent({ groupResolver, config });
await waitForApolloRequestRender(); await waitForApolloRequestRender();
...@@ -221,7 +208,7 @@ describe('List Page', () => { ...@@ -221,7 +208,7 @@ describe('List Page', () => {
}); });
it('cli commands is not visible', async () => { it('cli commands is not visible', async () => {
mountComponent({ groupResolver }); mountComponent({ groupResolver, config });
await waitForApolloRequestRender(); await waitForApolloRequestRender();
...@@ -229,7 +216,7 @@ describe('List Page', () => { ...@@ -229,7 +216,7 @@ describe('List Page', () => {
}); });
it('list header is not visible', async () => { it('list header is not visible', async () => {
mountComponent({ groupResolver }); mountComponent({ groupResolver, config });
await waitForApolloRequestRender(); await waitForApolloRequestRender();
......
import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
import { TEST_HOST } from 'helpers/test_constants';
import createFlash from '~/flash';
import Api from '~/api';
import axios from '~/lib/utils/axios_utils';
import * as actions from '~/registry/explorer/stores/actions';
import * as types from '~/registry/explorer/stores/mutation_types';
import { reposServerResponse, registryServerResponse } from '../mock_data';
import * as utils from '~/registry/explorer/utils';
import {
FETCH_IMAGES_LIST_ERROR_MESSAGE,
FETCH_TAGS_LIST_ERROR_MESSAGE,
FETCH_IMAGE_DETAILS_ERROR_MESSAGE,
} from '~/registry/explorer/constants/index';
jest.mock('~/flash.js');
jest.mock('~/registry/explorer/utils');
describe('Actions RegistryExplorer Store', () => {
let mock;
const endpoint = `${TEST_HOST}/endpoint.json`;
const url = `${endpoint}/1}`;
jest.spyOn(utils, 'pathGenerator').mockReturnValue(url);
beforeEach(() => {
mock = new MockAdapter(axios);
});
afterEach(() => {
mock.restore();
});
it('sets initial state', done => {
const initialState = {
config: {
endpoint,
},
};
testAction(
actions.setInitialState,
initialState,
null,
[{ type: types.SET_INITIAL_STATE, payload: initialState }],
[],
done,
);
});
it('setShowGarbageCollectionTip', done => {
testAction(
actions.setShowGarbageCollectionTip,
true,
null,
[{ type: types.SET_SHOW_GARBAGE_COLLECTION_TIP, payload: true }],
[],
done,
);
});
describe('receives api responses', () => {
const response = {
data: [1, 2, 3],
headers: {
page: 1,
perPage: 10,
},
};
it('images list response', done => {
testAction(
actions.receiveImagesListSuccess,
response,
null,
[
{ type: types.SET_IMAGES_LIST_SUCCESS, payload: response.data },
{ type: types.SET_PAGINATION, payload: response.headers },
],
[],
done,
);
});
it('tags list response', done => {
testAction(
actions.receiveTagsListSuccess,
response,
null,
[
{ type: types.SET_TAGS_LIST_SUCCESS, payload: response.data },
{ type: types.SET_TAGS_PAGINATION, payload: response.headers },
],
[],
done,
);
});
});
describe('fetch images list', () => {
it('sets the imagesList and pagination', done => {
mock.onGet(endpoint).replyOnce(200, reposServerResponse, {});
testAction(
actions.requestImagesList,
{},
{
config: {
endpoint,
},
},
[
{ type: types.SET_MAIN_LOADING, payload: true },
{ type: types.SET_MAIN_LOADING, payload: false },
],
[{ type: 'receiveImagesListSuccess', payload: { data: reposServerResponse, headers: {} } }],
done,
);
});
it('should create flash on error', done => {
testAction(
actions.requestImagesList,
{},
{
config: {
endpoint: null,
},
},
[
{ type: types.SET_MAIN_LOADING, payload: true },
{ type: types.SET_MAIN_LOADING, payload: false },
],
[],
() => {
expect(createFlash).toHaveBeenCalledWith({ message: FETCH_IMAGES_LIST_ERROR_MESSAGE });
done();
},
);
});
});
describe('fetch tags list', () => {
it('sets the tagsList', done => {
mock.onGet(url).replyOnce(200, registryServerResponse, {});
testAction(
actions.requestTagsList,
{},
{},
[
{ type: types.SET_MAIN_LOADING, payload: true },
{ type: types.SET_MAIN_LOADING, payload: false },
],
[
{
type: 'receiveTagsListSuccess',
payload: { data: registryServerResponse, headers: {} },
},
],
done,
);
});
it('should create flash on error', done => {
testAction(
actions.requestTagsList,
{},
{},
[
{ type: types.SET_MAIN_LOADING, payload: true },
{ type: types.SET_MAIN_LOADING, payload: false },
],
[],
() => {
expect(createFlash).toHaveBeenCalledWith({ message: FETCH_TAGS_LIST_ERROR_MESSAGE });
done();
},
);
});
});
describe('request delete single tag', () => {
it('successfully performs the delete request', done => {
const deletePath = 'delete/path';
mock.onDelete(deletePath).replyOnce(200);
testAction(
actions.requestDeleteTag,
{
tag: {
destroy_path: deletePath,
},
},
{
tagsPagination: {},
},
[
{ type: types.SET_MAIN_LOADING, payload: true },
{ type: types.SET_MAIN_LOADING, payload: false },
],
[
{
type: 'setShowGarbageCollectionTip',
payload: true,
},
{
type: 'requestTagsList',
payload: {},
},
],
done,
);
});
it('should turn off loading on error', done => {
testAction(
actions.requestDeleteTag,
{
tag: {
destroy_path: null,
},
},
{},
[
{ type: types.SET_MAIN_LOADING, payload: true },
{ type: types.SET_MAIN_LOADING, payload: false },
],
[],
).catch(() => done());
});
});
describe('requestImageDetailsAndTagsList', () => {
it('sets the imageDetails and dispatch requestTagsList', done => {
const resolvedValue = { foo: 'bar' };
jest.spyOn(Api, 'containerRegistryDetails').mockResolvedValue({ data: resolvedValue });
testAction(
actions.requestImageDetailsAndTagsList,
1,
{},
[
{ type: types.SET_MAIN_LOADING, payload: true },
{ type: types.SET_IMAGE_DETAILS, payload: resolvedValue },
],
[
{
type: 'requestTagsList',
},
],
done,
);
});
it('should create flash on error', done => {
jest.spyOn(Api, 'containerRegistryDetails').mockRejectedValue();
testAction(
actions.requestImageDetailsAndTagsList,
1,
{},
[
{ type: types.SET_MAIN_LOADING, payload: true },
{ type: types.SET_MAIN_LOADING, payload: false },
],
[],
() => {
expect(createFlash).toHaveBeenCalledWith({ message: FETCH_IMAGE_DETAILS_ERROR_MESSAGE });
done();
},
);
});
});
describe('request delete multiple tags', () => {
it('successfully performs the delete request', done => {
mock.onDelete(url).replyOnce(200);
testAction(
actions.requestDeleteTags,
{
ids: [1, 2],
},
{
tagsPagination: {},
},
[
{ type: types.SET_MAIN_LOADING, payload: true },
{ type: types.SET_MAIN_LOADING, payload: false },
],
[
{
type: 'setShowGarbageCollectionTip',
payload: true,
},
{
type: 'requestTagsList',
payload: {},
},
],
done,
);
});
it('should turn off loading on error', done => {
mock.onDelete(url).replyOnce(500);
testAction(
actions.requestDeleteTags,
{
ids: [1, 2],
},
{
tagsPagination: {},
},
[
{ type: types.SET_MAIN_LOADING, payload: true },
{ type: types.SET_MAIN_LOADING, payload: false },
],
[],
).catch(() => done());
});
});
describe('request delete single image', () => {
const image = {
destroy_path: 'delete/path',
};
it('successfully performs the delete request', done => {
mock.onDelete(image.destroy_path).replyOnce(200);
testAction(
actions.requestDeleteImage,
image,
{},
[
{ type: types.SET_MAIN_LOADING, payload: true },
{ type: types.UPDATE_IMAGE, payload: { ...image, deleting: true } },
{ type: types.SET_MAIN_LOADING, payload: false },
],
[],
done,
);
});
it('should turn off loading on error', done => {
mock.onDelete(image.destroy_path).replyOnce(400);
testAction(
actions.requestDeleteImage,
image,
{},
[
{ type: types.SET_MAIN_LOADING, payload: true },
{ type: types.SET_MAIN_LOADING, payload: false },
],
[],
).catch(() => done());
});
});
});
import * as getters from '~/registry/explorer/stores/getters';
describe('Getters RegistryExplorer store', () => {
let state;
describe.each`
getter | prefix | configParameter | suffix
${'dockerBuildCommand'} | ${'docker build -t'} | ${'repositoryUrl'} | ${'.'}
${'dockerPushCommand'} | ${'docker push'} | ${'repositoryUrl'} | ${null}
${'dockerLoginCommand'} | ${'docker login'} | ${'registryHostUrlWithPort'} | ${null}
`('$getter', ({ getter, prefix, configParameter, suffix }) => {
beforeEach(() => {
state = {
config: { repositoryUrl: 'foo', registryHostUrlWithPort: 'bar' },
};
});
it(`returns ${prefix} concatenated with ${configParameter} and optionally suffixed with ${suffix}`, () => {
const expectedPieces = [prefix, state.config[configParameter], suffix].filter(p => p);
expect(getters[getter](state)).toBe(expectedPieces.join(' '));
});
});
describe('showGarbageCollection', () => {
it.each`
result | showGarbageCollectionTip | isAdmin
${true} | ${true} | ${true}
${false} | ${true} | ${false}
${false} | ${false} | ${true}
`(
'return $result when showGarbageCollectionTip $showGarbageCollectionTip and isAdmin is $isAdmin',
({ result, showGarbageCollectionTip, isAdmin }) => {
state = {
config: { isAdmin },
showGarbageCollectionTip,
};
expect(getters.showGarbageCollection(state)).toBe(result);
},
);
});
});
import mutations from '~/registry/explorer/stores/mutations';
import * as types from '~/registry/explorer/stores/mutation_types';
describe('Mutations Registry Explorer Store', () => {
let mockState;
beforeEach(() => {
mockState = {};
});
describe('SET_INITIAL_STATE', () => {
it('should set the initial state', () => {
const payload = {
endpoint: 'foo',
isGroupPage: '',
expirationPolicy: { foo: 'bar' },
isAdmin: '',
};
const expectedState = {
...mockState,
config: { ...payload, isGroupPage: false, isAdmin: false },
};
mutations[types.SET_INITIAL_STATE](mockState, {
...payload,
expirationPolicy: JSON.stringify(payload.expirationPolicy),
});
expect(mockState).toEqual(expectedState);
});
});
describe('SET_IMAGES_LIST_SUCCESS', () => {
it('should set the images list', () => {
const images = [{ name: 'foo' }, { name: 'bar' }];
const defaultStatus = { deleting: false, failedDelete: false };
const expectedState = {
...mockState,
images: [{ name: 'foo', ...defaultStatus }, { name: 'bar', ...defaultStatus }],
};
mutations[types.SET_IMAGES_LIST_SUCCESS](mockState, images);
expect(mockState).toEqual(expectedState);
});
});
describe('UPDATE_IMAGE', () => {
it('should update an image', () => {
mockState.images = [{ id: 1, name: 'foo' }, { id: 2, name: 'bar' }];
const payload = { id: 1, name: 'baz' };
const expectedState = {
...mockState,
images: [payload, { id: 2, name: 'bar' }],
};
mutations[types.UPDATE_IMAGE](mockState, payload);
expect(mockState).toEqual(expectedState);
});
});
describe('SET_TAGS_LIST_SUCCESS', () => {
it('should set the tags list', () => {
const tags = [1, 2, 3];
const expectedState = { ...mockState, tags };
mutations[types.SET_TAGS_LIST_SUCCESS](mockState, tags);
expect(mockState).toEqual(expectedState);
});
});
describe('SET_MAIN_LOADING', () => {
it('should set the isLoading', () => {
const expectedState = { ...mockState, isLoading: true };
mutations[types.SET_MAIN_LOADING](mockState, true);
expect(mockState).toEqual(expectedState);
});
});
describe('SET_SHOW_GARBAGE_COLLECTION_TIP', () => {
it('should set the showGarbageCollectionTip', () => {
const expectedState = { ...mockState, showGarbageCollectionTip: true };
mutations[types.SET_SHOW_GARBAGE_COLLECTION_TIP](mockState, true);
expect(mockState).toEqual(expectedState);
});
});
describe('SET_PAGINATION', () => {
const generatePagination = () => [
{
'X-PAGE': '1',
'X-PER-PAGE': '20',
'X-TOTAL': '100',
'X-TOTAL-PAGES': '5',
'X-NEXT-PAGE': '2',
'X-PREV-PAGE': '0',
},
{
page: 1,
perPage: 20,
total: 100,
totalPages: 5,
nextPage: 2,
previousPage: 0,
},
];
it('should set the images pagination', () => {
const [headers, expectedResult] = generatePagination();
const expectedState = { ...mockState, pagination: expectedResult };
mutations[types.SET_PAGINATION](mockState, headers);
expect(mockState).toEqual(expectedState);
});
it('should set the tags pagination', () => {
const [headers, expectedResult] = generatePagination();
const expectedState = { ...mockState, tagsPagination: expectedResult };
mutations[types.SET_TAGS_PAGINATION](mockState, headers);
expect(mockState).toEqual(expectedState);
});
});
describe('SET_IMAGE_DETAILS', () => {
it('should set imageDetails', () => {
const expectedState = { ...mockState, imageDetails: { foo: 'bar' } };
mutations[types.SET_IMAGE_DETAILS](mockState, { foo: 'bar' });
expect(mockState).toEqual(expectedState);
});
});
});
import { pathGenerator } from '~/registry/explorer/utils';
describe('Utils', () => {
describe('pathGenerator', () => {
const imageDetails = {
path: 'foo/bar/baz',
name: 'baz',
id: 1,
};
beforeEach(() => {
window.gon.relative_url_root = null;
});
it('returns the fetch url when no ending is passed', () => {
expect(pathGenerator(imageDetails)).toBe('/foo/bar/registry/repository/1/tags?format=json');
});
it('returns the url with an ending when is passed', () => {
expect(pathGenerator(imageDetails, '/foo')).toBe('/foo/bar/registry/repository/1/tags/foo');
});
describe.each`
path | name | result
${'foo/foo'} | ${''} | ${'/foo/foo/registry/repository/1/tags?format=json'}
${'foo/foo/foo'} | ${'foo'} | ${'/foo/foo/registry/repository/1/tags?format=json'}
${'baz/foo/foo/foo'} | ${'foo'} | ${'/baz/foo/foo/registry/repository/1/tags?format=json'}
${'baz/foo/foo/foo'} | ${'foo'} | ${'/baz/foo/foo/registry/repository/1/tags?format=json'}
${'foo/foo/baz/foo/foo'} | ${'foo/foo'} | ${'/foo/foo/baz/registry/repository/1/tags?format=json'}
${'foo/foo/baz/foo/bar'} | ${'foo/bar'} | ${'/foo/foo/baz/registry/repository/1/tags?format=json'}
${'baz/foo/foo'} | ${'foo'} | ${'/baz/foo/registry/repository/1/tags?format=json'}
${'baz/foo/bar'} | ${'foo'} | ${'/baz/foo/bar/registry/repository/1/tags?format=json'}
`('when path is $path and name is $name', ({ name, path, result }) => {
it('returns the correct value', () => {
expect(pathGenerator({ id: 1, name, path })).toBe(result);
});
it('produces a correct relative url', () => {
window.gon.relative_url_root = '/gitlab';
expect(pathGenerator({ id: 1, name, path })).toBe(`/gitlab${result}`);
});
});
it('returns the url unchanged when imageDetails have no name', () => {
const imageDetailsWithoutName = {
path: 'foo/bar/baz',
name: '',
id: 1,
};
expect(pathGenerator(imageDetailsWithoutName)).toBe(
'/foo/bar/baz/registry/repository/1/tags?format=json',
);
});
});
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment