Commit d075751f authored by Kushal Pandya's avatar Kushal Pandya

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

Create shared list_item component for registry

See merge request gitlab-org/gitlab!39705
parents d8e6347d 4a1e26ac
......@@ -4,6 +4,7 @@ import PackageTags from './package_tags.vue';
import PublishMethod from './publish_method.vue';
import { getPackageTypeLabel } from '../utils';
import timeagoMixin from '~/vue_shared/mixins/timeago';
import ListItem from '~/vue_shared/components/registry/list_item.vue';
export default {
name: 'PackageListRow',
......@@ -14,6 +15,7 @@ export default {
GlSprintf,
PackageTags,
PublishMethod,
ListItem,
},
directives: {
GlTooltip: GlTooltipDirective,
......@@ -59,14 +61,10 @@ export default {
</script>
<template>
<div class="gl-responsive-table-row" data-qa-selector="packages-row">
<div class="table-section section-50 d-flex flex-md-column justify-content-between flex-wrap">
<div class="d-flex align-items-center mr-2">
<gl-link
:href="packageLink"
data-qa-selector="package_link"
class="text-dark font-weight-bold mb-md-1"
>
<list-item data-qa-selector="packages-row">
<template #left-primary>
<div class="gl-display-flex gl-align-items-center gl-mr-3">
<gl-link :href="packageLink" class="gl-text-body" data-qa-selector="package_link">
{{ packageEntity.name }}
</gl-link>
......@@ -78,41 +76,41 @@ export default {
:tag-display-limit="1"
/>
</div>
<div class="d-flex text-secondary text-truncate mt-md-2">
</template>
<template #left-secondary>
<div class="gl-display-flex">
<span>{{ packageEntity.version }}</span>
<div v-if="hasPipeline" class="d-none d-md-inline-block ml-1">
<div v-if="hasPipeline" class="gl-display-none gl-display-sm-flex gl-ml-2">
<gl-sprintf :message="s__('PackageRegistry|published by %{author}')">
<template #author>{{ packageEntity.pipeline.user.name }}</template>
</gl-sprintf>
</div>
<div v-if="hasProjectLink" class="d-flex align-items-center">
<gl-icon name="review-list" class="text-secondary ml-2 mr-1" />
<div v-if="hasProjectLink" class="gl-display-flex gl-align-items-center">
<gl-icon name="review-list" class="gl-ml-3 gl-mr-2" />
<gl-link
class="gl-text-body"
data-testid="packages-row-project"
:href="`/${packageEntity.project_path}`"
class="text-secondary"
>{{ packageEntity.projectPathName }}</gl-link
>
{{ packageEntity.projectPathName }}
</gl-link>
</div>
<div v-if="showPackageType" class="d-flex align-items-center" data-testid="package-type">
<gl-icon name="package" class="text-secondary ml-2 mr-1" />
<gl-icon name="package" class="gl-ml-3 gl-mr-2" />
<span>{{ packageType }}</span>
</div>
</div>
</div>
</template>
<div
class="table-section d-flex flex-md-column justify-content-between align-items-md-end"
:class="disableDelete ? 'section-50' : 'section-40'"
>
<template #right-primary>
<publish-method :package-entity="packageEntity" :is-group="isGroup" />
</template>
<div class="text-secondary order-0 order-md-1 mt-md-2">
<template #right-secondary>
<gl-sprintf :message="__('Created %{timestamp}')">
<template #timestamp>
<span v-gl-tooltip :title="tooltipTitle(packageEntity.created_at)">
......@@ -120,10 +118,9 @@ export default {
</span>
</template>
</gl-sprintf>
</div>
</div>
</template>
<div v-if="!disableDelete" class="table-section section-10 d-flex justify-content-end">
<template v-if="!disableDelete" #right-action>
<gl-button
data-testid="action-delete"
icon="remove"
......@@ -134,6 +131,6 @@ export default {
:disabled="!packageEntity._links.delete_api_path"
@click="$emit('packageToDelete', packageEntity)"
/>
</div>
</div>
</template>
</list-item>
</template>
......@@ -36,10 +36,10 @@ export default {
</script>
<template>
<div class="d-flex align-items-center text-secondary order-1 order-md-0 mb-md-1">
<div class="d-flex align-items-center order-1 order-md-0 mb-md-1">
<template v-if="hasPipeline">
<gl-icon name="git-merge" class="mr-1" />
<strong ref="pipeline-ref" class="mr-1 text-dark">{{ packageEntity.pipeline.ref }}</strong>
<span ref="pipeline-ref" class="mr-1">{{ packageEntity.pipeline.ref }}</span>
<gl-icon name="commit" class="mr-1" />
<gl-link ref="pipeline-sha" :href="linkToCommit" class="mr-1">{{ packageShaShort }}</gl-link>
......@@ -47,15 +47,13 @@ export default {
<clipboard-button
:text="packageEntity.pipeline.sha"
:title="__('Copy commit SHA')"
css-class="border-0 text-secondary py-0 px-1"
css-class="border-0 py-0 px-1"
/>
</template>
<template v-else>
<gl-icon name="upload" class="mr-1" />
<strong ref="manual-ref" class="text-dark">{{
s__('PackageRegistry|Manually Published')
}}</strong>
<span ref="manual-ref">{{ s__('PackageRegistry|Manually Published') }}</span>
</template>
</div>
</template>
......@@ -47,7 +47,6 @@ export default {
:disabled="disabled"
:title="title"
:aria-label="title"
category="secondary"
variant="danger"
icon="remove"
@click="$emit('delete')"
......
......@@ -67,7 +67,6 @@ export default {
:key="tag.path"
:tag="tag"
:first="index === 0"
:last="index === tags.length - 1"
:selected="selectedItems[tag.name]"
:is-desktop="isDesktop"
@select="updateSelectedItems(tag.name)"
......
......@@ -5,8 +5,8 @@ import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import { numberToHumanSize } from '~/lib/utils/number_utils';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import { formatDate } from '~/lib/utils/datetime_utility';
import ListItem from '~/vue_shared/components/registry/list_item.vue';
import DeleteButton from '../delete_button.vue';
import ListItem from '../list_item.vue';
import DetailsRow from '~/registry/shared/components/details_row.vue';
import {
REMOVE_TAG_BUTTON_TITLE,
......
......@@ -38,7 +38,6 @@ export default {
:key="index"
:item="listItem"
:first="index === 0"
:last="index === images.length - 1"
@delete="$emit('delete', $event)"
/>
......
......@@ -2,7 +2,7 @@
import { GlTooltipDirective, GlIcon, GlSprintf } from '@gitlab/ui';
import { n__ } from '~/locale';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import ListItem from '../list_item.vue';
import ListItem from '~/vue_shared/components/registry/list_item.vue';
import DeleteButton from '../delete_button.vue';
import {
......
......@@ -10,11 +10,6 @@ export default {
default: false,
required: false,
},
last: {
type: Boolean,
default: false,
required: false,
},
disabled: {
type: Boolean,
default: false,
......@@ -35,12 +30,10 @@ export default {
computed: {
optionalClasses() {
return {
'gl-border-t-1': !this.first,
'gl-border-t-2': this.first,
'gl-border-b-1': !this.last,
'gl-border-b-2': this.last,
'gl-border-t-transparent': !this.first && !this.selected,
'gl-border-t-gray-100': this.first && !this.selected,
'disabled-content': this.disabled,
'gl-border-gray-100': !this.selected,
'gl-border-b-gray-100': !this.selected,
'gl-bg-blue-50 gl-border-blue-200': this.selected,
};
},
......@@ -58,21 +51,26 @@ export default {
<template>
<div
class="gl-display-flex gl-flex-direction-column gl-border-b-solid gl-border-t-solid"
class="gl-display-flex gl-flex-direction-column gl-border-b-solid gl-border-t-solid gl-border-t-1 gl-border-b-1"
:class="optionalClasses"
>
<div class="gl-display-flex gl-align-items-center gl-py-4 gl-px-2">
<div class="gl-display-flex gl-align-items-center gl-py-5">
<div
v-if="$slots['left-action']"
class="gl-w-7 gl-display-none gl-display-sm-flex gl-justify-content-start gl-pl-2"
>
<slot name="left-action"></slot>
</div>
<div class="gl-display-flex gl-flex-direction-column gl-flex-fill-1">
<div
class="gl-display-flex gl-align-items-center gl-justify-content-space-between gl-text-body gl-font-weight-bold"
class="gl-display-flex gl-xs-flex-direction-column gl-justify-content-space-between gl-align-items-stretch gl-flex-fill-1"
>
<div
class="gl-display-flex gl-flex-direction-column gl-justify-content-space-between gl-xs-mb-3"
>
<div
v-if="$slots['left-primary']"
class="gl-display-flex gl-align-items-center gl-text-body gl-font-weight-bold gl-min-h-6"
>
<div class="gl-display-flex gl-align-items-center">
<slot name="left-primary"></slot>
<gl-button
v-if="detailsSlots.length > 0"
......@@ -83,24 +81,27 @@ export default {
@click="toggleDetails"
/>
</div>
<div>
<slot name="right-primary"></slot>
<div v-if="$slots['left-secondary']" class="gl-text-gray-500 gl-mt-1 gl-min-h-6">
<slot name="left-secondary"></slot>
</div>
</div>
<div
class="gl-display-flex gl-align-items-center gl-justify-content-space-between gl-font-sm gl-text-gray-300"
class="gl-display-flex gl-flex-direction-column gl-sm-align-items-flex-end gl-justify-content-space-between gl-text-gray-500"
>
<div>
<slot name="left-secondary"></slot>
<div
v-if="$slots['right-primary']"
class="gl-sm-text-body gl-sm-font-weight-bold gl-min-h-6"
>
<slot name="right-primary"></slot>
</div>
<div>
<div v-if="$slots['right-secondary']" class="gl-mt-1 gl-min-h-6">
<slot name="right-secondary"></slot>
</div>
</div>
</div>
<div
v-if="$slots['right-action']"
class="gl-w-9 gl-display-none gl-display-sm-flex gl-justify-content-end gl-pr-2"
class="gl-w-9 gl-display-none gl-display-sm-flex gl-justify-content-end gl-pr-1"
>
<slot name="right-action"></slot>
</div>
......
......@@ -120,6 +120,43 @@
}
}
.gl-shadow-x0-y0-b3-s1-blue-500 {
box-shadow: inset 0 0 3px $gl-border-size-1 $blue-500;
}
// remove when https://gitlab.com/gitlab-org/gitlab-ui/-/merge_requests/1692 is merged
.gl-border-t-transparent {
border-top-color: transparent;
}
.gl-align-items-flex-end {
align-items: flex-end;
}
.gl-sm-align-items-flex-end {
@media (min-width: $breakpoint-sm) {
align-items: flex-end;
}
}
.gl-sm-text-body {
@media (min-width: $breakpoint-sm) {
color: $body-color;
}
}
.gl-sm-font-weight-bold {
@media (min-width: $breakpoint-sm) {
font-weight: $gl-font-weight-bold;
}
}
.gl-align-items-stretch {
align-items: stretch;
}
.gl-min-h-6 {
min-height: $gl-spacing-scale-6;
}
......@@ -2,17 +2,28 @@
exports[`packages_list_row renders 1`] = `
<div
class="gl-responsive-table-row"
class="gl-display-flex gl-flex-direction-column gl-border-b-solid gl-border-t-solid gl-border-t-1 gl-border-b-1 gl-border-t-transparent gl-border-b-gray-100"
data-qa-selector="packages-row"
>
<div
class="table-section section-50 d-flex flex-md-column justify-content-between flex-wrap"
class="gl-display-flex gl-align-items-center gl-py-5"
>
<!---->
<div
class="d-flex align-items-center mr-2"
class="gl-display-flex gl-xs-flex-direction-column gl-justify-content-space-between gl-align-items-stretch gl-flex-fill-1"
>
<div
class="gl-display-flex gl-flex-direction-column gl-justify-content-space-between gl-xs-mb-3"
>
<div
class="gl-display-flex gl-align-items-center gl-text-body gl-font-weight-bold gl-min-h-6"
>
<div
class="gl-display-flex gl-align-items-center gl-mr-3"
>
<gl-link-stub
class="text-dark font-weight-bold mb-md-1"
class="gl-text-body"
data-qa-selector="package_link"
href="foo"
>
......@@ -24,8 +35,14 @@ exports[`packages_list_row renders 1`] = `
<!---->
</div>
<!---->
</div>
<div
class="gl-text-gray-500 gl-mt-1 gl-min-h-6"
>
<div
class="d-flex text-secondary text-truncate mt-md-2"
class="gl-display-flex"
>
<span>
1.0.0
......@@ -34,20 +51,22 @@ exports[`packages_list_row renders 1`] = `
<!---->
<div
class="d-flex align-items-center"
class="gl-display-flex gl-align-items-center"
>
<gl-icon-stub
class="text-secondary ml-2 mr-1"
class="gl-ml-3 gl-mr-2"
name="review-list"
size="16"
/>
<gl-link-stub
class="text-secondary"
class="gl-text-body"
data-testid="packages-row-project"
href="/foo/bar/baz"
>
</gl-link-stub>
</div>
......@@ -56,7 +75,7 @@ exports[`packages_list_row renders 1`] = `
data-testid="package-type"
>
<gl-icon-stub
class="text-secondary ml-2 mr-1"
class="gl-ml-3 gl-mr-2"
name="package"
size="16"
/>
......@@ -67,25 +86,31 @@ exports[`packages_list_row renders 1`] = `
</div>
</div>
</div>
</div>
<div
class="table-section d-flex flex-md-column justify-content-between align-items-md-end section-40"
class="gl-display-flex gl-flex-direction-column gl-sm-align-items-flex-end gl-justify-content-space-between gl-text-gray-500"
>
<div
class="gl-sm-text-body gl-sm-font-weight-bold gl-min-h-6"
>
<publish-method-stub
packageentity="[object Object]"
/>
</div>
<div
class="text-secondary order-0 order-md-1 mt-md-2"
class="gl-mt-1 gl-min-h-6"
>
<gl-sprintf-stub
message="Created %{timestamp}"
/>
</div>
</div>
</div>
<div
class="table-section section-10 d-flex justify-content-end"
class="gl-w-9 gl-display-none gl-display-sm-flex gl-justify-content-end gl-pr-1"
>
<gl-button-stub
aria-label="Remove package"
......@@ -97,5 +122,20 @@ exports[`packages_list_row renders 1`] = `
variant="danger"
/>
</div>
</div>
<div
class="gl-display-flex"
>
<div
class="gl-w-7"
/>
<!---->
<div
class="gl-w-9"
/>
</div>
</div>
`;
......@@ -2,7 +2,7 @@
exports[`publish_method renders 1`] = `
<div
class="d-flex align-items-center text-secondary order-1 order-md-0 mb-md-1"
class="d-flex align-items-center order-1 order-md-0 mb-md-1"
>
<gl-icon-stub
class="mr-1"
......@@ -10,11 +10,11 @@ exports[`publish_method renders 1`] = `
size="16"
/>
<strong
class="mr-1 text-dark"
<span
class="mr-1"
>
branch-name
</strong>
</span>
<gl-icon-stub
class="mr-1"
......@@ -30,7 +30,7 @@ exports[`publish_method renders 1`] = `
</gl-link-stub>
<clipboard-button-stub
cssclass="border-0 text-secondary py-0 px-1"
cssclass="border-0 py-0 px-1"
text="sha-baz"
title="Copy commit SHA"
tooltipplacement="top"
......
import { mount, shallowMount } from '@vue/test-utils';
import { shallowMount } from '@vue/test-utils';
import PackagesListRow from '~/packages/shared/components/package_list_row.vue';
import PackageTags from '~/packages/shared/components/package_tags.vue';
import ListItem from '~/vue_shared/components/registry/list_item.vue';
import { packageList } from '../../mock_data';
describe('packages_list_row', () => {
......@@ -17,14 +18,12 @@ describe('packages_list_row', () => {
const mountComponent = ({
isGroup = false,
packageEntity = packageWithoutTags,
shallow = true,
showPackageType = true,
disableDelete = false,
} = {}) => {
const mountFunc = shallow ? shallowMount : mount;
wrapper = mountFunc(PackagesListRow, {
wrapper = shallowMount(PackagesListRow, {
store,
stubs: { ListItem },
propsData: {
packageLink: 'foo',
packageEntity,
......@@ -92,15 +91,14 @@ describe('packages_list_row', () => {
});
describe('delete event', () => {
beforeEach(() => mountComponent({ packageEntity: packageWithoutTags, shallow: false }));
beforeEach(() => mountComponent({ packageEntity: packageWithoutTags }));
it('emits the packageToDelete event when the delete button is clicked', () => {
findDeleteButton().trigger('click');
it('emits the packageToDelete event when the delete button is clicked', async () => {
findDeleteButton().vm.$emit('click');
return wrapper.vm.$nextTick().then(() => {
await wrapper.vm.$nextTick();
expect(wrapper.emitted('packageToDelete')).toBeTruthy();
expect(wrapper.emitted('packageToDelete')[0]).toEqual([packageWithoutTags]);
});
});
});
});
......@@ -54,7 +54,6 @@ describe('delete_button', () => {
mountComponent({ disabled: true });
expect(findButton().attributes()).toMatchObject({
'aria-label': 'Foo title',
category: 'secondary',
icon: 'remove',
title: 'Foo title',
variant: 'danger',
......
......@@ -115,7 +115,6 @@ describe('Tags List', () => {
// The list has only two tags and for some reasons .at(-1) does not work
expect(rows.at(1).attributes()).toMatchObject({
last: 'true',
isdesktop: 'true',
});
});
......
......@@ -3,7 +3,7 @@ import { GlIcon, GlSprintf } from '@gitlab/ui';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import Component from '~/registry/explorer/components/list_page/image_list_row.vue';
import ListItem from '~/registry/explorer/components/list_item.vue';
import ListItem from '~/vue_shared/components/registry/list_item.vue';
import DeleteButton from '~/registry/explorer/components/delete_button.vue';
import {
ROW_SCHEDULED_FOR_DELETION,
......
import RealDeleteModal from '~/registry/explorer/components/details_page/delete_modal.vue';
import RealListItem from '~/registry/explorer/components/list_item.vue';
import RealListItem from '~/vue_shared/components/registry/list_item.vue';
export const GlModal = {
template: '<div><slot name="modal-title"></slot><slot></slot><slot name="modal-ok"></slot></div>',
......
import { GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import component from '~/registry/explorer/components/list_item.vue';
import component from '~/vue_shared/components/registry/list_item.vue';
describe('list item', () => {
let wrapper;
......@@ -34,7 +34,7 @@ describe('list item', () => {
wrapper = null;
});
it.each`
describe.each`
slotName | finderFunction
${'left-primary'} | ${findLeftPrimarySlot}
${'left-secondary'} | ${findLeftSecondarySlot}
......@@ -42,12 +42,20 @@ describe('list item', () => {
${'right-secondary'} | ${findRightSecondarySlot}
${'left-action'} | ${findLeftActionSlot}
${'right-action'} | ${findRightActionSlot}
`('has a $slotName slot', ({ finderFunction }) => {
`('$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({}, { [slotName]: '' });
expect(finderFunction().exists()).toBe(false);
});
});
describe.each`
slotNames
${['details_foo']}
......@@ -106,51 +114,22 @@ describe('list item', () => {
});
});
describe('first prop', () => {
it('when is true displays a double top border', () => {
mountComponent({ first: true });
expect(wrapper.classes('gl-border-t-2')).toBe(true);
});
it('when is false display a single top border', () => {
mountComponent({ first: false });
expect(wrapper.classes('gl-border-t-1')).toBe(true);
});
});
describe('last prop', () => {
it('when is true displays a double bottom border', () => {
mountComponent({ last: true });
expect(wrapper.classes('gl-border-b-2')).toBe(true);
});
it('when is false display a single bottom border', () => {
mountComponent({ last: false });
expect(wrapper.classes('gl-border-b-1')).toBe(true);
});
});
describe('selected prop', () => {
it('when true applies the selected border and background', () => {
mountComponent({ selected: true });
expect(wrapper.classes()).toEqual(
expect.arrayContaining(['gl-bg-blue-50', 'gl-border-blue-200']),
);
expect(wrapper.classes()).toEqual(expect.not.arrayContaining(['gl-border-gray-100']));
});
it('when false applies the default border', () => {
mountComponent({ selected: false });
expect(wrapper.classes()).toEqual(
expect.not.arrayContaining(['gl-bg-blue-50', 'gl-border-blue-200']),
describe('borders and selection', () => {
it.each`
first | selected | shouldHave | shouldNotHave
${true} | ${true} | ${['gl-bg-blue-50', 'gl-border-blue-200']} | ${['gl-border-t-transparent', 'gl-border-t-gray-100']}
${false} | ${true} | ${['gl-bg-blue-50', 'gl-border-blue-200']} | ${['gl-border-t-transparent', 'gl-border-t-gray-100']}
${true} | ${false} | ${['gl-border-b-gray-100']} | ${['gl-bg-blue-50', 'gl-border-blue-200']}
${false} | ${false} | ${['gl-border-b-gray-100']} | ${['gl-bg-blue-50', 'gl-border-blue-200']}
`(
'when first is $first and selected is $selected',
({ first, selected, shouldHave, shouldNotHave }) => {
mountComponent({ first, selected });
expect(wrapper.classes()).toEqual(expect.arrayContaining(shouldHave));
expect(wrapper.classes()).toEqual(expect.not.arrayContaining(shouldNotHave));
},
);
expect(wrapper.classes()).toEqual(expect.arrayContaining(['gl-border-gray-100']));
});
});
});
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