Commit addaa925 authored by Paul Slaughter's avatar Paul Slaughter

Merge branch '247899-add-info_line-to-registry_title-component' into 'master'

Add info_line to registry/title_area component

See merge request gitlab-org/gitlab!42443
parents 2b5947ce e3616635
<script> <script>
import { GlSprintf, GlLink } from '@gitlab/ui';
import TitleArea from '~/vue_shared/components/registry/title_area.vue'; import TitleArea from '~/vue_shared/components/registry/title_area.vue';
import MetadataItem from '~/vue_shared/components/registry/metadata_item.vue'; import MetadataItem from '~/vue_shared/components/registry/metadata_item.vue';
import { n__, sprintf } from '~/locale'; import { n__, sprintf } from '~/locale';
...@@ -15,8 +14,6 @@ import { ...@@ -15,8 +14,6 @@ import {
export default { export default {
components: { components: {
GlSprintf,
GlLink,
TitleArea, TitleArea,
MetadataItem, MetadataItem,
}, },
...@@ -54,8 +51,6 @@ export default { ...@@ -54,8 +51,6 @@ export default {
}, },
i18n: { i18n: {
CONTAINER_REGISTRY_TITLE, CONTAINER_REGISTRY_TITLE,
LIST_INTRO_TEXT,
EXPIRATION_POLICY_DISABLED_MESSAGE,
}, },
computed: { computed: {
imagesCountText() { imagesCountText() {
...@@ -83,52 +78,40 @@ export default { ...@@ -83,52 +78,40 @@ export default {
!this.expirationPolicyEnabled && this.imagesCount > 0 && !this.hideExpirationPolicyData !this.expirationPolicyEnabled && this.imagesCount > 0 && !this.hideExpirationPolicyData
); );
}, },
infoMessages() {
const base = [{ text: LIST_INTRO_TEXT, link: this.helpPagePath }];
return this.showExpirationPolicyTip
? [
...base,
{ text: EXPIRATION_POLICY_DISABLED_MESSAGE, link: this.expirationPolicyHelpPagePath },
]
: base;
},
}, },
}; };
</script> </script>
<template> <template>
<div> <title-area :title="$options.i18n.CONTAINER_REGISTRY_TITLE" :info-messages="infoMessages">
<title-area :title="$options.i18n.CONTAINER_REGISTRY_TITLE"> <template #right-actions>
<template #right-actions> <slot name="commands"></slot>
<slot name="commands"></slot> </template>
</template> <template #metadata_count>
<template #metadata_count> <metadata-item
<metadata-item v-if="imagesCount"
v-if="imagesCount" data-testid="images-count"
data-testid="images-count" icon="container-image"
icon="container-image" :text="imagesCountText"
:text="imagesCountText" />
/> </template>
</template> <template #metadata_exp_policies>
<template #metadata_exp_policies> <metadata-item
<metadata-item v-if="!hideExpirationPolicyData"
v-if="!hideExpirationPolicyData" data-testid="expiration-policy"
data-testid="expiration-policy" icon="expire"
icon="expire" :text="expirationPolicyText"
:text="expirationPolicyText" size="xl"
size="xl" />
/> </template>
</template> </title-area>
</title-area>
<div data-testid="info-area">
<p>
<span data-testid="default-intro">
<gl-sprintf :message="$options.i18n.LIST_INTRO_TEXT">
<template #docLink="{content}">
<gl-link :href="helpPagePath" target="_blank">{{ content }}</gl-link>
</template>
</gl-sprintf>
</span>
<span v-if="showExpirationPolicyTip" data-testid="expiration-disabled-message">
<gl-sprintf :message="$options.i18n.EXPIRATION_POLICY_DISABLED_MESSAGE">
<template #docLink="{content}">
<gl-link :href="expirationPolicyHelpPagePath" target="_blank">{{ content }}</gl-link>
</template>
</gl-sprintf>
</span>
</p>
</div>
</div>
</template> </template>
<script> <script>
import { GlAvatar } from '@gitlab/ui'; import { GlAvatar, GlSprintf, GlLink } from '@gitlab/ui';
export default { export default {
name: 'TitleArea', name: 'TitleArea',
components: { components: {
GlAvatar, GlAvatar,
GlSprintf,
GlLink,
}, },
props: { props: {
avatar: { avatar: {
...@@ -17,6 +19,11 @@ export default { ...@@ -17,6 +19,11 @@ export default {
default: null, default: null,
required: false, required: false,
}, },
infoMessages: {
type: Array,
default: () => [],
required: false,
},
}, },
data() { data() {
return { return {
...@@ -30,37 +37,58 @@ export default { ...@@ -30,37 +37,58 @@ export default {
</script> </script>
<template> <template>
<div class="gl-display-flex gl-justify-content-space-between gl-py-3"> <div class="gl-display-flex gl-flex-direction-column">
<div class="gl-flex-direction-column"> <div class="gl-display-flex gl-justify-content-space-between gl-py-3">
<div class="gl-display-flex"> <div class="gl-flex-direction-column">
<gl-avatar v-if="avatar" :src="avatar" shape="rect" class="gl-align-self-center gl-mr-4" /> <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"> <div class="gl-display-flex gl-flex-direction-column">
<h1 class="gl-font-size-h1 gl-mt-3 gl-mb-2" data-testid="title"> <h1 class="gl-font-size-h1 gl-mt-3 gl-mb-2" data-testid="title">
<slot name="title">{{ title }}</slot> <slot name="title">{{ title }}</slot>
</h1> </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 <div
v-if="$slots['sub-header']" v-for="(row, metadataIndex) in metadataSlots"
class="gl-display-flex gl-align-items-center gl-text-gray-500 gl-mt-1" :key="metadataIndex"
class="gl-display-flex gl-align-items-center gl-mr-5"
> >
<slot name="sub-header"></slot> <slot :name="row"></slot>
</div> </div>
</div> </div>
</div> </div>
<div v-if="$slots['right-actions']" class="gl-mt-3">
<div class="gl-display-flex gl-flex-wrap gl-align-items-center gl-mt-3"> <slot name="right-actions"></slot>
<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> </div>
<div v-if="$slots['right-actions']" class="gl-mt-3"> <p>
<slot name="right-actions"></slot> <span
</div> v-for="(message, index) in infoMessages"
:key="index"
class="gl-mr-2"
data-testid="info-message"
>
<gl-sprintf :message="message.text">
<template #docLink="{content}">
<gl-link :href="message.link" target="_blank">{{ content }}</gl-link>
</template>
</gl-sprintf>
</span>
</p>
</div> </div>
</template> </template>
...@@ -2,151 +2,163 @@ ...@@ -2,151 +2,163 @@
exports[`PackageTitle renders with tags 1`] = ` exports[`PackageTitle renders with tags 1`] = `
<div <div
class="gl-display-flex gl-justify-content-space-between gl-py-3" class="gl-display-flex gl-flex-direction-column"
data-qa-selector="package_title" data-qa-selector="package_title"
> >
<div <div
class="gl-flex-direction-column" class="gl-display-flex gl-justify-content-space-between gl-py-3"
> >
<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"
data-testid="title"
>
Test package
</h1>
<div <div
class="gl-display-flex gl-align-items-center gl-text-gray-500 gl-mt-1" 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-mt-3"
>
<div
class="gl-display-flex gl-align-items-center gl-mr-5"
>
<metadata-item-stub
data-testid="package-type"
icon="package"
link=""
size="s"
text="maven"
/>
</div>
<div
class="gl-display-flex gl-align-items-center gl-mr-5"
>
<metadata-item-stub
data-testid="package-size"
icon="disk"
link=""
size="s"
text="300 bytes"
/>
</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"
> >
<package-tags-stub <div
hidelabel="true" class="gl-display-flex gl-align-items-center gl-mr-5"
tagdisplaylimit="2" >
tags="[object Object],[object Object],[object Object],[object Object]" <metadata-item-stub
/> data-testid="package-type"
icon="package"
link=""
size="s"
text="maven"
/>
</div>
<div
class="gl-display-flex gl-align-items-center gl-mr-5"
>
<metadata-item-stub
data-testid="package-size"
icon="disk"
link=""
size="s"
text="300 bytes"
/>
</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>
<!----> <p />
</div> </div>
`; `;
exports[`PackageTitle renders without tags 1`] = ` exports[`PackageTitle renders without tags 1`] = `
<div <div
class="gl-display-flex gl-justify-content-space-between gl-py-3" class="gl-display-flex gl-flex-direction-column"
data-qa-selector="package_title" data-qa-selector="package_title"
> >
<div <div
class="gl-flex-direction-column" class="gl-display-flex gl-justify-content-space-between gl-py-3"
> >
<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"
data-testid="title"
>
Test package
</h1>
<div <div
class="gl-display-flex gl-align-items-center gl-text-gray-500 gl-mt-1" 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-mt-3"
>
<div
class="gl-display-flex gl-align-items-center gl-mr-5"
>
<metadata-item-stub
data-testid="package-type"
icon="package"
link=""
size="s"
text="maven"
/>
</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"
> >
<metadata-item-stub <div
data-testid="package-size" class="gl-display-flex gl-align-items-center gl-mr-5"
icon="disk" >
link="" <metadata-item-stub
size="s" data-testid="package-type"
text="300 bytes" icon="package"
/> link=""
size="s"
text="maven"
/>
</div>
<div
class="gl-display-flex gl-align-items-center gl-mr-5"
>
<metadata-item-stub
data-testid="package-size"
icon="disk"
link=""
size="s"
text="300 bytes"
/>
</div>
</div> </div>
</div> </div>
<!---->
</div> </div>
<!----> <p />
</div> </div>
`; `;
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import { GlSprintf, GlLink } from '@gitlab/ui'; import { GlSprintf } 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 TitleArea from '~/vue_shared/components/registry/title_area.vue';
import { import {
...@@ -19,12 +19,8 @@ describe('registry_header', () => { ...@@ -19,12 +19,8 @@ describe('registry_header', () => {
const findTitleArea = () => wrapper.find(TitleArea); const findTitleArea = () => wrapper.find(TitleArea);
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 findIntroText = () => wrapper.find('[data-testid="default-intro"]');
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 = () =>
wrapper.find('[data-testid="expiration-disabled-message"]');
const mountComponent = (propsData, slots) => { const mountComponent = (propsData, slots) => {
wrapper = shallowMount(Component, { wrapper = shallowMount(Component, {
...@@ -123,44 +119,18 @@ describe('registry_header', () => { ...@@ -123,44 +119,18 @@ describe('registry_header', () => {
}); });
}); });
describe('info area', () => { describe('info messages', () => {
it('exists', () => {
mountComponent();
expect(findInfoArea().exists()).toBe(true);
});
describe('default message', () => { describe('default message', () => {
beforeEach(() => { it('is correctly bound to title_area props', () => {
return mountComponent({ helpPagePath: 'bar' }); mountComponent({ helpPagePath: 'foo' });
});
it('exists', () => {
expect(findIntroText().exists()).toBe(true);
});
it('has the correct copy', () => {
expect(findIntroText().text()).toMatchInterpolatedText(LIST_INTRO_TEXT);
});
it('has the correct link', () => { expect(findTitleArea().props('infoMessages')).toEqual([
expect( { text: LIST_INTRO_TEXT, link: 'foo' },
findIntroText() ]);
.find(GlLink)
.attributes('href'),
).toBe('bar');
}); });
}); });
describe('expiration policy info message', () => { describe('expiration policy info message', () => {
describe('when there are no images', () => {
it('is hidden', () => {
mountComponent();
expect(findDisabledExpirationPolicyMessage().exists()).toBe(false);
});
});
describe('when there are images', () => { describe('when there are images', () => {
describe('when expiration policy is disabled', () => { describe('when expiration policy is disabled', () => {
beforeEach(() => { beforeEach(() => {
...@@ -170,43 +140,27 @@ describe('registry_header', () => { ...@@ -170,43 +140,27 @@ describe('registry_header', () => {
imagesCount: 1, imagesCount: 1,
}); });
}); });
it('message exist', () => {
expect(findDisabledExpirationPolicyMessage().exists()).toBe(true);
});
it('has the correct copy', () => {
expect(findDisabledExpirationPolicyMessage().text()).toMatchInterpolatedText(
EXPIRATION_POLICY_DISABLED_MESSAGE,
);
});
it('has the correct link', () => { it('the prop is correctly bound', () => {
expect( expect(findTitleArea().props('infoMessages')).toEqual([
findDisabledExpirationPolicyMessage() { text: LIST_INTRO_TEXT, link: '' },
.find(GlLink) { text: EXPIRATION_POLICY_DISABLED_MESSAGE, link: 'foo' },
.attributes('href'), ]);
).toBe('foo');
}); });
}); });
describe('when expiration policy is enabled', () => { describe.each`
desc | props
${'when there are no images'} | ${{ expirationPolicy: { enabled: false }, imagesCount: 0 }}
${'when expiration policy is enabled'} | ${{ expirationPolicy: { enabled: true }, imagesCount: 1 }}
${'when the expiration policy is completely disabled'} | ${{ expirationPolicy: { enabled: false }, imagesCount: 1, hideExpirationPolicyData: true }}
`('$desc', ({ props }) => {
it('message does not exist', () => { it('message does not exist', () => {
mountComponent({ mountComponent(props);
expirationPolicy: { enabled: true },
imagesCount: 1,
});
expect(findDisabledExpirationPolicyMessage().exists()).toBe(false);
});
});
describe('when the expiration policy is completely disabled', () => {
it('message does not exist', () => {
mountComponent({
expirationPolicy: { enabled: true },
imagesCount: 1,
hideExpirationPolicyData: true,
});
expect(findDisabledExpirationPolicyMessage().exists()).toBe(false); expect(findTitleArea().props('infoMessages')).toEqual([
{ text: LIST_INTRO_TEXT, link: '' },
]);
}); });
}); });
}); });
......
import { GlAvatar } from '@gitlab/ui'; import { GlAvatar, GlSprintf, GlLink } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import component from '~/vue_shared/components/registry/title_area.vue'; import component from '~/vue_shared/components/registry/title_area.vue';
...@@ -10,10 +10,12 @@ describe('title area', () => { ...@@ -10,10 +10,12 @@ describe('title area', () => {
const findMetadataSlot = name => wrapper.find(`[data-testid="${name}"]`); const findMetadataSlot = name => wrapper.find(`[data-testid="${name}"]`);
const findTitle = () => wrapper.find('[data-testid="title"]'); const findTitle = () => wrapper.find('[data-testid="title"]');
const findAvatar = () => wrapper.find(GlAvatar); const findAvatar = () => wrapper.find(GlAvatar);
const findInfoMessages = () => wrapper.findAll('[data-testid="info-message"]');
const mountComponent = ({ propsData = { title: 'foo' }, slots } = {}) => { const mountComponent = ({ propsData = { title: 'foo' }, slots } = {}) => {
wrapper = shallowMount(component, { wrapper = shallowMount(component, {
propsData, propsData,
stubs: { GlSprintf },
slots: { slots: {
'sub-header': '<div data-testid="sub-header" />', 'sub-header': '<div data-testid="sub-header" />',
'right-actions': '<div data-testid="right-actions" />', 'right-actions': '<div data-testid="right-actions" />',
...@@ -95,4 +97,33 @@ describe('title area', () => { ...@@ -95,4 +97,33 @@ describe('title area', () => {
}); });
}); });
}); });
describe('info-messages', () => {
it('shows a message when the props contains one', () => {
mountComponent({ propsData: { infoMessages: [{ text: 'foo foo bar bar' }] } });
const messages = findInfoMessages();
expect(messages).toHaveLength(1);
expect(messages.at(0).text()).toBe('foo foo bar bar');
});
it('shows a link when the props contains one', () => {
mountComponent({
propsData: {
infoMessages: [{ text: 'foo %{docLinkStart}link%{docLinkEnd}', link: 'bar' }],
},
});
const message = findInfoMessages().at(0);
expect(message.find(GlLink).attributes('href')).toBe('bar');
expect(message.text()).toBe('foo link');
});
it('multiple messages generates multiple spans', () => {
mountComponent({ propsData: { infoMessages: [{ text: 'foo' }, { text: 'bar' }] } });
expect(findInfoMessages()).toHaveLength(2);
});
});
}); });
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