Commit 18d08f81 authored by Andrew Fontaine's avatar Andrew Fontaine

Merge branch '346422-segmented-control-broken-in-the-pipeline-page' into 'master'

Fix broken Segmented control in the pipeline page

See merge request gitlab-org/gitlab!75788
parents 8ccdb8ea f38af547
<script>
import { GlAlert, GlLoadingIcon, GlSegmentedControl, GlToggle } from '@gitlab/ui';
import { GlAlert, GlButton, GlButtonGroup, GlLoadingIcon, GlToggle } from '@gitlab/ui';
import { __, s__ } from '~/locale';
import { STAGE_VIEW, LAYER_VIEW } from './constants';
......@@ -7,8 +7,9 @@ export default {
name: 'GraphViewSelector',
components: {
GlAlert,
GlButton,
GlButtonGroup,
GlLoadingIcon,
GlSegmentedControl,
GlToggle,
},
props: {
......@@ -96,6 +97,9 @@ export default {
this.hoverTipDismissed = true;
this.$emit('dismissHoverTip');
},
isCurrentType(type) {
return this.segmentSelectedType === type;
},
/*
In both toggle methods, we use setTimeout so that the loading indicator displays,
then the work is done to update the DOM. The process is:
......@@ -110,11 +114,14 @@ export default {
See https://www.hesselinkwebdesign.nl/2019/nexttick-vs-settimeout-in-vue/ for more details.
*/
toggleView(type) {
this.isSwitcherLoading = true;
setTimeout(() => {
this.$emit('updateViewType', type);
});
setViewType(type) {
if (!this.isCurrentType(type)) {
this.isSwitcherLoading = true;
this.segmentSelectedType = type;
setTimeout(() => {
this.$emit('updateViewType', type);
});
}
},
toggleShowLinksActive(val) {
this.isToggleLoading = true;
......@@ -136,14 +143,16 @@ export default {
size="lg"
/>
<span class="gl-font-weight-bold">{{ $options.i18n.viewLabelText }}</span>
<gl-segmented-control
v-model="segmentSelectedType"
:options="viewTypesList"
:disabled="isSwitcherLoading"
data-testid="pipeline-view-selector"
class="gl-mx-4"
@input="toggleView"
/>
<gl-button-group class="gl-mx-4">
<gl-button
v-for="viewType in viewTypesList"
:key="viewType.value"
:selected="isCurrentType(viewType.value)"
@click="setViewType(viewType.value)"
>
{{ viewType.text }}
</gl-button>
</gl-button-group>
<div v-if="showLinksToggle" class="gl-display-flex gl-align-items-center">
<gl-toggle
......
import { GlAlert, GlLoadingIcon } from '@gitlab/ui';
import { GlAlert, GlButton, GlButtonGroup, GlLoadingIcon } from '@gitlab/ui';
import { mount, shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import Vue from 'vue';
import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import { useLocalStorageSpy } from 'helpers/local_storage_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
......@@ -98,7 +98,6 @@ describe('Pipeline graph wrapper', () => {
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
beforeAll(() => {
......@@ -136,7 +135,7 @@ describe('Pipeline graph wrapper', () => {
beforeEach(async () => {
createComponentWithApollo();
jest.runOnlyPendingTimers();
await wrapper.vm.$nextTick();
await nextTick();
});
it('does not display the loading icon', () => {
......@@ -165,7 +164,7 @@ describe('Pipeline graph wrapper', () => {
getPipelineDetailsHandler: jest.fn().mockRejectedValue(new Error('GraphQL error')),
});
jest.runOnlyPendingTimers();
await wrapper.vm.$nextTick();
await nextTick();
});
it('does not display the loading icon', () => {
......@@ -189,7 +188,7 @@ describe('Pipeline graph wrapper', () => {
},
});
jest.runOnlyPendingTimers();
await wrapper.vm.$nextTick();
await nextTick();
});
it('does not display the loading icon', () => {
......@@ -211,7 +210,7 @@ describe('Pipeline graph wrapper', () => {
createComponentWithApollo();
jest.spyOn(wrapper.vm.$apollo.queries.headerPipeline, 'refetch');
jest.spyOn(wrapper.vm.$apollo.queries.pipeline, 'refetch');
await wrapper.vm.$nextTick();
await nextTick();
getGraph().vm.$emit('refreshPipelineGraph');
});
......@@ -225,8 +224,8 @@ describe('Pipeline graph wrapper', () => {
describe('when query times out', () => {
const advanceApolloTimers = async () => {
jest.runOnlyPendingTimers();
await wrapper.vm.$nextTick();
await wrapper.vm.$nextTick();
await nextTick();
await nextTick();
};
beforeEach(async () => {
......@@ -246,7 +245,7 @@ describe('Pipeline graph wrapper', () => {
.mockResolvedValueOnce(errorData);
createComponentWithApollo({ getPipelineDetailsHandler: failSucceedFail });
await wrapper.vm.$nextTick();
await nextTick();
});
it('shows correct errors and does not overwrite populated data when data is empty', async () => {
......@@ -276,7 +275,7 @@ describe('Pipeline graph wrapper', () => {
});
jest.runOnlyPendingTimers();
await wrapper.vm.$nextTick();
await nextTick();
});
it('appears when pipeline uses needs', () => {
......@@ -319,7 +318,7 @@ describe('Pipeline graph wrapper', () => {
});
jest.runOnlyPendingTimers();
await wrapper.vm.$nextTick();
await nextTick();
});
it('sets showLinks to true', async () => {
......@@ -329,7 +328,7 @@ describe('Pipeline graph wrapper', () => {
expect(getViewSelector().props('type')).toBe(LAYER_VIEW);
await getDependenciesToggle().vm.$emit('change', true);
jest.runOnlyPendingTimers();
await wrapper.vm.$nextTick();
await nextTick();
expect(wrapper.findComponent(LinksLayer).props('showLinks')).toBe(true);
});
});
......@@ -345,7 +344,7 @@ describe('Pipeline graph wrapper', () => {
});
jest.runOnlyPendingTimers();
await wrapper.vm.$nextTick();
await nextTick();
});
it('shows the hover tip in the view selector', async () => {
......@@ -366,7 +365,7 @@ describe('Pipeline graph wrapper', () => {
});
jest.runOnlyPendingTimers();
await wrapper.vm.$nextTick();
await nextTick();
});
it('does not show the hover tip', async () => {
......@@ -384,7 +383,7 @@ describe('Pipeline graph wrapper', () => {
});
jest.runOnlyPendingTimers();
await wrapper.vm.$nextTick();
await nextTick();
});
afterEach(() => {
......@@ -393,9 +392,10 @@ describe('Pipeline graph wrapper', () => {
it('reads the view type from localStorage when available', () => {
const viewSelectorNeedsSegment = wrapper
.findAll('[data-testid="pipeline-view-selector"] > label')
.find(GlButtonGroup)
.findAllComponents(GlButton)
.at(1);
expect(viewSelectorNeedsSegment.classes()).toContain('active');
expect(viewSelectorNeedsSegment.classes()).toContain('selected');
});
});
......@@ -412,7 +412,7 @@ describe('Pipeline graph wrapper', () => {
});
jest.runOnlyPendingTimers();
await wrapper.vm.$nextTick();
await nextTick();
});
afterEach(() => {
......@@ -435,7 +435,7 @@ describe('Pipeline graph wrapper', () => {
});
jest.runOnlyPendingTimers();
await wrapper.vm.$nextTick();
await nextTick();
});
it('does not appear when pipeline does not use needs', () => {
......@@ -462,7 +462,7 @@ describe('Pipeline graph wrapper', () => {
beforeEach(async () => {
createComponentWithApollo();
jest.runOnlyPendingTimers();
await wrapper.vm.$nextTick();
await nextTick();
});
it('is not called', () => {
......@@ -506,7 +506,7 @@ describe('Pipeline graph wrapper', () => {
});
jest.runOnlyPendingTimers();
await wrapper.vm.$nextTick();
await nextTick();
});
it('attempts to collect metrics', () => {
......
import { GlAlert, GlLoadingIcon, GlSegmentedControl } from '@gitlab/ui';
import { GlAlert, GlButton, GlButtonGroup, GlLoadingIcon } from '@gitlab/ui';
import { mount, shallowMount } from '@vue/test-utils';
import { LAYER_VIEW, STAGE_VIEW } from '~/pipelines/components/graph/constants';
import GraphViewSelector from '~/pipelines/components/graph/graph_view_selector.vue';
......@@ -7,9 +7,9 @@ describe('the graph view selector component', () => {
let wrapper;
const findDependenciesToggle = () => wrapper.find('[data-testid="show-links-toggle"]');
const findViewTypeSelector = () => wrapper.findComponent(GlSegmentedControl);
const findStageViewLabel = () => findViewTypeSelector().findAll('label').at(0);
const findLayersViewLabel = () => findViewTypeSelector().findAll('label').at(1);
const findViewTypeSelector = () => wrapper.findComponent(GlButtonGroup);
const findStageViewButton = () => findViewTypeSelector().findAllComponents(GlButton).at(0);
const findLayerViewButton = () => findViewTypeSelector().findAllComponents(GlButton).at(1);
const findSwitcherLoader = () => wrapper.find('[data-testid="switcher-loading-state"]');
const findToggleLoader = () => findDependenciesToggle().find(GlLoadingIcon);
const findHoverTip = () => wrapper.findComponent(GlAlert);
......@@ -51,8 +51,13 @@ describe('the graph view selector component', () => {
createComponent({ mountFn: mount });
});
it('shows the Stage view label as active in the selector', () => {
expect(findStageViewLabel().classes()).toContain('active');
it('shows the Stage view button as selected', () => {
expect(findStageViewButton().classes('selected')).toBe(true);
});
it('shows the Job dependencies view button not selected', () => {
expect(findLayerViewButton().exists()).toBe(true);
expect(findLayerViewButton().classes('selected')).toBe(false);
});
it('does not show the Job dependencies (links) toggle', () => {
......@@ -70,8 +75,13 @@ describe('the graph view selector component', () => {
});
});
it('shows the Job dependencies view label as active in the selector', () => {
expect(findLayersViewLabel().classes()).toContain('active');
it('shows the Job dependencies view as selected', () => {
expect(findLayerViewButton().classes('selected')).toBe(true);
});
it('shows the Stage button as not selected', () => {
expect(findStageViewButton().exists()).toBe(true);
expect(findStageViewButton().classes('selected')).toBe(false);
});
it('shows the Job dependencies (links) toggle', () => {
......@@ -94,7 +104,7 @@ describe('the graph view selector component', () => {
expect(wrapper.emitted().updateViewType).toBeUndefined();
expect(findSwitcherLoader().exists()).toBe(false);
await findStageViewLabel().trigger('click');
await findStageViewButton().trigger('click');
/*
Loading happens before the event is emitted or timers are run.
Then we run the timer because the event is emitted in setInterval
......@@ -123,6 +133,14 @@ describe('the graph view selector component', () => {
expect(wrapper.emitted().updateShowLinksState).toHaveLength(1);
expect(wrapper.emitted().updateShowLinksState).toEqual([[true]]);
});
it('does not emit an event if the click occurs on the currently selected view button', async () => {
expect(wrapper.emitted().updateShowLinksState).toBeUndefined();
await findLayerViewButton().trigger('click');
expect(wrapper.emitted().updateShowLinksState).toBeUndefined();
});
});
describe('hover tip callout', () => {
......
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