Commit 4e06bab8 authored by Scott Hampton's avatar Scott Hampton

Add test report empty state

The test report empty state was very basic,
and only had text. This switches over to use
a complete empty state, and checks to see
if the user has an empty test report or no report
at all.
parent 1fd3674a
<script>
import { GlEmptyState } from '@gitlab/ui';
import { s__ } from '~/locale';
export const i18n = {
noTestsButton: s__('TestReports|Learn more about pipeline test reports'),
noTestsDescription: s__('TestReports|No test cases were found in the test report.'),
noTestsTitle: s__('TestReports|There are no tests to display'),
noReportsButton: s__('TestReports|Learn how to upload pipeline test reports'),
noReportsDescription: s__(
'TestReports|You can configure your job to use unit test reports, and GitLab will display a report here and in the related merge request.',
),
noReportsTitle: s__('TestReports|There are no test reports for this pipeline'),
};
export default {
i18n,
components: {
GlEmptyState,
},
inject: {
emptyStateImagePath: {
type: String,
default: '',
},
hasTestReport: {
type: Boolean,
default: false,
},
testReportDocPath: {
type: String,
default: '',
},
},
computed: {
emptyStateText() {
if (this.hasTestReport) {
return {
button: this.$options.i18n.noTestsButton,
description: this.$options.i18n.noTestsDescription,
title: this.$options.i18n.noTestsTitle,
};
}
return {
button: this.$options.i18n.noReportsButton,
description: this.$options.i18n.noReportsDescription,
title: this.$options.i18n.noReportsTitle,
};
},
},
};
</script>
<template>
<gl-empty-state
:title="emptyStateText.title"
:description="emptyStateText.description"
:svg-path="emptyStateImagePath"
:primary-button-link="testReportDocPath"
:primary-button-text="emptyStateText.button"
/>
</template>
<script>
import { GlLoadingIcon } from '@gitlab/ui';
import { mapActions, mapGetters, mapState } from 'vuex';
import EmptyState from './empty_state.vue';
import TestSuiteTable from './test_suite_table.vue';
import TestSummary from './test_summary.vue';
import TestSummaryTable from './test_summary_table.vue';
......@@ -8,11 +9,18 @@ import TestSummaryTable from './test_summary_table.vue';
export default {
name: 'TestReports',
components: {
EmptyState,
GlLoadingIcon,
TestSuiteTable,
TestSummary,
TestSummaryTable,
},
inject: {
hasTestReport: {
type: Boolean,
default: false,
},
},
computed: {
...mapState(['isLoading', 'selectedSuiteIndex', 'testReports']),
...mapGetters(['getSelectedSuite']),
......@@ -25,7 +33,9 @@ export default {
},
},
created() {
this.fetchSummary();
if (this.hasTestReport) {
this.fetchSummary();
}
},
methods: {
...mapActions([
......@@ -83,11 +93,5 @@ export default {
</transition>
</div>
<div v-else>
<div class="row gl-mt-3">
<div class="col-12">
<p data-testid="no-tests-to-show">{{ s__('TestReports|There are no tests to show.') }}</p>
</div>
</div>
</div>
<empty-state v-else />
</template>
......@@ -63,7 +63,14 @@ const createLegacyPipelinesDetailApp = (mediator) => {
const createTestDetails = () => {
const el = document.querySelector(SELECTORS.PIPELINE_TESTS);
const { blobPath, summaryEndpoint, suiteEndpoint } = el?.dataset || {};
const {
blobPath,
emptyStateImagePath,
hasTestReport,
summaryEndpoint,
suiteEndpoint,
testReportDocPath,
} = el?.dataset || {};
const testReportsStore = createTestReportsStore({
blobPath,
summaryEndpoint,
......@@ -76,6 +83,11 @@ const createTestDetails = () => {
components: {
TestReports,
},
provide: {
emptyStateImagePath,
hasTestReport: hasTestReport !== undefined, // if hasTestReport is false that means the attribute isn't included
testReportDocPath,
},
store: testReportsStore,
render(createElement) {
return createElement('test-reports');
......
......@@ -83,5 +83,8 @@
#js-tab-tests.tab-pane
#js-pipeline-tests-detail{ data: { summary_endpoint: summary_project_pipeline_tests_path(@project, @pipeline, format: :json),
suite_endpoint: project_pipeline_test_path(@project, @pipeline, suite_name: 'suite', format: :json),
blob_path: project_blob_path(@project, @pipeline.sha) } }
blob_path: project_blob_path(@project, @pipeline.sha),
has_test_report: @pipeline.has_reports?(Ci::JobArtifact.test_reports),
test_report_doc_path: help_page_path('ci/unit_test_reports'),
empty_state_image_path: image_path('illustrations/empty-state/empty-test-cases-lg.svg') } }
= render_if_exists "projects/pipelines/tabs_content", pipeline: @pipeline, project: @project
---
title: Add link to documentation in empty pipeline test reports
merge_request: 59812
author:
type: added
......@@ -30986,16 +30986,28 @@ msgstr ""
msgid "TestReports|Jobs"
msgstr ""
msgid "TestReports|Learn how to upload pipeline test reports"
msgstr ""
msgid "TestReports|Learn more about pipeline test reports"
msgstr ""
msgid "TestReports|No test cases were found in the test report."
msgstr ""
msgid "TestReports|Tests"
msgstr ""
msgid "TestReports|There are no test cases to display."
msgstr ""
msgid "TestReports|There are no test reports for this pipeline"
msgstr ""
msgid "TestReports|There are no test suites to show."
msgstr ""
msgid "TestReports|There are no tests to show."
msgid "TestReports|There are no tests to display"
msgstr ""
msgid "TestReports|There was an error fetching the summary."
......@@ -31004,6 +31016,9 @@ msgstr ""
msgid "TestReports|There was an error fetching the test suite."
msgstr ""
msgid "TestReports|You can configure your job to use unit test reports, and GitLab will display a report here and in the related merge request."
msgstr ""
msgid "Tests"
msgstr ""
......
import { GlEmptyState } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import EmptyState, { i18n } from '~/pipelines/components/test_reports/empty_state.vue';
describe('Test report empty state', () => {
let wrapper;
const findEmptyState = () => wrapper.findComponent(GlEmptyState);
const createComponent = (hasTestReport = true) => {
wrapper = shallowMount(EmptyState, {
provide: {
emptyStateImagePath: '/image/path',
hasTestReport,
testReportDocPath: '/docs/path',
},
stubs: {
GlEmptyState,
},
});
};
describe('when pipeline has a test report', () => {
it('should render empty test report message', () => {
createComponent();
expect(findEmptyState().props()).toMatchObject({
primaryButtonText: i18n.noTestsButton,
description: i18n.noTestsDescription,
title: i18n.noTestsTitle,
});
});
});
describe('when pipeline does not have a test report', () => {
it('should render no test report message', () => {
createComponent(false);
expect(findEmptyState().props()).toMatchObject({
primaryButtonText: i18n.noReportsButton,
description: i18n.noReportsDescription,
title: i18n.noReportsTitle,
});
});
});
});
......@@ -2,6 +2,8 @@ import { GlLoadingIcon } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import { getJSONFixture } from 'helpers/fixtures';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import EmptyState from '~/pipelines/components/test_reports/empty_state.vue';
import TestReports from '~/pipelines/components/test_reports/test_reports.vue';
import TestSummary from '~/pipelines/components/test_reports/test_summary.vue';
import TestSummaryTable from '~/pipelines/components/test_reports/test_summary_table.vue';
......@@ -16,11 +18,11 @@ describe('Test reports app', () => {
const testReports = getJSONFixture('pipelines/test_report.json');
const loadingSpinner = () => wrapper.find(GlLoadingIcon);
const testsDetail = () => wrapper.find('[data-testid="tests-detail"]');
const noTestsToShow = () => wrapper.find('[data-testid="no-tests-to-show"]');
const testSummary = () => wrapper.find(TestSummary);
const testSummaryTable = () => wrapper.find(TestSummaryTable);
const loadingSpinner = () => wrapper.findComponent(GlLoadingIcon);
const testsDetail = () => wrapper.findByTestId('tests-detail');
const emptyState = () => wrapper.findComponent(EmptyState);
const testSummary = () => wrapper.findComponent(TestSummary);
const testSummaryTable = () => wrapper.findComponent(TestSummaryTable);
const actionSpies = {
fetchTestSuite: jest.fn(),
......@@ -29,7 +31,7 @@ describe('Test reports app', () => {
removeSelectedSuiteIndex: jest.fn(),
};
const createComponent = (state = {}) => {
const createComponent = (state = {}, hasTestReport = true) => {
store = new Vuex.Store({
state: {
isLoading: false,
......@@ -41,10 +43,15 @@ describe('Test reports app', () => {
getters,
});
wrapper = shallowMount(TestReports, {
store,
localVue,
});
wrapper = extendedWrapper(
shallowMount(TestReports, {
store,
localVue,
provide: {
hasTestReport,
},
}),
);
};
afterEach(() => {
......@@ -52,33 +59,34 @@ describe('Test reports app', () => {
});
describe('when component is created', () => {
beforeEach(() => {
it('should call fetchSummary when pipeline has test report', () => {
createComponent();
});
it('should call fetchSummary', () => {
expect(actionSpies.fetchSummary).toHaveBeenCalled();
});
it('should not call fetchSummary when pipeline does not have test report', () => {
createComponent({}, false);
expect(actionSpies.fetchSummary).not.toHaveBeenCalled();
});
});
describe('when loading', () => {
beforeEach(() => createComponent({ isLoading: true }));
it('shows the loading spinner', () => {
expect(noTestsToShow().exists()).toBe(false);
expect(emptyState().exists()).toBe(false);
expect(testsDetail().exists()).toBe(false);
expect(loadingSpinner().exists()).toBe(true);
});
});
describe('when the api returns no data', () => {
beforeEach(() => createComponent({ testReports: {} }));
it('displays that there are no tests to show', () => {
const noTests = noTestsToShow();
it('displays empty state component', () => {
createComponent({ testReports: {} });
expect(noTests.exists()).toBe(true);
expect(noTests.text()).toBe('There are no tests to show.');
expect(emptyState().exists()).toBe(true);
});
});
......
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