Commit 9db64056 authored by Natalia Tepluhina's avatar Natalia Tepluhina

Merge branch '335519-project-avatar-env-dashboard' into 'master'

Use GlAvatar in env dashboard project header

See merge request gitlab-org/gitlab!80776
parents a4821a75 3fcf9eda
<script>
import { GlDropdown, GlDropdownItem, GlTooltipDirective, GlLink, GlIcon } from '@gitlab/ui';
import { s__ } from '~/locale';
import ProjectAvatar from '~/vue_shared/components/deprecated_project_avatar/default.vue';
import ProjectAvatar from '~/vue_shared/components/project_avatar.vue';
export default {
components: {
......@@ -28,27 +28,44 @@ export default {
removeProjectText: s__('EnvironmentsDashboard|Remove'),
moreActionsText: s__('EnvironmentsDashboard|More actions'),
avatarSize: 24,
};
</script>
<template>
<div
class="gl-display-flex gl-align-items-center page-title-holder text-secondary gl-justify-content-space-between pb-2 mb-3"
class="gl-display-flex gl-align-items-center gl-text-gray-500 gl-justify-content-space-between gl-pb-3 gl-mb-5 gl-border-b-solid gl-border-gray-100 gl-border-1"
>
<div class="gl-display-flex gl-align-items-center">
<project-avatar :project="project.namespace" :size="20" class="flex-shrink-0" />
<gl-link class="js-namespace-link text-secondary" :href="`/${project.namespace.full_path}`">
<span class="js-namespace gl-mr-3"> {{ project.namespace.name }} </span>
<project-avatar
:project-name="project.namespace.name"
:project-avatar-url="project.namespace.avatar_url"
:size="$options.avatarSize"
class="gl-mr-3"
/>
<gl-link
class="gl-text-gray-500 gl-mr-3"
:href="`/${project.namespace.full_path}`"
data-testid="namespace-link"
>
{{ project.namespace.name }}
</gl-link>
<span class="gl-mr-3">&gt;</span>
<project-avatar :project="project" :size="20" class="flex-shrink-0" />
<gl-link class="js-project-link text-secondary" :href="project.web_url">
<span class="js-name gl-mr-3"> {{ project.name }} </span>
<project-avatar
:project-name="project.name"
:project-avatar-url="project.avatar_url"
:size="$options.avatarSize"
class="gl-mr-3"
/>
<gl-link class="gl-text-gray-500 gl-mr-3" :href="project.web_url" data-testid="project-link">
{{ project.name }}
</gl-link>
</div>
<div class="gl-display-flex js-more-actions">
<div class="gl-display-flex">
<gl-dropdown
toggle-class="js-more-actions-toggle gl-display-flex gl-align-items-center gl-px-3! gl-bg-transparent gl-shadow-none!"
toggle-class="gl-display-flex gl-align-items-center gl-px-3! gl-bg-transparent gl-shadow-none!"
right
>
<template #button-content>
......@@ -56,11 +73,11 @@ export default {
v-gl-tooltip
:title="$options.moreActionsText"
name="ellipsis_v"
class="text-secondary"
class="gl-text-gray-500"
/>
</template>
<gl-dropdown-item class="js-remove-button" variant="link" @click="onRemove()">
<span class="text-danger"> {{ $options.removeProjectText }} </span>
<gl-dropdown-item variant="link" data-testid="remove-project-button" @click="onRemove()">
<span class="gl-text-red-500"> {{ $options.removeProjectText }} </span>
</gl-dropdown-item>
</gl-dropdown>
</div>
......
......@@ -2,26 +2,26 @@
exports[`Project Header matches the snapshot 1`] = `
<div
class="gl-display-flex gl-align-items-center page-title-holder text-secondary gl-justify-content-space-between pb-2 mb-3"
class="gl-display-flex gl-align-items-center gl-text-gray-500 gl-justify-content-space-between gl-pb-3 gl-mb-5 gl-border-b-solid gl-border-gray-100 gl-border-1"
>
<div
class="gl-display-flex gl-align-items-center"
>
<project-avatar-stub
class="flex-shrink-0"
project="[object Object]"
size="20"
class="gl-mr-3"
projectavatarurl="/namespace-avatar"
projectname="hello"
size="24"
/>
<gl-link-stub
class="js-namespace-link text-secondary"
class="gl-text-gray-500 gl-mr-3"
data-testid="namespace-link"
href="/hello"
>
<span
class="js-namespace gl-mr-3"
>
hello
</span>
hello
</gl-link-stub>
<span
......@@ -31,24 +31,24 @@ exports[`Project Header matches the snapshot 1`] = `
</span>
<project-avatar-stub
class="flex-shrink-0"
project="[object Object]"
size="20"
class="gl-mr-3"
projectavatarurl="/project-avatar"
projectname="world"
size="24"
/>
<gl-link-stub
class="js-project-link text-secondary"
class="gl-text-gray-500 gl-mr-3"
data-testid="project-link"
>
<span
class="js-name gl-mr-3"
>
world
</span>
world
</gl-link-stub>
</div>
<div
class="gl-display-flex js-more-actions"
class="gl-display-flex"
>
<gl-dropdown-stub
category="primary"
......@@ -61,13 +61,13 @@ exports[`Project Header matches the snapshot 1`] = `
right="true"
size="medium"
text=""
toggleclass="js-more-actions-toggle gl-display-flex gl-align-items-center gl-px-3! gl-bg-transparent gl-shadow-none!"
toggleclass="gl-display-flex gl-align-items-center gl-px-3! gl-bg-transparent gl-shadow-none!"
variant="default"
>
<gl-dropdown-item-stub
avatarurl=""
class="js-remove-button"
data-testid="remove-project-button"
iconcolor=""
iconname=""
iconrightarialabel=""
......@@ -76,7 +76,7 @@ exports[`Project Header matches the snapshot 1`] = `
variant="link"
>
<span
class="text-danger"
class="gl-text-red-500"
>
Remove
</span>
......
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import component from 'ee/environments_dashboard/components/dashboard/project_header.vue';
import ProjectAvatar from '~/vue_shared/components/deprecated_project_avatar/default.vue';
import ProjectAvatar from '~/vue_shared/components/project_avatar.vue';
const mockProject = {
namespace: {
name: 'hello',
full_path: 'hello',
avatar_url: '/namespace-avatar',
},
name: 'world',
remove_path: '/hello/world/remove',
avatar_url: '/project-avatar',
};
describe('Project Header', () => {
let wrapper;
let propsData;
beforeEach(() => {
propsData = {
project: {
namespace: {
name: 'hello',
full_path: 'hello',
},
name: 'world',
remove_path: '/hello/world/remove',
},
};
});
const findRemoveButton = () =>
wrapper
.findComponent(GlDropdown)
.findAllComponents(GlDropdownItem)
.filter((w) => w.text() === 'Remove');
beforeEach(() => {
wrapper = shallowMount(component, {
propsData,
wrapper = shallowMountExtended(component, {
propsData: { project: mockProject },
});
});
......@@ -38,50 +41,46 @@ describe('Project Header', () => {
describe('renders project namespace, name, and avatars', () => {
it('shows the project namespace avatar', () => {
const projectNamespaceAvatar = wrapper.findAllComponents(ProjectAvatar).at(0);
expect(projectNamespaceAvatar.props('project')).toEqual(propsData.project.namespace);
});
it('shows the project namespace', () => {
expect(wrapper.find('.js-namespace').text()).toBe(propsData.project.namespace.name);
expect(projectNamespaceAvatar.props()).toMatchObject({
projectName: mockProject.namespace.name,
projectAvatarUrl: mockProject.namespace.avatar_url,
});
});
it('links to the project namespace', () => {
const expectedUrl = `/${propsData.project.namespace.full_path}`;
expect(wrapper.find('.js-namespace-link').attributes('href')).toBe(expectedUrl);
const expectedUrl = `/${mockProject.namespace.full_path}`;
const namespaceLink = wrapper.findByTestId('namespace-link');
expect(namespaceLink.attributes('href')).toBe(expectedUrl);
expect(namespaceLink.text()).toMatchInterpolatedText(mockProject.namespace.name);
});
it('shows the project avatar', () => {
const projectAvatar = wrapper.findAllComponents(ProjectAvatar).at(1);
expect(projectAvatar.props('project')).toEqual(propsData.project);
});
it('shows the project name', () => {
expect(wrapper.find('.js-name').text()).toBe(propsData.project.name);
expect(projectAvatar.props()).toMatchObject({
projectName: mockProject.name,
projectAvatarUrl: mockProject.avatar_url,
});
});
it('links to the project', () => {
expect(wrapper.find('.js-project-link').attributes('href')).toBe(propsData.project.web_url);
const projectLink = wrapper.findByTestId('project-link');
expect(projectLink.attributes('href')).toBe(mockProject.web_url);
expect(projectLink.text()).toMatchInterpolatedText(mockProject.name);
});
});
describe('more actions', () => {
it('should list "remove" as an action', () => {
const removeLink = wrapper
.findComponent(GlDropdown)
.findAllComponents(GlDropdownItem)
.filter((w) => w.text() === 'Remove');
expect(removeLink.exists()).toBe(true);
expect(findRemoveButton().exists()).toBe(true);
});
it('should emit a "remove" event when "remove" is clicked', async () => {
const removeLink = wrapper
.findComponent(GlDropdown)
.findAllComponents(GlDropdownItem)
.filter((w) => w.text() === 'Remove');
removeLink.at(0).vm.$emit('click');
findRemoveButton().at(0).vm.$emit('click');
await nextTick();
expect(wrapper.emitted('remove')).toContainEqual([propsData.project.remove_path]);
expect(wrapper.emitted('remove')).toContainEqual([mockProject.remove_path]);
});
});
});
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