diff --git a/app/assets/javascripts/snippets/components/show.vue b/app/assets/javascripts/snippets/components/show.vue index 7a60fc6d26c0198ea0b0c2dd69dfcc5a9804f548..a71e208bc8ccc972b282ecd05d013e853f038996 100644 --- a/app/assets/javascripts/snippets/components/show.vue +++ b/app/assets/javascripts/snippets/components/show.vue @@ -15,7 +15,7 @@ import { markBlobPerformance } from '../utils/blob'; import EmbedDropdown from './embed_dropdown.vue'; import SnippetBlob from './snippet_blob_view.vue'; import SnippetHeader from './snippet_header.vue'; -import SnippetTitle from './snippet_title.vue'; +import SnippetDescription from './snippet_description.vue'; eventHub.$on(SNIPPET_MEASURE_BLOBS_CONTENT, markBlobPerformance); @@ -23,7 +23,7 @@ export default { components: { EmbedDropdown, SnippetHeader, - SnippetTitle, + SnippetDescription, GlAlert, GlLoadingIcon, SnippetBlob, @@ -54,7 +54,7 @@ export default { }; </script> <template> - <div class="js-snippet-view"> + <div class="gl-pt-3 js-snippet-view"> <gl-loading-icon v-if="isLoading" :label="__('Loading snippet')" @@ -63,7 +63,7 @@ export default { /> <template v-else> <snippet-header :snippet="snippet" /> - <snippet-title :snippet="snippet" /> + <snippet-description :snippet="snippet" /> <div class="gl-display-flex gl-justify-content-end gl-mb-5"> <embed-dropdown v-if="embeddable" diff --git a/app/assets/javascripts/snippets/components/snippet_title.vue b/app/assets/javascripts/snippets/components/snippet_description.vue similarity index 51% rename from app/assets/javascripts/snippets/components/snippet_title.vue rename to app/assets/javascripts/snippets/components/snippet_description.vue index 33058fcc58b8b75ee1d3b29e9ae980935d525471..5956580e7d0783ba04a0f00ebfbcf6c149a27780 100644 --- a/app/assets/javascripts/snippets/components/snippet_title.vue +++ b/app/assets/javascripts/snippets/components/snippet_description.vue @@ -1,17 +1,11 @@ <script> -import { GlIcon, GlTooltipDirective, GlSprintf } from '@gitlab/ui'; -import { __, s__ } from '~/locale'; +import { GlTooltipDirective, GlSprintf } from '@gitlab/ui'; import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import SnippetDescription from './snippet_description_view.vue'; export default { name: 'SnippetTitle', - i18n: { - hiddenTooltip: s__('Snippets|This snippet is hidden because its author has been banned'), - hiddenAriaLabel: __('Hidden'), - }, components: { - GlIcon, TimeAgoTooltip, GlSprintf, SnippetDescription, @@ -28,25 +22,7 @@ export default { }; </script> <template> - <div class="snippet-header limited-header-width gl-py-3"> - <div class="gl-display-flex"> - <span - v-if="snippet.hidden" - class="gl-bg-orange-50 gl-text-orange-600 gl-h-6 gl-w-6 border-radius-default gl-line-height-24 gl-text-center gl-mr-3 gl-mt-2" - > - <gl-icon - v-gl-tooltip.bottom - name="spam" - :title="$options.i18n.hiddenTooltip" - :aria-label="$options.i18n.hiddenAriaLabel" - /> - </span> - - <h2 class="snippet-title gl-mt-0 gl-mb-5" data-testid="snippet-title-content"> - {{ snippet.title }} - </h2> - </div> - + <div data-testid="snippet-description"> <snippet-description v-if="snippet.description" :description="snippet.descriptionHtml" /> <small diff --git a/app/assets/javascripts/snippets/components/snippet_description_view.vue b/app/assets/javascripts/snippets/components/snippet_description_view.vue index 9eae096d6f2d801065afa9a1cae1749e41fa33cb..129a09d5a2288377e6db229d5b0ce44112429638 100644 --- a/app/assets/javascripts/snippets/components/snippet_description_view.vue +++ b/app/assets/javascripts/snippets/components/snippet_description_view.vue @@ -20,7 +20,7 @@ export default { }; </script> <template> - <markdown-field-view class="snippet-description" data-testid="snippet-description-content"> + <markdown-field-view data-testid="snippet-description-content"> <div v-safe-html:[$options.safeHtmlConfig]="description" class="md js-snippet-description" diff --git a/app/assets/javascripts/snippets/components/snippet_header.vue b/app/assets/javascripts/snippets/components/snippet_header.vue index 8f6b98c4587198ddedff04837f6c8d3204a4a0dd..ec6822a87e8a289211c5b9e397490f49f14cb5a5 100644 --- a/app/assets/javascripts/snippets/components/snippet_header.vue +++ b/app/assets/javascripts/snippets/components/snippet_header.vue @@ -1,6 +1,5 @@ <script> import { - GlAvatar, GlIcon, GlSprintf, GlModal, @@ -30,12 +29,13 @@ export const i18n = { { spammable_titlecase: __('Snippet') }, ), snippetSpamFailure: s__('Snippets|Error with Akismet. Please check the logs for more info.'), + hiddenTooltip: s__('Snippets|This snippet is hidden because its author has been banned'), + hiddenAriaLabel: __('Hidden'), snippetAction: s__('Snippets|Snippet actions'), }; export default { components: { - GlAvatar, GlIcon, GlSprintf, GlModal, @@ -233,145 +233,156 @@ export default { }; </script> <template> - <div class="detail-page-header"> - <div class="detail-page-header-body gl-align-items-baseline"> - <div - class="snippet-box has-tooltip d-flex gl-align-items-center gl-mr-2 mb-1" - data-testid="snippet-container" - :title="snippetVisibilityLevelDescription" - data-container="body" - > - <span class="sr-only">{{ s__(`VisibilityLevel|${visibility}`) }}</span> - <gl-icon :name="visibilityLevelIcon" :size="14" /> - </div> - <div class="creator" data-testid="authored-message"> - <gl-sprintf :message="authoredMessage"> - <template #timeago> - <time-ago-tooltip - :time="snippet.createdAt" - tooltip-placement="bottom" - css-class="snippet_updated_ago" - /> - </template> - <template #author> - <a :href="snippet.author.webUrl" class="d-inline"> - <gl-avatar :size="24" :src="snippet.author.avatarUrl" /> - <span class="bold gl-display-none gl-sm-display-inline">{{ - snippet.author.name - }}</span> - <strong - v-if="snippet.author.username" - data-testid="authored-username" - class="gl-display-inline gl-sm-display-none!" - >@{{ snippet.author.username }}</strong - > - </a> - <gl-emoji - v-if="snippet.author.status" - v-gl-tooltip - class="gl-vertical-align-baseline gl-reset-font-size gl-mr-1" - :title="snippet.author.status.message" - :data-name="snippet.author.status.emoji" - /> - </template> - </gl-sprintf> - </div> - </div> - + <div> <div - v-if="hasPersonalSnippetActions" - class="detail-page-header-actions gl-display-flex gl-align-self-center gl-gap-3 gl-relative" + class="gl-display-flex gl-align-items-flex-start gl-flex-direction-column gl-sm-flex-direction-row gl-gap-3 gl-pt-3" > - <gl-button - v-if="snippet.userPermissions.updateSnippet" - :href="editItem.href" - :title="editItem.title" - :disabled="editItem.disabled" - class="gl-display-none gl-sm-display-inline-block" - data-testid="snippet-action-button" - :data-qa-action="editItem.text" + <span + v-if="snippet.hidden" + class="gl-bg-orange-50 gl-text-orange-600 gl-h-6 gl-w-6 gl-rounded-base gl-line-height-24 gl-text-center gl-mt-2" > - {{ editItem.text }} - </gl-button> + <gl-icon + v-gl-tooltip.bottom + name="spam" + :title="$options.i18n.hiddenTooltip" + :aria-label="$options.i18n.hiddenAriaLabel" + /> + </span> - <gl-disclosure-dropdown - data-testid="snippets-more-actions-dropdown" - @shown="onShowDropdown" - @hidden="onHideDropdown" + <h1 + class="gl-font-size-h-display gl-w-full gl-m-0! gl-flex-grow-1" + data-testid="snippet-title-content" > - <template #toggle> - <div class="gl-w-full gl-min-h-7"> - <gl-button - class="gl-sm-display-none! gl-new-dropdown-toggle gl-absolute gl-top-0 gl-left-0 gl-w-full" - button-text-classes="gl-display-flex gl-justify-content-space-between gl-w-full" - category="secondary" - tabindex="0" - > - <span>{{ $options.i18n.snippetAction }}</span> - <gl-icon class="dropdown-chevron" name="chevron-down" /> - </gl-button> - <gl-button - v-gl-tooltip="showDropdownTooltip" - class="gl-display-none gl-sm-display-flex! gl-new-dropdown-toggle gl-new-dropdown-icon-only gl-new-dropdown-toggle-no-caret" - category="tertiary" - icon="ellipsis_v" - :aria-label="$options.i18n.snippetAction" - tabindex="0" - data-testid="snippets-more-actions-dropdown-toggle" - /> - </div> - </template> - <gl-disclosure-dropdown-item + {{ snippet.title }} + </h1> + + <div + v-if="hasPersonalSnippetActions" + class="detail-page-header-actions gl-display-flex gl-align-self-center gl-gap-3 gl-relative gl-w-full gl-sm-w-auto" + > + <gl-button v-if="snippet.userPermissions.updateSnippet" - :item="editItem" - /> - <gl-disclosure-dropdown-item v-if="canCreateSnippet" :item="newSnippetItem" /> - <gl-disclosure-dropdown-group bordered> - <gl-disclosure-dropdown-item v-if="canReportSpaCheck" :item="spamItem" /> + :href="editItem.href" + :title="editItem.title" + :disabled="editItem.disabled" + class="gl-display-none gl-sm-display-inline-block" + data-testid="snippet-action-button" + :data-qa-action="editItem.text" + > + {{ editItem.text }} + </gl-button> + + <gl-disclosure-dropdown + data-testid="snippets-more-actions-dropdown" + @shown="onShowDropdown" + @hidden="onHideDropdown" + > + <template #toggle> + <div class="gl-w-full gl-min-h-7"> + <gl-button + class="gl-sm-display-none! gl-new-dropdown-toggle gl-absolute gl-top-0 gl-left-0 gl-w-full" + button-text-classes="gl-display-flex gl-justify-content-space-between gl-w-full" + category="secondary" + tabindex="0" + > + <span>{{ $options.i18n.snippetAction }}</span> + <gl-icon class="dropdown-chevron" name="chevron-down" /> + </gl-button> + <gl-button + v-gl-tooltip="showDropdownTooltip" + class="gl-display-none gl-sm-display-flex! gl-new-dropdown-toggle gl-new-dropdown-icon-only gl-new-dropdown-toggle-no-caret" + category="tertiary" + icon="ellipsis_v" + :aria-label="$options.i18n.snippetAction" + tabindex="0" + data-testid="snippets-more-actions-dropdown-toggle" + /> + </div> + </template> <gl-disclosure-dropdown-item - v-if="snippet.userPermissions.adminSnippet" - :item="deleteItem" + v-if="snippet.userPermissions.updateSnippet" + :item="editItem" /> - </gl-disclosure-dropdown-group> - </gl-disclosure-dropdown> + <gl-disclosure-dropdown-item v-if="canCreateSnippet" :item="newSnippetItem" /> + <gl-disclosure-dropdown-group bordered> + <gl-disclosure-dropdown-item v-if="canReportSpaCheck" :item="spamItem" /> + <gl-disclosure-dropdown-item + v-if="snippet.userPermissions.adminSnippet" + :item="deleteItem" + /> + </gl-disclosure-dropdown-group> + </gl-disclosure-dropdown> + </div> </div> - <gl-modal - ref="deleteModal" - v-model="isDeleteModalVisible" - modal-id="delete-modal" - title="Example title" + <div + class="detail-page-header gl-flex-direction-column gl-md-flex-direction-row gl-p-0 gl-mt-2 gl-mb-6" > - <template #modal-title>{{ __('Delete snippet?') }}</template> + <div class="detail-page-header-body gl-align-items-baseline"> + <div + class="snippet-box has-tooltip gl-display-flex gl-align-self-baseline gl-mt-3 gl-mr-2" + data-testid="snippet-container" + :title="snippetVisibilityLevelDescription" + data-container="body" + > + <span class="gl-sr-only">{{ s__(`VisibilityLevel|${visibility}`) }}</span> + <gl-icon :name="visibilityLevelIcon" :size="14" class="gl-relative gl-top-1" /> + </div> + <div data-testid="authored-message" class="gl-line-height-20"> + <gl-sprintf :message="authoredMessage"> + <template #timeago> + <time-ago-tooltip + :time="snippet.createdAt" + tooltip-placement="bottom" + css-class="snippet_updated_ago" + /> + </template> + <template #author> + <a :href="snippet.author.webUrl" class="gl-font-weight-bold"> + {{ snippet.author.name }} + </a> + </template> + </gl-sprintf> + </div> + </div> - <gl-alert - v-if="errorMessage" - variant="danger" - class="mb-2" - data-testid="delete-alert" - @dismiss="errorMessage = ''" - >{{ errorMessage }}</gl-alert + <gl-modal + ref="deleteModal" + v-model="isDeleteModalVisible" + modal-id="delete-modal" + :title="__('Delete snippet modal')" > + <template #modal-title>{{ __('Delete snippet?') }}</template> - <gl-sprintf :message="__('Are you sure you want to delete %{name}?')"> - <template #name> - <strong>{{ snippet.title }}</strong> - </template> - </gl-sprintf> - - <template #modal-footer> - <gl-button @click="closeDeleteModal">{{ __('Cancel') }}</gl-button> - <gl-button + <gl-alert + v-if="errorMessage" variant="danger" - category="primary" - :disabled="isLoading" - data-testid="delete-snippet-button" - @click="deleteSnippet" + class="gl-mb-3" + data-testid="delete-alert" + @dismiss="errorMessage = ''" + >{{ errorMessage }}</gl-alert > - <gl-loading-icon v-if="isLoading" size="sm" inline /> - {{ __('Delete snippet') }} - </gl-button> - </template> - </gl-modal> + + <gl-sprintf :message="__('Are you sure you want to delete %{name}?')"> + <template #name> + <strong>{{ snippet.title }}</strong> + </template> + </gl-sprintf> + + <template #modal-footer> + <gl-button @click="closeDeleteModal">{{ __('Cancel') }}</gl-button> + <gl-button + variant="danger" + category="primary" + :disabled="isLoading" + data-testid="delete-snippet-button" + @click="deleteSnippet" + > + <gl-loading-icon v-if="isLoading" size="sm" inline /> + {{ __('Delete snippet') }} + </gl-button> + </template> + </gl-modal> + </div> </div> </template> diff --git a/locale/gitlab.pot b/locale/gitlab.pot index c80738d283e803b53dcfc9a0c8f8298fdb8e384b..c5345c5f4f7e1f91698628dbc1fa1e3febb84ad4 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -16841,6 +16841,9 @@ msgstr "" msgid "Delete snippet" msgstr "" +msgid "Delete snippet modal" +msgstr "" + msgid "Delete snippet?" msgstr "" diff --git a/qa/qa/page/component/snippet.rb b/qa/qa/page/component/snippet.rb index a710cf0dbb3dff76d3c5ffb41d0333a7f6dfa2c7..0b85527967e50330ae5d0dc5fbbae0ffca7c2765 100644 --- a/qa/qa/page/component/snippet.rb +++ b/qa/qa/page/component/snippet.rb @@ -13,15 +13,12 @@ def self.included(base) include QA::Page::Component::ConfirmModal end - base.view 'app/assets/javascripts/snippets/components/snippet_title.vue' do - element 'snippet-title-content' - end - base.view 'app/assets/javascripts/snippets/components/snippet_description_view.vue' do element 'snippet-description-content' end base.view 'app/assets/javascripts/snippets/components/snippet_header.vue' do + element 'snippet-title-content' element 'snippet-container' element 'snippet-action-button' element 'delete-snippet-button' diff --git a/qa/qa/page/dashboard/snippet/show.rb b/qa/qa/page/dashboard/snippet/show.rb index c0eea5cbe1b182687a3da7e6ec9aa1e4e3850907..0b62c84737d96d03d8f73da7164b0fd8e2ded250 100644 --- a/qa/qa/page/dashboard/snippet/show.rb +++ b/qa/qa/page/dashboard/snippet/show.rb @@ -8,7 +8,7 @@ class Show < Page::Base include Page::Component::Snippet include Page::Component::BlobContent - view 'app/assets/javascripts/snippets/components/snippet_title.vue' do + view 'app/assets/javascripts/snippets/components/snippet_header.vue' do element 'snippet-title-content' end end diff --git a/spec/features/projects/snippets/create_snippet_spec.rb b/spec/features/projects/snippets/create_snippet_spec.rb index a28416f3ca3cfede72ea95b7da58b26a345222ba..2c5c42710d4ea1aca98de8e2b2b1cfca4cdcec1f 100644 --- a/spec/features/projects/snippets/create_snippet_spec.rb +++ b/spec/features/projects/snippets/create_snippet_spec.rb @@ -47,7 +47,7 @@ def fill_form expect(page).to have_content(title) expect(page).to have_content(file_content) - page.within('.snippet-header .snippet-description') do + within_testid('snippet-description') do expect(page).to have_content(description) expect(page).to have_selector('strong') end diff --git a/spec/frontend/snippets/components/__snapshots__/snippet_description_view_spec.js.snap b/spec/frontend/snippets/components/__snapshots__/snippet_description_view_spec.js.snap index 7c5fbf4cfb7c65f56e0735308d902d57852fbd5c..245143058fc0b9f4e170f7a26a709b564727cee5 100644 --- a/spec/frontend/snippets/components/__snapshots__/snippet_description_view_spec.js.snap +++ b/spec/frontend/snippets/components/__snapshots__/snippet_description_view_spec.js.snap @@ -2,7 +2,6 @@ exports[`Snippet Description component matches the snapshot 1`] = ` <markdown-field-view-stub - class="snippet-description" data-testid="snippet-description-content" > <div diff --git a/spec/frontend/snippets/components/show_spec.js b/spec/frontend/snippets/components/show_spec.js index 5973768c337ff998fa5b33e02e80f90fc34a56b2..7a46fb08ff6a0d56f15e61c8e95ac3945bd57c02 100644 --- a/spec/frontend/snippets/components/show_spec.js +++ b/spec/frontend/snippets/components/show_spec.js @@ -5,7 +5,7 @@ import EmbedDropdown from '~/snippets/components/embed_dropdown.vue'; import SnippetApp from '~/snippets/components/show.vue'; import SnippetBlob from '~/snippets/components/snippet_blob_view.vue'; import SnippetHeader from '~/snippets/components/snippet_header.vue'; -import SnippetTitle from '~/snippets/components/snippet_title.vue'; +import SnippetDescription from '~/snippets/components/snippet_description.vue'; import { VISIBILITY_LEVEL_INTERNAL_STRING, VISIBILITY_LEVEL_PRIVATE_STRING, @@ -58,7 +58,7 @@ describe('Snippet view app', () => { it('renders all simple components required after the query is finished', () => { createComponent(); expect(wrapper.findComponent(SnippetHeader).exists()).toBe(true); - expect(wrapper.findComponent(SnippetTitle).exists()).toBe(true); + expect(wrapper.findComponent(SnippetDescription).exists()).toBe(true); }); it('renders embed dropdown component if visibility allows', () => { diff --git a/spec/frontend/snippets/components/snippet_description_spec.js b/spec/frontend/snippets/components/snippet_description_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..5e519da69d8b9d5f5fabe1f3b7c29221b16d42f4 --- /dev/null +++ b/spec/frontend/snippets/components/snippet_description_spec.js @@ -0,0 +1,68 @@ +import { GlSprintf } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; +import SnippetDescription from '~/snippets/components/snippet_description.vue'; +import SnippetDescriptionView from '~/snippets/components/snippet_description_view.vue'; + +describe('Snippet description component', () => { + let wrapper; + const description = 'Do not touch this hammer'; + const descriptionHtml = `<h2>${description}</h2>`; + + function createComponent({ propsData = {} } = {}) { + wrapper = shallowMount(SnippetDescription, { + propsData: { + snippet: { + description, + descriptionHtml, + }, + ...propsData, + }, + }); + } + + describe('default state', () => { + beforeEach(() => { + createComponent(); + }); + + it('renders itself', () => { + expect(wrapper.find('[data-testid="snippet-description"]').exists()).toBe(true); + }); + + it('renders snippets description', () => { + expect(wrapper.findComponent(SnippetDescriptionView).props('description')).toBe( + descriptionHtml, + ); + }); + + it('does not render recent changes time stamp if there were no updates', () => { + expect(wrapper.findComponent(GlSprintf).exists()).toBe(false); + }); + + it('does not render recent changes time stamp if the time for creation and updates match', () => { + createComponent({ + propsData: { + snippet: { + createdAt: '2019-12-16T21:45:36Z', + updatedAt: '2019-12-16T21:45:36Z', + }, + }, + }); + + expect(wrapper.findComponent(GlSprintf).exists()).toBe(false); + }); + + it('renders translated string with most recent changes timestamp if changes were made', () => { + createComponent({ + propsData: { + snippet: { + createdAt: '2019-12-16T21:45:36Z', + updatedAt: '2019-15-16T21:45:36Z', + }, + }, + }); + + expect(wrapper.findComponent(GlSprintf).exists()).toBe(true); + }); + }); +}); diff --git a/spec/frontend/snippets/components/snippet_header_spec.js b/spec/frontend/snippets/components/snippet_header_spec.js index 73a69ccda9b6d8b8e88b69bcdca404cc9dbe73c4..131a299772e0d276308d14393f4b7dba177de2a1 100644 --- a/spec/frontend/snippets/components/snippet_header_spec.js +++ b/spec/frontend/snippets/components/snippet_header_spec.js @@ -4,6 +4,7 @@ import { GlDisclosureDropdown, GlDisclosureDropdownGroup, GlDisclosureDropdownItem, + GlIcon, } from '@gitlab/ui'; import VueApollo from 'vue-apollo'; import MockAdapter from 'axios-mock-adapter'; @@ -20,6 +21,7 @@ import axios from '~/lib/utils/axios_utils'; import { createAlert, VARIANT_DANGER, VARIANT_SUCCESS } from '~/alert'; import CanCreateProjectSnippet from 'shared_queries/snippet/project_permissions.query.graphql'; import CanCreatePersonalSnippet from 'shared_queries/snippet/user_permissions.query.graphql'; +import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; import { getCanCreateProjectSnippetMock, getCanCreatePersonalSnippetMock } from '../mock_data'; const ERROR_MSG = 'Foo bar'; @@ -43,8 +45,6 @@ describe('Snippet header component', () => { const reportAbusePath = '/-/snippets/42/mark_as_spam'; const canReportSpam = true; - const GlEmoji = { template: '<img/>' }; - function createComponent({ permissions = {}, snippetProps = {}, @@ -78,19 +78,20 @@ describe('Snippet header component', () => { }, }, stubs: { - GlEmoji, GlButton, GlDisclosureDropdown, GlDisclosureDropdownGroup, GlDisclosureDropdownItem, + GlIcon, + }, + directives: { + GlTooltip: createMockDirective('gl-tooltip'), }, apolloProvider: mockApollo, }); } - const findAuthorEmoji = () => wrapper.findComponent(GlEmoji); const findAuthoredMessage = () => wrapper.findByTestId('authored-message').text(); - const findAuthorUsername = () => wrapper.findByTestId('authored-username'); const findEditButton = () => wrapper.findByTestId('snippet-action-button'); const findDropdown = () => wrapper.findComponent(GlDisclosureDropdown); const findDropdownItems = () => wrapper.findAllComponents(GlDisclosureDropdownItem); @@ -98,12 +99,17 @@ describe('Snippet header component', () => { const findSpamAction = () => wrapper.findByText('Submit as spam'); const findDeleteAction = () => wrapper.findByText('Delete'); const findDeleteModal = () => wrapper.findComponent(GlModal); + const findIcon = () => wrapper.findComponent(GlIcon); + const findTooltip = () => getBinding(findIcon().element, 'gl-tooltip'); + const findSpamIcon = () => wrapper.findComponent('[data-testid="snippets-spam-icon"]'); + + const title = 'The property of Thor'; beforeEach(() => { gon.relative_url_root = '/foo/'; snippet = { id: 'gid://gitlab/PersonalSnippet/50', - title: 'The property of Thor', + title, visibilityLevel: 'private', webUrl: 'http://personal.dev.null/42', userPermissions: { @@ -134,43 +140,33 @@ describe('Snippet header component', () => { expect(wrapper.find('.detail-page-header').exists()).toBe(true); }); + it('renders snippets title', () => { + createComponent(); + + expect(wrapper.text().trim()).toContain(title); + }); + + it('does not render spam icon when author is not banned', () => { + createComponent(); + + expect(findSpamIcon().exists()).toBe(false); + }); + it('renders a message showing snippet creation date and author full name, without username when not available', () => { createComponent(); const text = findAuthoredMessage(); expect(text).toContain('Authored 1 month ago by'); expect(text).toContain('Thor Odinson'); - expect(findAuthorUsername().exists()).toBe(false); }); - it('renders a message showing snippet creation date, author full name and username', () => { + it('renders a message showing snippet creation date and author full name', () => { snippet.author.username = 'todinson'; createComponent(); const text = findAuthoredMessage(); expect(text).toContain('Authored 1 month ago by'); expect(text).toContain('Thor Odinson'); - expect(text).toContain('@todinson'); - expect(findAuthorUsername().exists()).toBe(true); - }); - - describe('author status', () => { - it('is rendered when it is set', () => { - snippet.author.status = { - message: 'At work', - emoji: 'hammer', - }; - createComponent(); - - expect(findAuthorEmoji().attributes('title')).toBe(snippet.author.status.message); - expect(findAuthorEmoji().attributes('data-name')).toBe(snippet.author.status.emoji); - }); - - it('is not rendered when the user has no status', () => { - createComponent(); - - expect(findAuthorEmoji().exists()).toBe(false); - }); }); it('renders a message showing only snippet creation date if author is null', () => { @@ -369,4 +365,26 @@ describe('Snippet header component', () => { }); }); }); + + describe('when author of snippet is banned', () => { + it('renders spam icon and tooltip', () => { + createComponent({ + snippetProps: { + hidden: true, + }, + }); + + expect(findIcon().props()).toMatchObject({ + ariaLabel: 'Hidden', + name: 'spam', + size: 16, + }); + + expect(findIcon().attributes('title')).toBe( + 'This snippet is hidden because its author has been banned', + ); + + expect(findTooltip()).toBeDefined(); + }); + }); }); diff --git a/spec/frontend/snippets/components/snippet_title_spec.js b/spec/frontend/snippets/components/snippet_title_spec.js deleted file mode 100644 index 9e6a30885d46c318c8e23e0798e8f1686e6d47c8..0000000000000000000000000000000000000000 --- a/spec/frontend/snippets/components/snippet_title_spec.js +++ /dev/null @@ -1,104 +0,0 @@ -import { GlSprintf, GlIcon } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; -import SnippetDescription from '~/snippets/components/snippet_description_view.vue'; -import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; -import SnippetTitle from '~/snippets/components/snippet_title.vue'; - -describe('Snippet title component', () => { - let wrapper; - const title = 'The property of Thor'; - const description = 'Do not touch this hammer'; - const descriptionHtml = `<h2>${description}</h2>`; - - function createComponent({ propsData = {} } = {}) { - wrapper = shallowMount(SnippetTitle, { - propsData: { - snippet: { - title, - description, - descriptionHtml, - }, - ...propsData, - }, - directives: { - GlTooltip: createMockDirective('gl-tooltip'), - }, - }); - } - - const findIcon = () => wrapper.findComponent(GlIcon); - const findTooltip = () => getBinding(findIcon().element, 'gl-tooltip'); - - describe('default state', () => { - beforeEach(() => { - createComponent(); - }); - - it('renders itself', () => { - expect(wrapper.find('.snippet-header').exists()).toBe(true); - }); - - it('does not render spam icon when author is not banned', () => { - expect(findIcon().exists()).toBe(false); - }); - - it('renders snippets title and description', () => { - expect(wrapper.text().trim()).toContain(title); - expect(wrapper.findComponent(SnippetDescription).props('description')).toBe(descriptionHtml); - }); - - it('does not render recent changes time stamp if there were no updates', () => { - expect(wrapper.findComponent(GlSprintf).exists()).toBe(false); - }); - - it('does not render recent changes time stamp if the time for creation and updates match', () => { - createComponent({ - propsData: { - snippet: { - createdAt: '2019-12-16T21:45:36Z', - updatedAt: '2019-12-16T21:45:36Z', - }, - }, - }); - - expect(wrapper.findComponent(GlSprintf).exists()).toBe(false); - }); - - it('renders translated string with most recent changes timestamp if changes were made', () => { - createComponent({ - propsData: { - snippet: { - createdAt: '2019-12-16T21:45:36Z', - updatedAt: '2019-15-16T21:45:36Z', - }, - }, - }); - - expect(wrapper.findComponent(GlSprintf).exists()).toBe(true); - }); - }); - - describe('when author is snippet is banned', () => { - it('renders spam icon and tooltip when author is banned', () => { - createComponent({ - propsData: { - snippet: { - hidden: true, - }, - }, - }); - - expect(findIcon().props()).toMatchObject({ - ariaLabel: 'Hidden', - name: 'spam', - size: 16, - }); - - expect(findIcon().attributes('title')).toBe( - 'This snippet is hidden because its author has been banned', - ); - - expect(findTooltip()).toBeDefined(); - }); - }); -}); diff --git a/spec/support/helpers/features/snippet_spec_helpers.rb b/spec/support/helpers/features/snippet_spec_helpers.rb index 19393f6e43859881791af42b3761ab252b9199d5..2f1c2f2dc9e03d11bb7007bd39ab77a3d05fafc3 100644 --- a/spec/support/helpers/features/snippet_spec_helpers.rb +++ b/spec/support/helpers/features/snippet_spec_helpers.rb @@ -19,7 +19,7 @@ def snippet_blob_path_locator end def snippet_description_view_selector - '.snippet-header .snippet-description' + '[data-testid=snippet-description-content]' end def snippet_description_field_collapsed