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>
import { GlSprintf, GlLink } from '@gitlab/ui';
import TitleArea from '~/vue_shared/components/registry/title_area.vue';
import MetadataItem from '~/vue_shared/components/registry/metadata_item.vue';
import { n__, sprintf } from '~/locale';
......@@ -15,8 +14,6 @@ import {
export default {
components: {
GlSprintf,
GlLink,
TitleArea,
MetadataItem,
},
......@@ -54,8 +51,6 @@ export default {
},
i18n: {
CONTAINER_REGISTRY_TITLE,
LIST_INTRO_TEXT,
EXPIRATION_POLICY_DISABLED_MESSAGE,
},
computed: {
imagesCountText() {
......@@ -83,52 +78,40 @@ export default {
!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>
<template>
<div>
<title-area :title="$options.i18n.CONTAINER_REGISTRY_TITLE">
<template #right-actions>
<slot name="commands"></slot>
</template>
<template #metadata_count>
<metadata-item
v-if="imagesCount"
data-testid="images-count"
icon="container-image"
:text="imagesCountText"
/>
</template>
<template #metadata_exp_policies>
<metadata-item
v-if="!hideExpirationPolicyData"
data-testid="expiration-policy"
icon="expire"
:text="expirationPolicyText"
size="xl"
/>
</template>
</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>
<title-area :title="$options.i18n.CONTAINER_REGISTRY_TITLE" :info-messages="infoMessages">
<template #right-actions>
<slot name="commands"></slot>
</template>
<template #metadata_count>
<metadata-item
v-if="imagesCount"
data-testid="images-count"
icon="container-image"
:text="imagesCountText"
/>
</template>
<template #metadata_exp_policies>
<metadata-item
v-if="!hideExpirationPolicyData"
data-testid="expiration-policy"
icon="expire"
:text="expirationPolicyText"
size="xl"
/>
</template>
</title-area>
</template>
<script>
import { GlAvatar } from '@gitlab/ui';
import { GlAvatar, GlSprintf, GlLink } from '@gitlab/ui';
export default {
name: 'TitleArea',
components: {
GlAvatar,
GlSprintf,
GlLink,
},
props: {
avatar: {
......@@ -17,6 +19,11 @@ export default {
default: null,
required: false,
},
infoMessages: {
type: Array,
default: () => [],
required: false,
},
},
data() {
return {
......@@ -30,37 +37,58 @@ export default {
</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">
<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 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-if="$slots['sub-header']"
class="gl-display-flex gl-align-items-center gl-text-gray-500 gl-mt-1"
v-for="(row, metadataIndex) in metadataSlots"
: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 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 v-if="$slots['right-actions']" class="gl-mt-3">
<slot name="right-actions"></slot>
</div>
</div>
<div v-if="$slots['right-actions']" class="gl-mt-3">
<slot name="right-actions"></slot>
</div>
<p>
<span
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>
</template>
......@@ -2,151 +2,163 @@
exports[`PackageTitle renders with tags 1`] = `
<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"
>
<div
class="gl-flex-direction-column"
class="gl-display-flex gl-justify-content-space-between gl-py-3"
>
<div
class="gl-display-flex"
class="gl-flex-direction-column"
>
<!---->
<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
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
class="gl-mr-3"
name="eye"
size="16"
/>
<h1
class="gl-font-size-h1 gl-mt-3 gl-mb-2"
data-testid="title"
>
Test package
</h1>
<gl-sprintf-stub
message="v%{version} published %{timeAgo}"
/>
<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-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
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
hidelabel="true"
tagdisplaylimit="2"
tags="[object Object],[object Object],[object Object],[object Object]"
/>
<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
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>
<!---->
<p />
</div>
`;
exports[`PackageTitle renders without tags 1`] = `
<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"
>
<div
class="gl-flex-direction-column"
class="gl-display-flex gl-justify-content-space-between gl-py-3"
>
<div
class="gl-display-flex"
class="gl-flex-direction-column"
>
<!---->
<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
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
class="gl-mr-3"
name="eye"
size="16"
/>
<h1
class="gl-font-size-h1 gl-mt-3 gl-mb-2"
data-testid="title"
>
Test package
</h1>
<gl-sprintf-stub
message="v%{version} published %{timeAgo}"
/>
<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-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"
class="gl-display-flex gl-flex-wrap gl-align-items-center gl-mt-3"
>
<metadata-item-stub
data-testid="package-size"
icon="disk"
link=""
size="s"
text="300 bytes"
/>
<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>
<!---->
</div>
<!---->
<p />
</div>
`;
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 TitleArea from '~/vue_shared/components/registry/title_area.vue';
import {
......@@ -19,12 +19,8 @@ describe('registry_header', () => {
const findTitleArea = () => wrapper.find(TitleArea);
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 findExpirationPolicySubHeader = () => wrapper.find('[data-testid="expiration-policy"]');
const findDisabledExpirationPolicyMessage = () =>
wrapper.find('[data-testid="expiration-disabled-message"]');
const mountComponent = (propsData, slots) => {
wrapper = shallowMount(Component, {
......@@ -123,44 +119,18 @@ describe('registry_header', () => {
});
});
describe('info area', () => {
it('exists', () => {
mountComponent();
expect(findInfoArea().exists()).toBe(true);
});
describe('info messages', () => {
describe('default message', () => {
beforeEach(() => {
return mountComponent({ helpPagePath: 'bar' });
});
it('exists', () => {
expect(findIntroText().exists()).toBe(true);
});
it('has the correct copy', () => {
expect(findIntroText().text()).toMatchInterpolatedText(LIST_INTRO_TEXT);
});
it('is correctly bound to title_area props', () => {
mountComponent({ helpPagePath: 'foo' });
it('has the correct link', () => {
expect(
findIntroText()
.find(GlLink)
.attributes('href'),
).toBe('bar');
expect(findTitleArea().props('infoMessages')).toEqual([
{ text: LIST_INTRO_TEXT, link: 'foo' },
]);
});
});
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 expiration policy is disabled', () => {
beforeEach(() => {
......@@ -170,43 +140,27 @@ describe('registry_header', () => {
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', () => {
expect(
findDisabledExpirationPolicyMessage()
.find(GlLink)
.attributes('href'),
).toBe('foo');
it('the prop is correctly bound', () => {
expect(findTitleArea().props('infoMessages')).toEqual([
{ text: LIST_INTRO_TEXT, link: '' },
{ text: EXPIRATION_POLICY_DISABLED_MESSAGE, link: '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', () => {
mountComponent({
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,
});
mountComponent(props);
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 component from '~/vue_shared/components/registry/title_area.vue';
......@@ -10,10 +10,12 @@ describe('title area', () => {
const findMetadataSlot = name => wrapper.find(`[data-testid="${name}"]`);
const findTitle = () => wrapper.find('[data-testid="title"]');
const findAvatar = () => wrapper.find(GlAvatar);
const findInfoMessages = () => wrapper.findAll('[data-testid="info-message"]');
const mountComponent = ({ propsData = { title: 'foo' }, slots } = {}) => {
wrapper = shallowMount(component, {
propsData,
stubs: { GlSprintf },
slots: {
'sub-header': '<div data-testid="sub-header" />',
'right-actions': '<div data-testid="right-actions" />',
......@@ -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