diff --git a/packages/vue-instantsearch/package.json b/packages/vue-instantsearch/package.json
index bbeefe2361..02dc834220 100644
--- a/packages/vue-instantsearch/package.json
+++ b/packages/vue-instantsearch/package.json
@@ -46,7 +46,7 @@
},
"dependencies": {
"algoliasearch-helper": "^3.1.0",
- "instantsearch.js": "^4.16.1"
+ "instantsearch.js": "^4.20.0"
},
"peerDependencies": {
"algoliasearch": ">= 3.32.0 < 5",
@@ -114,7 +114,7 @@
"bundlesize": [
{
"path": "./dist/vue-instantsearch.js",
- "maxSize": "52.50 kB"
+ "maxSize": "52.75 kB"
},
{
"path": "./dist/vue-instantsearch.common.js",
diff --git a/packages/vue-instantsearch/src/components/InstantSearch.js b/packages/vue-instantsearch/src/components/InstantSearch.js
index e5d54f0633..928f279898 100644
--- a/packages/vue-instantsearch/src/components/InstantSearch.js
+++ b/packages/vue-instantsearch/src/components/InstantSearch.js
@@ -69,6 +69,10 @@ export default createInstantSearchComponent({
return false;
},
},
+ middlewares: {
+ type: Array,
+ default: null,
+ },
},
data() {
return {
diff --git a/packages/vue-instantsearch/src/components/__tests__/InstantSearch-integration.js b/packages/vue-instantsearch/src/components/__tests__/InstantSearch-integration.js
index b994f0c7e1..74ec59301e 100644
--- a/packages/vue-instantsearch/src/components/__tests__/InstantSearch-integration.js
+++ b/packages/vue-instantsearch/src/components/__tests__/InstantSearch-integration.js
@@ -1,10 +1,13 @@
+import Vue from 'vue';
import { mount } from '@vue/test-utils';
import InstantSearch from '../InstantSearch';
import { createWidgetMixin } from '../../mixins/widget';
import { createFakeClient } from '../../util/testutils/client';
-
+import SearchBox from '../SearchBox.vue';
jest.unmock('instantsearch.js/es');
+const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
+
it('child widgets get added to its parent instantsearch', () => {
const widgetInstance = {
render() {},
@@ -32,3 +35,154 @@ it('child widgets get added to its parent instantsearch', () => {
widgetInstance
);
});
+
+describe('middlewares', () => {
+ const createFakeMiddleware = () => {
+ const middlewareSpy = {
+ onStateChange: jest.fn(),
+ subscribe: jest.fn(),
+ unsubscribe: jest.fn(),
+ };
+ const middleware = jest.fn(() => middlewareSpy);
+
+ return [middleware, middlewareSpy];
+ };
+
+ it('subscribes middlewares', async () => {
+ const [middleware, middlewareSpy] = createFakeMiddleware();
+
+ mount(InstantSearch, {
+ propsData: {
+ searchClient: createFakeClient(),
+ indexName: 'indexName',
+ middlewares: [middleware],
+ },
+ });
+ await Vue.nextTick();
+
+ expect(middlewareSpy.subscribe).toHaveBeenCalledTimes(1);
+ });
+
+ it('subscribes newly added middleware', async () => {
+ const [middleware1, middlewareSpy1] = createFakeMiddleware();
+
+ const wrapper = mount({
+ components: {
+ AisInstantSearch: InstantSearch,
+ AisSearchBox: SearchBox,
+ },
+ template: `
+
+
+
+ `,
+ data() {
+ return {
+ searchClient: createFakeClient(),
+ indexName: 'indexName',
+ middlewares: [middleware1],
+ };
+ },
+ });
+
+ await wait(20);
+ expect(middlewareSpy1.subscribe).toHaveBeenCalledTimes(1);
+
+ await wrapper.find('input').setValue('a');
+ await Vue.nextTick();
+
+ expect(middlewareSpy1.onStateChange).toHaveBeenCalledTimes(1);
+ expect(middlewareSpy1.onStateChange).toHaveBeenCalledWith({
+ uiState: { indexName: { query: 'a' } },
+ });
+
+ const [middleware2, middlewareSpy2] = createFakeMiddleware();
+ wrapper.setData({
+ middlewares: [middleware1, middleware2],
+ });
+ await Vue.nextTick();
+
+ expect(middlewareSpy2.subscribe).toHaveBeenCalledTimes(1);
+ expect(middlewareSpy2.onStateChange).toHaveBeenCalledTimes(0);
+
+ await wrapper.find('input').setValue('b');
+ await Vue.nextTick();
+
+ expect(middlewareSpy1.onStateChange).toHaveBeenCalledTimes(2);
+ expect(middlewareSpy1.onStateChange).toHaveBeenCalledWith({
+ uiState: { indexName: { query: 'b' } },
+ });
+ expect(middlewareSpy2.onStateChange).toHaveBeenCalledTimes(1);
+ expect(middlewareSpy2.onStateChange).toHaveBeenCalledWith({
+ uiState: { indexName: { query: 'b' } },
+ });
+
+ expect(middlewareSpy1.unsubscribe).toHaveBeenCalledTimes(0);
+ expect(middlewareSpy2.unsubscribe).toHaveBeenCalledTimes(0);
+ });
+
+ it('unsubscribes removed middleware', async () => {
+ const [middleware1, middlewareSpy1] = createFakeMiddleware();
+ const [middleware2, middlewareSpy2] = createFakeMiddleware();
+
+ const wrapper = mount({
+ components: {
+ AisInstantSearch: InstantSearch,
+ AisSearchBox: SearchBox,
+ },
+ template: `
+
+
+
+ `,
+ data() {
+ return {
+ searchClient: createFakeClient(),
+ indexName: 'indexName',
+ middlewares: [middleware1, middleware2],
+ };
+ },
+ });
+
+ await wait(20);
+ expect(middlewareSpy1.subscribe).toHaveBeenCalledTimes(1);
+ expect(middlewareSpy2.subscribe).toHaveBeenCalledTimes(1);
+
+ await wrapper.find('input').setValue('a');
+ await Vue.nextTick();
+
+ expect(middlewareSpy1.onStateChange).toHaveBeenCalledTimes(1);
+ expect(middlewareSpy1.onStateChange).toHaveBeenCalledWith({
+ uiState: { indexName: { query: 'a' } },
+ });
+ expect(middlewareSpy2.onStateChange).toHaveBeenCalledTimes(1);
+ expect(middlewareSpy2.onStateChange).toHaveBeenCalledWith({
+ uiState: { indexName: { query: 'a' } },
+ });
+
+ wrapper.setData({
+ middlewares: [middleware1],
+ });
+ await Vue.nextTick();
+
+ expect(middlewareSpy1.unsubscribe).toHaveBeenCalledTimes(0);
+ expect(middlewareSpy2.unsubscribe).toHaveBeenCalledTimes(1);
+
+ await wrapper.find('input').setValue('b');
+ await Vue.nextTick();
+
+ expect(middlewareSpy1.onStateChange).toHaveBeenCalledTimes(2);
+ expect(middlewareSpy1.onStateChange).toHaveBeenCalledWith({
+ uiState: { indexName: { query: 'b' } },
+ });
+ expect(middlewareSpy2.onStateChange).toHaveBeenCalledTimes(1);
+ });
+});
diff --git a/packages/vue-instantsearch/src/util/createInstantSearchComponent.js b/packages/vue-instantsearch/src/util/createInstantSearchComponent.js
index 3501d6c66c..5d3770dec4 100644
--- a/packages/vue-instantsearch/src/util/createInstantSearchComponent.js
+++ b/packages/vue-instantsearch/src/util/createInstantSearchComponent.js
@@ -34,6 +34,22 @@ export const createInstantSearchComponent = component =>
// private InstantSearch.js API:
this.instantSearchInstance._searchFunction = searchFunction;
},
+ middlewares: {
+ immediate: true,
+ handler(next, prev) {
+ (prev || [])
+ .filter(middleware => (next || []).indexOf(middleware) === -1)
+ .forEach(middlewareToRemove => {
+ this.instantSearchInstance.unuse(middlewareToRemove);
+ });
+
+ (next || [])
+ .filter(middleware => (prev || []).indexOf(middleware) === -1)
+ .forEach(middlewareToAdd => {
+ this.instantSearchInstance.use(middlewareToAdd);
+ });
+ },
+ },
},
created() {
const searchClient = this.instantSearchInstance.client;