vue-instantsearch 4.0.1 → 4.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.
Files changed (42) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/package.json +5 -5
  3. package/src/components/DynamicWidgets.js +11 -1
  4. package/src/components/ExperimentalDynamicWidgets.js +10 -0
  5. package/src/components/__tests__/DynamicWidgets.js +45 -0
  6. package/src/components/__tests__/ExperimentalDynamicWidgets.js +41 -0
  7. package/src/instantsearch.umd.js +3 -2
  8. package/src/mixins/widget.js +1 -1
  9. package/src/util/__tests__/createServerRootMixin.test.js +141 -61
  10. package/src/util/createServerRootMixin.js +36 -107
  11. package/src/util/testutils/helper.js +2 -2
  12. package/src/widgets.js +3 -0
  13. package/vue2/cjs/index.js +1 -1
  14. package/vue2/cjs/index.js.map +1 -1
  15. package/vue2/es/package.json.js +1 -1
  16. package/vue2/es/src/components/DynamicWidgets.js +1 -1
  17. package/vue2/es/src/components/DynamicWidgets.js.map +1 -1
  18. package/vue2/es/src/components/ExperimentalDynamicWidgets.js +2 -0
  19. package/vue2/es/src/components/ExperimentalDynamicWidgets.js.map +1 -0
  20. package/vue2/es/src/instantsearch.js +1 -1
  21. package/vue2/es/src/mixins/widget.js +1 -1
  22. package/vue2/es/src/mixins/widget.js.map +1 -1
  23. package/vue2/es/src/util/createServerRootMixin.js +1 -1
  24. package/vue2/es/src/util/createServerRootMixin.js.map +1 -1
  25. package/vue2/es/src/widgets.js +1 -1
  26. package/vue2/umd/index.js +1 -1
  27. package/vue2/umd/index.js.map +1 -1
  28. package/vue3/cjs/index.js +1 -1
  29. package/vue3/cjs/index.js.map +1 -1
  30. package/vue3/es/package.json.js +1 -1
  31. package/vue3/es/src/components/DynamicWidgets.js +1 -1
  32. package/vue3/es/src/components/DynamicWidgets.js.map +1 -1
  33. package/vue3/es/src/components/ExperimentalDynamicWidgets.js +2 -0
  34. package/vue3/es/src/components/ExperimentalDynamicWidgets.js.map +1 -0
  35. package/vue3/es/src/instantsearch.js +1 -1
  36. package/vue3/es/src/mixins/widget.js +1 -1
  37. package/vue3/es/src/mixins/widget.js.map +1 -1
  38. package/vue3/es/src/util/createServerRootMixin.js +1 -1
  39. package/vue3/es/src/util/createServerRootMixin.js.map +1 -1
  40. package/vue3/es/src/widgets.js +1 -1
  41. package/vue3/umd/index.js +1 -1
  42. package/vue3/umd/index.js.map +1 -1
package/CHANGELOG.md CHANGED
@@ -1,3 +1,41 @@
1
+ # [4.3.0](https://github.com/algolia/vue-instantsearch/compare/v4.2.0...v4.3.0) (2021-12-16)
2
+
3
+
4
+ ### Features
5
+
6
+ * **dynamicWidgets:** pass parameters to connector ([#1093](https://github.com/algolia/vue-instantsearch/issues/1093)) ([247ed0f](https://github.com/algolia/vue-instantsearch/commit/247ed0f101b5dabd5eebf35a7b8c3a2504eef2e2))
7
+
8
+
9
+
10
+ # [4.2.0](https://github.com/algolia/vue-instantsearch/compare/v4.1.1...v4.2.0) (2021-12-13)
11
+
12
+
13
+ ### Features
14
+
15
+ * **dynamicWidgets:** release as production ([#1086](https://github.com/algolia/vue-instantsearch/issues/1086)) ([9e0902b](https://github.com/algolia/vue-instantsearch/commit/9e0902bc7a3afe3eb2211b78e681e65b3677dd78))
16
+ * **instantsearch:** update version ([dbf485f](https://github.com/algolia/vue-instantsearch/commit/dbf485f6263d9821aaf169e682ff1ab94157d15f))
17
+ * **ssr:** prevent initial network request ([#1090](https://github.com/algolia/vue-instantsearch/issues/1090)) ([d97eea2](https://github.com/algolia/vue-instantsearch/commit/d97eea21fbd059b0ca0634a6984a6a8b02acdd3d))
18
+
19
+
20
+
21
+ ## [4.1.1](https://github.com/algolia/vue-instantsearch/compare/v4.1.0...v4.1.1) (2021-10-27)
22
+
23
+
24
+ ### Bug Fixes
25
+
26
+ * **vue3:** disable automatic plugin registration ([#1081](https://github.com/algolia/vue-instantsearch/issues/1081)) ([6077413](https://github.com/algolia/vue-instantsearch/commit/607741312db620242b62a1fa2181563d87e69705))
27
+
28
+
29
+
30
+ # [4.1.0](https://github.com/algolia/vue-instantsearch/compare/v4.0.1...v4.1.0) (2021-10-26)
31
+
32
+
33
+ ### Features
34
+
35
+ * **dependencies:** update downstream ([#1075](https://github.com/algolia/vue-instantsearch/issues/1075)) ([ede11a5](https://github.com/algolia/vue-instantsearch/commit/ede11a5aa907f33123df7f19013680633228d0cf))
36
+
37
+
38
+
1
39
  ## [4.0.1](https://github.com/algolia/vue-instantsearch/compare/v4.0.0...v4.0.1) (2021-09-15)
2
40
 
3
41
 
package/package.json CHANGED
@@ -16,7 +16,7 @@
16
16
  "autocomplete"
17
17
  ],
18
18
  "license": "MIT",
19
- "version": "4.0.1",
19
+ "version": "4.3.0",
20
20
  "files": [
21
21
  "vue2",
22
22
  "vue3",
@@ -48,8 +48,7 @@
48
48
  "release": "shipjs prepare"
49
49
  },
50
50
  "dependencies": {
51
- "algoliasearch-helper": "^3.5.5",
52
- "instantsearch.js": "^4.30.0",
51
+ "instantsearch.js": "^4.36.0",
53
52
  "mitt": "^2.1.0"
54
53
  },
55
54
  "peerDependencies": {
@@ -82,6 +81,7 @@
82
81
  "@wdio/spec-reporter": "^5.11.7",
83
82
  "@wdio/static-server-service": "^5.11.0",
84
83
  "algoliasearch": "4.0.1",
84
+ "algoliasearch-helper": "3.7.0",
85
85
  "babel-eslint": "10.0.1",
86
86
  "babel-jest": "23.6.0",
87
87
  "babel-preset-es2015": "6.24.1",
@@ -132,11 +132,11 @@
132
132
  "bundlesize": [
133
133
  {
134
134
  "path": "./vue2/umd/index.js",
135
- "maxSize": "55.25 kB"
135
+ "maxSize": "56.00 kB"
136
136
  },
137
137
  {
138
138
  "path": "./vue3/umd/index.js",
139
- "maxSize": "56.75 kB"
139
+ "maxSize": "57.00 kB"
140
140
  },
141
141
  {
142
142
  "path": "./vue2/cjs/index.js",
@@ -40,7 +40,7 @@ function getWidgetAttribute(vnode) {
40
40
  }
41
41
 
42
42
  export default {
43
- name: 'AisExperimentalDynamicWidgets',
43
+ name: 'AisDynamicWidgets',
44
44
  mixins: [
45
45
  createWidgetMixin({ connector: connectDynamicWidgets }),
46
46
  createSuitMixin({ name: 'DynamicWidgets' }),
@@ -50,6 +50,14 @@ export default {
50
50
  type: Function,
51
51
  default: undefined,
52
52
  },
53
+ facets: {
54
+ type: Array,
55
+ default: undefined,
56
+ },
57
+ maxValuesPerFacet: {
58
+ type: Number,
59
+ default: undefined,
60
+ },
53
61
  },
54
62
  render: renderCompat(function(h) {
55
63
  const components = new Map();
@@ -91,6 +99,8 @@ export default {
91
99
  widgetParams() {
92
100
  return {
93
101
  transformItems: this.transformItems,
102
+ facets: this.facets,
103
+ maxValuesPerFacet: this.maxValuesPerFacet,
94
104
  // we do not pass "widgets" to the connector, since Vue is in charge of rendering
95
105
  widgets: [],
96
106
  };
@@ -0,0 +1,10 @@
1
+ import AisDynamicWidgets from './DynamicWidgets';
2
+ import { warn } from '../util/warn';
3
+
4
+ // @MAJOR remove this file
5
+ export default Object.assign({}, AisDynamicWidgets, {
6
+ name: 'AisExperimentalDynamicWidgets',
7
+ mounted() {
8
+ warn('Use AisDynamicWidgets instead of AisExperimentalDynamicWidgets.');
9
+ },
10
+ });
@@ -46,6 +46,51 @@ const MockHierarchicalMenu = {
46
46
  `,
47
47
  };
48
48
 
49
+ it('passes arguments to connector', () => {
50
+ __setState(null);
51
+
52
+ const transformItems = items => items;
53
+ const facets = ['test'];
54
+ const maxValuesPerFacet = 100;
55
+ const wrapper = mount({
56
+ data() {
57
+ return { props: { transformItems, facets, maxValuesPerFacet } };
58
+ },
59
+ template: `
60
+ <DynamicWidgets v-bind="props">
61
+ <MockRefinementList attribute="test1"/>
62
+ <MockMenu attribute="test2"/>
63
+ <AisPanel>
64
+ <MockHierarchicalMenu :attributes="['test3', 'test4']" />
65
+ </AisPanel>
66
+ </DynamicWidgets>
67
+ `,
68
+ components: {
69
+ DynamicWidgets,
70
+ MockRefinementList,
71
+ MockMenu,
72
+ MockHierarchicalMenu,
73
+ AisPanel,
74
+ },
75
+ });
76
+
77
+ const dynamicWidgets = wrapper.findComponent(DynamicWidgets);
78
+
79
+ expect(dynamicWidgets.props()).toEqual({
80
+ classNames: undefined,
81
+ transformItems,
82
+ facets: ['test'],
83
+ maxValuesPerFacet: 100,
84
+ });
85
+
86
+ expect(dynamicWidgets.vm.widgetParams).toEqual({
87
+ transformItems,
88
+ facets: ['test'],
89
+ maxValuesPerFacet: 100,
90
+ widgets: [],
91
+ });
92
+ });
93
+
49
94
  it('renders all children without state', () => {
50
95
  __setState(null);
51
96
 
@@ -0,0 +1,41 @@
1
+ import { mount } from '../../../test/utils';
2
+ import ExperimentalDynamicWidgets from '../ExperimentalDynamicWidgets';
3
+ import { warn } from '../../util/warn';
4
+ import { __setState } from '../../mixins/widget';
5
+ import DynamicWidgets from '../DynamicWidgets';
6
+
7
+ jest.mock('../../mixins/widget');
8
+ jest.mock('../../util/warn', () => ({ warn: jest.fn() }));
9
+
10
+ it('warns on mount', () => {
11
+ __setState(null);
12
+
13
+ mount({
14
+ template: '<ExperimentalDynamicWidgets />',
15
+ components: {
16
+ ExperimentalDynamicWidgets,
17
+ },
18
+ });
19
+ expect(warn).toHaveBeenCalledTimes(1);
20
+ expect(warn.mock.calls[0][0]).toMatchInlineSnapshot(
21
+ `"Use AisDynamicWidgets instead of AisExperimentalDynamicWidgets."`
22
+ );
23
+ });
24
+
25
+ it('behaves the same as DynamicWidgets', () => {
26
+ Object.keys(ExperimentalDynamicWidgets).forEach(key => {
27
+ if (key === 'name') {
28
+ // name is different
29
+ expect(ExperimentalDynamicWidgets[key]).toBe(
30
+ 'AisExperimentalDynamicWidgets'
31
+ );
32
+ } else if (key === 'mounted') {
33
+ // mounted has the warning
34
+ expect(ExperimentalDynamicWidgets[key]).toEqual(expect.any(Function));
35
+ } else if (key[0] === '_') {
36
+ // private Vue behavior, not tested
37
+ } else {
38
+ expect(ExperimentalDynamicWidgets[key]).toEqual(DynamicWidgets[key]);
39
+ }
40
+ });
41
+ });
@@ -1,7 +1,8 @@
1
1
  import { plugin } from './plugin';
2
+ import { isVue2 } from './util/vue-compat';
2
3
 
3
- // Automatically register Algolia Search components if Vue is available globally
4
- if (typeof window !== 'undefined' && window.Vue) {
4
+ // Automatically register Algolia Search components if Vue 2.x is available globally.
5
+ if (typeof window !== 'undefined' && window.Vue && isVue2) {
5
6
  window.Vue.use(plugin);
6
7
  }
7
8
 
@@ -31,7 +31,7 @@ export const createWidgetMixin = ({ connector } = {}) => ({
31
31
  this.getParentIndex().addWidgets([this.widget]);
32
32
 
33
33
  if (
34
- this.instantSearchInstance.__initialSearchResults &&
34
+ this.instantSearchInstance._initialResults &&
35
35
  !this.instantSearchInstance.started
36
36
  ) {
37
37
  if (typeof this.instantSearchInstance.__forceRender !== 'function') {
@@ -11,9 +11,9 @@ import { createFakeClient } from '../testutils/client';
11
11
  import { createSerializedState } from '../testutils/helper';
12
12
  import { isVue3, isVue2, Vue2, renderCompat } from '../vue-compat';
13
13
  import {
14
- SearchResults,
15
- SearchParameters,
16
14
  AlgoliaSearchHelper,
15
+ SearchParameters,
16
+ SearchResults,
17
17
  } from 'algoliasearch-helper';
18
18
 
19
19
  jest.unmock('instantsearch.js/es');
@@ -57,9 +57,11 @@ describe('createServerRootMixin', () => {
57
57
  }),
58
58
  ],
59
59
  })
60
- ).toThrowErrorMatchingInlineSnapshot(
61
- `"createServerRootMixin requires \`searchClient\` and \`indexName\` in the first argument"`
62
- );
60
+ ).toThrowErrorMatchingInlineSnapshot(`
61
+ "The \`searchClient\` option is required.
62
+
63
+ See documentation: https://www.algolia.com/doc/api-reference/widgets/instantsearch/js/"
64
+ `);
63
65
  });
64
66
 
65
67
  it('requires indexName', () => {
@@ -72,9 +74,11 @@ describe('createServerRootMixin', () => {
72
74
  }),
73
75
  ],
74
76
  })
75
- ).toThrowErrorMatchingInlineSnapshot(
76
- `"createServerRootMixin requires \`searchClient\` and \`indexName\` in the first argument"`
77
- );
77
+ ).toThrowErrorMatchingInlineSnapshot(`
78
+ "The \`indexName\` option is required.
79
+
80
+ See documentation: https://www.algolia.com/doc/api-reference/widgets/instantsearch/js/"
81
+ `);
78
82
  });
79
83
 
80
84
  it('creates an instantsearch instance on "data"', () => {
@@ -301,14 +305,13 @@ Array [
301
305
  renderToString,
302
306
  });
303
307
  expect(state).toEqual({
304
- __identifier: 'stringified',
305
308
  hello: {
306
- _rawResults: [
309
+ results: [
307
310
  {
308
311
  query: '',
309
312
  },
310
313
  ],
311
- _state: {
314
+ state: {
312
315
  disjunctiveFacets: [],
313
316
  disjunctiveFacetsRefinements: {},
314
317
  facets: [],
@@ -534,7 +537,7 @@ Array [
534
537
  this.instantsearch.mainIndex.getWidgets().map(w => w.$$type)
535
538
  ).toEqual(['ais.configure']);
536
539
 
537
- expect(res.hello._state.hitsPerPage).toBe(100);
540
+ expect(res.hello.state.hitsPerPage).toBe(100);
538
541
  })
539
542
  // jest throws an error we need to catch, since stuck in the flow
540
543
  .catch(e => {
@@ -656,11 +659,73 @@ Array [
656
659
 
657
660
  await renderToString(wrapper);
658
661
  });
662
+
663
+ it('searches only once', async () => {
664
+ const searchClient = createFakeClient();
665
+ const app = {
666
+ mixins: [
667
+ forceIsServerMixin,
668
+ createServerRootMixin({
669
+ searchClient,
670
+ indexName: 'hello',
671
+ }),
672
+ ],
673
+ render: renderCompat(h =>
674
+ /**
675
+ * This code triggers this warning in Vue 3:
676
+ * > Non-function value encountered for default slot. Prefer function slots for better performance.
677
+ *
678
+ * To fix it, replace the third argument
679
+ * > [h(...), h(...)]
680
+ * with
681
+ * > { default: () => [h(...), h(...)] }
682
+ *
683
+ * but it's not important (and not compatible in vue2), we're leaving it as-is.
684
+ */
685
+ h(InstantSearchSsr, {}, [
686
+ h(Configure, {
687
+ attrs: {
688
+ hitsPerPage: 100,
689
+ },
690
+ }),
691
+ h(SearchBox),
692
+ ])
693
+ ),
694
+ serverPrefetch() {
695
+ return this.instantsearch.findResultsState({
696
+ component: this,
697
+ renderToString,
698
+ });
699
+ },
700
+ };
701
+
702
+ const wrapper = createSSRApp({
703
+ mixins: [forceIsServerMixin],
704
+ render: renderCompat(h => h(app)),
705
+ });
706
+
707
+ await renderToString(wrapper);
708
+
709
+ expect(searchClient.search).toHaveBeenCalledTimes(1);
710
+ expect(searchClient.search.mock.calls[0][0]).toMatchInlineSnapshot(`
711
+ Array [
712
+ Object {
713
+ "indexName": "hello",
714
+ "params": Object {
715
+ "facets": Array [],
716
+ "hitsPerPage": 100,
717
+ "query": "",
718
+ "tagFilters": "",
719
+ },
720
+ },
721
+ ]
722
+ `);
723
+ });
659
724
  }
660
725
  });
661
726
 
662
727
  describe('hydrate', () => {
663
- it('sets __initialSearchResults', () => {
728
+ it('sets _initialResults', () => {
664
729
  const serialized = createSerializedState();
665
730
 
666
731
  const app = {
@@ -683,7 +748,6 @@ Array [
683
748
  // in test, beforeCreated doesn't have $data yet, but IRL it does
684
749
  created() {
685
750
  this.instantsearch.hydrate({
686
- __identifier: 'stringified',
687
751
  hello: serialized,
688
752
  });
689
753
  },
@@ -693,57 +757,20 @@ Array [
693
757
  vm: { instantsearch },
694
758
  } = mount(app);
695
759
 
696
- expect(instantsearch.__initialSearchResults).toEqual(
697
- expect.objectContaining({ hello: expect.any(SearchResults) })
760
+ expect(instantsearch._initialResults).toEqual(
761
+ expect.objectContaining({
762
+ hello: {
763
+ state: expect.any(Object),
764
+ results: expect.any(Object),
765
+ },
766
+ })
698
767
  );
699
768
 
700
- expect(instantsearch.__initialSearchResults.hello).toEqual(
769
+ expect(instantsearch._initialResults.hello).toEqual(
701
770
  expect.objectContaining(serialized)
702
771
  );
703
772
  });
704
773
 
705
- it('accepts non-stringified results', () => {
706
- const serialized = createSerializedState();
707
- const nonSerialized = new SearchResults(
708
- new SearchParameters(serialized._state),
709
- serialized._rawResults
710
- );
711
-
712
- const app = {
713
- mixins: [
714
- createServerRootMixin({
715
- searchClient: createFakeClient(),
716
- indexName: 'movies',
717
- }),
718
- ],
719
- render: renderCompat(h =>
720
- h(InstantSearchSsr, {}, [
721
- h(Configure, {
722
- attrs: {
723
- hitsPerPage: 100,
724
- },
725
- }),
726
- h(SearchBox),
727
- ])
728
- ),
729
- created() {
730
- this.instantsearch.hydrate({
731
- movies: nonSerialized,
732
- });
733
-
734
- expect(this.instantsearch.__initialSearchResults).toEqual(
735
- expect.objectContaining({ movies: expect.any(SearchResults) })
736
- );
737
-
738
- expect(this.instantsearch.__initialSearchResults.movies).toEqual(
739
- nonSerialized
740
- );
741
- },
742
- };
743
-
744
- mount(app);
745
- });
746
-
747
774
  it('inits the main index', () => {
748
775
  const serialized = createSerializedState();
749
776
 
@@ -773,7 +800,6 @@ Array [
773
800
  expect(instantsearch.mainIndex.getHelper()).toBe(null);
774
801
 
775
802
  instantsearch.hydrate({
776
- __identifier: 'stringified',
777
803
  hello: serialized,
778
804
  });
779
805
 
@@ -812,7 +838,6 @@ Array [
812
838
  expect(instantsearch.mainHelper).toBe(null);
813
839
 
814
840
  instantsearch.hydrate({
815
- __identifier: 'stringified',
816
841
  hello: serialized,
817
842
  });
818
843
 
@@ -834,6 +859,7 @@ Array [
834
859
  created() {
835
860
  instantSearchInstance = this.instantsearch;
836
861
  },
862
+ render() {},
837
863
  });
838
864
 
839
865
  const widget = {
@@ -894,6 +920,58 @@ Object {
894
920
  );
895
921
  });
896
922
 
923
+ it('uses the results passed to hydrate for rendering', () => {
924
+ let instantSearchInstance;
925
+ mount({
926
+ mixins: [
927
+ createServerRootMixin({
928
+ searchClient: createFakeClient(),
929
+ indexName: 'lol',
930
+ }),
931
+ ],
932
+ created() {
933
+ instantSearchInstance = this.instantsearch;
934
+ },
935
+ render() {},
936
+ });
937
+
938
+ const widget = {
939
+ init: jest.fn(),
940
+ render: jest.fn(),
941
+ };
942
+
943
+ const resultsState = createSerializedState();
944
+ const state = new SearchParameters(resultsState.state);
945
+ const results = new SearchResults(state, resultsState.results);
946
+
947
+ instantSearchInstance.hydrate({
948
+ lol: resultsState,
949
+ });
950
+
951
+ instantSearchInstance.__forceRender(
952
+ widget,
953
+ instantSearchInstance.mainIndex
954
+ );
955
+
956
+ expect(widget.init).toHaveBeenCalledTimes(0);
957
+ expect(widget.render).toHaveBeenCalledTimes(1);
958
+
959
+ const renderArgs = widget.render.mock.calls[0][0];
960
+
961
+ expect(renderArgs).toEqual(
962
+ expect.objectContaining({
963
+ state,
964
+ results,
965
+ scopedResults: [
966
+ expect.objectContaining({
967
+ indexId: 'lol',
968
+ results,
969
+ }),
970
+ ],
971
+ })
972
+ );
973
+ });
974
+
897
975
  describe('createURL', () => {
898
976
  it('returns # if instantsearch has no routing', () => {
899
977
  let instantSearchInstance;
@@ -907,6 +985,7 @@ Object {
907
985
  created() {
908
986
  instantSearchInstance = this.instantsearch;
909
987
  },
988
+ render() {},
910
989
  });
911
990
 
912
991
  const widget = {
@@ -940,6 +1019,7 @@ Object {
940
1019
  created() {
941
1020
  instantSearchInstance = this.instantsearch;
942
1021
  },
1022
+ render() {},
943
1023
  });
944
1024
 
945
1025
  const widget = {