Commit 2fc53516 authored by Enrique Alcántara's avatar Enrique Alcántara

Merge branch '220803-embed-dropdown-snippets' into 'master'

Use embed_dropdown for snippets

See merge request gitlab-org/gitlab!39885
parents cf8b5288 7a313530
<script>
import { GlFormInputGroup, GlDeprecatedButton, GlIcon } from '@gitlab/ui';
import { __ } from '~/locale';
export default {
components: {
GlFormInputGroup,
GlDeprecatedButton,
GlIcon,
},
props: {
url: {
type: String,
required: true,
},
},
data() {
return {
optionValues: [
// eslint-disable-next-line no-useless-escape
{ name: __('Embed'), value: `<script src='${this.url}.js'><\/script>` },
{ name: __('Share'), value: this.url },
],
};
},
};
</script>
<template>
<gl-form-input-group
id="embeddable-text"
:predefined-options="optionValues"
readonly
select-on-click
>
<template #append>
<gl-deprecated-button new-style data-clipboard-target="#embeddable-text">
<gl-icon name="copy-to-clipboard" :title="__('Copy')" />
</gl-deprecated-button>
</template>
</gl-form-input-group>
</template>
<script>
import { escape as esc } from 'lodash';
import {
GlButton,
GlDropdown,
GlDropdownHeader,
GlDropdownText,
GlFormInputGroup,
GlTooltipDirective,
} from '@gitlab/ui';
import { __ } from '~/locale';
const MSG_EMBED = __('Embed');
const MSG_SHARE = __('Share');
const MSG_COPY = __('Copy');
export default {
components: {
GlButton,
GlDropdown,
GlDropdownHeader,
GlDropdownText,
GlFormInputGroup,
},
directives: {
GlTooltip: GlTooltipDirective,
},
props: {
url: {
type: String,
required: true,
},
},
computed: {
sections() {
return [
// eslint-disable-next-line no-useless-escape
{ name: MSG_EMBED, value: `<script src="${esc(this.url)}.js"><\/script>` },
{ name: MSG_SHARE, value: this.url },
];
},
},
MSG_EMBED,
MSG_COPY,
};
</script>
<template>
<gl-dropdown
right
:text="$options.MSG_EMBED"
menu-class="gl-px-1! gl-pb-5! gl-dropdown-menu-wide"
>
<template v-for="{ name, value } in sections">
<gl-dropdown-header :key="`header_${name}`" data-testid="header">{{
name
}}</gl-dropdown-header>
<gl-dropdown-text
:key="`input_${name}`"
tag="div"
class="gl-dropdown-text-py-0 gl-dropdown-text-block"
data-testid="input"
>
<gl-form-input-group :value="value" readonly select-on-click>
<template #append>
<gl-button
v-gl-tooltip.hover
:title="$options.MSG_COPY"
:data-clipboard-text="value"
icon="copy-to-clipboard"
/>
</template>
</gl-form-input-group>
</gl-dropdown-text>
</template>
</gl-dropdown>
</template>
<script> <script>
import { GlLoadingIcon } from '@gitlab/ui'; import { GlLoadingIcon } from '@gitlab/ui';
import BlobEmbeddable from '~/blob/components/blob_embeddable.vue'; import EmbedDropdown from './embed_dropdown.vue';
import SnippetHeader from './snippet_header.vue'; import SnippetHeader from './snippet_header.vue';
import SnippetTitle from './snippet_title.vue'; import SnippetTitle from './snippet_title.vue';
import SnippetBlob from './snippet_blob_view.vue'; import SnippetBlob from './snippet_blob_view.vue';
...@@ -13,7 +13,7 @@ import { SNIPPET_MARK_VIEW_APP_START } from '~/performance_constants'; ...@@ -13,7 +13,7 @@ import { SNIPPET_MARK_VIEW_APP_START } from '~/performance_constants';
export default { export default {
components: { components: {
BlobEmbeddable, EmbedDropdown,
SnippetHeader, SnippetHeader,
SnippetTitle, SnippetTitle,
GlLoadingIcon, GlLoadingIcon,
...@@ -46,7 +46,7 @@ export default { ...@@ -46,7 +46,7 @@ export default {
<snippet-header :snippet="snippet" /> <snippet-header :snippet="snippet" />
<snippet-title :snippet="snippet" /> <snippet-title :snippet="snippet" />
<div class="gl-display-flex gl-justify-content-end gl-mb-5"> <div class="gl-display-flex gl-justify-content-end gl-mb-5">
<blob-embeddable v-if="embeddable" class="gl-flex-fill-1" :url="snippet.webUrl" /> <embed-dropdown v-if="embeddable" :url="snippet.webUrl" />
<clone-dropdown-button <clone-dropdown-button
v-if="canBeCloned" v-if="canBeCloned"
class="gl-ml-3" class="gl-ml-3"
......
...@@ -1104,3 +1104,28 @@ header.header-content .dropdown-menu.frequent-items-dropdown-menu { ...@@ -1104,3 +1104,28 @@ header.header-content .dropdown-menu.frequent-items-dropdown-menu {
} }
} }
} }
// This class won't be needed once we can directly add utility classes to the child
// https://github.com/bootstrap-vue/bootstrap-vue/issues/5669
.gl-dropdown-text-py-0 {
.b-dropdown-text {
padding-top: 0;
padding-bottom: 0;
}
}
// This class won't be needed once we can directly add utility classes to the child
// https://github.com/bootstrap-vue/bootstrap-vue/issues/5669
.gl-dropdown-text-block {
.b-dropdown-text {
display: block;
}
}
// This class won't be needed once we can add a prop for this in the GitLab UI component
// https://gitlab.com/gitlab-org/gitlab-ui/-/issues/966
.gl-new-dropdown {
.gl-dropdown-menu-wide {
width: $gl-dropdown-width-wide;
}
}
---
title: Use dropdown for embed in snippets
merge_request: 39885
author:
type: changed
import { shallowMount } from '@vue/test-utils';
import { GlFormInputGroup } from '@gitlab/ui';
import BlobEmbeddable from '~/blob/components/blob_embeddable.vue';
describe('Blob Embeddable', () => {
let wrapper;
const url = 'https://foo.bar';
function createComponent() {
wrapper = shallowMount(BlobEmbeddable, {
propsData: {
url,
},
});
}
beforeEach(() => {
createComponent();
});
afterEach(() => {
wrapper.destroy();
});
it('renders gl-form-input-group component', () => {
expect(wrapper.find(GlFormInputGroup).exists()).toBe(true);
});
it('makes up optionValues based on the url prop', () => {
expect(wrapper.vm.optionValues).toEqual([
{ name: 'Embed', value: expect.stringContaining(`${url}.js`) },
{ name: 'Share', value: url },
]);
});
});
import { escape as esc } from 'lodash';
import { mount } from '@vue/test-utils';
import { GlFormInputGroup } from '@gitlab/ui';
import { TEST_HOST } from 'helpers/test_constants';
import EmbedDropdown from '~/snippets/components/embed_dropdown.vue';
const TEST_URL = `${TEST_HOST}/test/no">'xss`;
describe('snippets/components/embed_dropdown', () => {
let wrapper;
const createComponent = () => {
wrapper = mount(EmbedDropdown, {
propsData: {
url: TEST_URL,
},
});
};
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
const findSectionsData = () => {
const sections = [];
let current = {};
wrapper.findAll('[data-testid="header"],[data-testid="input"]').wrappers.forEach(x => {
const type = x.attributes('data-testid');
if (type === 'header') {
current = {
header: x.text(),
};
sections.push(current);
} else {
const value = x.find(GlFormInputGroup).props('value');
const copyValue = x.find('button[title="Copy"]').attributes('data-clipboard-text');
Object.assign(current, {
value,
copyValue,
});
}
});
return sections;
};
it('renders dropdown items', () => {
createComponent();
const embedValue = `<script src="${esc(TEST_URL)}.js"></script>`;
expect(findSectionsData()).toEqual([
{
header: 'Embed',
value: embedValue,
copyValue: embedValue,
},
{
header: 'Share',
value: TEST_URL,
copyValue: TEST_URL,
},
]);
});
});
...@@ -2,7 +2,7 @@ import { GlLoadingIcon } from '@gitlab/ui'; ...@@ -2,7 +2,7 @@ import { GlLoadingIcon } from '@gitlab/ui';
import { Blob, BinaryBlob } from 'jest/blob/components/mock_data'; import { Blob, BinaryBlob } from 'jest/blob/components/mock_data';
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import SnippetApp from '~/snippets/components/show.vue'; import SnippetApp from '~/snippets/components/show.vue';
import BlobEmbeddable from '~/blob/components/blob_embeddable.vue'; import EmbedDropdown from '~/snippets/components/embed_dropdown.vue';
import SnippetHeader from '~/snippets/components/snippet_header.vue'; import SnippetHeader from '~/snippets/components/snippet_header.vue';
import SnippetTitle from '~/snippets/components/snippet_title.vue'; import SnippetTitle from '~/snippets/components/snippet_title.vue';
import SnippetBlob from '~/snippets/components/snippet_blob_view.vue'; import SnippetBlob from '~/snippets/components/snippet_blob_view.vue';
...@@ -57,7 +57,7 @@ describe('Snippet view app', () => { ...@@ -57,7 +57,7 @@ describe('Snippet view app', () => {
expect(wrapper.find(SnippetTitle).exists()).toBe(true); expect(wrapper.find(SnippetTitle).exists()).toBe(true);
}); });
it('renders embeddable component if visibility allows', () => { it('renders embed dropdown component if visibility allows', () => {
createComponent({ createComponent({
data: { data: {
snippet: { snippet: {
...@@ -66,7 +66,7 @@ describe('Snippet view app', () => { ...@@ -66,7 +66,7 @@ describe('Snippet view app', () => {
}, },
}, },
}); });
expect(wrapper.contains(BlobEmbeddable)).toBe(true); expect(wrapper.contains(EmbedDropdown)).toBe(true);
}); });
it('renders correct snippet-blob components', () => { it('renders correct snippet-blob components', () => {
...@@ -88,7 +88,7 @@ describe('Snippet view app', () => { ...@@ -88,7 +88,7 @@ describe('Snippet view app', () => {
${SNIPPET_VISIBILITY_PRIVATE} | ${'not render'} | ${false} ${SNIPPET_VISIBILITY_PRIVATE} | ${'not render'} | ${false}
${'foo'} | ${'not render'} | ${false} ${'foo'} | ${'not render'} | ${false}
${SNIPPET_VISIBILITY_PUBLIC} | ${'render'} | ${true} ${SNIPPET_VISIBILITY_PUBLIC} | ${'render'} | ${true}
`('does $condition blob-embeddable by default', ({ visibilityLevel, isRendered }) => { `('does $condition embed-dropdown by default', ({ visibilityLevel, isRendered }) => {
createComponent({ createComponent({
data: { data: {
snippet: { snippet: {
...@@ -97,7 +97,7 @@ describe('Snippet view app', () => { ...@@ -97,7 +97,7 @@ describe('Snippet view app', () => {
}, },
}, },
}); });
expect(wrapper.contains(BlobEmbeddable)).toBe(isRendered); expect(wrapper.contains(EmbedDropdown)).toBe(isRendered);
}); });
}); });
......
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