Commit 624da315 authored by Mark Florian's avatar Mark Florian

Merge branch...

Merge branch '238594-package-detail-on-mobile-view-is-impossible-to-see-multiple-tags' into 'master'

Package details: on mobile show all the tags

See merge request gitlab-org/gitlab!46679
parents 26c44b87 f5c1aa66
<script>
/* eslint-disable vue/v-slot-style */
import { mapState, mapGetters } from 'vuex';
import { GlIcon, GlSprintf, GlTooltipDirective } from '@gitlab/ui';
import { GlIcon, GlSprintf, GlTooltipDirective, GlBadge } from '@gitlab/ui';
import { GlBreakpointInstance } from '@gitlab/ui/dist/utils';
import PackageTags from '../../shared/components/package_tags.vue';
import { numberToHumanSize } from '~/lib/utils/number_utils';
import timeagoMixin from '~/vue_shared/mixins/timeago';
......@@ -16,11 +18,20 @@ export default {
GlSprintf,
PackageTags,
MetadataItem,
GlBadge,
},
directives: {
GlTooltip: GlTooltipDirective,
},
mixins: [timeagoMixin],
i18n: {
packageInfo: __('v%{version} published %{timeAgo}'),
},
data() {
return {
isDesktop: true,
};
},
computed: {
...mapState(['packageEntity', 'packageFiles']),
...mapGetters(['packageTypeDisplay', 'packagePipeline', 'packageIcon']),
......@@ -31,8 +42,13 @@ export default {
return numberToHumanSize(this.packageFiles.reduce((acc, p) => acc + p.size, 0));
},
},
i18n: {
packageInfo: __('v%{version} published %{timeAgo}'),
mounted() {
this.isDesktop = GlBreakpointInstance.isDesktop();
},
methods: {
dynamicSlotName(index) {
return `metadata-tag${index}`;
},
},
};
</script>
......@@ -75,10 +91,21 @@ export default {
<metadata-item data-testid="package-ref" icon="branch" :text="packagePipeline.ref" />
</template>
<template v-if="hasTagsToDisplay" #metadata-tags>
<template v-if="isDesktop && hasTagsToDisplay" #metadata-tags>
<package-tags :tag-display-limit="2" :tags="packageEntity.tags" hide-label />
</template>
<!-- we need to duplicate the package tags on mobile to ensure proper styling inside the flex wrap -->
<template
v-for="(tag, index) in packageEntity.tags"
v-else-if="hasTagsToDisplay"
v-slot:[dynamicSlotName(index)]
>
<gl-badge :key="index" class="gl-my-1" data-testid="tag-badge" variant="info" size="sm">
{{ tag.name }}
</gl-badge>
</template>
<template #right-actions>
<slot name="delete-button"></slot>
</template>
......
......@@ -30,8 +30,13 @@ export default {
metadataSlots: [],
};
},
mounted() {
this.metadataSlots = Object.keys(this.$slots).filter(k => k.startsWith('metadata-'));
async mounted() {
const METADATA_PREFIX = 'metadata-';
this.metadataSlots = Object.keys(this.$slots).filter(k => k.startsWith(METADATA_PREFIX));
// we need to wait for next tick to ensure that dynamic names slots are picked up
await this.$nextTick();
this.metadataSlots = Object.keys(this.$slots).filter(k => k.startsWith(METADATA_PREFIX));
},
};
</script>
......
---
title: 'Package details: on mobile show all the tags'
merge_request: 46679
author:
type: changed
import Vuex from 'vuex';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import { GlBreakpointInstance } from '@gitlab/ui/dist/utils';
import PackageTitle from '~/packages/details/components/package_title.vue';
import PackageTags from '~/packages/shared/components/package_tags.vue';
import TitleArea from '~/vue_shared/components/registry/title_area.vue';
......@@ -53,6 +54,7 @@ describe('PackageTitle', () => {
const pipelineProject = () => wrapper.find('[data-testid="pipeline-project"]');
const packageRef = () => wrapper.find('[data-testid="package-ref"]');
const packageTags = () => wrapper.find(PackageTags);
const packageBadges = () => wrapper.findAll('[data-testid="tag-badge"]');
afterEach(() => {
wrapper.destroy();
......@@ -70,6 +72,14 @@ describe('PackageTitle', () => {
expect(wrapper.element).toMatchSnapshot();
});
it('with tags on mobile', async () => {
jest.spyOn(GlBreakpointInstance, 'isDesktop').mockReturnValue(false);
await createComponent({ packageEntity: { ...mavenPackage, tags: mockTags } });
await wrapper.vm.$nextTick();
expect(packageBadges()).toHaveLength(mockTags.length);
});
});
describe('package title', () => {
......
......@@ -5,12 +5,16 @@ import component from '~/vue_shared/components/registry/title_area.vue';
describe('title area', () => {
let wrapper;
const DYNAMIC_SLOT = 'metadata-dynamic-slot';
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 findInfoMessages = () => wrapper.findAll('[data-testid="info-message"]');
const findDynamicSlot = () => wrapper.find(`[data-testid="${DYNAMIC_SLOT}`);
const findSlotOrderElements = () => wrapper.findAll('[slot-test]');
const mountComponent = ({ propsData = { title: 'foo' }, slots } = {}) => {
wrapper = shallowMount(component, {
......@@ -98,6 +102,59 @@ describe('title area', () => {
});
});
describe('dynamic slots', () => {
const createDynamicSlot = () => {
return wrapper.vm.$createElement('div', {
attrs: {
'data-testid': DYNAMIC_SLOT,
'slot-test': true,
},
});
};
it('shows dynamic slots', async () => {
mountComponent();
// we manually add a new slot to simulate dynamic slots being evaluated after the initial mount
wrapper.vm.$slots[DYNAMIC_SLOT] = createDynamicSlot();
await wrapper.vm.$nextTick();
expect(findDynamicSlot().exists()).toBe(false);
await wrapper.vm.$nextTick();
expect(findDynamicSlot().exists()).toBe(true);
});
it('preserve the order of the slots', async () => {
mountComponent({
slots: {
'metadata-foo': '<div slot-test data-testid="metadata-foo"></div>',
},
});
// rewrite slot putting dynamic slot as first
wrapper.vm.$slots = {
'metadata-dynamic-slot': createDynamicSlot(),
'metadata-foo': wrapper.vm.$slots['metadata-foo'],
};
await wrapper.vm.$nextTick();
expect(findDynamicSlot().exists()).toBe(false);
expect(findMetadataSlot('metadata-foo').exists()).toBe(true);
await wrapper.vm.$nextTick();
expect(
findSlotOrderElements()
.at(0)
.attributes('data-testid'),
).toBe(DYNAMIC_SLOT);
expect(
findSlotOrderElements()
.at(1)
.attributes('data-testid'),
).toBe('metadata-foo');
});
});
describe('info-messages', () => {
it('shows a message when the props contains one', () => {
mountComponent({ propsData: { infoMessages: [{ text: 'foo foo bar bar' }] } });
......
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