From 07d1fea2fb40a7a994f1f30151c9f0161a7622c9 Mon Sep 17 00:00:00 2001 From: vvo Date: Tue, 26 Apr 2016 15:58:55 +0200 Subject: [PATCH] fix(lifecycle): save configuration done in widget.init Before this commit, every configuration done in widget.init would not be remembered when going back in the browser history. Scenario: You built a custom widget using helper.setQueryParameter in widget.init. Then you load "/", widget.init() sets some helper params, you go to page 2, you click on <= back. Before this commit we would forget what was done in widget.init(). Now it works. This was needed to fix the "rootPath" option of the hierarchicalMenu. The rootPath option triggers a state change from inside the helper (which may have been a bad idea after all) => it was not remembered when going back to empty url. Thanks to @maxiloc for the bug report and big help solving this! --- dev/app.js | 1 + dev/index.html | 2 +- src/lib/InstantSearch.js | 13 ++++++++--- src/lib/__tests__/InstantSearch-test.js | 29 ++++++++++++++++++++++--- 4 files changed, 38 insertions(+), 7 deletions(-) diff --git a/dev/app.js b/dev/app.js index 1d577dae34..8371a0f0df 100644 --- a/dev/app.js +++ b/dev/app.js @@ -305,6 +305,7 @@ search.addWidget( link: 'facet-value', count: 'facet-count pull-right' }, + rootPath: 'Cameras & Camcorders > Digital Camera Accessories', templates: { header: 'Hierarchical categories' } diff --git a/dev/index.html b/dev/index.html index 2809cb5ff3..aa16f3737e 100644 --- a/dev/index.html +++ b/dev/index.html @@ -67,11 +67,11 @@

Instant search demo using instantsearch.js +

Results overview

Results

- diff --git a/src/lib/InstantSearch.js b/src/lib/InstantSearch.js index cd8126db31..4d503e857f 100644 --- a/src/lib/InstantSearch.js +++ b/src/lib/InstantSearch.js @@ -102,14 +102,15 @@ Usage: instantsearch({ start() { if (!this.widgets) throw new Error('No widgets were added to instantsearch.js'); + let syncWidget; + if (this.urlSync) { - let syncWidget = urlSyncWidget(this.urlSync); + syncWidget = urlSyncWidget(this.urlSync); this._createURL = syncWidget.createURL.bind(syncWidget); this._onHistoryChange = syncWidget.onHistoryChange.bind(syncWidget); - this.widgets.push(syncWidget); } else { this._createURL = defaultCreateURL; - this._onHistoryChange = function() {}; + this._onHistoryChange = () => {}; } this.searchParameters = this.widgets.reduce(enhanceConfiguration, this.searchParameters); @@ -128,6 +129,12 @@ Usage: instantsearch({ this.helper = helper; this._init(helper.state, helper); + + if (this.urlSync) { + helper.setState(enhanceConfiguration(helper.state, syncWidget)); + syncWidget.init({helper}); + } + helper.on('result', this._render.bind(this, helper)); helper.search(); } diff --git a/src/lib/__tests__/InstantSearch-test.js b/src/lib/__tests__/InstantSearch-test.js index adc36b49b9..4de37868e3 100644 --- a/src/lib/__tests__/InstantSearch-test.js +++ b/src/lib/__tests__/InstantSearch-test.js @@ -23,6 +23,7 @@ describe('InstantSearch lifecycle', () => { let searchParameters; let search; let helperSearchSpy; + let urlSync; beforeEach(() => { client = {algolia: 'client', addAlgoliaAgent: () => {}}; @@ -33,10 +34,18 @@ describe('InstantSearch lifecycle', () => { helperSearchSpy = sinon.spy(); helper.search = helperSearchSpy; helper.getState = sinon.stub().returns({}); + helper.setState = sinon.spy(); helper.state = { setQueryParameters: function(params) { return new SearchParameters(params); } }; + urlSync = { + createURL: sinon.spy(), + onHistoryChange: () => {}, + getConfiguration: sinon.spy(), + init: () => {} + }; + algoliasearch = sinon.stub().returns(client); algoliasearchHelper = sinon.stub().returns(helper); @@ -51,6 +60,7 @@ describe('InstantSearch lifecycle', () => { another: {config: 'parameter'} }; + InstantSearch.__Rewire__('urlSyncWidget', () => urlSync); InstantSearch.__Rewire__('algoliasearch', algoliasearch); InstantSearch.__Rewire__('algoliasearchHelper', algoliasearchHelper); @@ -64,6 +74,7 @@ describe('InstantSearch lifecycle', () => { }); afterEach(() => { + InstantSearch.__ResetDependency__('urlSyncWidget'); InstantSearch.__ResetDependency__('algoliasearch'); InstantSearch.__ResetDependency__('algoliasearchHelper'); }); @@ -111,7 +122,9 @@ describe('InstantSearch lifecycle', () => { beforeEach(() => { widget = { getConfiguration: sinon.stub().returns({some: 'modified', another: {different: 'parameter'}}), - init: sinon.spy(), + init: sinon.spy(() => { + helper.state.sendMeToUrlSync = true; + }), render: sinon.spy() }; search.addWidget(widget); @@ -160,6 +173,14 @@ describe('InstantSearch lifecycle', () => { expect(args.onHistoryChange).toBe(search._onHistoryChange); }); + it('calls urlSync.getConfiguration after every widget', () => { + expect(urlSync.getConfiguration.calledOnce).toBe(true, 'urlSync.getConfiguration called once'); + expect(widget.init.calledAfter(widget.getConfiguration)) + .toBe(true, 'urlSync.getConfiguration was called after widget.init'); + expect(urlSync.getConfiguration.getCall(0).args[0].sendMeToUrlSync) + .toBe(true, 'state modifications done in widget.init should be sent to urlSync.getConfiguration'); + }); + it('does not call widget.render', () => { expect(widget.render.notCalled).toBe(true); }); @@ -235,8 +256,10 @@ describe('InstantSearch lifecycle', () => { search.start(); }); - it('creates a URL', () => { - expect(search.createURL({hitsPerPage: 42})).toEqual('?q=&hPP=42&idx=&p=0'); + it('has a createURL method', () => { + search.createURL({hitsPerPage: 542}); + expect(urlSync.createURL.calledOnce).toBe(true); + expect(urlSync.createURL.getCall(0).args[0].hitsPerPage).toBe(542); }); it('emits render when all render are done (using on)', () => {