diff --git a/src/connectors/autocomplete/connectAutocomplete.ts b/src/connectors/autocomplete/connectAutocomplete.ts index c9bc84deb0..8b23517b5f 100644 --- a/src/connectors/autocomplete/connectAutocomplete.ts +++ b/src/connectors/autocomplete/connectAutocomplete.ts @@ -143,7 +143,7 @@ search.addWidgets([ }; }, - getWidgetRenderState({ helper, scopedResults }) { + getWidgetRenderState({ helper, scopedResults, instantSearchInstance }) { const indices = scopedResults.map(scopedResult => { // We need to escape the hits because highlighting // exposes HTML tags to the end-user. diff --git a/src/connectors/hits/__tests__/connectHits-test.ts b/src/connectors/hits/__tests__/connectHits-test.ts index fd911b920c..10bf07d42e 100644 --- a/src/connectors/hits/__tests__/connectHits-test.ts +++ b/src/connectors/hits/__tests__/connectHits-test.ts @@ -444,6 +444,124 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/hits/js/#co expect(((results.hits as unknown) as EscapedHits).__escaped).toBe(true); }); + describe('getRenderState', () => { + it('returns the render state', () => { + const renderFn = jest.fn(); + const unmountFn = jest.fn(); + const createHits = connectHits(renderFn, unmountFn); + const hitsWidget = createHits({}); + const helper = algoliasearchHelper(createSearchClient(), 'indexName', { + index: 'indexName', + }); + + const renderState1 = hitsWidget.getRenderState( + {}, + createInitOptions({ state: helper.state, helper }) + ); + + expect(renderState1.hits).toEqual({ + hits: [], + results: undefined, + sendEvent: expect.any(Function), + bindEvent: expect.any(Function), + widgetParams: {}, + }); + + const hits = [ + { objectID: '1', name: 'name 1' }, + { objectID: '2', name: 'name 2' }, + ]; + + const results = new SearchResults(helper.state, [ + createSingleSearchResponse({ hits, queryID: 'theQueryID' }), + ]); + + const renderState2 = hitsWidget.getRenderState( + {}, + createRenderOptions({ + helper, + state: helper.state, + results, + }) + ); + + const expectedHits = [ + { objectID: '1', name: 'name 1', __queryID: 'theQueryID' }, + { objectID: '2', name: 'name 2', __queryID: 'theQueryID' }, + ]; + + ((expectedHits as unknown) as EscapedHits).__escaped = true; + + expect(renderState2.hits).toEqual( + expect.objectContaining({ + hits: expectedHits, + results, + sendEvent: expect.any(Function), + bindEvent: expect.any(Function), + widgetParams: {}, + }) + ); + }); + }); + + describe('getWidgetRenderState', () => { + it('returns the widget render state', () => { + const renderFn = jest.fn(); + const unmountFn = jest.fn(); + const createHits = connectHits(renderFn, unmountFn); + const hitsWidget = createHits({}); + const helper = algoliasearchHelper(createSearchClient(), 'indexName', { + index: 'indexName', + }); + + const renderState1 = hitsWidget.getWidgetRenderState( + createInitOptions({ state: helper.state, helper }) + ); + + expect(renderState1).toEqual({ + hits: [], + results: undefined, + sendEvent: expect.any(Function), + bindEvent: expect.any(Function), + widgetParams: {}, + }); + + const hits = [ + { objectID: '1', name: 'name 1' }, + { objectID: '2', name: 'name 2' }, + ]; + + const results = new SearchResults(helper.state, [ + createSingleSearchResponse({ hits, queryID: 'theQueryID' }), + ]); + + const renderState2 = hitsWidget.getWidgetRenderState( + createRenderOptions({ + helper, + state: helper.state, + results, + }) + ); + + const expectedHits = [ + { objectID: '1', name: 'name 1', __queryID: 'theQueryID' }, + { objectID: '2', name: 'name 2', __queryID: 'theQueryID' }, + ]; + + ((expectedHits as unknown) as EscapedHits).__escaped = true; + + expect(renderState2).toEqual( + expect.objectContaining({ + hits: expectedHits, + results, + sendEvent: expect.any(Function), + bindEvent: expect.any(Function), + widgetParams: {}, + }) + ); + }); + }); + describe('getWidgetSearchParameters', () => { it('adds the TAG_PLACEHOLDER to the `SearchParameters`', () => { const render = () => {}; diff --git a/src/connectors/hits/connectHits.ts b/src/connectors/hits/connectHits.ts index 5d8ad77b85..9cc531c8cc 100644 --- a/src/connectors/hits/connectHits.ts +++ b/src/connectors/hits/connectHits.ts @@ -71,31 +71,62 @@ const connectHits: HitsConnector = function connectHits( return { $$type: 'ais.hits', - init({ instantSearchInstance, helper }) { - sendEvent = createSendEventForHits({ - instantSearchInstance, - index: helper.getIndex(), - widgetType: this.$$type!, - }); - bindEvent = createBindEventForHits({ - index: helper.getIndex(), - widgetType: this.$$type!, - }); + init(initOptions) { + renderFn( + { + ...this.getWidgetRenderState(initOptions), + instantSearchInstance: initOptions.instantSearchInstance, + }, + true + ); + }, + + render(renderOptions) { + const renderState = this.getWidgetRenderState(renderOptions); + renderState.sendEvent('view', renderState.hits); renderFn( { + ...renderState, + instantSearchInstance: renderOptions.instantSearchInstance, + }, + false + ); + }, + + getRenderState(renderState, renderOptions) { + return { + ...renderState, + hits: this.getWidgetRenderState(renderOptions), + }; + }, + + getWidgetRenderState({ results, helper, instantSearchInstance }) { + if (!sendEvent) { + sendEvent = createSendEventForHits({ + instantSearchInstance, + index: helper.getIndex(), + widgetType: this.$$type!, + }); + } + + if (!bindEvent) { + bindEvent = createBindEventForHits({ + index: helper.getIndex(), + widgetType: this.$$type!, + }); + } + + if (!results) { + return { hits: [], results: undefined, sendEvent, bindEvent, - instantSearchInstance, widgetParams, - }, - true - ); - }, + }; + } - render({ results, instantSearchInstance }) { if (escapeHTML && results.hits.length > 0) { results.hits = escapeHits(results.hits); } @@ -120,19 +151,13 @@ const connectHits: HitsConnector = function connectHits( typeof escapeHits >).__escaped = initialEscaped; - sendEvent('view', results.hits); - - renderFn( - { - hits: results.hits, - results, - sendEvent, - bindEvent, - instantSearchInstance, - widgetParams, - }, - false - ); + return { + hits: results.hits, + results, + sendEvent, + bindEvent, + widgetParams, + }; }, dispose({ state }) { diff --git a/src/types/widget.ts b/src/types/widget.ts index af159312c4..133b9f1135 100644 --- a/src/types/widget.ts +++ b/src/types/widget.ts @@ -27,6 +27,10 @@ import { CurrentRefinementsRendererOptions, CurrentRefinementsConnectorParams, } from '../connectors/current-refinements/connectCurrentRefinements'; +import { + HitsRendererOptions, + HitsConnectorParams, +} from '../connectors/hits/connectHits'; export type ScopedResult = { indexId: string; @@ -201,6 +205,7 @@ export type IndexRenderState = Partial<{ } >; }; + hits: WidgetRenderState; }>; type WidgetRenderState<