diff options
author | Mike Greiling <mike@pixelcog.com> | 2019-04-05 05:19:23 +0000 |
---|---|---|
committer | Mike Greiling <mike@pixelcog.com> | 2019-04-05 05:19:23 +0000 |
commit | e605c83397739547589d76e84643776ee3f5b166 (patch) | |
tree | 834120f95f856355a988098151237b05414e5650 | |
parent | b54d466caeed724c73f4447c6ad26b7755262968 (diff) | |
parent | e0725fa436708209f431fc874d1ee148bbfb50f0 (diff) | |
download | gitlab-ce-e605c83397739547589d76e84643776ee3f5b166.tar.gz |
Merge branch 'resize-vuln-graph' into 'master'
Backport Resizable container component for gl-charts
See merge request gitlab-org/gitlab-ce!26843
4 files changed, 128 insertions, 0 deletions
diff --git a/app/assets/javascripts/contextual_sidebar.js b/app/assets/javascripts/contextual_sidebar.js index 67fcdd082a2..03dea1ec0a5 100644 --- a/app/assets/javascripts/contextual_sidebar.js +++ b/app/assets/javascripts/contextual_sidebar.js @@ -41,6 +41,9 @@ export default class ContextualSidebar { this.toggleCollapsedSidebar(value, true); } }); + this.$page.on('transitionstart transitionend', () => { + $(document).trigger('content.resize'); + }); $(window).on('resize', () => _.debounce(this.render(), 100)); } diff --git a/app/assets/javascripts/vue_shared/components/resizable_chart/resizable_chart_container.vue b/app/assets/javascripts/vue_shared/components/resizable_chart/resizable_chart_container.vue new file mode 100644 index 00000000000..1f3d248e991 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/resizable_chart/resizable_chart_container.vue @@ -0,0 +1,40 @@ +<script> +import { debounceByAnimationFrame } from '~/lib/utils/common_utils'; +import $ from 'jquery'; + +export default { + data() { + return { + width: 0, + height: 0, + }; + }, + beforeDestroy() { + this.contentResizeHandler.off('content.resize', this.debouncedResize); + window.removeEventListener('resize', this.debouncedResize); + }, + created() { + this.debouncedResize = debounceByAnimationFrame(this.onResize); + + // Handle when we explicictly trigger a custom resize event + this.contentResizeHandler = $(document).on('content.resize', this.debouncedResize); + + // Handle window resize + window.addEventListener('resize', this.debouncedResize); + }, + methods: { + onResize() { + // Slot dimensions + const { clientWidth, clientHeight } = this.$refs.chartWrapper; + this.width = clientWidth; + this.height = clientHeight; + }, + }, +}; +</script> + +<template> + <div ref="chartWrapper"> + <slot :width="width" :height="height"> </slot> + </div> +</template> diff --git a/spec/frontend/vue_shared/components/__snapshots__/resizable_chart_container_spec.js.snap b/spec/frontend/vue_shared/components/__snapshots__/resizable_chart_container_spec.js.snap new file mode 100644 index 00000000000..add0c36a120 --- /dev/null +++ b/spec/frontend/vue_shared/components/__snapshots__/resizable_chart_container_spec.js.snap @@ -0,0 +1,21 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Resizable Chart Container renders the component 1`] = ` +<div> + <div + class="slot" + > + <span + class="width" + > + 0 + </span> + + <span + class="height" + > + 0 + </span> + </div> +</div> +`; diff --git a/spec/frontend/vue_shared/components/resizable_chart_container_spec.js b/spec/frontend/vue_shared/components/resizable_chart_container_spec.js new file mode 100644 index 00000000000..8f533e8ab24 --- /dev/null +++ b/spec/frontend/vue_shared/components/resizable_chart_container_spec.js @@ -0,0 +1,64 @@ +import Vue from 'vue'; +import { mount } from '@vue/test-utils'; +import ResizableChartContainer from '~/vue_shared/components/resizable_chart/resizable_chart_container.vue'; +import $ from 'jquery'; + +jest.mock('~/lib/utils/common_utils', () => ({ + debounceByAnimationFrame(callback) { + return jest.spyOn({ callback }, 'callback'); + }, +})); + +describe('Resizable Chart Container', () => { + let wrapper; + + beforeEach(() => { + wrapper = mount(ResizableChartContainer, { + attachToDocument: true, + scopedSlots: { + default: ` + <div class="slot" slot-scope="{ width, height }"> + <span class="width">{{width}}</span> + <span class="height">{{height}}</span> + </div> + `, + }, + }); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('renders the component', () => { + expect(wrapper.element).toMatchSnapshot(); + }); + + it('updates the slot width and height props', () => { + const width = 1920; + const height = 1080; + + // JSDOM mocks and sets clientWidth/clientHeight to 0 so we set manually + wrapper.vm.$refs.chartWrapper = { clientWidth: width, clientHeight: height }; + + $(document).trigger('content.resize'); + + return Vue.nextTick().then(() => { + const widthNode = wrapper.find('.slot > .width'); + const heightNode = wrapper.find('.slot > .height'); + + expect(parseInt(widthNode.text(), 10)).toEqual(width); + expect(parseInt(heightNode.text(), 10)).toEqual(height); + }); + }); + + it('calls onResize on manual resize', () => { + $(document).trigger('content.resize'); + expect(wrapper.vm.debouncedResize).toHaveBeenCalled(); + }); + + it('calls onResize on page resize', () => { + window.dispatchEvent(new Event('resize')); + expect(wrapper.vm.debouncedResize).toHaveBeenCalled(); + }); +}); |