diff --git a/app/assets/javascripts/reports/components/issues_list.vue b/app/assets/javascripts/reports/components/issues_list.vue index 50f2910e02dc6cd7490b2e936210001967a95ecc..ee07efea3b09cffda14f272fa0de8ee1d9aa56ab 100644 --- a/app/assets/javascripts/reports/components/issues_list.vue +++ b/app/assets/javascripts/reports/components/issues_list.vue @@ -52,11 +52,21 @@ export default { required: false, default: '', }, - showReportSectionStatus: { + showReportSectionStatusIcon: { type: Boolean, required: false, default: true, }, + issuesUlElementClass: { + type: String, + required: false, + default: '', + }, + issueItemClass: { + type: String, + required: false, + default: null, + }, }, computed: { issuesWithState() { @@ -67,6 +77,9 @@ export default { ...this.resolvedIssues.map(wrapIssueWithState(STATUS_SUCCESS)), ]; }, + wclass() { + return `report-block-list ${this.issuesUlElementClass}`; + }, }, }; @@ -77,7 +90,7 @@ export default { :size="$options.typicalReportItemHeight" class="report-block-container" wtag="ul" - wclass="report-block-list" + :wclass="wclass" > diff --git a/app/assets/javascripts/reports/components/report_section.vue b/app/assets/javascripts/reports/components/report_section.vue index 241185e3126d297bf6128cc5d7a5505bd9c5478a..3d576caaf8f6a9e3142e7f3836e40c7601bf5146 100644 --- a/app/assets/javascripts/reports/components/report_section.vue +++ b/app/assets/javascripts/reports/components/report_section.vue @@ -3,10 +3,7 @@ import { __ } from '~/locale'; import StatusIcon from '~/vue_merge_request_widget/components/mr_widget_status_icon.vue'; import Popover from '~/vue_shared/components/help_popover.vue'; import IssuesList from './issues_list.vue'; - -const LOADING = 'LOADING'; -const ERROR = 'ERROR'; -const SUCCESS = 'SUCCESS'; +import { status } from '../constants'; export default { name: 'ReportSection', @@ -42,7 +39,8 @@ export default { }, successText: { type: String, - required: true, + required: false, + default: '', }, unresolvedIssues: { type: Array, @@ -78,6 +76,21 @@ export default { required: false, default: true, }, + issuesUlElementClass: { + type: String, + required: false, + default: undefined, + }, + issuesListContainerClass: { + type: String, + required: false, + default: undefined, + }, + issueItemClass: { + type: String, + required: false, + default: undefined, + }, }, data() { @@ -91,13 +104,13 @@ export default { return this.isCollapsed ? __('Expand') : __('Collapse'); }, isLoading() { - return this.status === LOADING; + return this.status === status.LOADING; }, loadingFailed() { - return this.status === ERROR; + return this.status === status.ERROR; }, isSuccess() { - return this.status === SUCCESS; + return this.status === status.SUCCESS; }, isCollapsible() { return !this.alwaysOpen && this.hasIssues; @@ -132,6 +145,15 @@ export default { hasPopover() { return Object.keys(this.popoverOptions).length > 0; }, + slotName() { + if (this.isSuccess) { + return 'success'; + } else if (this.isLoading) { + return 'loading'; + } + + return 'error'; + }, }, methods: { toggleCollapsed() { @@ -147,6 +169,7 @@ export default {
{{ headerText }} + @@ -172,6 +195,9 @@ export default { :neutral-issues="neutralIssues" :component="component" :show-report-section-status-icon="showReportSectionStatusIcon" + :issues-ul-element-class="issuesUlElementClass" + :class="issuesListContainerClass" + :issue-item-class="issueItemClass" />
diff --git a/app/assets/javascripts/reports/constants.js b/app/assets/javascripts/reports/constants.js index c323dc543f36b3975a52f8229398389416b915b7..66ac1af062b7ed82d11e122d9d991e20c3de97e5 100644 --- a/app/assets/javascripts/reports/constants.js +++ b/app/assets/javascripts/reports/constants.js @@ -16,3 +16,9 @@ export const STATUS_NEUTRAL = 'neutral'; export const ICON_WARNING = 'warning'; export const ICON_SUCCESS = 'success'; export const ICON_NOTFOUND = 'notfound'; + +export const status = { + LOADING: 'LOADING', + ERROR: 'ERROR', + SUCCESS: 'SUCCESS', +}; diff --git a/app/assets/javascripts/vue_shared/components/issue/related_issuable_item.vue b/app/assets/javascripts/vue_shared/components/issue/related_issuable_item.vue index b807a35b42154b54338a4a621ef015225e56ae2d..05ad7710a621cd6a44bdd5bfbc6e17bb570667b7 100644 --- a/app/assets/javascripts/vue_shared/components/issue/related_issuable_item.vue +++ b/app/assets/javascripts/vue_shared/components/issue/related_issuable_item.vue @@ -24,6 +24,11 @@ export default { required: false, default: false, }, + greyLinkWhenMerged: { + type: Boolean, + required: false, + default: false, + }, }, computed: { stateTitle() { @@ -36,6 +41,11 @@ export default { }, ); }, + issueableLinkClass() { + return this.greyLinkWhenMerged + ? `sortable-link ${this.state === 'merged' ? ' text-secondary' : ''}` + : 'sortable-link'; + }, }, }; @@ -69,7 +79,7 @@ export default { class="confidential-icon append-right-4 align-self-baseline align-self-md-auto mt-xl-0" :aria-label="__('Confidential')" /> - {{ title }} + {{ title }}
+import RelatedIssuableItem from '~/vue_shared/components/issue/related_issuable_item.vue'; +import Icon from '~/vue_shared/components/icon.vue'; +import { n__ } from '~/locale'; + +export default { + name: 'BlockingMergeRequestsBody', + components: { RelatedIssuableItem, Icon }, + props: { + issue: { + type: Object, + required: true, + }, + status: { + type: String, + required: true, + }, + isNew: { + type: Boolean, + required: true, + }, + }, + computed: { + hiddenBlockingMRsText() { + return n__( + "%d merge request that you don't have access to.", + "%d merge requests that you don't have access to.", + this.issue.hiddenCount, + ); + }, + }, +}; + + + diff --git a/ee/app/assets/javascripts/vue_merge_request_widget/components/blocking_merge_requests/blocking_merge_requests_report.vue b/ee/app/assets/javascripts/vue_merge_request_widget/components/blocking_merge_requests/blocking_merge_requests_report.vue new file mode 100644 index 0000000000000000000000000000000000000000..91ae06fd8b38ebe76c94e36dbe2cd486adf7dfc2 --- /dev/null +++ b/ee/app/assets/javascripts/vue_merge_request_widget/components/blocking_merge_requests/blocking_merge_requests_report.vue @@ -0,0 +1,120 @@ + + + diff --git a/ee/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue b/ee/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue index 1fea3b6570b22e66cc510d18b045893ed1d13ae7..317fb45bb3fd0d3b1a7257876a64b0d4abf6d332 100644 --- a/ee/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue +++ b/ee/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue @@ -5,6 +5,7 @@ import GroupedMetricsReportsApp from 'ee/vue_shared/metrics_reports/grouped_metr import reportsMixin from 'ee/vue_shared/security_reports/mixins/reports_mixin'; import { componentNames } from 'ee/vue_shared/components/reports/issue_body'; import MrWidgetLicenses from 'ee/vue_shared/license_management/mr_widget_license_report.vue'; +import BlockingMergeRequestsReport from './components/blocking_merge_requests/blocking_merge_requests_report.vue'; import { n__, s__, __, sprintf } from '~/locale'; import CEWidgetOptions from '~/vue_merge_request_widget/mr_widget_options.vue'; @@ -16,6 +17,7 @@ export default { MrWidgetLicenses, MrWidgetApprovals, MrWidgetGeoSecondaryNode, + BlockingMergeRequestsReport, GroupedSecurityReportsApp, GroupedMetricsReportsApp, ReportSection, @@ -216,6 +218,7 @@ export default { :service="service" />
+ (mr, _) { mr&.target_project&.feature_available?(:blocking_merge_requests) } + + private + + def blocking_merge_requests + visible_mrs_by_state = Hash.new { |h, k| h[k] = [] } + visible_count = 0 + hidden_blocking_count = 0 + + object.blocking_merge_requests.each do |mr| + if can?(current_user, :read_merge_request, mr) + visible_mrs_by_state[mr.state_name] << represent_blocking_mr(mr) + visible_count += 1 + elsif !mr.merged? # Ignore merged hidden MRs to make display simpler + hidden_blocking_count += 1 + end + end + + { + total_count: visible_count + hidden_blocking_count, + hidden_count: hidden_blocking_count, + visible_merge_requests: visible_mrs_by_state + } + end end - private + def represent_blocking_mr(blocking_mr) + blocking_mr_options = options.merge(from_project: object.target_project) + + ::BlockingMergeRequestEntity.represent(blocking_mr, blocking_mr_options) + end def head_pipeline_downloadable_path_for_report_type(file_type) object.head_pipeline&.present(current_user: current_user) diff --git a/ee/changelogs/unreleased/9688-fe-mr-merge-order.yml b/ee/changelogs/unreleased/9688-fe-mr-merge-order.yml new file mode 100644 index 0000000000000000000000000000000000000000..a2f3cb09611ed4f889f04177815a598e36e3cf5c --- /dev/null +++ b/ee/changelogs/unreleased/9688-fe-mr-merge-order.yml @@ -0,0 +1,5 @@ +--- +title: When a merge request is blocked by other unmerged merge requests, display them on the show page of a merge request +merge_request: 12357 +author: +type: added diff --git a/ee/spec/features/merge_request/user_views_blocked_merge_request_spec.rb b/ee/spec/features/merge_request/user_views_blocked_merge_request_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..3d2195bb500bc373176430c30aba62a4a843a0eb --- /dev/null +++ b/ee/spec/features/merge_request/user_views_blocked_merge_request_spec.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'Merge Request > User views blocked MR', :js do + let(:block) { create(:merge_request_block) } + let(:blocking_mr) { block.blocking_merge_request } + let(:blocked_mr) { block.blocked_merge_request } + let(:project) { blocked_mr.target_project } + let(:user) { create(:user) } + + let(:merge_button) { find('.qa-merge-button') } + + before do + project.add_developer(user) + + sign_in(user) + end + + context 'blocking merge requests are disabled' do + before do + stub_licensed_features(blocking_merge_requests: false) + end + + it 'is mergeable' do + visit project_merge_request_path(project, blocked_mr) + + expect(page).to have_button('Merge', disabled: false) + end + end + + context 'blocking merge requests are enabled' do + before do + stub_licensed_features(blocking_merge_requests: true) + end + + context 'blocking MR is not visible' do + it 'is not mergeable' do + visit project_merge_request_path(project, blocked_mr) + + expect(page).to have_content('Blocked by 1 merge request') + expect(page).to have_button('Merge', disabled: true) + + click_button 'Expand' + + expect(page).not_to have_content(blocking_mr.title) + expect(page).to have_content("1 merge request that you don't have access to") + end + end + + context 'blocking MR is visible' do + before do + blocking_mr.target_project.add_developer(user) + end + + it 'is not mergeable' do + visit project_merge_request_path(project, blocked_mr) + + expect(page).to have_content('Blocked by 1 merge request') + expect(page).to have_button('Merge', disabled: true) + + click_button 'Expand' + + expect(page).to have_content(blocking_mr.title) + end + end + end +end diff --git a/ee/spec/frontend/vue_mr_widget/components/blocking_merge_requests/blocking_merge_requests_body_spec.js b/ee/spec/frontend/vue_mr_widget/components/blocking_merge_requests/blocking_merge_requests_body_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..a99e796d4e9467abdac1ad5d649b5d68ae28cffe --- /dev/null +++ b/ee/spec/frontend/vue_mr_widget/components/blocking_merge_requests/blocking_merge_requests_body_spec.js @@ -0,0 +1,30 @@ +import { shallowMount } from '@vue/test-utils'; +import BlockingMergeRequestBody from 'ee/vue_merge_request_widget/components/blocking_merge_requests/blocking_merge_request_body.vue'; +import RelatedIssuableItem from '~/vue_shared/components/issue/related_issuable_item.vue'; + +describe('BlockingMergeRequestBody', () => { + it('shows hidden merge request text if hidden MRs exist', () => { + const wrapper = shallowMount(BlockingMergeRequestBody, { + propsData: { + issue: { hiddenCount: 10000000, id: 10 }, + status: 'string', + isNew: true, + }, + }); + + expect(wrapper.html()).toContain("merge requests that you don't have access to"); + }); + + it('does not show hidden merge request if hidden MRs do not exist', () => { + const wrapper = shallowMount(BlockingMergeRequestBody, { + propsData: { + issue: {}, + status: 'string', + isNew: true, + }, + }); + + expect(wrapper.html()).not.toContain("merge requests that you don't have access to"); + expect(wrapper.find(RelatedIssuableItem).exists()).toBe(true); + }); +}); diff --git a/ee/spec/frontend/vue_mr_widget/components/blocking_merge_requests/blocking_merge_requests_report_spec.js b/ee/spec/frontend/vue_mr_widget/components/blocking_merge_requests/blocking_merge_requests_report_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..5461ac5c523b275e482f607868fab7d63d2478c8 --- /dev/null +++ b/ee/spec/frontend/vue_mr_widget/components/blocking_merge_requests/blocking_merge_requests_report_spec.js @@ -0,0 +1,133 @@ +import { createLocalVue, shallowMount, config } from '@vue/test-utils'; +import BlockingMergeRequestsReport from 'ee/vue_merge_request_widget/components/blocking_merge_requests/blocking_merge_requests_report.vue'; +import ReportSection from '~/reports/components/report_section.vue'; +import { status as reportStatus } from '~/reports/constants'; + +const localVue = createLocalVue(); + +describe('BlockingMergeRequestsReport', () => { + let wrapper; + let props; + + // Remove these hooks once we update @vue/test-utils + // See this issue: https://github.com/vuejs/vue-test-utils/issues/973 + beforeAll(() => { + config.logModifiedComponents = false; + }); + + afterAll(() => { + config.logModifiedComponents = true; + }); + + beforeEach(() => { + props = { + mr: { + blockingMergeRequests: { + total_count: 3, + hidden_count: 0, + visible_merge_requests: { + opened: [{ id: 1, state: 'opened' }], + closed: [{ id: 2, state: 'closed' }], + merged: [{ id: 3, state: 'merged' }], + }, + }, + }, + }; + }); + + afterEach(() => { + wrapper.destroy(); + }); + + const createComponent = (propsData = props) => { + wrapper = shallowMount(BlockingMergeRequestsReport, { + propsData, + localVue, + }); + }; + + it('does not render blocking merge requests report if no blocking MRs exist', () => { + props.mr.blockingMergeRequests.total_count = 0; + props.mr.blockingMergeRequests.visible_merge_requests = {}; + createComponent(props); + + expect(wrapper.html()).toBeUndefined(); + }); + + it('passes merged MRs as resolved issues and anything else as unresolved ', () => { + createComponent(); + const reportSectionProps = wrapper.find(ReportSection).props(); + + expect(reportSectionProps.resolvedIssues).toHaveLength(1); + expect(reportSectionProps.resolvedIssues[0].id).toBe(3); + }); + + it('passes all non "merged" MRs as unresolved issues', () => { + createComponent(); + const reportSectionProps = wrapper.find(ReportSection).props(); + + expect(reportSectionProps.unresolvedIssues.map(issue => issue.id)).toEqual([2, 1]); + }); + + it('sets status to "ERROR" when there are unmerged blocking MRs', () => { + createComponent(); + + expect(wrapper.find(ReportSection).props().status).toBe(reportStatus.ERROR); + }); + + it('sets status to "SUCCESS" when all blocking MRs are merged', () => { + props.mr.blockingMergeRequests.total_count = 1; + props.mr.blockingMergeRequests.visible_merge_requests = { + merged: [{ id: 3, state: 'merged' }], + }; + createComponent(); + + expect(wrapper.find(ReportSection).props().status).toBe(reportStatus.SUCCESS); + }); + + describe('blockedByText', () => { + it('contains closed information if some are closed, but not all', () => { + createComponent(); + + expect(wrapper.vm.blockedByText).toBe( + 'Blocked by 2 merge requests (1 closed)', + ); + }); + + it('does not contain closed information if no blocking MRs are closed', () => { + delete props.mr.blockingMergeRequests.visible_merge_requests.closed; + createComponent(); + + expect(wrapper.vm.blockedByText).not.toContain('closed'); + }); + + it('states when all blocking mrs are closed', () => { + delete props.mr.blockingMergeRequests.visible_merge_requests.opened; + createComponent(); + + expect(wrapper.vm.blockedByText).toEqual( + 'Blocked by 1 closed merge request.', + ); + }); + }); + + describe('unmergedBlockingMergeRequests', () => { + it('does not include merged MRs', () => { + createComponent(); + const containsMergedMRs = wrapper.vm.unmergedBlockingMergeRequests.some( + mr => mr.state === 'merged', + ); + + expect(containsMergedMRs).toBe(false); + }); + + it('puts closed MRs first', () => { + createComponent(); + const closedIndex = wrapper.vm.unmergedBlockingMergeRequests.findIndex( + mr => mr.state === 'closed', + ); + + expect(closedIndex).toBe(0); + }); + }); +}); diff --git a/ee/spec/javascripts/vue_shared/components/reports/report_item_spec.js b/ee/spec/javascripts/vue_shared/components/reports/report_item_spec.js index 80ad296a4cc4aae7dd781be8e73bae7ffeeb5d91..94ea04b0f6c0324d4af0a6a4962ccc1000039704 100644 --- a/ee/spec/javascripts/vue_shared/components/reports/report_item_spec.js +++ b/ee/spec/javascripts/vue_shared/components/reports/report_item_spec.js @@ -125,4 +125,33 @@ describe('Report issue', () => { ); }); }); + + describe('showReportSectionStatusIcon', () => { + it('does not render CI Status Icon when showReportSectionStatusIcon is false', () => { + vm = mountComponentWithStore(ReportIssue, { + store, + props: { + issue: parsedDast[0], + component: componentNames.DastIssueBody, + status: STATUS_SUCCESS, + showReportSectionStatusIcon: false, + }, + }); + + expect(vm.$el.querySelectorAll('.report-block-list-icon')).toHaveLength(0); + }); + + it('shows status icon when unspecified', () => { + vm = mountComponentWithStore(ReportIssue, { + store, + props: { + issue: parsedDast[0], + component: componentNames.DastIssueBody, + status: STATUS_SUCCESS, + }, + }); + + expect(vm.$el.querySelectorAll('.report-block-list-icon')).toHaveLength(1); + }); + }); }); diff --git a/ee/spec/serializers/blocking_merge_request_entity_spec.rb b/ee/spec/serializers/blocking_merge_request_entity_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..8301cc3a5ca1678f10801a83e904cc3f7d2e4f78 --- /dev/null +++ b/ee/spec/serializers/blocking_merge_request_entity_spec.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe BlockingMergeRequestEntity do + set(:merge_request) { create(:merge_request) } + set(:user) { create(:user) } + + let(:web_url) { Gitlab::Routing.url_helpers.project_merge_request_path(merge_request.project, merge_request) } + let(:request) { double('request', current_user: user) } + let(:extra_options) { {} } + + subject(:entity) do + options = extra_options.merge(current_user: user, request: request) + described_class.new(merge_request, options) + end + + it 'exposes simple attributes' do + expect(entity.as_json).to include( + id: merge_request.id, + iid: merge_request.iid, + title: merge_request.title, + state: merge_request.state, + created_at: merge_request.created_at, + merged_at: merge_request.merged_at, + closed_at: merge_request.metrics.latest_closed_at, + web_url: web_url + ) + end + + describe '#reference' do + let(:other_project) { create(:project) } + + subject { entity.as_json[:reference] } + + it { is_expected.to eq(merge_request.to_reference) } + + context 'from another project' do + let(:extra_options) { { from_project: other_project } } + + it 'includes the fully-qualified reference when needed' do + is_expected.to eq(merge_request.to_reference(other_project)) + end + end + end +end diff --git a/ee/spec/serializers/merge_request_widget_entity_spec.rb b/ee/spec/serializers/merge_request_widget_entity_spec.rb index ef939bb6d3c46d9e504ec90d36672804f00dfd72..6e68c175e7d28af95f1c2fb4ea8725c0881a0191 100644 --- a/ee/spec/serializers/merge_request_widget_entity_spec.rb +++ b/ee/spec/serializers/merge_request_widget_entity_spec.rb @@ -14,7 +14,7 @@ project.add_developer(user) end - subject do + subject(:entity) do described_class.new(merge_request, current_user: user, request: request) end @@ -227,4 +227,56 @@ end end end + + describe 'blocking merge requests' do + set(:merge_request_block) { create(:merge_request_block, blocked_merge_request: merge_request) } + + let(:blocking_mr) { merge_request_block.blocking_merge_request } + + subject { entity.as_json[:blocking_merge_requests] } + + context 'feature disabled' do + before do + stub_licensed_features(blocking_merge_requests: false) + end + + it 'does not have the blocking_merge_requests member' do + expect(entity.as_json).not_to include(:blocking_merge_requests) + end + end + + context 'feature enabled' do + before do + stub_licensed_features(blocking_merge_requests: true) + end + + it 'shows the blocking merge request if visible' do + blocking_mr.project.add_developer(user) + + is_expected.to include( + hidden_count: 0, + total_count: 1, + visible_merge_requests: { opened: [kind_of(BlockingMergeRequestEntity)] } + ) + end + + it 'hides the blocking merge request if not visible' do + is_expected.to eq( + hidden_count: 1, + total_count: 1, + visible_merge_requests: {} + ) + end + + it 'does not count a merged and hidden blocking MR' do + blocking_mr.update_columns(state: 'merged') + + is_expected.to eq( + hidden_count: 0, + total_count: 0, + visible_merge_requests: {} + ) + end + end + end end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 3f2f1f4715d5b62db40ec1d012a831512d6377df..0547bfd9d847fe31642f5a802c335fad7d8b92f5 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -105,6 +105,11 @@ msgid_plural "%d merge requests" msgstr[0] "" msgstr[1] "" +msgid "%d merge request that you don't have access to." +msgid_plural "%d merge requests that you don't have access to." +msgstr[0] "" +msgstr[1] "" + msgid "%d metric" msgid_plural "%d metrics" msgstr[0] "" @@ -315,6 +320,14 @@ msgstr "" msgid "'%{source}' is not a import source" msgstr "" +msgid "(%d closed)" +msgid_plural "(%d closed)" +msgstr[0] "" +msgstr[1] "" + +msgid "(%{mrCount} merged)" +msgstr "" + msgid "(No changes)" msgstr "" @@ -1944,6 +1957,16 @@ msgstr "" msgid "Blocked" msgstr "" +msgid "Blocked by %d merge request" +msgid_plural "Blocked by %d merge requests" +msgstr[0] "" +msgstr[1] "" + +msgid "Blocked by %d closed merge request." +msgid_plural "Blocked by %d closed merge requests." +msgstr[0] "" +msgstr[1] "" + msgid "Blog" msgstr "" @@ -8638,6 +8661,9 @@ msgstr "" msgid "No available namespaces to fork the project." msgstr "" +msgid "No blocking merge requests " +msgstr "" + msgid "No branches found" msgstr "" diff --git a/spec/frontend/reports/components/report_section_spec.js b/spec/frontend/reports/components/report_section_spec.js index 3b609484b9e964483f04ea1f9e7b0caa85238286..d4a3073374a81befaa6e152175a8a65091dd849b 100644 --- a/spec/frontend/reports/components/report_section_spec.js +++ b/spec/frontend/reports/components/report_section_spec.js @@ -197,4 +197,44 @@ describe('Report section', () => { expect(vm.$el.querySelector('.js-collapse-btn').textContent.trim()).toEqual('Expand'); }); }); + + describe('Success and Error slots', () => { + const createComponent = status => { + vm = mountComponentWithSlots(ReportSection, { + props: { + status, + hasIssues: true, + }, + slots: { + success: ['This is a success'], + loading: ['This is loading'], + error: ['This is an error'], + }, + }); + }; + + it('only renders success slot when status is "SUCCESS"', () => { + createComponent('SUCCESS'); + + expect(vm.$el.textContent.trim()).toContain('This is a success'); + expect(vm.$el.textContent.trim()).not.toContain('This is an error'); + expect(vm.$el.textContent.trim()).not.toContain('This is loading'); + }); + + it('only renders error slot when status is "ERROR"', () => { + createComponent('ERROR'); + + expect(vm.$el.textContent.trim()).toContain('This is an error'); + expect(vm.$el.textContent.trim()).not.toContain('This is a success'); + expect(vm.$el.textContent.trim()).not.toContain('This is loading'); + }); + + it('only renders loading slot when status is "LOADING"', () => { + createComponent('LOADING'); + + expect(vm.$el.textContent.trim()).toContain('This is loading'); + expect(vm.$el.textContent.trim()).not.toContain('This is an error'); + expect(vm.$el.textContent.trim()).not.toContain('This is a success'); + }); + }); });