@gitlab/ui 38.10.0 → 38.10.1

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,10 @@
1
+ ## [38.10.1](https://gitlab.com/gitlab-org/gitlab-ui/compare/v38.10.0...v38.10.1) (2022-04-14)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **GlToast:** Event listener memory leak ([b799657](https://gitlab.com/gitlab-org/gitlab-ui/commit/b7996570d3f675c4ee11cc0eb26f2aa0a6fda817))
7
+
1
8
  # [38.10.0](https://gitlab.com/gitlab-org/gitlab-ui/compare/v38.9.0...v38.10.0) (2022-04-13)
2
9
 
3
10
 
@@ -46,11 +46,14 @@ function showToast(message) {
46
46
  };
47
47
 
48
48
  if (_isFunction(options.onComplete)) {
49
- this.$root.$on('bv::toast:hidden', e => {
49
+ const toastHiddenCallback = e => {
50
50
  if (e.componentId === id) {
51
+ this.$root.$off('bv::toast:hidden', toastHiddenCallback);
51
52
  options.onComplete(e);
52
53
  }
53
- });
54
+ };
55
+
56
+ this.$root.$on('bv::toast:hidden', toastHiddenCallback);
54
57
  }
55
58
 
56
59
  this.$bvToast.toast(message, { ...DEFAULT_OPTIONS,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitlab/ui",
3
- "version": "38.10.0",
3
+ "version": "38.10.1",
4
4
  "description": "GitLab UI Components",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -4,27 +4,51 @@ import GlPagination from './pagination.vue';
4
4
 
5
5
  const components = { GlPagination };
6
6
 
7
- const generateBaseProps = ({
8
- prevText = 'Previous',
9
- nextText = 'Next',
10
- disabled = false,
7
+ const defaultValue = (name) => GlPagination.props[name].default;
8
+
9
+ const generateProps = ({
10
+ page = 3,
11
+ align = defaultValue('align'),
12
+ disabled = defaultValue('disabled'),
13
+ linkGen = defaultValue('linkGen'),
14
+ nextPage = defaultValue('nextPage'),
15
+ nextText = defaultValue('nextText'),
16
+ perPage = 10, // Existing stories/shots used this value instead of the default
17
+ prevPage = defaultValue('prevPage'),
18
+ prevText = defaultValue('prevText'),
19
+ totalItems = 200, // The component's default value of 0 is for compact pagination, which most stories here do not use.
11
20
  } = {}) => ({
12
- prevText,
13
- nextText,
21
+ page,
22
+ align,
14
23
  disabled,
15
- });
16
-
17
- const generateFullProps = ({ page = 3, perPage = 10, totalItems = 200 } = {}) => ({
18
- initialPage: page,
24
+ linkGen,
25
+ nextPage,
26
+ nextText,
19
27
  perPage,
28
+ prevPage,
29
+ prevText,
20
30
  totalItems,
21
- ...generateBaseProps(),
22
31
  });
23
32
 
24
- const defaults = {
33
+ const template = `
34
+ <gl-pagination
35
+ v-model="page"
36
+ :align="align"
37
+ :disabled="disabled"
38
+ :link-gen="linkGen"
39
+ :next-page="nextPage"
40
+ :next-text="nextText"
41
+ :per-page="perPage"
42
+ :prev-page="prevPage"
43
+ :prev-text="prevText"
44
+ :total-items="totalItems"
45
+ />
46
+ `;
47
+
48
+ const defaults = (args) => ({
25
49
  data() {
26
50
  return {
27
- page: 3,
51
+ currentPage: args.page,
28
52
  alignOptions,
29
53
  };
30
54
  },
@@ -33,33 +57,20 @@ const defaults = {
33
57
  this.page = page;
34
58
  },
35
59
  },
36
- };
60
+ });
37
61
 
38
62
  export const Default = (args, { argTypes }) => ({
39
63
  components,
40
64
  props: Object.keys(argTypes),
41
- ...defaults,
42
- template: `<gl-pagination
43
- v-model="page"
44
- :per-page="perPage"
45
- :total-items="totalItems"
46
- :prev-text="prevText"
47
- :next-text="nextText"
48
- :disabled="disabled"
49
- />`,
65
+ ...defaults(args),
66
+ template,
50
67
  });
51
- Default.args = generateFullProps();
68
+ Default.args = generateProps();
52
69
 
53
- export const Compact = () => ({
70
+ export const Compact = (args, { argTypes }) => ({
54
71
  components,
55
- ...defaults,
56
- props: generateFullProps(),
57
- data() {
58
- return {
59
- page: 1,
60
- alignOptions,
61
- };
62
- },
72
+ props: Object.keys(argTypes),
73
+ ...defaults(args),
63
74
  computed: {
64
75
  prevPage() {
65
76
  return Math.max(this.page - 1, 0);
@@ -70,72 +81,37 @@ export const Compact = () => ({
70
81
  },
71
82
  },
72
83
  template: `
73
- <div class="text-center gl-font-base">
74
- <gl-pagination
75
- v-model="page"
76
- :prev-page="prevPage"
77
- :next-page="nextPage"
78
- :prev-text="prevText"
79
- :next-text="nextText"
80
- :disabled="disabled"
81
- :align="alignOptions.center"
82
- />
83
- Page {{ page }} of 3
84
- </div>`,
84
+ <div class="text-center gl-font-base">
85
+ ${template}
86
+ Page {{ page }} of 3
87
+ </div>
88
+ `,
85
89
  });
90
+ Compact.args = generateProps({ page: 1, totalItems: 0, align: alignOptions.center });
86
91
 
87
92
  export const LinkBased = (args, { argTypes }) => ({
88
93
  components,
89
94
  props: Object.keys(argTypes),
90
- ...defaults,
91
- methods: {
92
- linkGen(page) {
93
- return `/page/${page}`;
94
- },
95
- },
96
- template: `<gl-pagination
97
- v-model="page"
98
- :per-page="perPage"
99
- :total-items="totalItems"
100
- :prev-text="prevText"
101
- :next-text="nextText"
102
- :disabled="disabled"
103
- :link-gen="linkGen"
104
- />`,
95
+ ...defaults(args),
96
+ template,
105
97
  });
106
- LinkBased.args = generateFullProps();
98
+ LinkBased.args = generateProps({ linkGen: (page) => `/page/${page}` });
107
99
 
108
100
  export const AlignCenter = (args, { argTypes }) => ({
109
101
  components,
110
102
  props: Object.keys(argTypes),
111
- ...defaults,
112
- template: `<gl-pagination
113
- v-model="page"
114
- :per-page="perPage"
115
- :total-items="totalItems"
116
- :prev-text="prevText"
117
- :next-text="nextText"
118
- :disabled="disabled"
119
- :align="alignOptions.center"
120
- />`,
103
+ ...defaults(args),
104
+ template,
121
105
  });
122
- AlignCenter.args = generateFullProps();
106
+ AlignCenter.args = generateProps({ align: alignOptions.center });
123
107
 
124
108
  export const AlignRight = (args, { argTypes }) => ({
125
109
  components,
126
110
  props: Object.keys(argTypes),
127
- ...defaults,
128
- template: `<gl-pagination
129
- v-model="page"
130
- :per-page="perPage"
131
- :total-items="totalItems"
132
- :prev-text="prevText"
133
- :next-text="nextText"
134
- :disabled="disabled"
135
- :align="alignOptions.right"
136
- />`,
111
+ ...defaults(args),
112
+ template,
137
113
  });
138
- AlignRight.args = generateFullProps();
114
+ AlignRight.args = generateProps({ align: alignOptions.right });
139
115
 
140
116
  export default {
141
117
  title: 'base/pagination',
@@ -148,4 +124,10 @@ export default {
148
124
  },
149
125
  },
150
126
  },
127
+ argTypes: {
128
+ align: {
129
+ options: Object.values(alignOptions),
130
+ control: 'select',
131
+ },
132
+ },
151
133
  };
@@ -50,11 +50,14 @@ function showToast(message, options = {}) {
50
50
  const toast = { id, hide };
51
51
 
52
52
  if (isFunction(options.onComplete)) {
53
- this.$root.$on('bv::toast:hidden', (e) => {
53
+ const toastHiddenCallback = (e) => {
54
54
  if (e.componentId === id) {
55
+ this.$root.$off('bv::toast:hidden', toastHiddenCallback);
55
56
  options.onComplete(e);
56
57
  }
57
- });
58
+ };
59
+
60
+ this.$root.$on('bv::toast:hidden', toastHiddenCallback);
58
61
  }
59
62
 
60
63
  this.$bvToast.toast(message, {
@@ -27,4 +27,46 @@ describe('GlToast', () => {
27
27
  hide: expect.any(Function),
28
28
  });
29
29
  });
30
+
31
+ describe('onComplete callback', () => {
32
+ let onCompleteSpy;
33
+ let toast;
34
+
35
+ beforeEach(() => {
36
+ onCompleteSpy = jest.fn();
37
+
38
+ toast = wrapper.vm.$toast.show('foo', {
39
+ onComplete: onCompleteSpy,
40
+ });
41
+ });
42
+
43
+ it('calls onComplete callback only when matching toast hides', () => {
44
+ expect(onCompleteSpy).not.toHaveBeenCalled();
45
+
46
+ // Pretend some other toast was hidden.
47
+ wrapper.vm.$root.$emit('bv::toast:hidden', { componentId: 'some other toast' });
48
+ expect(onCompleteSpy).not.toHaveBeenCalled();
49
+
50
+ // Pretend our toast was hidden.
51
+ wrapper.vm.$root.$emit('bv::toast:hidden', { componentId: toast.id });
52
+ expect(onCompleteSpy).toHaveBeenCalledTimes(1);
53
+ });
54
+
55
+ it('unregisters global listener when toast is hidden', () => {
56
+ expect(onCompleteSpy).not.toHaveBeenCalled();
57
+
58
+ // Pretend the toast was hidden. Can't seem to do this directly in jsdom,
59
+ // so fake it by emitting the event we listen for.
60
+ const event = { componentId: toast.id };
61
+ wrapper.vm.$root.$emit('bv::toast:hidden', event);
62
+ expect(onCompleteSpy).toHaveBeenCalledTimes(1);
63
+
64
+ // This event won't fire more than once in practice, as once the toast is
65
+ // hidden it'll be destroyed and won't show again. This is here to
66
+ // indirectly test that the listener was removed when the toast is
67
+ // hidden the first time.
68
+ wrapper.vm.$root.$emit('bv::toast:hidden', event);
69
+ expect(onCompleteSpy).toHaveBeenCalledTimes(1);
70
+ });
71
+ });
30
72
  });