vue-instantsearch 3.6.0 → 3.9.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.
Files changed (96) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/dist/vue-instantsearch.common.js +1 -1
  3. package/dist/vue-instantsearch.common.js.map +1 -1
  4. package/dist/vue-instantsearch.js +1 -1
  5. package/dist/vue-instantsearch.js.map +1 -1
  6. package/es/package.json.js +1 -1
  7. package/es/src/components/Breadcrumb.vue.js +1 -1
  8. package/es/src/components/Breadcrumb.vue.js.map +1 -1
  9. package/es/src/components/ClearRefinements.vue.js +1 -1
  10. package/es/src/components/ClearRefinements.vue.js.map +1 -1
  11. package/es/src/components/CurrentRefinements.vue.js +1 -1
  12. package/es/src/components/CurrentRefinements.vue.js.map +1 -1
  13. package/es/src/components/DynamicWidgets.js +2 -0
  14. package/es/src/components/DynamicWidgets.js.map +1 -0
  15. package/es/src/components/HierarchicalMenu.vue.js +1 -1
  16. package/es/src/components/HierarchicalMenu.vue.js.map +1 -1
  17. package/es/src/components/Highlighter.vue.js.map +1 -1
  18. package/es/src/components/Hits.vue.js +1 -1
  19. package/es/src/components/Hits.vue.js.map +1 -1
  20. package/es/src/components/HitsPerPage.vue.js +1 -1
  21. package/es/src/components/HitsPerPage.vue.js.map +1 -1
  22. package/es/src/components/InfiniteHits.vue.js +1 -1
  23. package/es/src/components/InfiniteHits.vue.js.map +1 -1
  24. package/es/src/components/InstantSearch.js +1 -1
  25. package/es/src/components/InstantSearch.js.map +1 -1
  26. package/es/src/components/Menu.vue.js +1 -1
  27. package/es/src/components/Menu.vue.js.map +1 -1
  28. package/es/src/components/MenuSelect.vue.js +1 -1
  29. package/es/src/components/MenuSelect.vue.js.map +1 -1
  30. package/es/src/components/NumericMenu.vue.js +1 -1
  31. package/es/src/components/NumericMenu.vue.js.map +1 -1
  32. package/es/src/components/Pagination.vue.js +1 -1
  33. package/es/src/components/Pagination.vue.js.map +1 -1
  34. package/es/src/components/QueryRuleContext.js +1 -1
  35. package/es/src/components/QueryRuleContext.js.map +1 -1
  36. package/es/src/components/QueryRuleCustomData.vue.js +1 -1
  37. package/es/src/components/QueryRuleCustomData.vue.js.map +1 -1
  38. package/es/src/components/RangeInput.vue.js +1 -1
  39. package/es/src/components/RangeInput.vue.js.map +1 -1
  40. package/es/src/components/RatingMenu.vue.js +1 -1
  41. package/es/src/components/RatingMenu.vue.js.map +1 -1
  42. package/es/src/components/RefinementList.vue.js +1 -1
  43. package/es/src/components/RefinementList.vue.js.map +1 -1
  44. package/es/src/components/SortBy.vue.js +1 -1
  45. package/es/src/components/SortBy.vue.js.map +1 -1
  46. package/es/src/components/ToggleRefinement.vue.js +1 -1
  47. package/es/src/components/ToggleRefinement.vue.js.map +1 -1
  48. package/es/src/instantsearch.js +1 -1
  49. package/es/src/mixins/widget.js +1 -1
  50. package/es/src/mixins/widget.js.map +1 -1
  51. package/es/src/util/createInstantSearchComponent.js +1 -1
  52. package/es/src/util/createInstantSearchComponent.js.map +1 -1
  53. package/es/src/util/createServerRootMixin.js +1 -1
  54. package/es/src/util/createServerRootMixin.js.map +1 -1
  55. package/es/src/widgets.js +1 -1
  56. package/package.json +5 -5
  57. package/src/__tests__/index.js +2 -0
  58. package/src/components/Breadcrumb.vue +3 -5
  59. package/src/components/ClearRefinements.vue +3 -7
  60. package/src/components/CurrentRefinements.vue +3 -7
  61. package/src/components/DynamicWidgets.js +87 -0
  62. package/src/components/HierarchicalMenu.vue +8 -11
  63. package/src/components/Highlighter.vue +16 -4
  64. package/src/components/Hits.vue +2 -3
  65. package/src/components/HitsPerPage.vue +1 -4
  66. package/src/components/InfiniteHits.vue +2 -3
  67. package/src/components/InstantSearch.js +11 -7
  68. package/src/components/Menu.vue +5 -8
  69. package/src/components/MenuSelect.vue +2 -3
  70. package/src/components/NumericMenu.vue +2 -3
  71. package/src/components/Pagination.vue +1 -1
  72. package/src/components/QueryRuleContext.js +1 -1
  73. package/src/components/QueryRuleCustomData.vue +1 -1
  74. package/src/components/RangeInput.vue +3 -0
  75. package/src/components/RatingMenu.vue +2 -1
  76. package/src/components/RefinementList.vue +8 -7
  77. package/src/components/SortBy.vue +1 -3
  78. package/src/components/ToggleRefinement.vue +1 -2
  79. package/src/components/__tests__/DynamicWidgets.js +419 -0
  80. package/src/components/__tests__/HierarchicalMenu.js +23 -0
  81. package/src/components/__tests__/Hits.js +22 -1
  82. package/src/components/__tests__/InfiniteHits.js +21 -0
  83. package/src/components/__tests__/InstantSearch-integration.js +155 -1
  84. package/src/components/__tests__/Menu.js +22 -0
  85. package/src/components/__tests__/MenuSelect.js +22 -0
  86. package/src/components/__tests__/NumericMenu.js +22 -0
  87. package/src/components/__tests__/RangeInput.js +22 -0
  88. package/src/components/__tests__/RatingMenu.js +23 -0
  89. package/src/components/__tests__/RefinementList.js +22 -0
  90. package/src/components/__tests__/ToggleRefinement.js +22 -0
  91. package/src/mixins/widget.js +1 -1
  92. package/src/util/__tests__/createServerRootMixin.test.js +229 -83
  93. package/src/util/createInstantSearchComponent.js +16 -0
  94. package/src/util/createServerRootMixin.js +40 -104
  95. package/src/util/testutils/helper.js +2 -2
  96. package/src/widgets.js +1 -0
@@ -0,0 +1,419 @@
1
+ import { createLocalVue, mount } from '@vue/test-utils';
2
+ import DynamicWidgets from '../DynamicWidgets';
3
+ import { __setState } from '../../mixins/widget';
4
+ import { plugin } from '../../plugin';
5
+ jest.mock('../../mixins/widget');
6
+
7
+ const MockRefinementList = {
8
+ props: { attribute: { type: String } },
9
+ render(h) {
10
+ return h('div', {
11
+ attrs: {
12
+ 'widget-name': 'ais-refinement-list',
13
+ attribute: this.attribute,
14
+ },
15
+ });
16
+ },
17
+ };
18
+
19
+ const MockMenu = {
20
+ props: { attribute: { type: String } },
21
+ render(h) {
22
+ return h('div', {
23
+ attrs: { 'widget-name': 'ais-menu', attribute: this.attribute },
24
+ });
25
+ },
26
+ };
27
+
28
+ const MockHierarchicalMenu = {
29
+ props: { attributes: { type: Array } },
30
+ render(h) {
31
+ return h('div', {
32
+ attrs: {
33
+ 'widget-name': 'ais-hierarchical-menu',
34
+ attributes: this.attributes,
35
+ },
36
+ });
37
+ },
38
+ };
39
+
40
+ it('renders all children without state', () => {
41
+ const localVue = createLocalVue();
42
+
43
+ localVue.use(plugin);
44
+
45
+ __setState(null);
46
+
47
+ const wrapper = mount(DynamicWidgets, {
48
+ localVue,
49
+ propsData: {
50
+ transformItems: items => items,
51
+ },
52
+ slots: {
53
+ default: `
54
+ <ais-refinement-list attribute="test1"/>
55
+ <ais-menu attribute="test2"/>
56
+ <ais-panel>
57
+ <ais-hierarchical-menu :attributes="['test3', 'test4']" />
58
+ </ais-panel>
59
+ `,
60
+ },
61
+ stubs: {
62
+ 'ais-refinement-list': MockRefinementList,
63
+ 'ais-menu': MockMenu,
64
+ 'ais-hierarchical-menu': MockHierarchicalMenu,
65
+ },
66
+ });
67
+
68
+ expect(wrapper.html()).toMatchInlineSnapshot(`
69
+
70
+ <div hidden="hidden"
71
+ class="ais-DynamicWidgets"
72
+ >
73
+ <div class="ais-DynamicWidgets-widget">
74
+ <div widget-name="ais-refinement-list"
75
+ attribute="test1"
76
+ >
77
+ </div>
78
+ </div>
79
+ <div class="ais-DynamicWidgets-widget">
80
+ <div widget-name="ais-menu"
81
+ attribute="test2"
82
+ >
83
+ </div>
84
+ </div>
85
+ <div class="ais-DynamicWidgets-widget">
86
+ <div class="ais-Panel">
87
+ <div class="ais-Panel-body">
88
+ <div widget-name="ais-hierarchical-menu"
89
+ attributes="test3,test4"
90
+ >
91
+ </div>
92
+ </div>
93
+ </div>
94
+ </div>
95
+ </div>
96
+
97
+ `);
98
+ });
99
+
100
+ it('renders nothing without children', () => {
101
+ __setState({
102
+ attributesToRender: ['something-that-does-not-show'],
103
+ });
104
+
105
+ const wrapper = mount(DynamicWidgets, {
106
+ propsData: {
107
+ transformItems: items => items,
108
+ },
109
+ });
110
+ expect(wrapper.html()).toMatchInlineSnapshot(`
111
+
112
+ <div class="ais-DynamicWidgets">
113
+ </div>
114
+
115
+ `);
116
+ });
117
+
118
+ it('renders nothing with empty attributesToRender', () => {
119
+ const localVue = createLocalVue();
120
+
121
+ localVue.use(plugin);
122
+
123
+ __setState({
124
+ attributesToRender: [],
125
+ });
126
+
127
+ const wrapper = mount(DynamicWidgets, {
128
+ localVue,
129
+ propsData: {
130
+ transformItems: items => items,
131
+ },
132
+ slots: {
133
+ default: `<ais-refinement-list attribute="test1"/>`,
134
+ },
135
+ stubs: {
136
+ 'ais-refinement-list': MockRefinementList,
137
+ },
138
+ });
139
+
140
+ expect(wrapper.html()).toMatchInlineSnapshot(`
141
+
142
+ <div class="ais-DynamicWidgets">
143
+ </div>
144
+
145
+ `);
146
+ });
147
+
148
+ it('renders attributesToRender (menu)', () => {
149
+ const localVue = createLocalVue();
150
+
151
+ localVue.use(plugin);
152
+
153
+ __setState({
154
+ attributesToRender: ['test1'],
155
+ });
156
+
157
+ const wrapper = mount(DynamicWidgets, {
158
+ localVue,
159
+ propsData: {
160
+ transformItems: items => items,
161
+ },
162
+ slots: {
163
+ default: `
164
+ <ais-menu attribute="test1" />
165
+ <ais-refinement-list attribute="test2" />
166
+ `,
167
+ },
168
+ stubs: {
169
+ 'ais-refinement-list': MockRefinementList,
170
+ 'ais-menu': MockMenu,
171
+ },
172
+ });
173
+
174
+ expect(wrapper.html()).toMatchInlineSnapshot(`
175
+
176
+ <div class="ais-DynamicWidgets">
177
+ <div class="ais-DynamicWidgets-widget">
178
+ <div widget-name="ais-menu"
179
+ attribute="test1"
180
+ >
181
+ </div>
182
+ </div>
183
+ </div>
184
+
185
+ `);
186
+ });
187
+
188
+ it('renders attributesToRender (refinement list)', () => {
189
+ const localVue = createLocalVue();
190
+
191
+ localVue.use(plugin);
192
+
193
+ __setState({
194
+ attributesToRender: ['test2'],
195
+ });
196
+
197
+ const wrapper = mount(DynamicWidgets, {
198
+ localVue,
199
+ propsData: {
200
+ transformItems: items => items,
201
+ },
202
+ slots: {
203
+ default: `
204
+ <ais-menu attribute="test1" />
205
+ <ais-refinement-list attribute="test2" />
206
+ `,
207
+ },
208
+ stubs: {
209
+ 'ais-refinement-list': MockRefinementList,
210
+ 'ais-menu': MockMenu,
211
+ },
212
+ });
213
+
214
+ expect(wrapper.html()).toMatchInlineSnapshot(`
215
+
216
+ <div class="ais-DynamicWidgets">
217
+ <div class="ais-DynamicWidgets-widget">
218
+ <div widget-name="ais-refinement-list"
219
+ attribute="test2"
220
+ >
221
+ </div>
222
+ </div>
223
+ </div>
224
+
225
+ `);
226
+ });
227
+
228
+ it('renders attributesToRender (panel)', () => {
229
+ const localVue = createLocalVue();
230
+
231
+ localVue.use(plugin);
232
+
233
+ __setState({
234
+ attributesToRender: ['test2'],
235
+ });
236
+
237
+ const wrapper = mount(DynamicWidgets, {
238
+ localVue,
239
+ propsData: {
240
+ transformItems: items => items,
241
+ },
242
+ slots: {
243
+ default: `
244
+ <ais-menu attribute="test1" />
245
+ <ais-panel>
246
+ <ais-refinement-list attribute="test2" />
247
+ </ais-panel>
248
+ `,
249
+ },
250
+ stubs: {
251
+ 'ais-refinement-list': MockRefinementList,
252
+ 'ais-menu': MockMenu,
253
+ },
254
+ });
255
+
256
+ expect(wrapper.html()).toMatchInlineSnapshot(`
257
+
258
+ <div class="ais-DynamicWidgets">
259
+ <div class="ais-DynamicWidgets-widget">
260
+ <div class="ais-Panel">
261
+ <div class="ais-Panel-body">
262
+ <div widget-name="ais-refinement-list"
263
+ attribute="test2"
264
+ >
265
+ </div>
266
+ </div>
267
+ </div>
268
+ </div>
269
+ </div>
270
+
271
+ `);
272
+ });
273
+
274
+ it('renders attributesToRender (hierarchical menu)', () => {
275
+ const localVue = createLocalVue();
276
+
277
+ localVue.use(plugin);
278
+
279
+ __setState({
280
+ attributesToRender: ['test1'],
281
+ });
282
+
283
+ const wrapper = mount(DynamicWidgets, {
284
+ localVue,
285
+ propsData: {
286
+ transformItems: items => items,
287
+ },
288
+ slots: {
289
+ default: `
290
+ <ais-hierarchical-menu :attributes="['test1','test2']" />
291
+ <ais-menu attribute="test3" />
292
+ <ais-panel>
293
+ <ais-refinement-list attribute="test4" />
294
+ </ais-panel>
295
+ `,
296
+ },
297
+ stubs: {
298
+ 'ais-refinement-list': MockRefinementList,
299
+ 'ais-menu': MockMenu,
300
+ 'ais-hierarchical-menu': MockHierarchicalMenu,
301
+ },
302
+ });
303
+
304
+ expect(wrapper.html()).toMatchInlineSnapshot(`
305
+
306
+ <div class="ais-DynamicWidgets">
307
+ <div class="ais-DynamicWidgets-widget">
308
+ <div widget-name="ais-hierarchical-menu"
309
+ attributes="test1,test2"
310
+ >
311
+ </div>
312
+ </div>
313
+ </div>
314
+
315
+ `);
316
+ });
317
+
318
+ it('updates DOM when attributesToRender changes', () => {
319
+ const localVue = createLocalVue();
320
+
321
+ localVue.use(plugin);
322
+
323
+ let attributesToRender = ['test1'];
324
+
325
+ __setState({
326
+ get attributesToRender() {
327
+ return attributesToRender;
328
+ },
329
+ });
330
+
331
+ const wrapper = mount(DynamicWidgets, {
332
+ localVue,
333
+ propsData: {
334
+ transformItems: items => items,
335
+ },
336
+ slots: {
337
+ default: `
338
+ <ais-hierarchical-menu :attributes="['test1','test2']" />
339
+ <ais-menu attribute="test3" />
340
+ <ais-panel>
341
+ <ais-refinement-list attribute="test4" />
342
+ </ais-panel>
343
+ `,
344
+ },
345
+ stubs: {
346
+ 'ais-refinement-list': MockRefinementList,
347
+ 'ais-menu': MockMenu,
348
+ 'ais-hierarchical-menu': MockHierarchicalMenu,
349
+ },
350
+ });
351
+
352
+ expect(wrapper.html()).toMatchInlineSnapshot(`
353
+
354
+ <div class="ais-DynamicWidgets">
355
+ <div class="ais-DynamicWidgets-widget">
356
+ <div widget-name="ais-hierarchical-menu"
357
+ attributes="test1,test2"
358
+ >
359
+ </div>
360
+ </div>
361
+ </div>
362
+
363
+ `);
364
+
365
+ attributesToRender = ['test3'];
366
+
367
+ wrapper.vm.$forceUpdate();
368
+
369
+ expect(wrapper.html()).toMatchInlineSnapshot(`
370
+
371
+ <div class="ais-DynamicWidgets">
372
+ <div class="ais-DynamicWidgets-widget">
373
+ <div widget-name="ais-menu"
374
+ attribute="test3"
375
+ >
376
+ </div>
377
+ </div>
378
+ </div>
379
+
380
+ `);
381
+
382
+ attributesToRender = ['test1', 'test4'];
383
+
384
+ wrapper.vm.$forceUpdate();
385
+
386
+ expect(wrapper.html()).toMatchInlineSnapshot(`
387
+
388
+ <div class="ais-DynamicWidgets">
389
+ <div class="ais-DynamicWidgets-widget">
390
+ <div widget-name="ais-hierarchical-menu"
391
+ attributes="test1,test2"
392
+ >
393
+ </div>
394
+ </div>
395
+ <div class="ais-DynamicWidgets-widget">
396
+ <div class="ais-Panel">
397
+ <div class="ais-Panel-body">
398
+ <div widget-name="ais-refinement-list"
399
+ attribute="test4"
400
+ >
401
+ </div>
402
+ </div>
403
+ </div>
404
+ </div>
405
+ </div>
406
+
407
+ `);
408
+
409
+ attributesToRender = [];
410
+
411
+ wrapper.vm.$forceUpdate();
412
+
413
+ expect(wrapper.html()).toMatchInlineSnapshot(`
414
+
415
+ <div class="ais-DynamicWidgets">
416
+ </div>
417
+
418
+ `);
419
+ });
@@ -117,6 +117,7 @@ const defaultState = {
117
117
  isShowingMore: false,
118
118
  canToggleShowMore: true,
119
119
  toggleShowMore: () => {},
120
+ sendEvent: () => {},
120
121
  };
121
122
 
122
123
  const defaultProps = {
@@ -488,6 +489,28 @@ it('calls the Panel mixin with `items.length`', () => {
488
489
  expect(wrapper.vm.mapStateToCanRefine({})).toBe(false);
489
490
  });
490
491
 
492
+ it('exposes send-event method for insights middleware', () => {
493
+ const sendEvent = jest.fn();
494
+ __setState({
495
+ ...defaultState,
496
+ sendEvent,
497
+ });
498
+
499
+ const wrapper = mount(HierarchicalMenu, {
500
+ propsData: defaultProps,
501
+ scopedSlots: {
502
+ default: `
503
+ <div slot-scope="{ sendEvent }">
504
+ <button @click="sendEvent()">Send Event</button>
505
+ </div>
506
+ `,
507
+ },
508
+ });
509
+
510
+ wrapper.find('button').trigger('click');
511
+ expect(sendEvent).toHaveBeenCalledTimes(1);
512
+ });
513
+
491
514
  describe('custom default render', () => {
492
515
  const defaultScopedSlot = `
493
516
  <div
@@ -60,7 +60,7 @@ it('exposes insights prop to the default slot', () => {
60
60
  default: `
61
61
  <ul slot-scope="{ items, insights }">
62
62
  <li v-for="(item, itemIndex) in items" >
63
-
63
+
64
64
  <button :id="'add-to-cart-' + item.objectID" @click="insights('clickedObjectIDsAfterSearch', {eventName: 'Add to cart', objectIDs: [item.objectID]})">
65
65
  Add to cart
66
66
  </button>
@@ -100,3 +100,24 @@ it('exposes insights prop to the item slot', () => {
100
100
  objectIDs: ['two'],
101
101
  });
102
102
  });
103
+
104
+ it('exposes send-event method for insights middleware', () => {
105
+ const sendEvent = jest.fn();
106
+ __setState({
107
+ ...defaultState,
108
+ sendEvent,
109
+ });
110
+
111
+ const wrapper = mount(Hits, {
112
+ scopedSlots: {
113
+ default: `
114
+ <div slot-scope="{ sendEvent }">
115
+ <button @click="sendEvent()">Send Event</button>
116
+ </div>
117
+ `,
118
+ },
119
+ });
120
+
121
+ wrapper.find('button').trigger('click');
122
+ expect(sendEvent).toHaveBeenCalledTimes(1);
123
+ });
@@ -294,3 +294,24 @@ it('exposes insights prop to the item slot', () => {
294
294
  objectIDs: ['00002'],
295
295
  });
296
296
  });
297
+
298
+ it('exposes send-event method for insights middleware', () => {
299
+ const sendEvent = jest.fn();
300
+ __setState({
301
+ ...defaultState,
302
+ sendEvent,
303
+ });
304
+
305
+ const wrapper = mount(InfiniteHits, {
306
+ scopedSlots: {
307
+ default: `
308
+ <div slot-scope="{ sendEvent }">
309
+ <button @click="sendEvent()">Send Event</button>
310
+ </div>
311
+ `,
312
+ },
313
+ });
314
+
315
+ wrapper.find('button').trigger('click');
316
+ expect(sendEvent).toHaveBeenCalledTimes(1);
317
+ });
@@ -1,10 +1,13 @@
1
+ import Vue from 'vue';
1
2
  import { mount } from '@vue/test-utils';
2
3
  import InstantSearch from '../InstantSearch';
3
4
  import { createWidgetMixin } from '../../mixins/widget';
4
5
  import { createFakeClient } from '../../util/testutils/client';
5
-
6
+ import SearchBox from '../SearchBox.vue';
6
7
  jest.unmock('instantsearch.js/es');
7
8
 
9
+ const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
10
+
8
11
  it('child widgets get added to its parent instantsearch', () => {
9
12
  const widgetInstance = {
10
13
  render() {},
@@ -32,3 +35,154 @@ it('child widgets get added to its parent instantsearch', () => {
32
35
  widgetInstance
33
36
  );
34
37
  });
38
+
39
+ describe('middlewares', () => {
40
+ const createFakeMiddleware = () => {
41
+ const middlewareSpy = {
42
+ onStateChange: jest.fn(),
43
+ subscribe: jest.fn(),
44
+ unsubscribe: jest.fn(),
45
+ };
46
+ const middleware = jest.fn(() => middlewareSpy);
47
+
48
+ return [middleware, middlewareSpy];
49
+ };
50
+
51
+ it('subscribes middlewares', async () => {
52
+ const [middleware, middlewareSpy] = createFakeMiddleware();
53
+
54
+ mount(InstantSearch, {
55
+ propsData: {
56
+ searchClient: createFakeClient(),
57
+ indexName: 'indexName',
58
+ middlewares: [middleware],
59
+ },
60
+ });
61
+ await Vue.nextTick();
62
+
63
+ expect(middlewareSpy.subscribe).toHaveBeenCalledTimes(1);
64
+ });
65
+
66
+ it('subscribes newly added middleware', async () => {
67
+ const [middleware1, middlewareSpy1] = createFakeMiddleware();
68
+
69
+ const wrapper = mount({
70
+ components: {
71
+ AisInstantSearch: InstantSearch,
72
+ AisSearchBox: SearchBox,
73
+ },
74
+ template: `
75
+ <ais-instant-search
76
+ :search-client="searchClient"
77
+ :index-name="indexName"
78
+ :middlewares="middlewares"
79
+ >
80
+ <ais-search-box />
81
+ </ais-instant-search>
82
+ `,
83
+ data() {
84
+ return {
85
+ searchClient: createFakeClient(),
86
+ indexName: 'indexName',
87
+ middlewares: [middleware1],
88
+ };
89
+ },
90
+ });
91
+
92
+ await wait(20);
93
+ expect(middlewareSpy1.subscribe).toHaveBeenCalledTimes(1);
94
+
95
+ await wrapper.find('input').setValue('a');
96
+ await Vue.nextTick();
97
+
98
+ expect(middlewareSpy1.onStateChange).toHaveBeenCalledTimes(1);
99
+ expect(middlewareSpy1.onStateChange).toHaveBeenCalledWith({
100
+ uiState: { indexName: { query: 'a' } },
101
+ });
102
+
103
+ const [middleware2, middlewareSpy2] = createFakeMiddleware();
104
+ wrapper.setData({
105
+ middlewares: [middleware1, middleware2],
106
+ });
107
+ await Vue.nextTick();
108
+
109
+ expect(middlewareSpy2.subscribe).toHaveBeenCalledTimes(1);
110
+ expect(middlewareSpy2.onStateChange).toHaveBeenCalledTimes(0);
111
+
112
+ await wrapper.find('input').setValue('b');
113
+ await Vue.nextTick();
114
+
115
+ expect(middlewareSpy1.onStateChange).toHaveBeenCalledTimes(2);
116
+ expect(middlewareSpy1.onStateChange).toHaveBeenCalledWith({
117
+ uiState: { indexName: { query: 'b' } },
118
+ });
119
+ expect(middlewareSpy2.onStateChange).toHaveBeenCalledTimes(1);
120
+ expect(middlewareSpy2.onStateChange).toHaveBeenCalledWith({
121
+ uiState: { indexName: { query: 'b' } },
122
+ });
123
+
124
+ expect(middlewareSpy1.unsubscribe).toHaveBeenCalledTimes(0);
125
+ expect(middlewareSpy2.unsubscribe).toHaveBeenCalledTimes(0);
126
+ });
127
+
128
+ it('unsubscribes removed middleware', async () => {
129
+ const [middleware1, middlewareSpy1] = createFakeMiddleware();
130
+ const [middleware2, middlewareSpy2] = createFakeMiddleware();
131
+
132
+ const wrapper = mount({
133
+ components: {
134
+ AisInstantSearch: InstantSearch,
135
+ AisSearchBox: SearchBox,
136
+ },
137
+ template: `
138
+ <ais-instant-search
139
+ :search-client="searchClient"
140
+ :index-name="indexName"
141
+ :middlewares="middlewares"
142
+ >
143
+ <ais-search-box />
144
+ </ais-instant-search>
145
+ `,
146
+ data() {
147
+ return {
148
+ searchClient: createFakeClient(),
149
+ indexName: 'indexName',
150
+ middlewares: [middleware1, middleware2],
151
+ };
152
+ },
153
+ });
154
+
155
+ await wait(20);
156
+ expect(middlewareSpy1.subscribe).toHaveBeenCalledTimes(1);
157
+ expect(middlewareSpy2.subscribe).toHaveBeenCalledTimes(1);
158
+
159
+ await wrapper.find('input').setValue('a');
160
+ await Vue.nextTick();
161
+
162
+ expect(middlewareSpy1.onStateChange).toHaveBeenCalledTimes(1);
163
+ expect(middlewareSpy1.onStateChange).toHaveBeenCalledWith({
164
+ uiState: { indexName: { query: 'a' } },
165
+ });
166
+ expect(middlewareSpy2.onStateChange).toHaveBeenCalledTimes(1);
167
+ expect(middlewareSpy2.onStateChange).toHaveBeenCalledWith({
168
+ uiState: { indexName: { query: 'a' } },
169
+ });
170
+
171
+ wrapper.setData({
172
+ middlewares: [middleware1],
173
+ });
174
+ await Vue.nextTick();
175
+
176
+ expect(middlewareSpy1.unsubscribe).toHaveBeenCalledTimes(0);
177
+ expect(middlewareSpy2.unsubscribe).toHaveBeenCalledTimes(1);
178
+
179
+ await wrapper.find('input').setValue('b');
180
+ await Vue.nextTick();
181
+
182
+ expect(middlewareSpy1.onStateChange).toHaveBeenCalledTimes(2);
183
+ expect(middlewareSpy1.onStateChange).toHaveBeenCalledWith({
184
+ uiState: { indexName: { query: 'b' } },
185
+ });
186
+ expect(middlewareSpy2.onStateChange).toHaveBeenCalledTimes(1);
187
+ });
188
+ });
@@ -337,6 +337,28 @@ it('calls the Panel mixin with `canRefine`', () => {
337
337
  expect(wrapper.vm.mapStateToCanRefine({})).toBe(false);
338
338
  });
339
339
 
340
+ it('exposes send-event method for insights middleware', () => {
341
+ const sendEvent = jest.fn();
342
+ __setState({
343
+ ...defaultState,
344
+ sendEvent,
345
+ });
346
+
347
+ const wrapper = mount(Menu, {
348
+ propsData: defaultProps,
349
+ scopedSlots: {
350
+ default: `
351
+ <div slot-scope="{ sendEvent }">
352
+ <button @click="sendEvent()">Send Event</button>
353
+ </div>
354
+ `,
355
+ },
356
+ });
357
+
358
+ wrapper.find('button').trigger('click');
359
+ expect(sendEvent).toHaveBeenCalledTimes(1);
360
+ });
361
+
340
362
  describe('custom default render', () => {
341
363
  const defaultScopedSlot = `
342
364
  <div