summaryrefslogtreecommitdiff
path: root/spec/frontend/analytics
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend/analytics')
-rw-r--r--spec/frontend/analytics/devops_report/components/service_ping_disabled_spec.js61
-rw-r--r--spec/frontend/analytics/shared/components/daterange_spec.js120
-rw-r--r--spec/frontend/analytics/shared/components/metric_card_spec.js129
-rw-r--r--spec/frontend/analytics/shared/components/projects_dropdown_filter_spec.js264
-rw-r--r--spec/frontend/analytics/shared/utils_spec.js24
-rw-r--r--spec/frontend/analytics/usage_trends/components/usage_counts_spec.js (renamed from spec/frontend/analytics/usage_trends/components/instance_counts_spec.js)22
6 files changed, 484 insertions, 136 deletions
diff --git a/spec/frontend/analytics/devops_report/components/service_ping_disabled_spec.js b/spec/frontend/analytics/devops_report/components/service_ping_disabled_spec.js
new file mode 100644
index 00000000000..75ef9d9db94
--- /dev/null
+++ b/spec/frontend/analytics/devops_report/components/service_ping_disabled_spec.js
@@ -0,0 +1,61 @@
+import { GlEmptyState, GlSprintf } from '@gitlab/ui';
+import { TEST_HOST } from 'helpers/test_constants';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import ServicePingDisabled from '~/analytics/devops_report/components/service_ping_disabled.vue';
+
+describe('~/analytics/devops_report/components/service_ping_disabled.vue', () => {
+ let wrapper;
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ const createWrapper = ({ isAdmin = false } = {}) => {
+ wrapper = shallowMountExtended(ServicePingDisabled, {
+ provide: {
+ isAdmin,
+ svgPath: TEST_HOST,
+ docsLink: TEST_HOST,
+ primaryButtonPath: TEST_HOST,
+ },
+ stubs: { GlEmptyState, GlSprintf },
+ });
+ };
+
+ const findEmptyState = () => wrapper.findComponent(GlEmptyState);
+ const findMessageForRegularUsers = () => wrapper.findComponent(GlSprintf);
+ const findDocsLink = () => wrapper.findByTestId('docs-link');
+ const findPowerOnButton = () => wrapper.findByTestId('power-on-button');
+
+ it('renders empty state with provided SVG path', () => {
+ createWrapper();
+
+ expect(findEmptyState().props('svgPath')).toBe(TEST_HOST);
+ });
+
+ describe('for regular users', () => {
+ beforeEach(() => {
+ createWrapper({ isAdmin: false });
+ });
+
+ it('renders message without power-on button', () => {
+ expect(findMessageForRegularUsers().exists()).toBe(true);
+ expect(findPowerOnButton().exists()).toBe(false);
+ });
+
+ it('renders docs link', () => {
+ expect(findDocsLink().exists()).toBe(true);
+ expect(findDocsLink().attributes('href')).toBe(TEST_HOST);
+ });
+ });
+
+ describe('for admins', () => {
+ beforeEach(() => {
+ createWrapper({ isAdmin: true });
+ });
+
+ it('renders power-on button', () => {
+ expect(findPowerOnButton().exists()).toBe(true);
+ });
+ });
+});
diff --git a/spec/frontend/analytics/shared/components/daterange_spec.js b/spec/frontend/analytics/shared/components/daterange_spec.js
new file mode 100644
index 00000000000..854582abb82
--- /dev/null
+++ b/spec/frontend/analytics/shared/components/daterange_spec.js
@@ -0,0 +1,120 @@
+import { GlDaterangePicker } from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
+import { useFakeDate } from 'helpers/fake_date';
+import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
+import Daterange from '~/analytics/shared/components/daterange.vue';
+
+const defaultProps = {
+ startDate: new Date(2019, 8, 1),
+ endDate: new Date(2019, 8, 11),
+};
+
+describe('Daterange component', () => {
+ useFakeDate(2019, 8, 25);
+
+ let wrapper;
+
+ const factory = (props = defaultProps) => {
+ wrapper = mount(Daterange, {
+ propsData: {
+ ...defaultProps,
+ ...props,
+ },
+ directives: { GlTooltip: createMockDirective() },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ const findDaterangePicker = () => wrapper.find(GlDaterangePicker);
+
+ const findDateRangeIndicator = () => wrapper.find('.daterange-indicator');
+
+ describe('template', () => {
+ describe('when show is false', () => {
+ it('does not render the daterange picker', () => {
+ factory({ show: false });
+ expect(findDaterangePicker().exists()).toBe(false);
+ });
+ });
+
+ describe('when show is true', () => {
+ it('renders the daterange picker', () => {
+ factory({ show: true });
+ expect(findDaterangePicker().exists()).toBe(true);
+ });
+ });
+
+ describe('with a minDate being set', () => {
+ it('emits the change event with the minDate when the user enters a start date before the minDate', () => {
+ const startDate = new Date('2019-09-01');
+ const endDate = new Date('2019-09-30');
+ const minDate = new Date('2019-06-01');
+
+ factory({ show: true, startDate, endDate, minDate });
+
+ const input = findDaterangePicker().find('input');
+
+ input.setValue('2019-01-01');
+ input.trigger('change');
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.emitted().change).toEqual([[{ startDate: minDate, endDate }]]);
+ });
+ });
+ });
+
+ describe('with a maxDateRange being set', () => {
+ beforeEach(() => {
+ factory({ maxDateRange: 30 });
+ });
+
+ it('displays the max date range indicator', () => {
+ expect(findDateRangeIndicator().exists()).toBe(true);
+ });
+
+ it('displays the correct number of selected days in the indicator', () => {
+ expect(findDateRangeIndicator().find('span').text()).toBe('10 days selected');
+ });
+
+ it('displays a tooltip', () => {
+ const icon = wrapper.find('[data-testid="helper-icon"]');
+ const tooltip = getBinding(icon.element, 'gl-tooltip');
+
+ expect(tooltip).toBeDefined();
+ expect(icon.attributes('title')).toBe(
+ 'Showing data for workflow items created in this date range. Date range cannot exceed 30 days.',
+ );
+ });
+ });
+ });
+
+ describe('computed', () => {
+ describe('dateRange', () => {
+ beforeEach(() => {
+ factory({ show: true });
+ });
+
+ describe('set', () => {
+ it('emits the change event with an object containing startDate and endDate', () => {
+ const startDate = new Date('2019-10-01');
+ const endDate = new Date('2019-10-05');
+ wrapper.vm.dateRange = { startDate, endDate };
+
+ expect(wrapper.emitted().change).toEqual([[{ startDate, endDate }]]);
+ });
+ });
+
+ describe('get', () => {
+ it("returns value of dateRange from state's startDate and endDate", () => {
+ expect(wrapper.vm.dateRange).toEqual({
+ startDate: defaultProps.startDate,
+ endDate: defaultProps.endDate,
+ });
+ });
+ });
+ });
+ });
+});
diff --git a/spec/frontend/analytics/shared/components/metric_card_spec.js b/spec/frontend/analytics/shared/components/metric_card_spec.js
deleted file mode 100644
index 7f587d227ab..00000000000
--- a/spec/frontend/analytics/shared/components/metric_card_spec.js
+++ /dev/null
@@ -1,129 +0,0 @@
-import { GlDeprecatedSkeletonLoading as GlSkeletonLoading } from '@gitlab/ui';
-import { mount } from '@vue/test-utils';
-import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
-import MetricCard from '~/analytics/shared/components/metric_card.vue';
-
-const metrics = [
- { key: 'first_metric', value: 10, label: 'First metric', unit: 'days', link: 'some_link' },
- { key: 'second_metric', value: 20, label: 'Yet another metric' },
- { key: 'third_metric', value: null, label: 'Null metric without value', unit: 'parsecs' },
- { key: 'fourth_metric', value: '-', label: 'Metric without value', unit: 'parsecs' },
-];
-
-const defaultProps = {
- title: 'My fancy title',
- isLoading: false,
- metrics,
-};
-
-describe('MetricCard', () => {
- let wrapper;
-
- const factory = (props = defaultProps) => {
- wrapper = mount(MetricCard, {
- propsData: {
- ...defaultProps,
- ...props,
- },
- directives: {
- GlTooltip: createMockDirective(),
- },
- });
- };
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- const findTitle = () => wrapper.find({ ref: 'title' });
- const findLoadingIndicator = () => wrapper.find(GlSkeletonLoading);
- const findMetricsWrapper = () => wrapper.find({ ref: 'metricsWrapper' });
- const findMetricItem = () => wrapper.findAll({ ref: 'metricItem' });
- const findTooltip = () => wrapper.find('[data-testid="tooltip"]');
-
- describe('template', () => {
- it('renders the title', () => {
- factory();
-
- expect(findTitle().text()).toContain('My fancy title');
- });
-
- describe('when isLoading is true', () => {
- beforeEach(() => {
- factory({ isLoading: true });
- });
-
- it('displays a loading indicator', () => {
- expect(findLoadingIndicator().exists()).toBe(true);
- });
-
- it('does not display the metrics container', () => {
- expect(findMetricsWrapper().exists()).toBe(false);
- });
- });
-
- describe('when isLoading is false', () => {
- beforeEach(() => {
- factory({ isLoading: false });
- });
-
- it('does not display a loading indicator', () => {
- expect(findLoadingIndicator().exists()).toBe(false);
- });
-
- it('displays the metrics container', () => {
- expect(findMetricsWrapper().exists()).toBe(true);
- });
-
- it('renders two metrics', () => {
- expect(findMetricItem()).toHaveLength(metrics.length);
- });
-
- describe('with tooltip text', () => {
- const tooltipText = 'This is a tooltip';
- const tooltipMetric = {
- key: 'fifth_metric',
- value: '-',
- label: 'Metric with tooltip',
- unit: 'parsecs',
- tooltipText,
- };
-
- beforeEach(() => {
- factory({
- isLoading: false,
- metrics: [tooltipMetric],
- });
- });
-
- it('will render a tooltip', () => {
- const tt = getBinding(findTooltip().element, 'gl-tooltip');
- expect(tt.value.title).toEqual(tooltipText);
- });
- });
-
- describe.each`
- columnIndex | label | value | unit | link
- ${0} | ${'First metric'} | ${10} | ${' days'} | ${'some_link'}
- ${1} | ${'Yet another metric'} | ${20} | ${''} | ${null}
- ${2} | ${'Null metric without value'} | ${'-'} | ${''} | ${null}
- ${3} | ${'Metric without value'} | ${'-'} | ${''} | ${null}
- `('metric columns', ({ columnIndex, label, value, unit, link }) => {
- it(`renders ${value}${unit} ${label} with URL ${link}`, () => {
- const allMetricItems = findMetricItem();
- const metricItem = allMetricItems.at(columnIndex);
- const text = metricItem.text();
-
- expect(text).toContain(`${value}${unit}`);
- expect(text).toContain(label);
-
- if (link) {
- expect(metricItem.find('a').attributes('href')).toBe(link);
- } else {
- expect(metricItem.find('a').exists()).toBe(false);
- }
- });
- });
- });
- });
-});
diff --git a/spec/frontend/analytics/shared/components/projects_dropdown_filter_spec.js b/spec/frontend/analytics/shared/components/projects_dropdown_filter_spec.js
new file mode 100644
index 00000000000..2537b8fb816
--- /dev/null
+++ b/spec/frontend/analytics/shared/components/projects_dropdown_filter_spec.js
@@ -0,0 +1,264 @@
+import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
+import { TEST_HOST } from 'helpers/test_constants';
+import ProjectsDropdownFilter from '~/analytics/shared/components/projects_dropdown_filter.vue';
+import getProjects from '~/analytics/shared/graphql/projects.query.graphql';
+
+const projects = [
+ {
+ id: 'gid://gitlab/Project/1',
+ name: 'Gitlab Test',
+ fullPath: 'gitlab-org/gitlab-test',
+ avatarUrl: `${TEST_HOST}/images/home/nasa.svg`,
+ },
+ {
+ id: 'gid://gitlab/Project/2',
+ name: 'Gitlab Shell',
+ fullPath: 'gitlab-org/gitlab-shell',
+ avatarUrl: null,
+ },
+ {
+ id: 'gid://gitlab/Project/3',
+ name: 'Foo',
+ fullPath: 'gitlab-org/foo',
+ avatarUrl: null,
+ },
+];
+
+const defaultMocks = {
+ $apollo: {
+ query: jest.fn().mockResolvedValue({
+ data: { group: { projects: { nodes: projects } } },
+ }),
+ },
+};
+
+let spyQuery;
+
+describe('ProjectsDropdownFilter component', () => {
+ let wrapper;
+
+ const createComponent = (props = {}) => {
+ spyQuery = defaultMocks.$apollo.query;
+ wrapper = mount(ProjectsDropdownFilter, {
+ mocks: { ...defaultMocks },
+ propsData: {
+ groupId: 1,
+ groupNamespace: 'gitlab-org',
+ ...props,
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ const findDropdown = () => wrapper.find(GlDropdown);
+
+ const findDropdownItems = () =>
+ findDropdown()
+ .findAll(GlDropdownItem)
+ .filter((w) => w.text() !== 'No matching results');
+
+ const findDropdownAtIndex = (index) => findDropdownItems().at(index);
+
+ const findDropdownButton = () => findDropdown().find('.dropdown-toggle');
+ const findDropdownButtonAvatar = () => findDropdown().find('.gl-avatar');
+ const findDropdownButtonAvatarAtIndex = (index) =>
+ findDropdownAtIndex(index).find('img.gl-avatar');
+ const findDropdownButtonIdentIconAtIndex = (index) =>
+ findDropdownAtIndex(index).find('div.gl-avatar-identicon');
+
+ const findDropdownNameAtIndex = (index) =>
+ findDropdownAtIndex(index).find('[data-testid="project-name"');
+ const findDropdownFullPathAtIndex = (index) =>
+ findDropdownAtIndex(index).find('[data-testid="project-full-path"]');
+
+ const selectDropdownItemAtIndex = (index) =>
+ findDropdownAtIndex(index).find('button').trigger('click');
+
+ const selectedIds = () => wrapper.vm.selectedProjects.map(({ id }) => id);
+
+ describe('queryParams are applied when fetching data', () => {
+ beforeEach(() => {
+ createComponent({
+ queryParams: {
+ first: 50,
+ includeSubgroups: true,
+ },
+ });
+ });
+
+ it('applies the correct queryParams when making an api call', async () => {
+ wrapper.setData({ searchTerm: 'gitlab' });
+
+ expect(spyQuery).toHaveBeenCalledTimes(1);
+
+ await wrapper.vm.$nextTick(() => {
+ expect(spyQuery).toHaveBeenCalledWith({
+ query: getProjects,
+ variables: {
+ search: 'gitlab',
+ groupFullPath: wrapper.vm.groupNamespace,
+ first: 50,
+ includeSubgroups: true,
+ },
+ });
+ });
+ });
+ });
+
+ describe('when passed a an array of defaultProject as prop', () => {
+ beforeEach(() => {
+ createComponent({
+ defaultProjects: [projects[0]],
+ });
+ });
+
+ it("displays the defaultProject's name", () => {
+ expect(findDropdownButton().text()).toContain(projects[0].name);
+ });
+
+ it("renders the defaultProject's avatar", () => {
+ expect(findDropdownButtonAvatar().exists()).toBe(true);
+ });
+
+ it('marks the defaultProject as selected', () => {
+ expect(findDropdownAtIndex(0).props('isChecked')).toBe(true);
+ });
+ });
+
+ describe('when multiSelect is false', () => {
+ beforeEach(() => {
+ createComponent({ multiSelect: false });
+ });
+
+ describe('displays the correct information', () => {
+ it('contains 3 items', () => {
+ expect(findDropdownItems()).toHaveLength(3);
+ });
+
+ it('renders an avatar when the project has an avatarUrl', () => {
+ expect(findDropdownButtonAvatarAtIndex(0).exists()).toBe(true);
+ expect(findDropdownButtonIdentIconAtIndex(0).exists()).toBe(false);
+ });
+
+ it("renders an identicon when the project doesn't have an avatarUrl", () => {
+ expect(findDropdownButtonAvatarAtIndex(1).exists()).toBe(false);
+ expect(findDropdownButtonIdentIconAtIndex(1).exists()).toBe(true);
+ });
+
+ it('renders the project name', () => {
+ projects.forEach((project, index) => {
+ expect(findDropdownNameAtIndex(index).text()).toBe(project.name);
+ });
+ });
+
+ it('renders the project fullPath', () => {
+ projects.forEach((project, index) => {
+ expect(findDropdownFullPathAtIndex(index).text()).toBe(project.fullPath);
+ });
+ });
+ });
+
+ describe('on project click', () => {
+ it('should emit the "selected" event with the selected project', () => {
+ selectDropdownItemAtIndex(0);
+
+ expect(wrapper.emitted().selected).toEqual([[[projects[0]]]]);
+ });
+
+ it('should change selection when new project is clicked', () => {
+ selectDropdownItemAtIndex(1);
+
+ expect(wrapper.emitted().selected).toEqual([[[projects[1]]]]);
+ });
+
+ it('selection should be emptied when a project is deselected', () => {
+ selectDropdownItemAtIndex(0); // Select the item
+ selectDropdownItemAtIndex(0); // deselect it
+
+ expect(wrapper.emitted().selected).toEqual([[[projects[0]]], [[]]]);
+ });
+
+ it('renders an avatar in the dropdown button when the project has an avatarUrl', async () => {
+ selectDropdownItemAtIndex(0);
+
+ await wrapper.vm.$nextTick().then(() => {
+ expect(findDropdownButtonAvatarAtIndex(0).exists()).toBe(true);
+ expect(findDropdownButtonIdentIconAtIndex(0).exists()).toBe(false);
+ });
+ });
+
+ it("renders an identicon in the dropdown button when the project doesn't have an avatarUrl", async () => {
+ selectDropdownItemAtIndex(1);
+
+ await wrapper.vm.$nextTick().then(() => {
+ expect(findDropdownButtonAvatarAtIndex(1).exists()).toBe(false);
+ expect(findDropdownButtonIdentIconAtIndex(1).exists()).toBe(true);
+ });
+ });
+ });
+ });
+
+ describe('when multiSelect is true', () => {
+ beforeEach(() => {
+ createComponent({ multiSelect: true });
+ });
+
+ describe('displays the correct information', () => {
+ it('contains 3 items', () => {
+ expect(findDropdownItems()).toHaveLength(3);
+ });
+
+ it('renders an avatar when the project has an avatarUrl', () => {
+ expect(findDropdownButtonAvatarAtIndex(0).exists()).toBe(true);
+ expect(findDropdownButtonIdentIconAtIndex(0).exists()).toBe(false);
+ });
+
+ it("renders an identicon when the project doesn't have an avatarUrl", () => {
+ expect(findDropdownButtonAvatarAtIndex(1).exists()).toBe(false);
+ expect(findDropdownButtonIdentIconAtIndex(1).exists()).toBe(true);
+ });
+
+ it('renders the project name', () => {
+ projects.forEach((project, index) => {
+ expect(findDropdownNameAtIndex(index).text()).toBe(project.name);
+ });
+ });
+
+ it('renders the project fullPath', () => {
+ projects.forEach((project, index) => {
+ expect(findDropdownFullPathAtIndex(index).text()).toBe(project.fullPath);
+ });
+ });
+ });
+
+ describe('on project click', () => {
+ it('should add to selection when new project is clicked', () => {
+ selectDropdownItemAtIndex(0);
+ selectDropdownItemAtIndex(1);
+
+ expect(selectedIds()).toEqual([projects[0].id, projects[1].id]);
+ });
+
+ it('should remove from selection when clicked again', () => {
+ selectDropdownItemAtIndex(0);
+ expect(selectedIds()).toEqual([projects[0].id]);
+
+ selectDropdownItemAtIndex(0);
+ expect(selectedIds()).toEqual([]);
+ });
+
+ it('renders the correct placeholder text when multiple projects are selected', async () => {
+ selectDropdownItemAtIndex(0);
+ selectDropdownItemAtIndex(1);
+
+ await wrapper.vm.$nextTick().then(() => {
+ expect(findDropdownButton().text()).toBe('2 projects selected');
+ });
+ });
+ });
+ });
+});
diff --git a/spec/frontend/analytics/shared/utils_spec.js b/spec/frontend/analytics/shared/utils_spec.js
new file mode 100644
index 00000000000..e3293f2d8bd
--- /dev/null
+++ b/spec/frontend/analytics/shared/utils_spec.js
@@ -0,0 +1,24 @@
+import { filterBySearchTerm } from '~/analytics/shared/utils';
+
+describe('filterBySearchTerm', () => {
+ const data = [
+ { name: 'eins', title: 'one' },
+ { name: 'zwei', title: 'two' },
+ { name: 'drei', title: 'three' },
+ ];
+ const searchTerm = 'rei';
+
+ it('filters data by `name` for the provided search term', () => {
+ expect(filterBySearchTerm(data, searchTerm)).toEqual([data[2]]);
+ });
+
+ it('with no search term returns the data', () => {
+ ['', null].forEach((search) => {
+ expect(filterBySearchTerm(data, search)).toEqual(data);
+ });
+ });
+
+ it('with a key, filters by the provided key', () => {
+ expect(filterBySearchTerm(data, 'ne', 'title')).toEqual([data[0]]);
+ });
+});
diff --git a/spec/frontend/analytics/usage_trends/components/instance_counts_spec.js b/spec/frontend/analytics/usage_trends/components/usage_counts_spec.js
index 707d2cc310f..703767dab47 100644
--- a/spec/frontend/analytics/usage_trends/components/instance_counts_spec.js
+++ b/spec/frontend/analytics/usage_trends/components/usage_counts_spec.js
@@ -1,5 +1,6 @@
+import { GlDeprecatedSkeletonLoading as GlSkeletonLoading } from '@gitlab/ui';
+import { GlSingleStat } from '@gitlab/ui/dist/charts';
import { shallowMount } from '@vue/test-utils';
-import MetricCard from '~/analytics/shared/components/metric_card.vue';
import UsageCounts from '~/analytics/usage_trends/components/usage_counts.vue';
import { mockUsageCounts } from '../mock_data';
@@ -27,18 +28,18 @@ describe('UsageCounts', () => {
afterEach(() => {
wrapper.destroy();
- wrapper = null;
});
- const findMetricCard = () => wrapper.find(MetricCard);
+ const findSkeletonLoader = () => wrapper.findComponent(GlSkeletonLoading);
+ const findAllSingleStats = () => wrapper.findAllComponents(GlSingleStat);
describe('while loading', () => {
beforeEach(() => {
createComponent({ loading: true });
});
- it('displays the metric card with isLoading=true', () => {
- expect(findMetricCard().props('isLoading')).toBe(true);
+ it('displays a loading indicator', () => {
+ expect(findSkeletonLoader().exists()).toBe(true);
});
});
@@ -47,8 +48,15 @@ describe('UsageCounts', () => {
createComponent({ data: { counts: mockUsageCounts } });
});
- it('passes the counts data to the metric card', () => {
- expect(findMetricCard().props('metrics')).toEqual(mockUsageCounts);
+ it.each`
+ index | value | title
+ ${0} | ${mockUsageCounts[0].value} | ${mockUsageCounts[0].label}
+ ${1} | ${mockUsageCounts[1].value} | ${mockUsageCounts[1].label}
+ `('renders a GlSingleStat for "$title"', ({ index, value, title }) => {
+ const singleStat = findAllSingleStats().at(index);
+
+ expect(singleStat.props('value')).toBe(`${value}`);
+ expect(singleStat.props('title')).toBe(title);
});
});
});