Skip to content

Commit

Permalink
fix(core): correct escape highlight for arrays and nested objects (#2646
Browse files Browse the repository at this point in the history
)

* feat(stories): add hits with highlighted array
* fix(stories): apply hit style also on widget display
* fix(escape): correctly escape array
* fix(escape): correctly escape nested object
* refactor(escape): handle array of object
* fix(stories): add escapeHits to highlighted array
  • Loading branch information
samouss authored and bobylito committed Jan 3, 2018
1 parent 62410ea commit ed0ee73
Show file tree
Hide file tree
Showing 4 changed files with 200 additions and 72 deletions.
55 changes: 49 additions & 6 deletions dev/app/builtin/stories/hits.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,53 @@ import { wrapWithHits } from '../../utils/wrap-with-hits.js';
const stories = storiesOf('Hits');

export default () => {
stories.add(
'default',
wrapWithHits(container => {
window.search.addWidget(instantsearch.widgets.hits({ container }));
})
);
stories
.add(
'default',
wrapWithHits(container => {
window.search.addWidget(instantsearch.widgets.hits({ container }));
})
)
.add(
'with highlighted array',
wrapWithHits(
container => {
window.search.addWidget(
instantsearch.widgets.hits({
container,
escapeHits: true,
templates: {
item: `
<div class="hit" id="hit-{{objectID}}">
<div class="hit-content">
<div>
<span>{{{_highlightResult.name.value}}}</span>
<span>\${{price}}</span>
<span>{{rating}} stars</span>
</div>
<div class="hit-type">
{{{_highlightResult.type.value}}}
</div>
<div class="hit-description">
{{{_highlightResult.description.value}}}
</div>
<div class="hit-tags">
{{#_highlightResult.tags}}
<span>{{{value}}}</span>
{{/_highlightResult.tags}}
</div>
</div>
</div>
`,
},
})
);
},
{
appId: 'KY4PR9ORUL',
apiKey: 'a5ca312adab3b79e14054154efa00b37',
indexName: 'highlight_array',
}
)
);
};
8 changes: 4 additions & 4 deletions dev/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,22 @@
padding: 50px 40px 40px;
}

#results-display .hit {
.hit {
align-items: center;
display: flex;
margin: 10px 10px;
}

#results-display .hit .hit-picture img {
.hit .hit-picture img {
height: auto;
width: 80px;
}

#results-display .hit .hit-content {
.hit .hit-content {
padding: 0 10px;
}

#results-display .hit .hit-type {
.hit .hit-type {
color: #888;
font-size: 13px;
}
172 changes: 129 additions & 43 deletions src/lib/__tests__/escape-highlight-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ describe('escapeHits()', () => {
it('should escape highlighProperty simple text value', () => {
const hits = [
{
_snippetResult: {
_highlightResult: {
foobar: {
value: '<script>__ais-highlight__foobar__/ais-highlight__</script>',
},
},
_highlightResult: {
_snippetResult: {
foobar: {
value: '<script>__ais-highlight__foobar__/ais-highlight__</script>',
},
Expand Down Expand Up @@ -37,18 +37,18 @@ describe('escapeHits()', () => {
const hits = [
{
_highlightResult: {
foobar: {
value: {
foo: '<script>__ais-highlight__bar__/ais-highlight__</script>',
bar: '<script>__ais-highlight__foo__/ais-highlight__</script>',
foo: {
bar: {
value:
'<script>__ais-highlight__foobar__/ais-highlight__</script>',
},
},
},
_snippetResult: {
foobar: {
value: {
foo: '<script>__ais-highlight__bar__/ais-highlight__</script>',
bar: '<script>__ais-highlight__foo__/ais-highlight__</script>',
foo: {
bar: {
value:
'<script>__ais-highlight__foobar__/ais-highlight__</script>',
},
},
},
Expand All @@ -58,64 +58,150 @@ describe('escapeHits()', () => {
expect(escapeHits(hits)).toEqual([
{
_highlightResult: {
foobar: {
value: {
foo: '&lt;script&gt;<em>bar</em>&lt;/script&gt;',
bar: '&lt;script&gt;<em>foo</em>&lt;/script&gt;',
foo: {
bar: {
value: '&lt;script&gt;<em>foobar</em>&lt;/script&gt;',
},
},
},
_snippetResult: {
foobar: {
value: {
foo: '&lt;script&gt;<em>bar</em>&lt;/script&gt;',
bar: '&lt;script&gt;<em>foo</em>&lt;/script&gt;',
foo: {
bar: {
value: '&lt;script&gt;<em>foobar</em>&lt;/script&gt;',
},
},
},
},
]);
});

it('should escape highlighProperty array of string as value', () => {
it('should escape highlighProperty array of string', () => {
const hits = [
{
_highlightResult: {
foobar: {
value: [
'<script>__ais-highlight__bar__/ais-highlight__</script>',
'<script>__ais-highlight__foo__/ais-highlight__</script>',
],
},
foobar: [
{
value: '<script>__ais-highlight__bar__/ais-highlight__</script>',
},
{
value: '<script>__ais-highlight__foo__/ais-highlight__</script>',
},
],
},
_snippetResult: {
foobar: {
value: [
'<script>__ais-highlight__bar__/ais-highlight__</script>',
'<script>__ais-highlight__foo__/ais-highlight__</script>',
],
},
foobar: [
{
value: '<script>__ais-highlight__bar__/ais-highlight__</script>',
},
{
value: '<script>__ais-highlight__foo__/ais-highlight__</script>',
},
],
},
},
];

expect(escapeHits(hits)).toEqual([
{
_highlightResult: {
foobar: {
value: [
'&lt;script&gt;<em>bar</em>&lt;/script&gt;',
'&lt;script&gt;<em>foo</em>&lt;/script&gt;',
],
},
foobar: [
{ value: '&lt;script&gt;<em>bar</em>&lt;/script&gt;' },
{ value: '&lt;script&gt;<em>foo</em>&lt;/script&gt;' },
],
},
_snippetResult: {
foobar: {
value: [
'&lt;script&gt;<em>bar</em>&lt;/script&gt;',
'&lt;script&gt;<em>foo</em>&lt;/script&gt;',
],
},
foobar: [
{ value: '&lt;script&gt;<em>bar</em>&lt;/script&gt;' },
{ value: '&lt;script&gt;<em>foo</em>&lt;/script&gt;' },
],
},
},
]);
});

it('should escape highlighProperty array of object', () => {
const hits = [
{
_highlightResult: {
foobar: [
{
foo: {
bar: {
value:
'<script>__ais-highlight__bar__/ais-highlight__</script>',
},
},
},
{
foo: {
bar: {
value:
'<script>__ais-highlight__foo__/ais-highlight__</script>',
},
},
},
],
},
_snippetResult: {
foobar: [
{
foo: {
bar: {
value:
'<script>__ais-highlight__bar__/ais-highlight__</script>',
},
},
},
{
foo: {
bar: {
value:
'<script>__ais-highlight__foo__/ais-highlight__</script>',
},
},
},
],
},
},
];

expect(escapeHits(hits)).toEqual([
{
_highlightResult: {
foobar: [
{
foo: {
bar: {
value: '&lt;script&gt;<em>bar</em>&lt;/script&gt;',
},
},
},
{
foo: {
bar: {
value: '&lt;script&gt;<em>foo</em>&lt;/script&gt;',
},
},
},
],
},
_snippetResult: {
foobar: [
{
foo: {
bar: {
value: '&lt;script&gt;<em>bar</em>&lt;/script&gt;',
},
},
},
{
foo: {
bar: {
value: '&lt;script&gt;<em>foo</em>&lt;/script&gt;',
},
},
},
],
},
},
]);
Expand Down
37 changes: 18 additions & 19 deletions src/lib/escape-highlight.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import reduce from 'lodash/reduce';
import escape from 'lodash/escape';
import isPlainObject from 'lodash/isPlainObject';
import isArray from 'lodash/isArray';
import mapValues from 'lodash/mapValues';
import isPlainObject from 'lodash/isPlainObject';

export const tagConfig = {
highlightPreTag: '__ais-highlight__',
Expand All @@ -16,25 +15,25 @@ function replaceWithEmAndEscape(value) {
}

function recursiveEscape(input) {
return reduce(
input,
(output, value, key) => {
if (typeof value.value === 'string') {
value.value = replaceWithEmAndEscape(value.value);
}

if (isPlainObject(value.value)) {
value.value = mapValues(value.value, replaceWithEmAndEscape);
}
if (isPlainObject(input) && typeof input.value !== 'string') {
return reduce(
input,
(acc, item, key) => ({
...acc,
[key]: recursiveEscape(item),
}),
{}
);
}

if (isArray(value.value)) {
value.value = value.value.map(replaceWithEmAndEscape);
}
if (isArray(input)) {
return input.map(recursiveEscape);
}

return { ...output, [key]: value };
},
{}
);
return {
...input,
value: replaceWithEmAndEscape(input.value),
};
}

export default function escapeHits(hits) {
Expand Down

0 comments on commit ed0ee73

Please sign in to comment.