Skip to content
This repository has been archived by the owner on Dec 30, 2022. It is now read-only.

Commit

Permalink
fix(ssr): remove second instance of "query" in the response "params" …
Browse files Browse the repository at this point in the history
…for SSR (#2945)

* fix(ssr): remove second instance of "query" in the response "params" for SSR

closes #2941, the same solution but server-side

* chore(test): cleaner signature for mock

* ignore offset
  • Loading branch information
Haroenv committed Jul 8, 2020
1 parent 91f2102 commit bf837c5
Show file tree
Hide file tree
Showing 2 changed files with 294 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,14 @@ import { findResultsState } from '../createInstantSearchServer';
Enzyme.configure({ adapter: new Adapter() });

describe('findResultsState', () => {
const createSearchClient = () => ({
const createSearchClient = ({ transformResponseParams } = {}) => ({
search: requests =>
Promise.resolve({
results: requests.map(({ indexName, params: { query } }) => ({
query,
params: transformResponseParams
? transformResponseParams(query)
: `query=${encodeURIComponent(query)}`,
index: indexName,
})),
}),
Expand Down Expand Up @@ -233,6 +236,185 @@ describe('findResultsState', () => {
state: expect.objectContaining({ index: 'indexName', query: 'iPhone' }),
});
});

describe('cleaning "params"', () => {
it('with one query', async () => {
const Connected = createWidget();

const App = props => (
<InstantSearch {...props}>
<Connected />
</InstantSearch>
);

const props = {
...requiredProps,
searchClient: createSearchClient(),
searchState: {
query: 'iPhone',
},
};

const data = await findResultsState(App, props);

expect(data.rawResults[0].params).toMatchInlineSnapshot(
`"query=iPhone"`
);
});

it('with shadowing query', async () => {
const Connected = createWidget();

const App = props => (
<InstantSearch {...props}>
<Connected />
</InstantSearch>
);

const props = {
...requiredProps,
searchClient: createSearchClient(),
searchState: {
query: 'iPhone&query=iphone',
},
};

const data = await findResultsState(App, props);

expect(data.rawResults[0].params).toMatchInlineSnapshot(
`"query=iPhone%26query%3Diphone"`
);
});

it('with modified query', async () => {
const Connected = createWidget();

const App = props => (
<InstantSearch {...props}>
<Connected />
</InstantSearch>
);

const props = {
...requiredProps,
searchClient: createSearchClient({
transformResponseParams: query =>
`query=${encodeURIComponent(query)}&query=modified`,
}),
searchState: {
query: 'iPhone',
},
};

const data = await findResultsState(App, props);

expect(data.rawResults[0].params).toMatchInlineSnapshot(
`"query=iPhone"`
);
});

it('with shadowing query and modified query', async () => {
const Connected = createWidget();

const App = props => (
<InstantSearch {...props}>
<Connected />
</InstantSearch>
);

const props = {
...requiredProps,
searchClient: createSearchClient({
transformResponseParams: query =>
`query=${encodeURIComponent(query)}&query=modified`,
}),
searchState: {
query: 'iPhone&query=iphone',
},
};

const data = await findResultsState(App, props);

expect(data.rawResults[0].params).toMatchInlineSnapshot(
`"query=iPhone%26query%3Diphone"`
);
});

it('with padded, modified query', async () => {
const Connected = createWidget();

const App = props => (
<InstantSearch {...props}>
<Connected />
</InstantSearch>
);

const props = {
...requiredProps,
searchClient: createSearchClient({
transformResponseParams: query =>
`test=1&query=${encodeURIComponent(query)}&boo=ba&query=modified`,
}),
searchState: {
query: 'iPhone&query=iphone',
},
};

const data = await findResultsState(App, props);

expect(data.rawResults[0].params).toMatchInlineSnapshot(
`"test=1&query=iPhone%26query%3Diphone&boo=ba"`
);
});

it('with nothing returned', async () => {
const Connected = createWidget();

const App = props => (
<InstantSearch {...props}>
<Connected />
</InstantSearch>
);

const props = {
...requiredProps,
searchClient: createSearchClient({
transformResponseParams: () => '',
}),
searchState: {
query: 'iPhone&query=iphone',
},
};

const data = await findResultsState(App, props);

expect(data.rawResults[0].params).toMatchInlineSnapshot(`""`);
});

it('with no query returned', async () => {
const Connected = createWidget();

const App = props => (
<InstantSearch {...props}>
<Connected />
</InstantSearch>
);

const props = {
...requiredProps,
searchClient: createSearchClient({
transformResponseParams: () => 'lol=lol',
}),
searchState: {
query: 'iPhone&query=iphone',
},
};

const data = await findResultsState(App, props);

expect(data.rawResults[0].params).toMatchInlineSnapshot(`"lol=lol"`);
});
});
});

describe('multi index', () => {
Expand Down Expand Up @@ -556,5 +738,88 @@ describe('findResultsState', () => {
],
});
});

describe('cleaning "params"', () => {
it('multiple queries', async () => {
const Connected = createWidget();
const App = props => (
<InstantSearch {...props}>
<Connected />
<Index indexId="index1WithRefinement" indexName="index1">
<Connected />
</Index>
</InstantSearch>
);

const props = {
...requiredProps,
searchClient: createSearchClient(),
indexName: 'index1',
searchState: {
query: 'iPhone',
indices: {
index1WithRefinement: {
query: 'iPad&query=test',
},
},
},
};

const data = await findResultsState(App, props);

expect(data).toHaveLength(2);

const [first, second] = data;

expect(first.rawResults[0].params).toMatchInlineSnapshot(
`"query=iPhone"`
);
expect(second.rawResults[0].params).toMatchInlineSnapshot(
`"query=iPad%26query%3Dtest"`
);
});

it('server-side params', async () => {
const Connected = createWidget();
const App = props => (
<InstantSearch {...props}>
<Connected />
<Index indexId="index1WithRefinement" indexName="index1">
<Connected />
</Index>
</InstantSearch>
);

const props = {
...requiredProps,
searchClient: createSearchClient({
transformResponseParams: query =>
`query=${encodeURIComponent(query)}&query=modified`,
}),
indexName: 'index1',
searchState: {
query: 'iPhone',
indices: {
index1WithRefinement: {
query: 'iPad&query=test',
},
},
},
};

const data = await findResultsState(App, props);

expect(data).toHaveLength(2);

const [first, second] = data;

expect(first.rawResults[0].params).toMatchInlineSnapshot(
`"query=iPhone"`
);
expect(second.rawResults[0].params).toMatchInlineSnapshot(
`"query=iPad%26query%3Dtest"`
);
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,35 @@ const getSearchParameters = (indexName, searchParameters) => {
};
};

/**
* The engine can return params: "query=xxx&query=yyy" if e.g. a query rule modifies it.
* This however will cause us to miss the cache hydration, so we make sure to keep
* only the first query (always the one from the parameters).
*/
function removeDuplicateQuery(params) {
let hasFoundQuery = false;
const queryParamRegex = /&?query=[^&]*/g;
return params.replace(queryParamRegex, function replacer(match) {
if (hasFoundQuery) {
return '';
}
hasFoundQuery = true;
return match;
});
}

function cleanRawResults(rawResults) {
return rawResults.map(res => {
return {
...res,
params: removeDuplicateQuery(res.params),
};
});
}

const singleIndexSearch = (helper, parameters) =>
helper.searchOnce(parameters).then(res => ({
rawResults: res.content._rawResults,
rawResults: cleanRawResults(res.content._rawResults),
state: res.content._state,
}));

Expand Down Expand Up @@ -93,7 +119,7 @@ const multiIndexSearch = (
// may have multiple times the same index.
return Promise.all(searches).then(results =>
[indexName, ...indexIds].map((indexId, i) => ({
rawResults: results[i].content._rawResults,
rawResults: cleanRawResults(results[i].content._rawResults),
state: results[i].content._state,
_internalIndexId: indexId,
}))
Expand Down

0 comments on commit bf837c5

Please sign in to comment.