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
@@ -224,6 +224,28 @@ it('calls the Panel mixin with `canRefine`', () => {
224
224
  expect(wrapper.vm.mapStateToCanRefine({})).toBe(false);
225
225
  });
226
226
 
227
+ it('exposes send-event method for insights middleware', () => {
228
+ const sendEvent = jest.fn();
229
+ __setState({
230
+ ...defaultState,
231
+ sendEvent,
232
+ });
233
+
234
+ const wrapper = mount(MenuSelect, {
235
+ propsData: defaultProps,
236
+ scopedSlots: {
237
+ default: `
238
+ <div slot-scope="{ sendEvent }">
239
+ <button @click="sendEvent()">Send Event</button>
240
+ </div>
241
+ `,
242
+ },
243
+ });
244
+
245
+ wrapper.find('button').trigger('click');
246
+ expect(sendEvent).toHaveBeenCalledTimes(1);
247
+ });
248
+
227
249
  describe('custom item slot', () => {
228
250
  // can not be <template>
229
251
  // https://github.com/vuejs/vue-test-utils/pull/507
@@ -210,6 +210,28 @@ it('calls the Panel mixin with `hasNoResults`', () => {
210
210
  expect(wrapper.vm.mapStateToCanRefine({})).toBe(false);
211
211
  });
212
212
 
213
+ it('exposes send-event method for insights middleware', () => {
214
+ const sendEvent = jest.fn();
215
+ __setState({
216
+ ...defaultState,
217
+ sendEvent,
218
+ });
219
+
220
+ const wrapper = mount(NumericMenu, {
221
+ propsData: defaultProps,
222
+ scopedSlots: {
223
+ default: `
224
+ <div slot-scope="{ sendEvent }">
225
+ <button @click="sendEvent()">Send Event</button>
226
+ </div>
227
+ `,
228
+ },
229
+ });
230
+
231
+ wrapper.find('button').trigger('click');
232
+ expect(sendEvent).toHaveBeenCalledTimes(1);
233
+ });
234
+
213
235
  describe('custom default render', () => {
214
236
  const defaultScopedSlot = `
215
237
  <ul
@@ -281,6 +281,28 @@ it('calls the Panel mixin with `range`', () => {
281
281
  expect(wrapper.vm.mapStateToCanRefine({})).toBe(false);
282
282
  });
283
283
 
284
+ it('exposes send-event method for insights middleware', () => {
285
+ const sendEvent = jest.fn();
286
+ __setState({
287
+ ...defaultState,
288
+ sendEvent,
289
+ });
290
+
291
+ const wrapper = mount(RangeInput, {
292
+ propsData: defaultProps,
293
+ scopedSlots: {
294
+ default: `
295
+ <div slot-scope="{ sendEvent }">
296
+ <button @click="sendEvent()">Send Event</button>
297
+ </div>
298
+ `,
299
+ },
300
+ });
301
+
302
+ wrapper.find('button').trigger('click');
303
+ expect(sendEvent).toHaveBeenCalledTimes(1);
304
+ });
305
+
284
306
  describe('refinement', () => {
285
307
  it('uses the value of the inputs when the form is submited', () => {
286
308
  const refine = jest.fn();
@@ -148,3 +148,26 @@ it('calls the Panel mixin with `hasNoResults`', () => {
148
148
 
149
149
  expect(wrapper.vm.mapStateToCanRefine({})).toBe(false);
150
150
  });
151
+
152
+ it('exposes send-event method for insights middleware', () => {
153
+ const sendEvent = jest.fn();
154
+ __setState({
155
+ createURL: () => '#',
156
+ items: [],
157
+ sendEvent,
158
+ });
159
+
160
+ const wrapper = mount(RatingMenu, {
161
+ propsData: defaultProps,
162
+ scopedSlots: {
163
+ default: `
164
+ <div slot-scope="{ sendEvent }">
165
+ <button @click="sendEvent()">Send Event</button>
166
+ </div>
167
+ `,
168
+ },
169
+ });
170
+
171
+ wrapper.find('button').trigger('click');
172
+ expect(sendEvent).toHaveBeenCalledTimes(1);
173
+ });
@@ -188,3 +188,25 @@ it('calls the Panel mixin with `canRefine`', () => {
188
188
 
189
189
  expect(wrapper.vm.mapStateToCanRefine({})).toBe(false);
190
190
  });
191
+
192
+ it('exposes send-event method for insights middleware', () => {
193
+ const sendEvent = jest.fn();
194
+ __setState({
195
+ ...defaultState,
196
+ sendEvent,
197
+ });
198
+
199
+ const wrapper = mount(RefinementList, {
200
+ propsData: { attribute: 'something' },
201
+ scopedSlots: {
202
+ default: `
203
+ <div slot-scope="{ sendEvent }">
204
+ <button @click="sendEvent()">Send Event</button>
205
+ </div>
206
+ `,
207
+ },
208
+ });
209
+
210
+ wrapper.find('button').trigger('click');
211
+ expect(sendEvent).toHaveBeenCalledTimes(1);
212
+ });
@@ -195,6 +195,28 @@ it('calls the Panel mixin with `value.count`', () => {
195
195
  expect(wrapper.vm.mapStateToCanRefine({})).toBe(false);
196
196
  });
197
197
 
198
+ it('exposes send-event method for insights middleware', () => {
199
+ const sendEvent = jest.fn();
200
+ __setState({
201
+ ...defaultState,
202
+ sendEvent,
203
+ });
204
+
205
+ const wrapper = mount(Toggle, {
206
+ propsData: defaultProps,
207
+ scopedSlots: {
208
+ default: `
209
+ <div slot-scope="{ sendEvent }">
210
+ <button @click="sendEvent()">Send Event</button>
211
+ </div>
212
+ `,
213
+ },
214
+ });
215
+
216
+ wrapper.find('button').trigger('click');
217
+ expect(sendEvent).toHaveBeenCalledTimes(1);
218
+ });
219
+
198
220
  describe('custom default render', () => {
199
221
  const defaultScopedSlot = `
200
222
  <a
@@ -30,7 +30,7 @@ export const createWidgetMixin = ({ connector } = {}) => ({
30
30
  this.getParentIndex().addWidgets([this.widget]);
31
31
 
32
32
  if (
33
- this.instantSearchInstance.__initialSearchResults &&
33
+ this.instantSearchInstance._initialResults &&
34
34
  !this.instantSearchInstance.started
35
35
  ) {
36
36
  if (typeof this.instantSearchInstance.__forceRender !== 'function') {
@@ -10,11 +10,7 @@ import SearchBox from '../../components/SearchBox.vue';
10
10
  import { createWidgetMixin } from '../../mixins/widget';
11
11
  import { createFakeClient } from '../testutils/client';
12
12
  import { createSerializedState } from '../testutils/helper';
13
- import {
14
- SearchResults,
15
- SearchParameters,
16
- AlgoliaSearchHelper,
17
- } from 'algoliasearch-helper';
13
+ import { SearchParameters, SearchResults } from 'algoliasearch-helper';
18
14
 
19
15
  jest.unmock('instantsearch.js/es');
20
16
 
@@ -54,9 +50,11 @@ describe('createServerRootMixin', () => {
54
50
  }),
55
51
  ],
56
52
  })
57
- ).toThrowErrorMatchingInlineSnapshot(
58
- `"createServerRootMixin requires \`searchClient\` and \`indexName\` in the first argument"`
59
- );
53
+ ).toThrowErrorMatchingInlineSnapshot(`
54
+ "The \`searchClient\` option is required.
55
+
56
+ See documentation: https://www.algolia.com/doc/api-reference/widgets/instantsearch/js/"
57
+ `);
60
58
  });
61
59
 
62
60
  it('requires indexName', () => {
@@ -70,9 +68,11 @@ describe('createServerRootMixin', () => {
70
68
  }),
71
69
  ],
72
70
  })
73
- ).toThrowErrorMatchingInlineSnapshot(
74
- `"createServerRootMixin requires \`searchClient\` and \`indexName\` in the first argument"`
75
- );
71
+ ).toThrowErrorMatchingInlineSnapshot(`
72
+ "The \`indexName\` option is required.
73
+
74
+ See documentation: https://www.algolia.com/doc/api-reference/widgets/instantsearch/js/"
75
+ `);
76
76
  });
77
77
 
78
78
  it('creates an instantsearch instance on "data"', () => {
@@ -246,6 +246,7 @@ Array [
246
246
  h(SearchBox),
247
247
  ]);
248
248
  },
249
+
249
250
  serverPrefetch() {
250
251
  return this.instantsearch.findResultsState(this);
251
252
  },
@@ -388,7 +389,7 @@ Array [
388
389
  this.instantsearch.mainIndex.getWidgets().map(w => w.$$type)
389
390
  ).toEqual(['ais.configure']);
390
391
 
391
- expect(res.hello._state.hitsPerPage).toBe(100);
392
+ expect(res.hello.state.hitsPerPage).toBe(100);
392
393
  })
393
394
  // jest throws an error we need to catch, since stuck in the flow
394
395
  .catch(e => {
@@ -423,7 +424,7 @@ Array [
423
424
 
424
425
  expect.assertions(2);
425
426
 
426
- const App = Vue.component('App', {
427
+ const App = {
427
428
  mixins: [
428
429
  forceIsServerMixin,
429
430
  createServerRootMixin({
@@ -431,11 +432,8 @@ Array [
431
432
  indexName: 'hello',
432
433
  }),
433
434
  ],
434
- render(h) {
435
- return h(InstantSearchSsr, {}, [
436
- this.$scopedSlots.default({ test: true }),
437
- ]);
438
- },
435
+ render: h =>
436
+ h(InstantSearchSsr, {}, [this.$scopedSlots.default({ test: true })]),
439
437
  serverPrefetch() {
440
438
  return (
441
439
  this.instantsearch
@@ -453,26 +451,23 @@ Array [
453
451
  })
454
452
  );
455
453
  },
456
- });
454
+ };
457
455
 
458
456
  const wrapper = new Vue({
459
457
  mixins: [forceIsServerMixin],
460
- render(h) {
461
- return h(App, {
458
+ render: h =>
459
+ h(App, {
462
460
  scopedSlots: {
463
461
  default({ test }) {
464
462
  if (test) {
465
463
  return h(Configure, {
466
- attrs: {
467
- hitsPerPage: 100,
468
- },
464
+ hitsPerPage: 100,
469
465
  });
470
466
  }
471
467
  return null;
472
468
  },
473
469
  },
474
- });
475
- },
470
+ }),
476
471
  });
477
472
 
478
473
  await renderToString(wrapper);
@@ -519,63 +514,76 @@ Array [
519
514
 
520
515
  await renderToString(wrapper);
521
516
  });
522
- });
523
-
524
- describe('hydrate', () => {
525
- it('sets __initialSearchResults', () => {
526
- const serialized = createSerializedState();
527
517
 
518
+ it('searches only once', async () => {
519
+ const searchClient = createFakeClient();
528
520
  const app = {
529
521
  mixins: [
522
+ forceIsServerMixin,
530
523
  createServerRootMixin({
531
- searchClient: createFakeClient(),
524
+ searchClient,
532
525
  indexName: 'hello',
533
526
  }),
534
527
  ],
535
- render(h) {
536
- return h(InstantSearchSsr, {}, [
528
+ render: h =>
529
+ /**
530
+ * This code triggers this warning in Vue 3:
531
+ * > Non-function value encountered for default slot. Prefer function slots for better performance.
532
+ *
533
+ * To fix it, replace the third argument
534
+ * > [h(...), h(...)]
535
+ * with
536
+ * > { default: () => [h(...), h(...)] }
537
+ *
538
+ * but it's not important (and not compatible in vue2), we're leaving it as-is.
539
+ */
540
+ h(InstantSearchSsr, {}, [
537
541
  h(Configure, {
538
542
  attrs: {
539
543
  hitsPerPage: 100,
540
544
  },
541
545
  }),
542
546
  h(SearchBox),
543
- ]);
544
- },
545
- // in test, beforeCreated doesn't have $data yet, but IRL it does
546
- created() {
547
- this.instantsearch.hydrate({
548
- __identifier: 'stringified',
549
- hello: serialized,
550
- });
547
+ ]),
548
+ serverPrefetch() {
549
+ return this.instantsearch.findResultsState(this);
551
550
  },
552
551
  };
553
552
 
554
- const {
555
- vm: { instantsearch },
556
- } = mount(app);
553
+ const wrapper = new Vue({
554
+ mixins: [forceIsServerMixin],
555
+ render: h => h(app),
556
+ });
557
557
 
558
- expect(instantsearch.__initialSearchResults).toEqual(
559
- expect.objectContaining({ hello: expect.any(SearchResults) })
560
- );
558
+ await renderToString(wrapper);
561
559
 
562
- expect(instantsearch.__initialSearchResults.hello).toEqual(
563
- expect.objectContaining(serialized)
564
- );
560
+ expect(searchClient.search).toHaveBeenCalledTimes(1);
561
+ expect(searchClient.search.mock.calls[0][0]).toMatchInlineSnapshot(`
562
+ Array [
563
+ Object {
564
+ "indexName": "hello",
565
+ "params": Object {
566
+ "facets": Array [],
567
+ "hitsPerPage": 100,
568
+ "query": "",
569
+ "tagFilters": "",
570
+ },
571
+ },
572
+ ]
573
+ `);
565
574
  });
575
+ });
566
576
 
567
- it('accepts non-stringified results', () => {
577
+ describe('hydrate', () => {
578
+ it('sets _initialResults', () => {
568
579
  const serialized = createSerializedState();
569
- const nonSerialized = new SearchResults(
570
- new SearchParameters(serialized._state),
571
- serialized._rawResults
572
- );
573
580
 
574
- const app = {
581
+ let instantsearch;
582
+ const app = new Vue({
575
583
  mixins: [
576
584
  createServerRootMixin({
577
585
  searchClient: createFakeClient(),
578
- indexName: 'movies',
586
+ indexName: 'hello',
579
587
  }),
580
588
  ],
581
589
  render(h) {
@@ -590,27 +598,35 @@ Array [
590
598
  },
591
599
  // in test, beforeCreated doesn't have $data yet, but IRL it does
592
600
  created() {
601
+ instantsearch = this.instantsearch;
593
602
  this.instantsearch.hydrate({
594
- movies: nonSerialized,
603
+ hello: serialized,
595
604
  });
596
605
  },
597
- };
606
+ });
598
607
 
599
- const {
600
- vm: { instantsearch },
601
- } = mount(app);
608
+ mount(app);
602
609
 
603
- expect(instantsearch.__initialSearchResults).toEqual(
604
- expect.objectContaining({ movies: expect.any(SearchResults) })
610
+ expect(instantsearch._initialResults).toEqual(
611
+ expect.objectContaining({
612
+ hello: {
613
+ state: expect.any(Object),
614
+ results: expect.any(Object),
615
+ },
616
+ })
605
617
  );
606
618
 
607
- expect(instantsearch.__initialSearchResults.movies).toBe(nonSerialized);
619
+ expect(instantsearch._initialResults.hello).toEqual(
620
+ expect.objectContaining(serialized)
621
+ );
608
622
  });
609
623
 
610
624
  it('inits the main index', () => {
611
625
  const serialized = createSerializedState();
612
626
 
613
- const app = {
627
+ let instantsearch;
628
+
629
+ const app = new Vue({
614
630
  mixins: [
615
631
  createServerRootMixin({
616
632
  searchClient: createFakeClient(),
@@ -627,28 +643,30 @@ Array [
627
643
  h(SearchBox),
628
644
  ]);
629
645
  },
630
- };
646
+ created() {
647
+ instantsearch = this.instantsearch;
648
+ },
649
+ });
631
650
 
632
- const {
633
- vm: { instantsearch },
634
- } = mount(app);
651
+ mount(app);
635
652
 
636
653
  expect(instantsearch.mainIndex.getHelper()).toBe(null);
637
654
 
638
655
  instantsearch.hydrate({
639
- __identifier: 'stringified',
640
656
  hello: serialized,
641
657
  });
642
658
 
643
- // TODO: assert that this is expect.any(AlgoliaSearchHelper), but test fails
644
- // even though it's an object with all the right properties (including constructor)
645
- expect(instantsearch.mainIndex.getHelper()).not.toBeNull();
659
+ expect(instantsearch.mainIndex.getHelper().constructor.name).toBe(
660
+ 'AlgoliaSearchHelper'
661
+ );
646
662
  });
647
663
 
648
664
  it('sets helper & mainHelper', () => {
649
665
  const serialized = createSerializedState();
650
666
 
651
- const app = {
667
+ let instantsearch;
668
+
669
+ const app = new Vue({
652
670
  mixins: [
653
671
  createServerRootMixin({
654
672
  searchClient: createFakeClient(),
@@ -665,22 +683,96 @@ Array [
665
683
  h(SearchBox),
666
684
  ]);
667
685
  },
668
- };
686
+ created() {
687
+ instantsearch = this.instantsearch;
688
+ },
689
+ });
669
690
 
670
- const {
671
- vm: { instantsearch },
672
- } = mount(app);
691
+ mount(app);
673
692
 
674
693
  expect(instantsearch.helper).toBe(null);
675
694
  expect(instantsearch.mainHelper).toBe(null);
676
695
 
677
696
  instantsearch.hydrate({
678
- __identifier: 'stringified',
679
697
  hello: serialized,
680
698
  });
681
699
 
682
- expect(instantsearch.helper).toEqual(expect.any(AlgoliaSearchHelper));
683
- expect(instantsearch.mainHelper).toEqual(expect.any(AlgoliaSearchHelper));
700
+ expect(instantsearch.helper.constructor.name).toBe('AlgoliaSearchHelper');
701
+ expect(instantsearch.mainHelper.constructor.name).toBe(
702
+ 'AlgoliaSearchHelper'
703
+ );
704
+ });
705
+
706
+ it('works when component is at root (and therefore has no $vnode)', async () => {
707
+ const searchClient = createFakeClient();
708
+ let mainIndex;
709
+
710
+ const app = {
711
+ render: h =>
712
+ /**
713
+ * This code triggers this warning in Vue 3:
714
+ * > Non-function value encountered for default slot. Prefer function slots for better performance.
715
+ *
716
+ * To fix it, replace the third argument
717
+ * > [h(...), h(...)]
718
+ * with
719
+ * > { default: () => [h(...), h(...)] }
720
+ *
721
+ * but it's not important (and not compatible in vue2), we're leaving it as-is.
722
+ */
723
+ h(InstantSearchSsr, {}, [
724
+ h(Configure, {
725
+ attrs: {
726
+ hitsPerPage: 100,
727
+ },
728
+ }),
729
+ h(SearchBox),
730
+ ]),
731
+ };
732
+
733
+ const wrapper = new Vue({
734
+ mixins: [
735
+ forceIsServerMixin,
736
+ createServerRootMixin({
737
+ searchClient,
738
+ indexName: 'hello',
739
+ }),
740
+ ],
741
+ serverPrefetch() {
742
+ return this.instantsearch.findResultsState(this);
743
+ },
744
+ created() {
745
+ mainIndex = this.instantsearch.mainIndex;
746
+ },
747
+ render: h => h(app),
748
+ });
749
+
750
+ await renderToString(wrapper);
751
+
752
+ expect(mainIndex.getWidgetState()).toMatchInlineSnapshot(`
753
+ Object {
754
+ "hello": Object {
755
+ "configure": Object {
756
+ "hitsPerPage": 100,
757
+ },
758
+ },
759
+ }
760
+ `);
761
+
762
+ expect(searchClient.search).toHaveBeenCalledTimes(1);
763
+ expect(searchClient.search.mock.calls[0][0]).toMatchInlineSnapshot(`
764
+ Array [
765
+ Object {
766
+ "indexName": "hello",
767
+ "params": Object {
768
+ "facets": Array [],
769
+ "hitsPerPage": 100,
770
+ "query": "",
771
+ "tagFilters": "",
772
+ },
773
+ },
774
+ ]
775
+ `);
684
776
  });
685
777
  });
686
778
 
@@ -727,6 +819,7 @@ Array [
727
819
  results: expect.anything(),
728
820
  }),
729
821
  ]),
822
+ parent: expect.anything(),
730
823
  state: expect.anything(),
731
824
  instantSearchInstance: expect.anything(),
732
825
  },
@@ -735,6 +828,7 @@ Object {
735
828
  "createURL": [Function],
736
829
  "helper": Anything,
737
830
  "instantSearchInstance": Anything,
831
+ "parent": Anything,
738
832
  "results": Anything,
739
833
  "scopedResults": ArrayContaining [
740
834
  ObjectContaining {
@@ -753,6 +847,58 @@ Object {
753
847
  );
754
848
  });
755
849
 
850
+ it('uses the results passed to hydrate for rendering', () => {
851
+ let instantSearchInstance;
852
+ mount({
853
+ mixins: [
854
+ createServerRootMixin({
855
+ searchClient: createFakeClient(),
856
+ indexName: 'lol',
857
+ }),
858
+ ],
859
+ created() {
860
+ instantSearchInstance = this.instantsearch;
861
+ },
862
+ render() {},
863
+ });
864
+
865
+ const widget = {
866
+ init: jest.fn(),
867
+ render: jest.fn(),
868
+ };
869
+
870
+ const resultsState = createSerializedState();
871
+ const state = new SearchParameters(resultsState.state);
872
+ const results = new SearchResults(state, resultsState.results);
873
+
874
+ instantSearchInstance.hydrate({
875
+ lol: resultsState,
876
+ });
877
+
878
+ instantSearchInstance.__forceRender(
879
+ widget,
880
+ instantSearchInstance.mainIndex
881
+ );
882
+
883
+ expect(widget.init).toHaveBeenCalledTimes(0);
884
+ expect(widget.render).toHaveBeenCalledTimes(1);
885
+
886
+ const renderArgs = widget.render.mock.calls[0][0];
887
+
888
+ expect(renderArgs).toEqual(
889
+ expect.objectContaining({
890
+ state,
891
+ results,
892
+ scopedResults: [
893
+ expect.objectContaining({
894
+ indexId: 'lol',
895
+ results,
896
+ }),
897
+ ],
898
+ })
899
+ );
900
+ });
901
+
756
902
  describe('createURL', () => {
757
903
  it('returns # if instantsearch has no routing', () => {
758
904
  const app = new Vue({
@@ -34,6 +34,22 @@ export const createInstantSearchComponent = component =>
34
34
  // private InstantSearch.js API:
35
35
  this.instantSearchInstance._searchFunction = searchFunction;
36
36
  },
37
+ middlewares: {
38
+ immediate: true,
39
+ handler(next, prev) {
40
+ (prev || [])
41
+ .filter(middleware => (next || []).indexOf(middleware) === -1)
42
+ .forEach(middlewareToRemove => {
43
+ this.instantSearchInstance.unuse(middlewareToRemove);
44
+ });
45
+
46
+ (next || [])
47
+ .filter(middleware => (prev || []).indexOf(middleware) === -1)
48
+ .forEach(middlewareToAdd => {
49
+ this.instantSearchInstance.use(middlewareToAdd);
50
+ });
51
+ },
52
+ },
37
53
  },
38
54
  created() {
39
55
  const searchClient = this.instantSearchInstance.client;