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,77 +37,60 @@ export default { ...@@ -36,77 +37,60 @@ 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 <gl-icon name="eye" class="gl-mr-3" />
v-if="packageIcon" <gl-sprintf :message="$options.i18n.packageInfo">
:src="packageIcon" <template #version>
shape="rect" {{ packageEntity.version }}
class="gl-align-self-center gl-mr-4" </template>
data-testid="package-icon"
/>
<div class="gl-display-flex gl-flex-direction-column"> <template #timeAgo>
<h1 class="gl-font-size-h1 gl-mt-3 gl-mb-2"> <span v-gl-tooltip :title="tooltipTitle(packageEntity.created_at)">
{{ packageEntity.name }} &nbsp;{{ timeFormatted(packageEntity.created_at) }}
</h1> </span>
</template>
</gl-sprintf>
</template>
<div class="gl-display-flex gl-align-items-center gl-text-gray-500"> <template v-if="packageTypeDisplay" #metadata_type>
<gl-icon name="eye" class="gl-mr-3" /> <gl-icon name="package" class="gl-text-gray-500 gl-mr-3" />
<gl-sprintf :message="$options.i18n.packageInfo"> <span data-testid="package-type" class="gl-font-weight-bold">{{ packageTypeDisplay }}</span>
<template #version> </template>
{{ packageEntity.version }}
</template>
<template #timeAgo> <template #metadata_size>
<span v-gl-tooltip :title="tooltipTitle(packageEntity.created_at)"> <gl-icon name="disk" class="gl-text-gray-500 gl-mr-3" />
&nbsp;{{ timeFormatted(packageEntity.created_at) }} <span data-testid="package-size" class="gl-font-weight-bold">{{ totalSize }}</span>
</span> </template>
</template>
</gl-sprintf>
</div>
</div>
</div>
<div class="gl-display-flex gl-flex-wrap gl-align-items-center gl-mb-3"> <template v-if="packagePipeline" #metadata_pipeline>
<div v-if="packageTypeDisplay" class="gl-display-flex gl-align-items-center gl-mr-5"> <gl-icon name="review-list" class="gl-text-gray-500 gl-mr-3" />
<gl-icon name="package" class="gl-text-gray-500 gl-mr-3" /> <gl-link
<span data-testid="package-type" class="gl-font-weight-bold">{{ packageTypeDisplay }}</span> data-testid="pipeline-project"
</div> :href="packagePipeline.project.web_url"
class="gl-font-weight-bold gl-str-truncated"
<div class="gl-display-flex gl-align-items-center gl-mr-5"> >
<gl-icon name="disk" class="gl-text-gray-500 gl-mr-3" /> {{ packagePipeline.project.name }}
<span data-testid="package-size" class="gl-font-weight-bold">{{ totalSize }}</span> </gl-link>
</div> </template>
<div v-if="packagePipeline" class="gl-display-flex gl-align-items-center gl-mr-5">
<gl-icon name="review-list" class="gl-text-gray-500 gl-mr-3" />
<gl-link
data-testid="pipeline-project"
:href="packagePipeline.project.web_url"
class="gl-font-weight-bold text-truncate"
>
{{ packagePipeline.project.name }}
</gl-link>
</div>
<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" />
<span
v-gl-tooltip
data-testid="package-ref" data-testid="package-ref"
class="gl-display-flex gl-align-items-center gl-mr-5" class="gl-font-weight-bold gl-str-truncated mw-xs"
:title="packagePipeline.ref"
>{{ packagePipeline.ref }}</span
> >
<gl-icon name="branch" class="gl-text-gray-500 gl-mr-3" /> </template>
<span
v-gl-tooltip <template v-if="hasTagsToDisplay" #metadata_tags>
class="gl-font-weight-bold text-truncate mw-xs" <package-tags :tag-display-limit="2" :tags="packageEntity.tags" hide-label />
:title="packagePipeline.ref" </template>
>{{ packagePipeline.ref }}</span
>
</div>
<div v-if="hasTagsToDisplay" class="gl-display-flex gl-align-items-center gl-mr-5"> <template #right-actions>
<package-tags :tag-display-limit="2" :tags="packageEntity.tags" hide-label /> <slot name="delete-button"></slot>
</div> </template>
</div> </title-area>
</div>
</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,37 +87,32 @@ export default { ...@@ -85,37 +87,32 @@ 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" <gl-icon class="gl-mr-1" name="container-image" />
class="gl-display-flex gl-align-items-center gl-mt-1 gl-mb-3 gl-text-gray-500" <gl-sprintf :message="imagesCountText">
data-testid="subheader" <template #count>
> {{ imagesCount }}
<span class="gl-mr-3" data-testid="images-count"> </template>
<gl-icon class="gl-mr-1" name="container-image" /> </gl-sprintf>
<gl-sprintf :message="imagesCountText"> </span>
<template #count> </template>
{{ imagesCount }} <template #metadata_exp_policies>
</template> <span v-if="!hideExpirationPolicyData" data-testid="expiration-policy">
</gl-sprintf> <gl-icon class="gl-mr-1" name="expire" />
</span> <gl-sprintf :message="expirationPolicyText">
<span v-if="!hideExpirationPolicyData" data-testid="expiration-policy"> <template #time>
<gl-icon class="gl-mr-1" name="expire" /> {{ timeTillRun }}
<gl-sprintf :message="expirationPolicyText"> </template>
<template #time> </gl-sprintf>
{{ timeTillRun }} </span>
</template> </template>
</gl-sprintf> </title-area>
</span>
</div>
<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,172 +2,171 @@ ...@@ -2,172 +2,171 @@
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 <div
class="gl-display-flex" class="gl-flex-direction-column"
> >
<!----> <div
class="gl-display-flex"
>
<!---->
<div
class="gl-display-flex gl-flex-direction-column"
>
<h1
class="gl-font-size-h1 gl-mt-3 gl-mb-2"
data-testid="title"
>
Test package
</h1>
<div
class="gl-display-flex gl-align-items-center gl-text-gray-500 gl-mt-1"
>
<gl-icon-stub
class="gl-mr-3"
name="eye"
size="16"
/>
<gl-sprintf-stub
message="v%{version} published %{timeAgo}"
/>
</div>
</div>
</div>
<div <div
class="gl-display-flex gl-flex-direction-column" class="gl-display-flex gl-flex-wrap gl-align-items-center gl-mt-3"
> >
<h1 <div
class="gl-font-size-h1 gl-mt-3 gl-mb-2" class="gl-display-flex gl-align-items-center gl-mr-5"
> >
<gl-icon-stub
Test package class="gl-text-gray-500 gl-mr-3"
name="package"
</h1> size="16"
/>
<span
class="gl-font-weight-bold"
data-testid="package-type"
>
maven
</span>
</div>
<div <div
class="gl-display-flex gl-align-items-center gl-text-gray-500" class="gl-display-flex gl-align-items-center gl-mr-5"
> >
<gl-icon-stub <gl-icon-stub
class="gl-mr-3" class="gl-text-gray-500 gl-mr-3"
name="eye" name="disk"
size="16" size="16"
/> />
<gl-sprintf-stub <span
message="v%{version} published %{timeAgo}" class="gl-font-weight-bold"
data-testid="package-size"
>
300 bytes
</span>
</div>
<div
class="gl-display-flex gl-align-items-center gl-mr-5"
>
<package-tags-stub
hidelabel="true"
tagdisplaylimit="2"
tags="[object Object],[object Object],[object Object],[object Object]"
/> />
</div> </div>
</div> </div>
</div> </div>
<div <!---->
class="gl-display-flex gl-flex-wrap gl-align-items-center gl-mb-3"
>
<div
class="gl-display-flex gl-align-items-center gl-mr-5"
>
<gl-icon-stub
class="gl-text-gray-500 gl-mr-3"
name="package"
size="16"
/>
<span
class="gl-font-weight-bold"
data-testid="package-type"
>
maven
</span>
</div>
<div
class="gl-display-flex gl-align-items-center gl-mr-5"
>
<gl-icon-stub
class="gl-text-gray-500 gl-mr-3"
name="disk"
size="16"
/>
<span
class="gl-font-weight-bold"
data-testid="package-size"
>
300 bytes
</span>
</div>
<!---->
<!---->
<div
class="gl-display-flex gl-align-items-center gl-mr-5"
>
<package-tags-stub
hidelabel="true"
tagdisplaylimit="2"
tags="[object Object],[object Object],[object Object],[object Object]"
/>
</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 <div
class="gl-display-flex" class="gl-flex-direction-column"
> >
<!---->
<div <div
class="gl-display-flex gl-flex-direction-column" class="gl-display-flex"
> >
<h1 <!---->
class="gl-font-size-h1 gl-mt-3 gl-mb-2"
>
Test package
</h1>
<div <div
class="gl-display-flex gl-align-items-center gl-text-gray-500" class="gl-display-flex gl-flex-direction-column"
> >
<gl-icon-stub <h1
class="gl-mr-3" class="gl-font-size-h1 gl-mt-3 gl-mb-2"
name="eye" data-testid="title"
size="16" >
/> Test package
</h1>
<gl-sprintf-stub <div
message="v%{version} published %{timeAgo}" class="gl-display-flex gl-align-items-center gl-text-gray-500 gl-mt-1"
/> >
<gl-icon-stub
class="gl-mr-3"
name="eye"
size="16"
/>
<gl-sprintf-stub
message="v%{version} published %{timeAgo}"
/>
</div>
</div> </div>
</div> </div>
</div>
<div
class="gl-display-flex gl-flex-wrap gl-align-items-center gl-mb-3"
>
<div
class="gl-display-flex gl-align-items-center gl-mr-5"
>
<gl-icon-stub
class="gl-text-gray-500 gl-mr-3"
name="package"
size="16"
/>
<span
class="gl-font-weight-bold"
data-testid="package-type"
>
maven
</span>
</div>
<div <div
class="gl-display-flex gl-align-items-center gl-mr-5" class="gl-display-flex gl-flex-wrap gl-align-items-center gl-mt-3"
> >
<gl-icon-stub <div
class="gl-text-gray-500 gl-mr-3" class="gl-display-flex gl-align-items-center gl-mr-5"
name="disk"
size="16"
/>
<span
class="gl-font-weight-bold"
data-testid="package-size"
> >
300 bytes <gl-icon-stub
</span> class="gl-text-gray-500 gl-mr-3"
name="package"
size="16"
/>
<span
class="gl-font-weight-bold"
data-testid="package-type"
>
maven
</span>
</div>
<div
class="gl-display-flex gl-align-items-center gl-mr-5"
>
<gl-icon-stub
class="gl-text-gray-500 gl-mr-3"
name="disk"
size="16"
/>
<span
class="gl-font-weight-bold"
data-testid="package-size"
>
300 bytes
</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,90 +45,73 @@ describe('registry_header', () => { ...@@ -44,90 +45,73 @@ 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'); expect(findCommandsSlot().text()).toBe('baz');
}); });
});
describe('subheader', () => { describe('sub header parts', () => {
describe('when there are no images', () => { describe('images count', () => {
it('is hidden ', () => { it('exists', async () => {
mountComponent(); await mountComponent({ imagesCount: 1 });
expect(findSubHeader().exists()).toBe(false);
});
});
describe('when there are images', () => { expect(findImagesCountSubHeader().exists()).toBe(true);
it('is visible', () => { });
mountComponent({ imagesCount: 1 });
expect(findSubHeader().exists()).toBe(true);
});
describe('sub header parts', () => { it('when there is one image', async () => {
describe('images count', () => { await mountComponent({ imagesCount: 1 });
it('exists', () => {
mountComponent({ imagesCount: 1 });
expect(findImagesCountSubHeader().exists()).toBe(true);
});
it('when there is one image', () => { expect(findImagesCountSubHeader().text()).toMatchInterpolatedText('1 Image repository');
mountComponent({ imagesCount: 1 }); });
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();
expect(text.exists()).toBe(true);
expect(text.text()).toMatchInterpolatedText(EXPIRATION_POLICY_DISABLED_TEXT);
}); });
it('when is enabled', () => { const text = findExpirationPolicySubHeader();
mountComponent({ expect(text.exists()).toBe(true);
expirationPolicy: { enabled: true }, expect(text.text()).toMatchInterpolatedText(EXPIRATION_POLICY_DISABLED_TEXT);
expirationPolicyHelpPagePath: 'foo', });
imagesCount: 1,
}); it('when is enabled', async () => {
const text = findExpirationPolicySubHeader(); await mountComponent({
expect(text.exists()).toBe(true); expirationPolicy: { enabled: true },
expect(text.text()).toMatchInterpolatedText(EXPIRATION_POLICY_WILL_RUN_IN); expirationPolicyHelpPagePath: 'foo',
imagesCount: 1,
}); });
it('when the expiration policy is completely disabled', () => {
mountComponent({ const text = findExpirationPolicySubHeader();
expirationPolicy: { enabled: true }, expect(text.exists()).toBe(true);
expirationPolicyHelpPagePath: 'foo', expect(text.text()).toMatchInterpolatedText(EXPIRATION_POLICY_WILL_RUN_IN);
imagesCount: 1, });
hideExpirationPolicyData: true, it('when the expiration policy is completely disabled', async () => {
}); await mountComponent({
const text = findExpirationPolicySubHeader(); expirationPolicy: { enabled: true },
expect(text.exists()).toBe(false); expirationPolicyHelpPagePath: 'foo',
imagesCount: 1,
hideExpirationPolicyData: true,
}); });
const text = findExpirationPolicySubHeader();
expect(text.exists()).toBe(false);
}); });
}); });
}); });
...@@ -136,12 +120,13 @@ describe('registry_header', () => { ...@@ -136,12 +120,13 @@ describe('registry_header', () => {
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