Commit fa917599 authored by Denys Mishunov's avatar Denys Mishunov

Work with blobs array instead of object

Even if we only support just one blob per
snippet, we're peparing ourselves for the
multi-file scenario in Snippets
parent 567aef3c
<script>
import BlobEmbeddable from '~/blob/components/blob_embeddable.vue';
import SnippetHeader from './snippet_header.vue';
import SnippetTitle from './snippet_title.vue';
import SnippetBlob from './snippet_blob_view.vue';
import { GlLoadingIcon } from '@gitlab/ui';
import { getSnippetMixin } from '../mixins/snippets';
import { SNIPPET_VISIBILITY_PUBLIC } from '~/snippets/constants';
export default {
components: {
BlobEmbeddable,
SnippetHeader,
SnippetTitle,
GlLoadingIcon,
SnippetBlob,
},
mixins: [getSnippetMixin],
computed: {
embeddable() {
return this.snippet.visibilityLevel === SNIPPET_VISIBILITY_PUBLIC;
},
},
};
</script>
<template>
......@@ -28,7 +36,9 @@ export default {
<snippet-header :snippet="snippet" />
<snippet-title :snippet="snippet" />
<blob-embeddable v-if="embeddable" class="mb-3" :url="snippet.webUrl" />
<snippet-blob :snippet="snippet" />
<div v-for="blob in blobs" :key="blob.path">
<snippet-blob :snippet="snippet" :blob="blob" />
</div>
</template>
</div>
</template>
<script>
import BlobEmbeddable from '~/blob/components/blob_embeddable.vue';
import { SNIPPET_VISIBILITY_PUBLIC } from '../constants';
import BlobHeader from '~/blob/components/blob_header.vue';
import BlobContent from '~/blob/components/blob_content.vue';
import CloneDropdownButton from '~/vue_shared/components/clone_dropdown.vue';
......@@ -16,7 +14,6 @@ import {
export default {
components: {
BlobEmbeddable,
BlobHeader,
BlobContent,
CloneDropdownButton,
......@@ -49,21 +46,19 @@ export default {
type: Object,
required: true,
},
blob: {
type: Object,
required: true,
},
},
data() {
return {
blob: this.snippet.blob,
blobContent: '',
activeViewerType:
this.snippet.blob?.richViewer && !window.location.hash
? RICH_BLOB_VIEWER
: SIMPLE_BLOB_VIEWER,
this.blob?.richViewer && !window.location.hash ? RICH_BLOB_VIEWER : SIMPLE_BLOB_VIEWER,
};
},
computed: {
embeddable() {
return this.snippet.visibilityLevel === SNIPPET_VISIBILITY_PUBLIC;
},
isContentLoading() {
return this.$apollo.queries.blobContent.loading;
},
......@@ -92,32 +87,30 @@ export default {
};
</script>
<template>
<div>
<article class="file-holder snippet-file-content">
<blob-header
:blob="blob"
:active-viewer-type="viewer.type"
:has-render-error="hasRenderError"
@viewer-changed="switchViewer"
>
<template #actions>
<clone-dropdown-button
v-if="canBeCloned"
class="mr-2"
:ssh-link="snippet.sshUrlToRepo"
:http-link="snippet.httpUrlToRepo"
data-qa-selector="clone_button"
/>
</template>
</blob-header>
<blob-content
:loading="isContentLoading"
:content="blobContent"
:active-viewer="viewer"
:blob="blob"
@[$options.BLOB_RENDER_EVENT_LOAD]="forceQuery"
@[$options.BLOB_RENDER_EVENT_SHOW_SOURCE]="switchViewer"
/>
</article>
</div>
<article class="file-holder snippet-file-content">
<blob-header
:blob="blob"
:active-viewer-type="viewer.type"
:has-render-error="hasRenderError"
@viewer-changed="switchViewer"
>
<template #actions>
<clone-dropdown-button
v-if="canBeCloned"
class="mr-2"
:ssh-link="snippet.sshUrlToRepo"
:http-link="snippet.httpUrlToRepo"
data-qa-selector="clone_button"
/>
</template>
</blob-header>
<blob-content
:loading="isContentLoading"
:content="blobContent"
:active-viewer="viewer"
:blob="blob"
@[$options.BLOB_RENDER_EVENT_LOAD]="forceQuery"
@[$options.BLOB_RENDER_EVENT_SHOW_SOURCE]="switchViewer"
/>
</article>
</template>
......@@ -65,14 +65,17 @@ export default {
};
},
computed: {
snippetHasBinary() {
return Boolean(this.snippet.blobs.find(blob => blob.binary));
},
personalSnippetActions() {
return [
{
condition: this.snippet.userPermissions.updateSnippet,
text: __('Edit'),
href: this.editLink,
disabled: this.snippet.blob.binary,
title: this.snippet.blob.binary
disabled: this.snippetHasBinary,
title: this.snippetHasBinary
? __('Snippets with non-text files can only be edited via Git.')
: undefined,
},
......
---
title: Accept multiple blobs in snippets
merge_request: 35605
author:
type: changed
......@@ -32,6 +32,20 @@ export const Blob = {
},
};
export const BinaryBlob = {
binary: true,
name: 'dummy.png',
path: 'foo/bar/dummy.png',
rawPath: '/flightjs/flight/snippets/51/raw',
size: 75,
simpleViewer: {
...SimpleViewerMock,
},
richViewer: {
...RichViewerMock,
},
};
export const RichBlobContentMock = {
richData: '<h1>Rich</h1>',
};
......
import SnippetApp from '~/snippets/components/show.vue';
import BlobEmbeddable from '~/blob/components/blob_embeddable.vue';
import SnippetHeader from '~/snippets/components/snippet_header.vue';
import SnippetTitle from '~/snippets/components/snippet_title.vue';
import SnippetBlob from '~/snippets/components/snippet_blob_view.vue';
import { GlLoadingIcon } from '@gitlab/ui';
import { Blob, BinaryBlob } from 'jest/blob/components/mock_data';
import { shallowMount } from '@vue/test-utils';
import { SNIPPET_VISIBILITY_PUBLIC } from '~/snippets/constants';
describe('Snippet view app', () => {
let wrapper;
......@@ -12,7 +15,7 @@ describe('Snippet view app', () => {
snippetGid: 'gid://gitlab/PersonalSnippet/42',
};
function createComponent({ props = defaultProps, loading = false } = {}) {
function createComponent({ props = defaultProps, data = {}, loading = false } = {}) {
const $apollo = {
queries: {
snippet: {
......@@ -26,6 +29,9 @@ describe('Snippet view app', () => {
propsData: {
...props,
},
data() {
return data;
},
});
}
afterEach(() => {
......@@ -37,10 +43,33 @@ describe('Snippet view app', () => {
expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
});
it('renders all components after the query is finished', () => {
it('renders all simple components after the query is finished', () => {
createComponent();
expect(wrapper.find(SnippetHeader).exists()).toBe(true);
expect(wrapper.find(SnippetTitle).exists()).toBe(true);
expect(wrapper.find(SnippetBlob).exists()).toBe(true);
});
it('renders embeddable component if visibility allows', () => {
createComponent({
data: {
snippet: {
visibilityLevel: SNIPPET_VISIBILITY_PUBLIC,
webUrl: 'http://foo.bar',
},
},
});
expect(wrapper.contains(BlobEmbeddable)).toBe(true);
});
it('renders correct snippet-blob components', () => {
createComponent({
data: {
blobs: [Blob, BinaryBlob],
},
});
const blobs = wrapper.findAll(SnippetBlob);
expect(blobs.length).toBe(2);
expect(blobs.at(0).props('blob')).toEqual(Blob);
expect(blobs.at(1).props('blob')).toEqual(BinaryBlob);
});
});
......@@ -23,13 +23,17 @@ describe('Blob Embeddable', () => {
id: 'gid://foo.bar/snippet',
webUrl: 'https://foo.bar',
visibilityLevel: SNIPPET_VISIBILITY_PUBLIC,
blob: BlobMock,
};
const dataMock = {
activeViewerType: SimpleViewerMock.type,
};
function createComponent(props = {}, data = dataMock, contentLoading = false) {
function createComponent({
snippetProps = {},
data = dataMock,
blob = BlobMock,
contentLoading = false,
} = {}) {
const $apollo = {
queries: {
blobContent: {
......@@ -44,8 +48,9 @@ describe('Blob Embeddable', () => {
propsData: {
snippet: {
...snippet,
...props,
...snippetProps,
},
blob,
},
data() {
return {
......@@ -63,7 +68,6 @@ describe('Blob Embeddable', () => {
describe('rendering', () => {
it('renders correct components', () => {
createComponent();
expect(wrapper.find(BlobEmbeddable).exists()).toBe(true);
expect(wrapper.find(BlobHeader).exists()).toBe(true);
expect(wrapper.find(BlobContent).exists()).toBe(true);
});
......@@ -72,19 +76,14 @@ describe('Blob Embeddable', () => {
'does not render blob-embeddable by default',
visibilityLevel => {
createComponent({
visibilityLevel,
snippetProps: {
visibilityLevel,
},
});
expect(wrapper.find(BlobEmbeddable).exists()).toBe(false);
},
);
it('does render blob-embeddable for public snippet', () => {
createComponent({
visibilityLevel: SNIPPET_VISIBILITY_PUBLIC,
});
expect(wrapper.find(BlobEmbeddable).exists()).toBe(true);
});
it('sets simple viewer correctly', () => {
createComponent();
expect(wrapper.find(SimpleViewer).exists()).toBe(true);
......@@ -92,7 +91,9 @@ describe('Blob Embeddable', () => {
it('sets rich viewer correctly', () => {
const data = { ...dataMock, activeViewerType: RichViewerMock.type };
createComponent({}, data);
createComponent({
data,
});
expect(wrapper.find(RichViewer).exists()).toBe(true);
});
......@@ -137,7 +138,9 @@ describe('Blob Embeddable', () => {
});
it('renders simple viewer by default if URL contains hash', () => {
createComponent({}, {});
createComponent({
data: {},
});
expect(wrapper.vm.activeViewerType).toBe(SimpleViewerMock.type);
expect(wrapper.find(SimpleViewer).exists()).toBe(true);
......@@ -183,12 +186,11 @@ describe('Blob Embeddable', () => {
});
it(`sets '${SimpleViewerMock.type}' as active on ${BLOB_RENDER_EVENT_SHOW_SOURCE} event`, () => {
createComponent(
{},
{
createComponent({
data: {
activeViewerType: RichViewerMock.type,
},
);
});
findContentEl().vm.$emit(BLOB_RENDER_EVENT_SHOW_SOURCE);
expect(wrapper.vm.activeViewerType).toEqual(SimpleViewerMock.type);
......
......@@ -3,6 +3,7 @@ import DeleteSnippetMutation from '~/snippets/mutations/deleteSnippet.mutation.g
import { ApolloMutation } from 'vue-apollo';
import { GlButton, GlModal } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { Blob, BinaryBlob } from 'jest/blob/components/mock_data';
describe('Snippet header component', () => {
let wrapper;
......@@ -20,9 +21,7 @@ describe('Snippet header component', () => {
author: {
name: 'Thor Odinson',
},
blob: {
binary: false,
},
blobs: [Blob],
};
const mutationVariables = {
mutation: DeleteSnippetMutation,
......@@ -49,7 +48,6 @@ describe('Snippet header component', () => {
mutationRes = mutationTypes.RESOLVE,
snippetProps = {},
} = {}) {
// const defaultProps = Object.assign({}, snippet, snippetProps);
const defaultProps = Object.assign(snippet, snippetProps);
if (permissions) {
Object.assign(defaultProps.userPermissions, {
......@@ -131,15 +129,18 @@ describe('Snippet header component', () => {
expect(wrapper.find(GlModal).exists()).toBe(true);
});
it('renders Edit button as disabled for binary snippets', () => {
it.each`
blobs | isDisabled | condition
${[Blob]} | ${false} | ${'no binary'}
${[Blob, BinaryBlob]} | ${true} | ${'several blobs. incl. a binary'}
${[BinaryBlob]} | ${true} | ${'binary'}
`('renders Edit button when snippet contains $condition file', ({ blobs, isDisabled }) => {
createComponent({
snippetProps: {
blob: {
binary: true,
},
blobs,
},
});
expect(wrapper.find('[href*="edit"]').props('disabled')).toBe(true);
expect(wrapper.find('[href*="edit"]').props('disabled')).toBe(isDisabled);
});
describe('Delete mutation', () => {
......
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