Commit f38af547 authored by Frédéric Caplette's avatar Frédéric Caplette Committed by Andrew Fontaine

Fix broken Segmented control in the pipeline page

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