@gitlab/ui 67.1.0 → 67.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ # [67.3.0](https://gitlab.com/gitlab-org/gitlab-ui/compare/v67.2.0...v67.3.0) (2023-10-31)
2
+
3
+
4
+ ### Features
5
+
6
+ * **GlLineChart:** Defines a #tooltip-value slot ([4624919](https://gitlab.com/gitlab-org/gitlab-ui/commit/462491952e19832c9abd257b54130ed93244fd88))
7
+
8
+ # [67.2.0](https://gitlab.com/gitlab-org/gitlab-ui/compare/v67.1.0...v67.2.0) (2023-10-30)
9
+
10
+
11
+ ### Features
12
+
13
+ * **GlOutsideDirective:** respect mousedown events before click ([e2d72d5](https://gitlab.com/gitlab-org/gitlab-ui/commit/e2d72d5cc4d90c57fea3eb54f43d68338cab5ab4))
14
+
1
15
  # [67.1.0](https://gitlab.com/gitlab-org/gitlab-ui/compare/v67.0.0...v67.1.0) (2023-10-27)
2
16
 
3
17
 
@@ -306,7 +306,7 @@ const __vue_script__ = script;
306
306
  /* template */
307
307
  var __vue_render__ = function () {
308
308
  var _obj;
309
- var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{staticClass:"position-relative",class:( _obj = {}, _obj[_vm.$options.HEIGHT_AUTO_CLASSES] = _vm.autoHeight, _obj )},[_c('chart',_vm._g(_vm._b({class:{ 'gl-flex-grow-1': _vm.autoHeight },attrs:{"height":_vm.height,"options":_vm.options},on:{"created":_vm.onCreated}},'chart',_vm.$attrs,false),_vm.$listeners)),_vm._v(" "),(_vm.shouldShowAnnotationsTooltip)?_c('chart-tooltip',{ref:"annotationsTooltip",attrs:{"id":"annotationsTooltip","show":_vm.showAnnotationsTooltip,"chart":_vm.chart,"top":_vm.annotationsTooltipPosition.top,"left":_vm.annotationsTooltipPosition.left,"placement":"bottom"},scopedSlots:_vm._u([{key:"title",fn:function(){return [_c('div',[_vm._v(_vm._s(_vm.annotationsTooltipTitle))])]},proxy:true}],null,false,1889294429)},[_vm._v(" "),_c('div',[_vm._v(_vm._s(_vm.annotationsTooltipContent))])]):_vm._e(),_vm._v(" "),(_vm.chart)?_c('chart-tooltip',{ref:"dataTooltip",staticClass:"gl-pointer-events-none",attrs:{"id":"dataTooltip","show":_vm.showDataTooltip,"chart":_vm.chart,"top":_vm.dataTooltipPosition.top,"left":_vm.dataTooltipPosition.left},scopedSlots:_vm._u([{key:"title",fn:function(){return [(_vm.formatTooltipText)?_vm._t("tooltip-title"):_c('div',[_vm._v("\n "+_vm._s(_vm.dataTooltipTitle)+"\n "),(_vm.options.xAxis.name)?[_vm._v("("+_vm._s(_vm.options.xAxis.name)+")")]:_vm._e()],2)]},proxy:true}],null,true)},[_vm._v(" "),(_vm.formatTooltipText)?_vm._t("tooltip-content"):_c('tooltip-default-format',{attrs:{"tooltip-content":_vm.dataTooltipContent}})],2):_vm._e(),_vm._v(" "),(_vm.hasLegend)?_c('chart-legend',{style:(_vm.legendStyle),attrs:{"chart":_vm.chart,"series-info":_vm.seriesInfo,"text-style":_vm.compiledOptions.textStyle,"min-text":_vm.legendMinText,"max-text":_vm.legendMaxText,"average-text":_vm.legendAverageText,"current-text":_vm.legendCurrentText,"layout":_vm.legendLayout}}):_vm._e()],1)};
309
+ var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{staticClass:"position-relative",class:( _obj = {}, _obj[_vm.$options.HEIGHT_AUTO_CLASSES] = _vm.autoHeight, _obj )},[_c('chart',_vm._g(_vm._b({class:{ 'gl-flex-grow-1': _vm.autoHeight },attrs:{"height":_vm.height,"options":_vm.options},on:{"created":_vm.onCreated}},'chart',_vm.$attrs,false),_vm.$listeners)),_vm._v(" "),(_vm.shouldShowAnnotationsTooltip)?_c('chart-tooltip',{ref:"annotationsTooltip",attrs:{"id":"annotationsTooltip","show":_vm.showAnnotationsTooltip,"chart":_vm.chart,"top":_vm.annotationsTooltipPosition.top,"left":_vm.annotationsTooltipPosition.left,"placement":"bottom"},scopedSlots:_vm._u([{key:"title",fn:function(){return [_c('div',[_vm._v(_vm._s(_vm.annotationsTooltipTitle))])]},proxy:true}],null,false,1889294429)},[_vm._v(" "),_c('div',[_vm._v(_vm._s(_vm.annotationsTooltipContent))])]):_vm._e(),_vm._v(" "),(_vm.chart)?_c('chart-tooltip',{ref:"dataTooltip",staticClass:"gl-pointer-events-none",attrs:{"id":"dataTooltip","show":_vm.showDataTooltip,"chart":_vm.chart,"top":_vm.dataTooltipPosition.top,"left":_vm.dataTooltipPosition.left},scopedSlots:_vm._u([{key:"title",fn:function(){return [(_vm.formatTooltipText)?_vm._t("tooltip-title"):_c('div',[_vm._v("\n "+_vm._s(_vm.dataTooltipTitle)+"\n "),(_vm.options.xAxis.name)?[_vm._v("("+_vm._s(_vm.options.xAxis.name)+")")]:_vm._e()],2)]},proxy:true}],null,true)},[_vm._v(" "),(_vm.formatTooltipText)?_vm._t("tooltip-content"):_c('tooltip-default-format',{attrs:{"tooltip-content":_vm.dataTooltipContent},scopedSlots:_vm._u([(_vm.$scopedSlots['tooltip-value'])?{key:"tooltip-value",fn:function(scope){return [_vm._t("tooltip-value",null,null,scope)]}}:null],null,true)})],2):_vm._e(),_vm._v(" "),(_vm.hasLegend)?_c('chart-legend',{style:(_vm.legendStyle),attrs:{"chart":_vm.chart,"series-info":_vm.seriesInfo,"text-style":_vm.compiledOptions.textStyle,"min-text":_vm.legendMinText,"max-text":_vm.legendMaxText,"average-text":_vm.legendAverageText,"current-text":_vm.legendCurrentText,"layout":_vm.legendLayout}}):_vm._e()],1)};
310
310
  var __vue_staticRenderFns__ = [];
311
311
 
312
312
  /* style */
@@ -18,7 +18,7 @@ var script = {
18
18
  const __vue_script__ = script;
19
19
 
20
20
  /* template */
21
- var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',_vm._l((_vm.tooltipContent),function(value,label){return _c('div',{key:("" + label + (value.value)),staticClass:"gl-charts-tooltip-default-format-series"},[_c('series-label',{staticClass:"gl-charts-tooltip-default-format-series-label",attrs:{"color":value.color}},[_vm._v("\n "+_vm._s(label)+"\n ")]),_vm._v(" "),_c('div',{staticClass:"gl-charts-tooltip-default-format-series-value"},[_vm._v("\n "+_vm._s(value.value)+"\n ")])],1)}),0)};
21
+ var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',_vm._l((_vm.tooltipContent),function(value,label){return _c('div',{key:("" + label + (value.value)),staticClass:"gl-charts-tooltip-default-format-series"},[_c('series-label',{staticClass:"gl-charts-tooltip-default-format-series-label",attrs:{"color":value.color}},[_vm._v("\n "+_vm._s(label)+"\n ")]),_vm._v(" "),_c('div',{staticClass:"gl-charts-tooltip-default-format-series-value"},[_vm._t("tooltip-value",function(){return [_vm._v(_vm._s(value.value))]},{"value":value.value})],2)],1)}),0)};
22
22
  var __vue_staticRenderFns__ = [];
23
23
 
24
24
  /* style */
@@ -9,15 +9,17 @@ const callbacks = new Map();
9
9
  * Is a global listener already set up?
10
10
  */
11
11
  let listening = false;
12
+ let lastMousedown = null;
12
13
  const globalListener = event => {
13
14
  callbacks.forEach((_ref, element) => {
14
15
  let {
15
16
  bindTimeStamp,
16
17
  callback
17
18
  } = _ref;
19
+ const originalEvent = lastMousedown || event;
18
20
  if (
19
21
  // Ignore events that aren't targeted outside the element
20
- element.contains(event.target) ||
22
+ element.contains(originalEvent.target) ||
21
23
  // Only consider events triggered after the directive was bound
22
24
  event.timeStamp <= bindTimeStamp) {
23
25
  return;
@@ -31,20 +33,31 @@ const globalListener = event => {
31
33
  }
32
34
  }
33
35
  });
36
+ lastMousedown = null;
37
+ };
38
+
39
+ // We need to listen for mouse events because text selection fires click event only when selection ends.
40
+ // This means that the click event target could differ from the element where it originally started.
41
+ // As example: if we use mouse events we could guarantee that selecting text within a dropdown won't close it.
42
+ const onMousedown = event => {
43
+ lastMousedown = event;
34
44
  };
35
45
  const startListening = () => {
36
46
  if (listening) {
37
47
  return;
38
48
  }
49
+ document.addEventListener('mousedown', onMousedown);
39
50
  document.addEventListener('click', globalListener, {
40
51
  capture: true
41
52
  });
42
53
  listening = true;
54
+ lastMousedown = null;
43
55
  };
44
56
  const stopListening = () => {
45
57
  if (!listening) {
46
58
  return;
47
59
  }
60
+ document.removeEventListener('mousedown', onMousedown);
48
61
  document.removeEventListener('click', globalListener);
49
62
  listening = false;
50
63
  };
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Do not edit directly
3
- * Generated on Fri, 27 Oct 2023 13:55:39 GMT
3
+ * Generated on Tue, 31 Oct 2023 09:40:12 GMT
4
4
  */
5
5
 
6
6
  :root {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Do not edit directly
3
- * Generated on Fri, 27 Oct 2023 13:55:39 GMT
3
+ * Generated on Tue, 31 Oct 2023 09:40:12 GMT
4
4
  */
5
5
 
6
6
  :root.gl-dark {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Do not edit directly
3
- * Generated on Fri, 27 Oct 2023 13:55:39 GMT
3
+ * Generated on Tue, 31 Oct 2023 09:40:12 GMT
4
4
  */
5
5
 
6
6
  export const BLACK = "#fff";
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Do not edit directly
3
- * Generated on Fri, 27 Oct 2023 13:55:39 GMT
3
+ * Generated on Tue, 31 Oct 2023 09:40:12 GMT
4
4
  */
5
5
 
6
6
  export const BLACK = "#000";
@@ -1,6 +1,6 @@
1
1
 
2
2
  // Do not edit directly
3
- // Generated on Fri, 27 Oct 2023 13:55:39 GMT
3
+ // Generated on Tue, 31 Oct 2023 09:40:12 GMT
4
4
 
5
5
  $red-950: #fff4f3;
6
6
  $red-900: #fcf1ef;
@@ -1,6 +1,6 @@
1
1
 
2
2
  // Do not edit directly
3
- // Generated on Fri, 27 Oct 2023 13:55:39 GMT
3
+ // Generated on Tue, 31 Oct 2023 09:40:12 GMT
4
4
 
5
5
  $gl-line-height-52: 3.25rem;
6
6
  $gl-line-height-44: 2.75rem;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitlab/ui",
3
- "version": "67.1.0",
3
+ "version": "67.3.0",
4
4
  "description": "GitLab UI Components",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -94,7 +94,7 @@
94
94
  "@gitlab/eslint-plugin": "19.2.0",
95
95
  "@gitlab/fonts": "^1.3.0",
96
96
  "@gitlab/stylelint-config": "5.0.1",
97
- "@gitlab/svgs": "3.66.0",
97
+ "@gitlab/svgs": "3.68.0",
98
98
  "@rollup/plugin-commonjs": "^11.1.0",
99
99
  "@rollup/plugin-node-resolve": "^7.1.3",
100
100
  "@rollup/plugin-replace": "^2.3.2",
@@ -122,7 +122,7 @@
122
122
  "babel-loader": "^8.0.5",
123
123
  "babel-plugin-require-context-hook": "^1.0.0",
124
124
  "bootstrap": "4.6.2",
125
- "cypress": "13.3.2",
125
+ "cypress": "13.3.3",
126
126
  "cypress-axe": "^1.4.0",
127
127
  "dompurify": "^3.0.0",
128
128
  "emoji-regex": "^10.0.0",
@@ -1,10 +1,14 @@
1
+ import { nextTick } from 'vue';
1
2
  import { shallowMount } from '@vue/test-utils';
2
3
 
3
4
  import { LEGEND_LAYOUT_INLINE, LEGEND_LAYOUT_TABLE } from '~/utils/charts/constants';
4
5
  import { createMockChartInstance, ChartTooltipStub } from '~helpers/chart_stubs';
5
6
  import { expectHeightAutoClasses } from '~helpers/chart_height';
7
+
6
8
  import Chart from '../chart/chart.vue';
7
9
  import ChartLegend from '../legend/legend.vue';
10
+ import TooltipDefaultFormat from '../../shared_components/charts/tooltip_default_format.vue';
11
+
8
12
  import LineChart from './line.vue';
9
13
 
10
14
  let mockChartInstance;
@@ -39,7 +43,7 @@ describe('line component', () => {
39
43
  it('emits `created`, with the chart instance', async () => {
40
44
  createShallowWrapper();
41
45
 
42
- await wrapper.vm.$nextTick();
46
+ await nextTick();
43
47
 
44
48
  expect(wrapper.emitted('created').length).toBe(1);
45
49
  expect(wrapper.emitted('created')[0][0]).toBe(mockChartInstance);
@@ -49,7 +53,7 @@ describe('line component', () => {
49
53
  it('are hidden by default', async () => {
50
54
  createShallowWrapper();
51
55
 
52
- await wrapper.vm.$nextTick();
56
+ await nextTick();
53
57
 
54
58
  expect(findAnnotationsTooltip().exists()).toBe(false);
55
59
  });
@@ -64,7 +68,7 @@ describe('line component', () => {
64
68
  ],
65
69
  });
66
70
 
67
- await wrapper.vm.$nextTick();
71
+ await nextTick();
68
72
 
69
73
  expect(findAnnotationsTooltip().exists()).toBe(true);
70
74
  });
@@ -88,7 +92,7 @@ describe('line component', () => {
88
92
  },
89
93
  });
90
94
 
91
- await wrapper.vm.$nextTick();
95
+ await nextTick();
92
96
 
93
97
  expect(findAnnotationsTooltip().exists()).toBe(true);
94
98
  });
@@ -127,13 +131,65 @@ describe('line component', () => {
127
131
 
128
132
  wrapper.vm.onChartDataPointMouseOver(params);
129
133
 
130
- await wrapper.vm.$nextTick();
134
+ await nextTick();
131
135
 
132
136
  expect(findAnnotationsTooltip().html()).toContain(params.data.xAxis);
133
137
  expect(findAnnotationsTooltip().html()).toContain(params.data.tooltipData.content);
134
138
  });
135
139
  });
136
140
 
141
+ describe('tooltip', () => {
142
+ const tooltipParams = {
143
+ seriesData: [
144
+ {
145
+ seriesName: 'Series 1',
146
+ value: ['x', 1000],
147
+ color: '#fff',
148
+ },
149
+ {
150
+ seriesName: 'Series 2',
151
+ value: ['x', 1001],
152
+ color: '#fff',
153
+ },
154
+ ],
155
+ };
156
+
157
+ it('renders tooltip', async () => {
158
+ createShallowWrapper(
159
+ {},
160
+ {
161
+ stubs: { TooltipDefaultFormat },
162
+ }
163
+ );
164
+
165
+ wrapper.vm.defaultFormatTooltipText(tooltipParams); // force render of a tooltip
166
+ await nextTick();
167
+
168
+ const tooltipText = findDataTooltip().text();
169
+ expect(tooltipText).toContain('1000');
170
+ expect(tooltipText).toContain('1001');
171
+ });
172
+
173
+ it('renders formatted tooltip values', async () => {
174
+ createShallowWrapper(
175
+ {},
176
+ {
177
+ stubs: { TooltipDefaultFormat },
178
+ scopedSlots: {
179
+ 'tooltip-value': ({ value }) => `$ ${value.toLocaleString()}`,
180
+ },
181
+ }
182
+ );
183
+
184
+ wrapper.vm.defaultFormatTooltipText(tooltipParams); // force render of a tooltip
185
+ await nextTick();
186
+
187
+ const tooltipText = findDataTooltip().text();
188
+ expect(tooltipText).toContain('$ 1,000');
189
+ expect(tooltipText).toContain('$ 1,001');
190
+ });
191
+ });
192
+
137
193
  describe('tooltip position', () => {
138
194
  const dataTooltipTitle = 'FooBar';
139
195
 
@@ -160,7 +216,7 @@ describe('line component', () => {
160
216
 
161
217
  wrapper.setData({ dataTooltipPosition: { left, top }, dataTooltipTitle });
162
218
 
163
- await wrapper.vm.$nextTick();
219
+ await nextTick();
164
220
 
165
221
  expect(findDataTooltip().props('left')).toBe(`${left}`);
166
222
  expect(findDataTooltip().props('top')).toBe(`${top}`);
@@ -172,7 +228,7 @@ describe('line component', () => {
172
228
  it('is inline by default', async () => {
173
229
  createShallowWrapper();
174
230
 
175
- await wrapper.vm.$nextTick();
231
+ await nextTick();
176
232
 
177
233
  expect(findLegend().props('layout')).toBe(LEGEND_LAYOUT_INLINE);
178
234
  });
@@ -182,7 +238,7 @@ describe('line component', () => {
182
238
  legendLayout: LEGEND_LAYOUT_INLINE,
183
239
  });
184
240
 
185
- await wrapper.vm.$nextTick();
241
+ await nextTick();
186
242
 
187
243
  expect(findLegend().props('layout')).toBe(LEGEND_LAYOUT_INLINE);
188
244
  });
@@ -192,7 +248,7 @@ describe('line component', () => {
192
248
  legendLayout: LEGEND_LAYOUT_TABLE,
193
249
  });
194
250
 
195
- await wrapper.vm.$nextTick();
251
+ await nextTick();
196
252
 
197
253
  expect(findLegend().props('layout')).toBe(LEGEND_LAYOUT_TABLE);
198
254
  });
@@ -201,7 +257,7 @@ describe('line component', () => {
201
257
  showLegend: false,
202
258
  });
203
259
 
204
- await wrapper.vm.$nextTick();
260
+ await nextTick();
205
261
 
206
262
  expect(findLegend().exists()).toBe(false);
207
263
  });
@@ -390,7 +390,12 @@ export default {
390
390
  </div>
391
391
  </template>
392
392
  <slot v-if="formatTooltipText" name="tooltip-content"></slot>
393
- <tooltip-default-format v-else :tooltip-content="dataTooltipContent" />
393
+ <tooltip-default-format v-else :tooltip-content="dataTooltipContent">
394
+ <template v-if="$scopedSlots['tooltip-value']" #tooltip-value="scope">
395
+ <!-- @slot Tooltip value formatter -->
396
+ <slot name="tooltip-value" v-bind="scope"></slot>
397
+ </template>
398
+ </tooltip-default-format>
394
399
  </chart-tooltip>
395
400
  <chart-legend
396
401
  v-if="hasLegend"
@@ -0,0 +1,69 @@
1
+ import { shallowMount } from '@vue/test-utils';
2
+
3
+ import SeriesLabel from '../../charts/series_label/series_label.vue';
4
+ import TooltipDefaultFormat from './tooltip_default_format.vue';
5
+
6
+ const mockTooltipContent = {
7
+ 'Series A': {
8
+ color: '#000000',
9
+ value: 1000,
10
+ },
11
+ 'Series B': {
12
+ color: '#ffffff',
13
+ value: 5555.5,
14
+ },
15
+ };
16
+
17
+ describe('tooltip default format', () => {
18
+ let wrapper;
19
+
20
+ const createComponent = ({ props, ...options } = {}) => {
21
+ wrapper = shallowMount(TooltipDefaultFormat, {
22
+ propsData: {
23
+ tooltipContent: mockTooltipContent,
24
+ ...props,
25
+ },
26
+ ...options,
27
+ });
28
+ };
29
+
30
+ const findSeriesLabelAt = (i) => wrapper.findAllComponents(SeriesLabel).at(i);
31
+
32
+ it('renders default tooltip', () => {
33
+ createComponent();
34
+
35
+ const text = wrapper.text();
36
+
37
+ expect(text).toContain('Series A');
38
+ expect(text).toContain('1000');
39
+
40
+ expect(text).toContain('Series B');
41
+ expect(text).toContain('5555.5');
42
+ });
43
+
44
+ it('renders label colors', () => {
45
+ createComponent();
46
+
47
+ expect(findSeriesLabelAt(0).props('color')).toBe('#000000');
48
+ expect(findSeriesLabelAt(1).props('color')).toBe('#ffffff');
49
+ });
50
+
51
+ it('renders formatted values', () => {
52
+ createComponent({
53
+ scopedSlots: {
54
+ 'tooltip-value': ({ value }) =>
55
+ value.toLocaleString(undefined, {
56
+ minimumFractionDigits: 2,
57
+ }),
58
+ },
59
+ });
60
+
61
+ const text = wrapper.text();
62
+
63
+ expect(text).toContain('Series A');
64
+ expect(text).toContain('1,000.00');
65
+
66
+ expect(text).toContain('Series B');
67
+ expect(text).toContain('5,555.50');
68
+ });
69
+ });
@@ -25,7 +25,7 @@ export default {
25
25
  {{ label }}
26
26
  </series-label>
27
27
  <div class="gl-charts-tooltip-default-format-series-value">
28
- {{ value.value }}
28
+ <slot name="tooltip-value" :value="value.value">{{ value.value }}</slot>
29
29
  </div>
30
30
  </div>
31
31
  </div>
@@ -9,12 +9,14 @@ const callbacks = new Map();
9
9
  * Is a global listener already set up?
10
10
  */
11
11
  let listening = false;
12
+ let lastMousedown = null;
12
13
 
13
14
  const globalListener = (event) => {
14
15
  callbacks.forEach(({ bindTimeStamp, callback }, element) => {
16
+ const originalEvent = lastMousedown || event;
15
17
  if (
16
18
  // Ignore events that aren't targeted outside the element
17
- element.contains(event.target) ||
19
+ element.contains(originalEvent.target) ||
18
20
  // Only consider events triggered after the directive was bound
19
21
  event.timeStamp <= bindTimeStamp
20
22
  ) {
@@ -30,6 +32,14 @@ const globalListener = (event) => {
30
32
  }
31
33
  }
32
34
  });
35
+ lastMousedown = null;
36
+ };
37
+
38
+ // We need to listen for mouse events because text selection fires click event only when selection ends.
39
+ // This means that the click event target could differ from the element where it originally started.
40
+ // As example: if we use mouse events we could guarantee that selecting text within a dropdown won't close it.
41
+ const onMousedown = (event) => {
42
+ lastMousedown = event;
33
43
  };
34
44
 
35
45
  const startListening = () => {
@@ -37,8 +47,10 @@ const startListening = () => {
37
47
  return;
38
48
  }
39
49
 
50
+ document.addEventListener('mousedown', onMousedown);
40
51
  document.addEventListener('click', globalListener, { capture: true });
41
52
  listening = true;
53
+ lastMousedown = null;
42
54
  };
43
55
 
44
56
  const stopListening = () => {
@@ -46,6 +58,7 @@ const stopListening = () => {
46
58
  return;
47
59
  }
48
60
 
61
+ document.removeEventListener('mousedown', onMousedown);
49
62
  document.removeEventListener('click', globalListener);
50
63
  listening = false;
51
64
  };
@@ -126,35 +126,14 @@ describe('outside directive', () => {
126
126
  expect(document.addEventListener).not.toHaveBeenCalled();
127
127
  });
128
128
 
129
- it('attaches the global listener on first initialisation', async () => {
130
- await createComponent();
131
-
132
- expect(document.addEventListener.mock.calls).toEqual([
133
- ['click', expect.any(Function), { capture: true }],
134
- ]);
135
- });
136
-
137
129
  it('detaches the global listener when last binding is removed', async () => {
138
130
  await createComponent();
139
131
 
140
132
  wrapper.destroy();
141
133
 
142
- expect(document.removeEventListener.mock.calls).toEqual([['click', expect.any(Function)]]);
143
- });
144
-
145
- it('only binds once, even with multiple instances', async () => {
146
- await createComponent({
147
- template: `
148
- <div>
149
- <div v-outside="onClick"></div>
150
- <div v-outside="onClick"></div>
151
- </div>
152
- `,
153
- });
134
+ document.body.dispatchEvent(new MouseEvent('click'));
154
135
 
155
- expect(document.addEventListener.mock.calls).toEqual([
156
- ['click', expect.any(Function), { capture: true }],
157
- ]);
136
+ expect(onClick).not.toHaveBeenCalled();
158
137
  });
159
138
 
160
139
  it('only unbinds once there are no instances', async () => {
@@ -173,12 +152,16 @@ describe('outside directive', () => {
173
152
  wrapper.setData({ instances: 1 });
174
153
  await wrapper.vm.$nextTick();
175
154
 
176
- expect(document.removeEventListener).not.toHaveBeenCalled();
155
+ document.body.dispatchEvent(new MouseEvent('click'));
156
+
157
+ expect(onClick).toHaveBeenCalledTimes(1);
177
158
 
178
159
  wrapper.setData({ instances: 0 });
179
160
  await wrapper.vm.$nextTick();
180
161
 
181
- expect(document.removeEventListener.mock.calls).toEqual([['click', expect.any(Function)]]);
162
+ document.body.dispatchEvent(new MouseEvent('click'));
163
+
164
+ expect(onClick).toHaveBeenCalledTimes(1);
182
165
  });
183
166
  });
184
167
 
@@ -341,4 +324,15 @@ describe('outside directive', () => {
341
324
  expect(global.console.error.mock.calls).toEqual([[thrownError]]);
342
325
  });
343
326
  });
327
+
328
+ describe('mousedown before click', () => {
329
+ it('respects mousedown event before click', async () => {
330
+ await createComponent();
331
+
332
+ find('inside').trigger('mousedown');
333
+ find('outside').trigger('click');
334
+
335
+ expect(onClick).not.toHaveBeenCalled();
336
+ });
337
+ });
344
338
  });