Commit a4a25c0a authored by Kushal Pandya's avatar Kushal Pandya

Merge branch '225665-create-shared-components-for-package-and-container-registry-2' into 'master'

Create reusable Registry title component

See merge request gitlab-org/gitlab!40943
parents 8fca4b2d 2a33bcbc
...@@ -147,10 +147,8 @@ export default { ...@@ -147,10 +147,8 @@ export default {
/> />
<div v-else class="packages-app"> <div v-else class="packages-app">
<div class="detail-page-header d-flex justify-content-between flex-column flex-sm-row"> <package-title>
<package-title /> <template #delete-button>
<div class="mt-sm-2">
<gl-button <gl-button
v-if="canDeletePackage" v-if="canDeletePackage"
v-gl-modal="'delete-modal'" v-gl-modal="'delete-modal'"
...@@ -161,8 +159,8 @@ export default { ...@@ -161,8 +159,8 @@ export default {
> >
{{ __('Delete') }} {{ __('Delete') }}
</gl-button> </gl-button>
</div> </template>
</div> </package-title>
<gl-tabs> <gl-tabs>
<gl-tab :title="__('Detail')"> <gl-tab :title="__('Detail')">
......
<script> <script>
import { mapState, mapGetters } from 'vuex'; import { mapState, mapGetters } from 'vuex';
import { GlAvatar, GlIcon, GlLink, GlSprintf, GlTooltipDirective } from '@gitlab/ui'; import { GlIcon, GlLink, GlSprintf, GlTooltipDirective } from '@gitlab/ui';
import PackageTags from '../../shared/components/package_tags.vue'; import PackageTags from '../../shared/components/package_tags.vue';
import { numberToHumanSize } from '~/lib/utils/number_utils'; import { numberToHumanSize } from '~/lib/utils/number_utils';
import timeagoMixin from '~/vue_shared/mixins/timeago'; import timeagoMixin from '~/vue_shared/mixins/timeago';
import TitleArea from '~/vue_shared/components/registry/title_area.vue';
import { __ } from '~/locale'; import { __ } from '~/locale';
export default { export default {
name: 'PackageTitle', name: 'PackageTitle',
components: { components: {
GlAvatar, TitleArea,
GlIcon, GlIcon,
GlLink, GlLink,
GlSprintf, GlSprintf,
...@@ -36,22 +37,8 @@ export default { ...@@ -36,22 +37,8 @@ export default {
</script> </script>
<template> <template>
<div class="gl-flex-direction-column"> <title-area :title="packageEntity.name" :avatar="packageIcon" data-qa-selector="package_title">
<div class="gl-display-flex"> <template #sub-header>
<gl-avatar
v-if="packageIcon"
:src="packageIcon"
shape="rect"
class="gl-align-self-center gl-mr-4"
data-testid="package-icon"
/>
<div class="gl-display-flex gl-flex-direction-column">
<h1 class="gl-font-size-h1 gl-mt-3 gl-mb-2">
{{ packageEntity.name }}
</h1>
<div class="gl-display-flex gl-align-items-center gl-text-gray-500">
<gl-icon name="eye" class="gl-mr-3" /> <gl-icon name="eye" class="gl-mr-3" />
<gl-sprintf :message="$options.i18n.packageInfo"> <gl-sprintf :message="$options.i18n.packageInfo">
<template #version> <template #version>
...@@ -64,49 +51,46 @@ export default { ...@@ -64,49 +51,46 @@ export default {
</span> </span>
</template> </template>
</gl-sprintf> </gl-sprintf>
</div> </template>
</div>
</div>
<div class="gl-display-flex gl-flex-wrap gl-align-items-center gl-mb-3"> <template v-if="packageTypeDisplay" #metadata_type>
<div v-if="packageTypeDisplay" class="gl-display-flex gl-align-items-center gl-mr-5">
<gl-icon name="package" class="gl-text-gray-500 gl-mr-3" /> <gl-icon name="package" class="gl-text-gray-500 gl-mr-3" />
<span data-testid="package-type" class="gl-font-weight-bold">{{ packageTypeDisplay }}</span> <span data-testid="package-type" class="gl-font-weight-bold">{{ packageTypeDisplay }}</span>
</div> </template>
<div class="gl-display-flex gl-align-items-center gl-mr-5"> <template #metadata_size>
<gl-icon name="disk" class="gl-text-gray-500 gl-mr-3" /> <gl-icon name="disk" class="gl-text-gray-500 gl-mr-3" />
<span data-testid="package-size" class="gl-font-weight-bold">{{ totalSize }}</span> <span data-testid="package-size" class="gl-font-weight-bold">{{ totalSize }}</span>
</div> </template>
<div v-if="packagePipeline" class="gl-display-flex gl-align-items-center gl-mr-5"> <template v-if="packagePipeline" #metadata_pipeline>
<gl-icon name="review-list" class="gl-text-gray-500 gl-mr-3" /> <gl-icon name="review-list" class="gl-text-gray-500 gl-mr-3" />
<gl-link <gl-link
data-testid="pipeline-project" data-testid="pipeline-project"
:href="packagePipeline.project.web_url" :href="packagePipeline.project.web_url"
class="gl-font-weight-bold text-truncate" class="gl-font-weight-bold gl-str-truncated"
> >
{{ packagePipeline.project.name }} {{ packagePipeline.project.name }}
</gl-link> </gl-link>
</div> </template>
<div <template v-if="packagePipeline" #metadata_ref>
v-if="packagePipeline" <gl-icon name="branch" data-testid="package-ref-icon" class="gl-text-gray-500 gl-mr-3" />
data-testid="package-ref"
class="gl-display-flex gl-align-items-center gl-mr-5"
>
<gl-icon name="branch" class="gl-text-gray-500 gl-mr-3" />
<span <span
v-gl-tooltip v-gl-tooltip
class="gl-font-weight-bold text-truncate mw-xs" data-testid="package-ref"
class="gl-font-weight-bold gl-str-truncated mw-xs"
:title="packagePipeline.ref" :title="packagePipeline.ref"
>{{ packagePipeline.ref }}</span >{{ packagePipeline.ref }}</span
> >
</div> </template>
<div v-if="hasTagsToDisplay" class="gl-display-flex gl-align-items-center gl-mr-5"> <template v-if="hasTagsToDisplay" #metadata_tags>
<package-tags :tag-display-limit="2" :tags="packageEntity.tags" hide-label /> <package-tags :tag-display-limit="2" :tags="packageEntity.tags" hide-label />
</div> </template>
</div>
</div> <template #right-actions>
<slot name="delete-button"></slot>
</template>
</title-area>
</template> </template>
<script> <script>
import { GlSprintf } from '@gitlab/ui'; import { GlSprintf } from '@gitlab/ui';
import TitleArea from '~/vue_shared/components/registry/title_area.vue';
import { DETAILS_PAGE_TITLE } from '../../constants/index'; import { DETAILS_PAGE_TITLE } from '../../constants/index';
export default { export default {
components: { GlSprintf }, components: { GlSprintf, TitleArea },
props: { props: {
imageName: { imageName: {
type: String, type: String,
...@@ -18,13 +19,13 @@ export default { ...@@ -18,13 +19,13 @@ export default {
</script> </script>
<template> <template>
<div class="gl-display-flex gl-my-2 gl-align-items-center"> <title-area>
<h4> <template #title>
<gl-sprintf :message="$options.i18n.DETAILS_PAGE_TITLE"> <gl-sprintf :message="$options.i18n.DETAILS_PAGE_TITLE">
<template #imageName> <template #imageName>
{{ imageName }} {{ imageName }}
</template> </template>
</gl-sprintf> </gl-sprintf>
</h4> </template>
</div> </title-area>
</template> </template>
<script> <script>
import { GlSprintf, GlLink, GlIcon } from '@gitlab/ui'; import { GlSprintf, GlLink, GlIcon } from '@gitlab/ui';
import TitleArea from '~/vue_shared/components/registry/title_area.vue';
import { n__ } from '~/locale'; import { n__ } from '~/locale';
import { approximateDuration, calculateRemainingMilliseconds } from '~/lib/utils/datetime_utility'; import { approximateDuration, calculateRemainingMilliseconds } from '~/lib/utils/datetime_utility';
...@@ -16,6 +17,7 @@ export default { ...@@ -16,6 +17,7 @@ export default {
GlIcon, GlIcon,
GlSprintf, GlSprintf,
GlLink, GlLink,
TitleArea,
}, },
props: { props: {
expirationPolicy: { expirationPolicy: {
...@@ -85,21 +87,12 @@ export default { ...@@ -85,21 +87,12 @@ export default {
<template> <template>
<div> <div>
<div <title-area :title="$options.i18n.CONTAINER_REGISTRY_TITLE">
class="gl-display-flex gl-justify-content-space-between gl-align-items-center" <template #right-actions>
data-testid="header"
>
<h4 data-testid="title">{{ $options.i18n.CONTAINER_REGISTRY_TITLE }}</h4>
<div class="gl-display-none d-sm-block" data-testid="commands-slot">
<slot name="commands"></slot> <slot name="commands"></slot>
</div> </template>
</div> <template #metadata_count>
<div <span v-if="imagesCount" data-testid="images-count">
v-if="imagesCount"
class="gl-display-flex gl-align-items-center gl-mt-1 gl-mb-3 gl-text-gray-500"
data-testid="subheader"
>
<span class="gl-mr-3" data-testid="images-count">
<gl-icon class="gl-mr-1" name="container-image" /> <gl-icon class="gl-mr-1" name="container-image" />
<gl-sprintf :message="imagesCountText"> <gl-sprintf :message="imagesCountText">
<template #count> <template #count>
...@@ -107,6 +100,8 @@ export default { ...@@ -107,6 +100,8 @@ export default {
</template> </template>
</gl-sprintf> </gl-sprintf>
</span> </span>
</template>
<template #metadata_exp_policies>
<span v-if="!hideExpirationPolicyData" data-testid="expiration-policy"> <span v-if="!hideExpirationPolicyData" data-testid="expiration-policy">
<gl-icon class="gl-mr-1" name="expire" /> <gl-icon class="gl-mr-1" name="expire" />
<gl-sprintf :message="expirationPolicyText"> <gl-sprintf :message="expirationPolicyText">
...@@ -115,7 +110,9 @@ export default { ...@@ -115,7 +110,9 @@ export default {
</template> </template>
</gl-sprintf> </gl-sprintf>
</span> </span>
</div> </template>
</title-area>
<div data-testid="info-area"> <div data-testid="info-area">
<p> <p>
<span data-testid="default-intro"> <span data-testid="default-intro">
......
<script>
import { GlAvatar } from '@gitlab/ui';
export default {
name: 'TitleArea',
components: {
GlAvatar,
},
props: {
avatar: {
type: String,
default: null,
required: false,
},
title: {
type: String,
default: null,
required: false,
},
},
data() {
return {
metadataSlots: [],
};
},
mounted() {
this.metadataSlots = Object.keys(this.$slots).filter(k => k.startsWith('metadata_'));
},
};
</script>
<template>
<div class="gl-display-flex gl-justify-content-space-between gl-py-3">
<div class="gl-flex-direction-column">
<div class="gl-display-flex">
<gl-avatar v-if="avatar" :src="avatar" shape="rect" class="gl-align-self-center gl-mr-4" />
<div class="gl-display-flex gl-flex-direction-column">
<h1 class="gl-font-size-h1 gl-mt-3 gl-mb-2" data-testid="title">
<slot name="title">{{ title }}</slot>
</h1>
<div
v-if="$slots['sub-header']"
class="gl-display-flex gl-align-items-center gl-text-gray-500 gl-mt-1"
>
<slot name="sub-header"></slot>
</div>
</div>
</div>
<div class="gl-display-flex gl-flex-wrap gl-align-items-center gl-mt-3">
<div
v-for="(row, metadataIndex) in metadataSlots"
:key="metadataIndex"
class="gl-display-flex gl-align-items-center gl-mr-5"
>
<slot :name="row"></slot>
</div>
</div>
</div>
<div v-if="$slots['right-actions']" class="gl-mt-3">
<slot name="right-actions"></slot>
</div>
</div>
</template>
...@@ -2,8 +2,12 @@ ...@@ -2,8 +2,12 @@
exports[`PackageTitle renders with tags 1`] = ` exports[`PackageTitle renders with tags 1`] = `
<div <div
class="gl-flex-direction-column" class="gl-display-flex gl-justify-content-space-between gl-py-3"
data-qa-selector="package_title"
> >
<div
class="gl-flex-direction-column"
>
<div <div
class="gl-display-flex" class="gl-display-flex"
> >
...@@ -14,14 +18,13 @@ exports[`PackageTitle renders with tags 1`] = ` ...@@ -14,14 +18,13 @@ exports[`PackageTitle renders with tags 1`] = `
> >
<h1 <h1
class="gl-font-size-h1 gl-mt-3 gl-mb-2" class="gl-font-size-h1 gl-mt-3 gl-mb-2"
data-testid="title"
> >
Test package Test package
</h1> </h1>
<div <div
class="gl-display-flex gl-align-items-center gl-text-gray-500" class="gl-display-flex gl-align-items-center gl-text-gray-500 gl-mt-1"
> >
<gl-icon-stub <gl-icon-stub
class="gl-mr-3" class="gl-mr-3"
...@@ -37,7 +40,7 @@ exports[`PackageTitle renders with tags 1`] = ` ...@@ -37,7 +40,7 @@ exports[`PackageTitle renders with tags 1`] = `
</div> </div>
<div <div
class="gl-display-flex gl-flex-wrap gl-align-items-center gl-mb-3" class="gl-display-flex gl-flex-wrap gl-align-items-center gl-mt-3"
> >
<div <div
class="gl-display-flex gl-align-items-center gl-mr-5" class="gl-display-flex gl-align-items-center gl-mr-5"
...@@ -55,7 +58,6 @@ exports[`PackageTitle renders with tags 1`] = ` ...@@ -55,7 +58,6 @@ exports[`PackageTitle renders with tags 1`] = `
maven maven
</span> </span>
</div> </div>
<div <div
class="gl-display-flex gl-align-items-center gl-mr-5" class="gl-display-flex gl-align-items-center gl-mr-5"
> >
...@@ -72,11 +74,6 @@ exports[`PackageTitle renders with tags 1`] = ` ...@@ -72,11 +74,6 @@ exports[`PackageTitle renders with tags 1`] = `
300 bytes 300 bytes
</span> </span>
</div> </div>
<!---->
<!---->
<div <div
class="gl-display-flex gl-align-items-center gl-mr-5" class="gl-display-flex gl-align-items-center gl-mr-5"
> >
...@@ -87,13 +84,20 @@ exports[`PackageTitle renders with tags 1`] = ` ...@@ -87,13 +84,20 @@ exports[`PackageTitle renders with tags 1`] = `
/> />
</div> </div>
</div> </div>
</div>
<!---->
</div> </div>
`; `;
exports[`PackageTitle renders without tags 1`] = ` exports[`PackageTitle renders without tags 1`] = `
<div <div
class="gl-flex-direction-column" class="gl-display-flex gl-justify-content-space-between gl-py-3"
data-qa-selector="package_title"
> >
<div
class="gl-flex-direction-column"
>
<div <div
class="gl-display-flex" class="gl-display-flex"
> >
...@@ -104,14 +108,13 @@ exports[`PackageTitle renders without tags 1`] = ` ...@@ -104,14 +108,13 @@ exports[`PackageTitle renders without tags 1`] = `
> >
<h1 <h1
class="gl-font-size-h1 gl-mt-3 gl-mb-2" class="gl-font-size-h1 gl-mt-3 gl-mb-2"
data-testid="title"
> >
Test package Test package
</h1> </h1>
<div <div
class="gl-display-flex gl-align-items-center gl-text-gray-500" class="gl-display-flex gl-align-items-center gl-text-gray-500 gl-mt-1"
> >
<gl-icon-stub <gl-icon-stub
class="gl-mr-3" class="gl-mr-3"
...@@ -127,7 +130,7 @@ exports[`PackageTitle renders without tags 1`] = ` ...@@ -127,7 +130,7 @@ exports[`PackageTitle renders without tags 1`] = `
</div> </div>
<div <div
class="gl-display-flex gl-flex-wrap gl-align-items-center gl-mb-3" class="gl-display-flex gl-flex-wrap gl-align-items-center gl-mt-3"
> >
<div <div
class="gl-display-flex gl-align-items-center gl-mr-5" class="gl-display-flex gl-align-items-center gl-mr-5"
...@@ -145,7 +148,6 @@ exports[`PackageTitle renders without tags 1`] = ` ...@@ -145,7 +148,6 @@ exports[`PackageTitle renders without tags 1`] = `
maven maven
</span> </span>
</div> </div>
<div <div
class="gl-display-flex gl-align-items-center gl-mr-5" class="gl-display-flex gl-align-items-center gl-mr-5"
> >
...@@ -162,12 +164,9 @@ exports[`PackageTitle renders without tags 1`] = ` ...@@ -162,12 +164,9 @@ exports[`PackageTitle renders without tags 1`] = `
300 bytes 300 bytes
</span> </span>
</div> </div>
</div>
</div>
<!----> <!---->
<!---->
<!---->
</div>
</div> </div>
`; `;
...@@ -65,6 +65,8 @@ describe('PackagesApp', () => { ...@@ -65,6 +65,8 @@ describe('PackagesApp', () => {
store, store,
stubs: { stubs: {
...stubChildren(PackagesApp), ...stubChildren(PackagesApp),
PackageTitle: false,
TitleArea: false,
GlButton: false, GlButton: false,
GlModal: false, GlModal: false,
GlTab: false, GlTab: false,
......
...@@ -2,6 +2,7 @@ import Vuex from 'vuex'; ...@@ -2,6 +2,7 @@ import Vuex from 'vuex';
import { shallowMount, createLocalVue } from '@vue/test-utils'; import { shallowMount, createLocalVue } from '@vue/test-utils';
import PackageTitle from '~/packages/details/components/package_title.vue'; import PackageTitle from '~/packages/details/components/package_title.vue';
import PackageTags from '~/packages/shared/components/package_tags.vue'; import PackageTags from '~/packages/shared/components/package_tags.vue';
import TitleArea from '~/vue_shared/components/registry/title_area.vue';
import { import {
conanPackage, conanPackage,
mavenFiles, mavenFiles,
...@@ -39,14 +40,19 @@ describe('PackageTitle', () => { ...@@ -39,14 +40,19 @@ describe('PackageTitle', () => {
wrapper = shallowMount(PackageTitle, { wrapper = shallowMount(PackageTitle, {
localVue, localVue,
store, store,
stubs: {
TitleArea,
},
}); });
return wrapper.vm.$nextTick();
} }
const packageIcon = () => wrapper.find('[data-testid="package-icon"]'); const findTitleArea = () => wrapper.find(TitleArea);
const packageType = () => wrapper.find('[data-testid="package-type"]'); const packageType = () => wrapper.find('[data-testid="package-type"]');
const packageSize = () => wrapper.find('[data-testid="package-size"]'); const packageSize = () => wrapper.find('[data-testid="package-size"]');
const pipelineProject = () => wrapper.find('[data-testid="pipeline-project"]'); const pipelineProject = () => wrapper.find('[data-testid="pipeline-project"]');
const packageRef = () => wrapper.find('[data-testid="package-ref"]'); const packageRef = () => wrapper.find('[data-testid="package-ref"]');
const packageRefIcon = () => wrapper.find('[data-testid="package-ref-icon"]');
const packageTags = () => wrapper.find(PackageTags); const packageTags = () => wrapper.find(PackageTags);
afterEach(() => { afterEach(() => {
...@@ -54,38 +60,40 @@ describe('PackageTitle', () => { ...@@ -54,38 +60,40 @@ describe('PackageTitle', () => {
}); });
describe('renders', () => { describe('renders', () => {
it('without tags', () => { it('without tags', async () => {
createComponent(); await createComponent();
expect(wrapper.element).toMatchSnapshot(); expect(wrapper.element).toMatchSnapshot();
}); });
it('with tags', () => { it('with tags', async () => {
createComponent({ packageEntity: { ...mavenPackage, tags: mockTags } }); await createComponent({ packageEntity: { ...mavenPackage, tags: mockTags } });
expect(wrapper.element).toMatchSnapshot(); expect(wrapper.element).toMatchSnapshot();
}); });
}); });
describe('package icon', () => { describe('package title', () => {
const fakeSrc = 'a-fake-src'; it('is correctly bound', async () => {
await createComponent();
it('shows an icon when provided one from vuex', () => { expect(findTitleArea().props('title')).toBe('Test package');
createComponent({ icon: fakeSrc }); });
expect(packageIcon().exists()).toBe(true);
}); });
it('has the correct src attribute', () => { describe('package icon', () => {
createComponent({ icon: fakeSrc }); const fakeSrc = 'a-fake-src';
expect(packageIcon().props('src')).toBe(fakeSrc); it('binds an icon when provided one from vuex', async () => {
await createComponent({ icon: fakeSrc });
expect(findTitleArea().props('avatar')).toBe(fakeSrc);
}); });
it('does not show an icon when not provided one', () => { it('do not binds an icon when not provided one', async () => {
createComponent(); await createComponent();
expect(packageIcon().exists()).toBe(false); expect(findTitleArea().props('avatar')).toBe(null);
}); });
}); });
...@@ -104,22 +112,22 @@ describe('PackageTitle', () => { ...@@ -104,22 +112,22 @@ describe('PackageTitle', () => {
}); });
describe('calculates the package size', () => { describe('calculates the package size', () => {
it('correctly calulates when there is only 1 file', () => { it('correctly calculates when there is only 1 file', async () => {
createComponent({ packageEntity: npmPackage, packageFiles: npmFiles }); await createComponent({ packageEntity: npmPackage, packageFiles: npmFiles });
expect(packageSize().text()).toBe('200 bytes'); expect(packageSize().text()).toBe('200 bytes');
}); });
it('correctly calulates when there are multiple files', () => { it('correctly calulates when there are multiple files', async () => {
createComponent(); await createComponent();
expect(packageSize().text()).toBe('300 bytes'); expect(packageSize().text()).toBe('300 bytes');
}); });
}); });
describe('package tags', () => { describe('package tags', () => {
it('displays the package-tags component when the package has tags', () => { it('displays the package-tags component when the package has tags', async () => {
createComponent({ await createComponent({
packageEntity: { packageEntity: {
...npmPackage, ...npmPackage,
tags: mockTags, tags: mockTags,
...@@ -129,41 +137,36 @@ describe('PackageTitle', () => { ...@@ -129,41 +137,36 @@ describe('PackageTitle', () => {
expect(packageTags().exists()).toBe(true); expect(packageTags().exists()).toBe(true);
}); });
it('does not display the package-tags component when there are no tags', () => { it('does not display the package-tags component when there are no tags', async () => {
createComponent(); await createComponent();
expect(packageTags().exists()).toBe(false); expect(packageTags().exists()).toBe(false);
}); });
}); });
describe('package ref', () => { describe('package ref', () => {
it('does not display the ref if missing', () => { it('does not display the ref if missing', async () => {
createComponent(); await createComponent();
expect(packageRef().exists()).toBe(false); expect(packageRef().exists()).toBe(false);
}); });
it('correctly shows the package ref if there is one', () => { it('correctly shows the package ref if there is one', async () => {
createComponent({ packageEntity: npmPackage }); await createComponent({ packageEntity: npmPackage });
expect(packageRefIcon().exists()).toBe(true);
expect(
packageRef()
.find('gl-icon-stub')
.exists(),
).toBe(true);
expect(packageRef().text()).toBe(npmPackage.pipeline.ref); expect(packageRef().text()).toBe(npmPackage.pipeline.ref);
}); });
}); });
describe('pipeline project', () => { describe('pipeline project', () => {
it('does not display the project if missing', () => { it('does not display the project if missing', async () => {
createComponent(); await createComponent();
expect(pipelineProject().exists()).toBe(false); expect(pipelineProject().exists()).toBe(false);
}); });
it('correctly shows the pipeline project if there is one', () => { it('correctly shows the pipeline project if there is one', async () => {
createComponent({ packageEntity: npmPackage }); await createComponent({ packageEntity: npmPackage });
expect(pipelineProject().text()).toBe(npmPackage.pipeline.project.name); expect(pipelineProject().text()).toBe(npmPackage.pipeline.project.name);
expect(pipelineProject().attributes('href')).toBe(npmPackage.pipeline.project.web_url); expect(pipelineProject().attributes('href')).toBe(npmPackage.pipeline.project.web_url);
......
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import { GlSprintf } from '@gitlab/ui'; import { GlSprintf } from '@gitlab/ui';
import TitleArea from '~/vue_shared/components/registry/title_area.vue';
import component from '~/registry/explorer/components/details_page/details_header.vue'; import component from '~/registry/explorer/components/details_page/details_header.vue';
import { DETAILS_PAGE_TITLE } from '~/registry/explorer/constants'; import { DETAILS_PAGE_TITLE } from '~/registry/explorer/constants';
...@@ -11,6 +12,7 @@ describe('Details Header', () => { ...@@ -11,6 +12,7 @@ describe('Details Header', () => {
propsData, propsData,
stubs: { stubs: {
GlSprintf, GlSprintf,
TitleArea,
}, },
}); });
}; };
......
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import { GlSprintf, GlLink } from '@gitlab/ui'; import { GlSprintf, GlLink } from '@gitlab/ui';
import Component from '~/registry/explorer/components/list_page/registry_header.vue'; import Component from '~/registry/explorer/components/list_page/registry_header.vue';
import TitleArea from '~/vue_shared/components/registry/title_area.vue';
import { import {
CONTAINER_REGISTRY_TITLE, CONTAINER_REGISTRY_TITLE,
LIST_INTRO_TEXT, LIST_INTRO_TEXT,
...@@ -17,12 +18,10 @@ jest.mock('~/lib/utils/datetime_utility', () => ({ ...@@ -17,12 +18,10 @@ jest.mock('~/lib/utils/datetime_utility', () => ({
describe('registry_header', () => { describe('registry_header', () => {
let wrapper; let wrapper;
const findHeader = () => wrapper.find('[data-testid="header"]'); const findTitleArea = () => wrapper.find(TitleArea);
const findTitle = () => wrapper.find('[data-testid="title"]');
const findCommandsSlot = () => wrapper.find('[data-testid="commands-slot"]'); const findCommandsSlot = () => wrapper.find('[data-testid="commands-slot"]');
const findInfoArea = () => wrapper.find('[data-testid="info-area"]'); const findInfoArea = () => wrapper.find('[data-testid="info-area"]');
const findIntroText = () => wrapper.find('[data-testid="default-intro"]'); const findIntroText = () => wrapper.find('[data-testid="default-intro"]');
const findSubHeader = () => wrapper.find('[data-testid="subheader"]');
const findImagesCountSubHeader = () => wrapper.find('[data-testid="images-count"]'); const findImagesCountSubHeader = () => wrapper.find('[data-testid="images-count"]');
const findExpirationPolicySubHeader = () => wrapper.find('[data-testid="expiration-policy"]'); const findExpirationPolicySubHeader = () => wrapper.find('[data-testid="expiration-policy"]');
const findDisabledExpirationPolicyMessage = () => const findDisabledExpirationPolicyMessage = () =>
...@@ -32,10 +31,12 @@ describe('registry_header', () => { ...@@ -32,10 +31,12 @@ describe('registry_header', () => {
wrapper = shallowMount(Component, { wrapper = shallowMount(Component, {
stubs: { stubs: {
GlSprintf, GlSprintf,
TitleArea,
}, },
propsData, propsData,
slots, slots,
}); });
return wrapper.vm.$nextTick();
}; };
afterEach(() => { afterEach(() => {
...@@ -44,104 +45,88 @@ describe('registry_header', () => { ...@@ -44,104 +45,88 @@ describe('registry_header', () => {
}); });
describe('header', () => { describe('header', () => {
it('exists', () => { it('has a title', () => {
mountComponent(); mountComponent();
expect(findHeader().exists()).toBe(true);
});
it('contains the title of the page', () => { expect(findTitleArea().props('title')).toBe(CONTAINER_REGISTRY_TITLE);
mountComponent();
const title = findTitle();
expect(title.exists()).toBe(true);
expect(title.text()).toBe(CONTAINER_REGISTRY_TITLE);
}); });
it('has a commands slot', () => { it('has a commands slot', () => {
mountComponent(null, { commands: 'baz' }); mountComponent(null, { commands: '<div data-testid="commands-slot">baz</div>' });
expect(findCommandsSlot().text()).toBe('baz');
});
});
describe('subheader', () => { expect(findCommandsSlot().text()).toBe('baz');
describe('when there are no images', () => {
it('is hidden ', () => {
mountComponent();
expect(findSubHeader().exists()).toBe(false);
});
});
describe('when there are images', () => {
it('is visible', () => {
mountComponent({ imagesCount: 1 });
expect(findSubHeader().exists()).toBe(true);
}); });
describe('sub header parts', () => { describe('sub header parts', () => {
describe('images count', () => { describe('images count', () => {
it('exists', () => { it('exists', async () => {
mountComponent({ imagesCount: 1 }); await mountComponent({ imagesCount: 1 });
expect(findImagesCountSubHeader().exists()).toBe(true); expect(findImagesCountSubHeader().exists()).toBe(true);
}); });
it('when there is one image', () => { it('when there is one image', async () => {
mountComponent({ imagesCount: 1 }); await mountComponent({ imagesCount: 1 });
expect(findImagesCountSubHeader().text()).toMatchInterpolatedText('1 Image repository'); expect(findImagesCountSubHeader().text()).toMatchInterpolatedText('1 Image repository');
}); });
it('when there is more than one image', () => { it('when there is more than one image', async () => {
mountComponent({ imagesCount: 3 }); await mountComponent({ imagesCount: 3 });
expect(findImagesCountSubHeader().text()).toMatchInterpolatedText(
'3 Image repositories', expect(findImagesCountSubHeader().text()).toMatchInterpolatedText('3 Image repositories');
);
}); });
}); });
describe('expiration policy', () => { describe('expiration policy', () => {
it('when is disabled', () => { it('when is disabled', async () => {
mountComponent({ await mountComponent({
expirationPolicy: { enabled: false }, expirationPolicy: { enabled: false },
expirationPolicyHelpPagePath: 'foo', expirationPolicyHelpPagePath: 'foo',
imagesCount: 1, imagesCount: 1,
}); });
const text = findExpirationPolicySubHeader(); const text = findExpirationPolicySubHeader();
expect(text.exists()).toBe(true); expect(text.exists()).toBe(true);
expect(text.text()).toMatchInterpolatedText(EXPIRATION_POLICY_DISABLED_TEXT); expect(text.text()).toMatchInterpolatedText(EXPIRATION_POLICY_DISABLED_TEXT);
}); });
it('when is enabled', () => { it('when is enabled', async () => {
mountComponent({ await mountComponent({
expirationPolicy: { enabled: true }, expirationPolicy: { enabled: true },
expirationPolicyHelpPagePath: 'foo', expirationPolicyHelpPagePath: 'foo',
imagesCount: 1, imagesCount: 1,
}); });
const text = findExpirationPolicySubHeader(); const text = findExpirationPolicySubHeader();
expect(text.exists()).toBe(true); expect(text.exists()).toBe(true);
expect(text.text()).toMatchInterpolatedText(EXPIRATION_POLICY_WILL_RUN_IN); expect(text.text()).toMatchInterpolatedText(EXPIRATION_POLICY_WILL_RUN_IN);
}); });
it('when the expiration policy is completely disabled', () => { it('when the expiration policy is completely disabled', async () => {
mountComponent({ await mountComponent({
expirationPolicy: { enabled: true }, expirationPolicy: { enabled: true },
expirationPolicyHelpPagePath: 'foo', expirationPolicyHelpPagePath: 'foo',
imagesCount: 1, imagesCount: 1,
hideExpirationPolicyData: true, hideExpirationPolicyData: true,
}); });
const text = findExpirationPolicySubHeader(); const text = findExpirationPolicySubHeader();
expect(text.exists()).toBe(false); expect(text.exists()).toBe(false);
}); });
}); });
}); });
}); });
});
describe('info area', () => { describe('info area', () => {
it('exists', () => { it('exists', () => {
mountComponent(); mountComponent();
expect(findInfoArea().exists()).toBe(true); expect(findInfoArea().exists()).toBe(true);
}); });
describe('default message', () => { describe('default message', () => {
beforeEach(() => { beforeEach(() => {
mountComponent({ helpPagePath: 'bar' }); return mountComponent({ helpPagePath: 'bar' });
}); });
it('exists', () => { it('exists', () => {
...@@ -165,6 +150,7 @@ describe('registry_header', () => { ...@@ -165,6 +150,7 @@ describe('registry_header', () => {
describe('when there are no images', () => { describe('when there are no images', () => {
it('is hidden', () => { it('is hidden', () => {
mountComponent(); mountComponent();
expect(findDisabledExpirationPolicyMessage().exists()).toBe(false); expect(findDisabledExpirationPolicyMessage().exists()).toBe(false);
}); });
}); });
...@@ -172,7 +158,7 @@ describe('registry_header', () => { ...@@ -172,7 +158,7 @@ describe('registry_header', () => {
describe('when there are images', () => { describe('when there are images', () => {
describe('when expiration policy is disabled', () => { describe('when expiration policy is disabled', () => {
beforeEach(() => { beforeEach(() => {
mountComponent({ return mountComponent({
expirationPolicy: { enabled: false }, expirationPolicy: { enabled: false },
expirationPolicyHelpPagePath: 'foo', expirationPolicyHelpPagePath: 'foo',
imagesCount: 1, imagesCount: 1,
...@@ -202,6 +188,7 @@ describe('registry_header', () => { ...@@ -202,6 +188,7 @@ describe('registry_header', () => {
expirationPolicy: { enabled: true }, expirationPolicy: { enabled: true },
imagesCount: 1, imagesCount: 1,
}); });
expect(findDisabledExpirationPolicyMessage().exists()).toBe(false); expect(findDisabledExpirationPolicyMessage().exists()).toBe(false);
}); });
}); });
...@@ -212,6 +199,7 @@ describe('registry_header', () => { ...@@ -212,6 +199,7 @@ describe('registry_header', () => {
imagesCount: 1, imagesCount: 1,
hideExpirationPolicyData: true, hideExpirationPolicyData: true,
}); });
expect(findDisabledExpirationPolicyMessage().exists()).toBe(false); expect(findDisabledExpirationPolicyMessage().exists()).toBe(false);
}); });
}); });
......
...@@ -8,6 +8,7 @@ import GroupEmptyState from '~/registry/explorer/components/list_page/group_empt ...@@ -8,6 +8,7 @@ import GroupEmptyState from '~/registry/explorer/components/list_page/group_empt
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 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 { createStore } from '~/registry/explorer/stores/'; import { createStore } from '~/registry/explorer/stores/';
import { import {
SET_MAIN_LOADING, SET_MAIN_LOADING,
...@@ -54,6 +55,7 @@ describe('List Page', () => { ...@@ -54,6 +55,7 @@ describe('List Page', () => {
GlEmptyState, GlEmptyState,
GlSprintf, GlSprintf,
RegistryHeader, RegistryHeader,
TitleArea,
}, },
mocks: { mocks: {
$toast, $toast,
......
import { GlAvatar } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import component from '~/vue_shared/components/registry/title_area.vue';
describe('title area', () => {
let wrapper;
const findSubHeaderSlot = () => wrapper.find('[data-testid="sub-header"]');
const findRightActionsSlot = () => wrapper.find('[data-testid="right-actions"]');
const findMetadataSlot = name => wrapper.find(`[data-testid="${name}"]`);
const findTitle = () => wrapper.find('[data-testid="title"]');
const findAvatar = () => wrapper.find(GlAvatar);
const mountComponent = ({ propsData = { title: 'foo' }, slots } = {}) => {
wrapper = shallowMount(component, {
propsData,
slots: {
'sub-header': '<div data-testid="sub-header" />',
'right-actions': '<div data-testid="right-actions" />',
...slots,
},
});
};
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
describe('title', () => {
it('if slot is not present defaults to prop', () => {
mountComponent();
expect(findTitle().text()).toBe('foo');
});
it('if slot is present uses slot', () => {
mountComponent({
slots: {
title: 'slot_title',
},
});
expect(findTitle().text()).toBe('slot_title');
});
});
describe('avatar', () => {
it('is shown if avatar props exist', () => {
mountComponent({ propsData: { title: 'foo', avatar: 'baz' } });
expect(findAvatar().props('src')).toBe('baz');
});
it('is hidden if avatar props does not exist', () => {
mountComponent();
expect(findAvatar().exists()).toBe(false);
});
});
describe.each`
slotName | finderFunction
${'sub-header'} | ${findSubHeaderSlot}
${'right-actions'} | ${findRightActionsSlot}
`('$slotName slot', ({ finderFunction, slotName }) => {
it('exist when the slot is filled', () => {
mountComponent();
expect(finderFunction().exists()).toBe(true);
});
it('does not exist when the slot is empty', () => {
mountComponent({ slots: { [slotName]: '' } });
expect(finderFunction().exists()).toBe(false);
});
});
describe.each`
slotNames
${['metadata_foo']}
${['metadata_foo', 'metadata_bar']}
${['metadata_foo', 'metadata_bar', 'metadata_baz']}
`('$slotNames metadata slots', ({ slotNames }) => {
const slotMocks = slotNames.reduce((acc, current) => {
acc[current] = `<div data-testid="${current}" />`;
return acc;
}, {});
it('exist when the slot is present', async () => {
mountComponent({ slots: slotMocks });
await wrapper.vm.$nextTick();
slotNames.forEach(name => {
expect(findMetadataSlot(name).exists()).toBe(true);
});
});
});
});
...@@ -32,7 +32,7 @@ RSpec.shared_examples 'package details link' do |property| ...@@ -32,7 +32,7 @@ RSpec.shared_examples 'package details link' do |property|
expect(page).to have_current_path(project_package_path(package.project, package)) expect(page).to have_current_path(project_package_path(package.project, package))
page.within('.detail-page-header') do page.within('[data-qa-selector="package_title"]') do
expect(page).to have_content(package.name) expect(page).to have_content(package.name)
end end
......
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