Commit 665741cb authored by Nicolò Maria Mezzopera's avatar Nicolò Maria Mezzopera Committed by Jose Ivan Vargas

Update list_item component with details slots

- update source
- adjust tests
parent 2507dfd2
...@@ -63,7 +63,8 @@ export default { ...@@ -63,7 +63,8 @@ export default {
v-for="(tag, index) in tags" v-for="(tag, index) in tags"
:key="tag.path" :key="tag.path"
:tag="tag" :tag="tag"
:index="index" :first="index === 0"
:last="index === tags.length - 1"
:selected="selectedItems[tag.name]" :selected="selectedItems[tag.name]"
:is-desktop="isDesktop" :is-desktop="isDesktop"
@select="updateSelectedItems(tag.name)" @select="updateSelectedItems(tag.name)"
......
...@@ -30,16 +30,12 @@ export default { ...@@ -30,16 +30,12 @@ export default {
type: Object, type: Object,
required: true, required: true,
}, },
index: { isDesktop: {
type: Number,
required: true,
},
selected: {
type: Boolean, type: Boolean,
default: false, default: false,
required: false, required: false,
}, },
isDesktop: { selected: {
type: Boolean, type: Boolean,
default: false, default: false,
required: false, required: false,
...@@ -66,7 +62,7 @@ export default { ...@@ -66,7 +62,7 @@ export default {
</script> </script>
<template> <template>
<list-item :index="index" :selected="selected"> <list-item v-bind="$attrs" :selected="selected">
<template #left-action> <template #left-action>
<gl-form-checkbox class="gl-m-0" :checked="selected" @change="$emit('select')" /> <gl-form-checkbox class="gl-m-0" :checked="selected" @change="$emit('select')" />
</template> </template>
......
<script> <script>
import { GlButton } from '@gitlab/ui';
export default { export default {
name: 'ListItem', name: 'ListItem',
components: { GlButton },
props: { props: {
index: { first: {
type: Number, type: Boolean,
default: 0, default: false,
required: false,
},
last: {
type: Boolean,
default: false,
required: false, required: false,
}, },
disabled: { disabled: {
...@@ -18,33 +26,62 @@ export default { ...@@ -18,33 +26,62 @@ export default {
required: false, required: false,
}, },
}, },
data() {
return {
isDetailsShown: false,
detailsSlots: [],
};
},
computed: { computed: {
optionalClasses() { optionalClasses() {
return { return {
'gl-border-t-solid gl-border-t-1': this.index === 0, 'gl-border-t-1': !this.first,
'gl-border-t-2': this.first,
'gl-border-b-1': !this.last,
'gl-border-b-2': this.last,
'disabled-content': this.disabled, 'disabled-content': this.disabled,
'gl-border-gray-200': !this.selected, 'gl-border-gray-200': !this.selected,
'gl-bg-blue-50 gl-border-blue-200': this.selected, 'gl-bg-blue-50 gl-border-blue-200': this.selected,
}; };
}, },
}, },
mounted() {
this.detailsSlots = Object.keys(this.$slots).filter(k => k.startsWith('details_'));
},
methods: {
toggleDetails() {
this.isDetailsShown = !this.isDetailsShown;
},
},
}; };
</script> </script>
<template> <template>
<div <div
class="gl-display-flex gl-align-items-center gl-border-b-solid gl-border-b-1 gl-py-4 gl-px-2" class="gl-display-flex gl-flex-direction-column gl-border-b-solid gl-border-t-solid"
:class="optionalClasses" :class="optionalClasses"
> >
<div v-if="$slots['left-action']" class="gl-mr-5 gl-display-none gl-display-sm-block"> <div class="gl-display-flex gl-align-items-center gl-py-4 gl-px-2">
<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> <slot name="left-action"></slot>
</div> </div>
<div class="gl-display-flex gl-flex-direction-column gl-flex-fill-1"> <div class="gl-display-flex gl-flex-direction-column gl-flex-fill-1">
<div <div
class="gl-display-flex gl-align-items-center gl-justify-content-space-between gl-text-black-normal gl-font-weight-bold" class="gl-display-flex gl-align-items-center gl-justify-content-space-between gl-text-black-normal gl-font-weight-bold"
> >
<div> <div class="gl-display-flex gl-align-items-center">
<slot name="left-primary"></slot> <slot name="left-primary"></slot>
<gl-button
v-if="detailsSlots.length > 0"
:selected="isDetailsShown"
icon="ellipsis_h"
size="small"
class="gl-ml-2"
@click="toggleDetails"
/>
</div> </div>
<div> <div>
<slot name="right-primary"></slot> <slot name="right-primary"></slot>
...@@ -61,8 +98,31 @@ export default { ...@@ -61,8 +98,31 @@ export default {
</div> </div>
</div> </div>
</div> </div>
<div v-if="$slots['right-action']" class="gl-ml-5 gl-display-none gl-display-sm-block"> <div
v-if="$slots['right-action']"
class="gl-w-9 gl-display-none gl-display-sm-flex gl-justify-content-end gl-pr-2"
>
<slot name="right-action"></slot> <slot name="right-action"></slot>
</div> </div>
</div> </div>
<div class="gl-display-flex">
<div class="gl-w-7"></div>
<div
v-if="isDetailsShown"
class="gl-display-flex gl-flex-direction-column gl-flex-fill-1 gl-bg-gray-10 gl-rounded-base gl-inset-border-1-gray-200 gl-mb-3"
>
<div
v-for="(row, detailIndex) in detailsSlots"
:key="detailIndex"
class="gl-px-5 gl-py-2"
:class="{
'gl-border-gray-200 gl-border-t-solid gl-border-t-1': detailIndex !== 0,
}"
>
<slot :name="row"></slot>
</div>
</div>
<div class="gl-w-9"></div>
</div>
</div>
</template> </template>
...@@ -37,7 +37,8 @@ export default { ...@@ -37,7 +37,8 @@ export default {
v-for="(listItem, index) in images" v-for="(listItem, index) in images"
:key="index" :key="index"
:item="listItem" :item="listItem"
:index="index" :first="index === 0"
:last="index === images.length - 1"
@delete="$emit('delete', $event)" @delete="$emit('delete', $event)"
/> />
......
...@@ -29,11 +29,6 @@ export default { ...@@ -29,11 +29,6 @@ export default {
type: Object, type: Object,
required: true, required: true,
}, },
index: {
type: Number,
default: 0,
required: false,
},
}, },
i18n: { i18n: {
LIST_DELETE_BUTTON_DISABLED, LIST_DELETE_BUTTON_DISABLED,
...@@ -71,7 +66,7 @@ export default { ...@@ -71,7 +66,7 @@ export default {
disabled: !item.deleting, disabled: !item.deleting,
title: $options.i18n.ROW_SCHEDULED_FOR_DELETION, title: $options.i18n.ROW_SCHEDULED_FOR_DELETION,
}" }"
:index="index" v-bind="$attrs"
:disabled="item.deleting" :disabled="item.deleting"
> >
<template #left-primary> <template #left-primary>
......
...@@ -108,3 +108,12 @@ ...@@ -108,3 +108,12 @@
.gl-transition-property-stroke { .gl-transition-property-stroke {
transition-property: stroke; transition-property: stroke;
} }
// temporary class till giltab-ui one is merged
.gl-border-t-2 {
border-top-width: $gl-border-size-2;
}
.gl-border-b-2 {
border-bottom-width: $gl-border-size-2;
}
...@@ -102,12 +102,16 @@ describe('Tags List', () => { ...@@ -102,12 +102,16 @@ describe('Tags List', () => {
it('the correct props are bound to it', () => { it('the correct props are bound to it', () => {
mountComponent(); mountComponent();
expect( const rows = findTagsListRow();
findTagsListRow()
.at(0) expect(rows.at(0).attributes()).toMatchObject({
.attributes(), first: 'true',
).toMatchObject({ isdesktop: 'true',
index: '0', });
// 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', isdesktop: 'true',
}); });
}); });
......
import { GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import component from '~/registry/explorer/components/list_item.vue'; import component from '~/registry/explorer/components/list_item.vue';
...@@ -10,8 +11,10 @@ describe('list item', () => { ...@@ -10,8 +11,10 @@ describe('list item', () => {
const findRightPrimarySlot = () => wrapper.find('[data-testid="right-primary"]'); const findRightPrimarySlot = () => wrapper.find('[data-testid="right-primary"]');
const findRightSecondarySlot = () => wrapper.find('[data-testid="right-secondary"]'); const findRightSecondarySlot = () => wrapper.find('[data-testid="right-secondary"]');
const findRightActionSlot = () => wrapper.find('[data-testid="right-action"]'); const findRightActionSlot = () => wrapper.find('[data-testid="right-action"]');
const findDetailsSlot = name => wrapper.find(`[data-testid="${name}"]`);
const findToggleDetailsButton = () => wrapper.find(GlButton);
const mountComponent = propsData => { const mountComponent = (propsData, slots) => {
wrapper = shallowMount(component, { wrapper = shallowMount(component, {
propsData, propsData,
slots: { slots: {
...@@ -21,6 +24,7 @@ describe('list item', () => { ...@@ -21,6 +24,7 @@ describe('list item', () => {
'right-primary': '<div data-testid="right-primary" />', 'right-primary': '<div data-testid="right-primary" />',
'right-secondary': '<div data-testid="right-secondary" />', 'right-secondary': '<div data-testid="right-secondary" />',
'right-action': '<div data-testid="right-action" />', 'right-action': '<div data-testid="right-action" />',
...slots,
}, },
}); });
}; };
...@@ -44,6 +48,50 @@ describe('list item', () => { ...@@ -44,6 +48,50 @@ describe('list item', () => {
expect(finderFunction().exists()).toBe(true); expect(finderFunction().exists()).toBe(true);
}); });
describe.each`
slotNames
${['details_foo']}
${['details_foo', 'details_bar']}
${['details_foo', 'details_bar', 'details_baz']}
`('$slotNames details slots', ({ slotNames }) => {
const slotMocks = slotNames.reduce((acc, current) => {
acc[current] = `<div data-testid="${current}" />`;
return acc;
}, {});
it('are visible when details is shown', async () => {
mountComponent({}, slotMocks);
await wrapper.vm.$nextTick();
findToggleDetailsButton().vm.$emit('click');
await wrapper.vm.$nextTick();
slotNames.forEach(name => {
expect(findDetailsSlot(name).exists()).toBe(true);
});
});
it('are not visible when details are not shown', () => {
mountComponent({}, slotMocks);
slotNames.forEach(name => {
expect(findDetailsSlot(name).exists()).toBe(false);
});
});
});
describe('details toggle button', () => {
it('is visible when at least one details slot exists', async () => {
mountComponent({}, { details_foo: '<span></span>' });
await wrapper.vm.$nextTick();
expect(findToggleDetailsButton().exists()).toBe(true);
});
it('is hidden without details slot', () => {
mountComponent();
expect(findToggleDetailsButton().exists()).toBe(false);
});
});
describe('disabled prop', () => { describe('disabled prop', () => {
it('when true applies disabled-content class', () => { it('when true applies disabled-content class', () => {
mountComponent({ disabled: true }); mountComponent({ disabled: true });
...@@ -58,21 +106,31 @@ describe('list item', () => { ...@@ -58,21 +106,31 @@ describe('list item', () => {
}); });
}); });
describe('index prop', () => { describe('first prop', () => {
it('when index is 0 displays a top border', () => { it('when is true displays a double top border', () => {
mountComponent({ index: 0 }); mountComponent({ first: true });
expect(wrapper.classes()).toEqual( expect(wrapper.classes('gl-border-t-2')).toBe(true);
expect.arrayContaining(['gl-border-t-solid', 'gl-border-t-1']),
);
}); });
it('when index is not 0 hides top border', () => { it('when is false display a single top border', () => {
mountComponent({ index: 1 }); mountComponent({ first: false });
expect(wrapper.classes()).toEqual( expect(wrapper.classes('gl-border-t-1')).toBe(true);
expect.not.arrayContaining(['gl-border-t-solid', 'gl-border-t-1']), });
); });
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);
}); });
}); });
......
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