Skip to content

Commit ed0ee73

Browse files
samoussbobylito
authored andcommitted
fix(core): correct escape highlight for arrays and nested objects (#2646)
* 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
1 parent 62410ea commit ed0ee73

File tree

4 files changed

+200
-72
lines changed

4 files changed

+200
-72
lines changed

dev/app/builtin/stories/hits.stories.js

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,53 @@ import { wrapWithHits } from '../../utils/wrap-with-hits.js';
77
const stories = storiesOf('Hits');
88

99
export default () => {
10-
stories.add(
11-
'default',
12-
wrapWithHits(container => {
13-
window.search.addWidget(instantsearch.widgets.hits({ container }));
14-
})
15-
);
10+
stories
11+
.add(
12+
'default',
13+
wrapWithHits(container => {
14+
window.search.addWidget(instantsearch.widgets.hits({ container }));
15+
})
16+
)
17+
.add(
18+
'with highlighted array',
19+
wrapWithHits(
20+
container => {
21+
window.search.addWidget(
22+
instantsearch.widgets.hits({
23+
container,
24+
escapeHits: true,
25+
templates: {
26+
item: `
27+
<div class="hit" id="hit-{{objectID}}">
28+
<div class="hit-content">
29+
<div>
30+
<span>{{{_highlightResult.name.value}}}</span>
31+
<span>\${{price}}</span>
32+
<span>{{rating}} stars</span>
33+
</div>
34+
<div class="hit-type">
35+
{{{_highlightResult.type.value}}}
36+
</div>
37+
<div class="hit-description">
38+
{{{_highlightResult.description.value}}}
39+
</div>
40+
<div class="hit-tags">
41+
{{#_highlightResult.tags}}
42+
<span>{{{value}}}</span>
43+
{{/_highlightResult.tags}}
44+
</div>
45+
</div>
46+
</div>
47+
`,
48+
},
49+
})
50+
);
51+
},
52+
{
53+
appId: 'KY4PR9ORUL',
54+
apiKey: 'a5ca312adab3b79e14054154efa00b37',
55+
indexName: 'highlight_array',
56+
}
57+
)
58+
);
1659
};

dev/style.css

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,22 +17,22 @@
1717
padding: 50px 40px 40px;
1818
}
1919

20-
#results-display .hit {
20+
.hit {
2121
align-items: center;
2222
display: flex;
2323
margin: 10px 10px;
2424
}
2525

26-
#results-display .hit .hit-picture img {
26+
.hit .hit-picture img {
2727
height: auto;
2828
width: 80px;
2929
}
3030

31-
#results-display .hit .hit-content {
31+
.hit .hit-content {
3232
padding: 0 10px;
3333
}
3434

35-
#results-display .hit .hit-type {
35+
.hit .hit-type {
3636
color: #888;
3737
font-size: 13px;
3838
}

src/lib/__tests__/escape-highlight-test.js

Lines changed: 129 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@ describe('escapeHits()', () => {
44
it('should escape highlighProperty simple text value', () => {
55
const hits = [
66
{
7-
_snippetResult: {
7+
_highlightResult: {
88
foobar: {
99
value: '<script>__ais-highlight__foobar__/ais-highlight__</script>',
1010
},
1111
},
12-
_highlightResult: {
12+
_snippetResult: {
1313
foobar: {
1414
value: '<script>__ais-highlight__foobar__/ais-highlight__</script>',
1515
},
@@ -37,18 +37,18 @@ describe('escapeHits()', () => {
3737
const hits = [
3838
{
3939
_highlightResult: {
40-
foobar: {
41-
value: {
42-
foo: '<script>__ais-highlight__bar__/ais-highlight__</script>',
43-
bar: '<script>__ais-highlight__foo__/ais-highlight__</script>',
40+
foo: {
41+
bar: {
42+
value:
43+
'<script>__ais-highlight__foobar__/ais-highlight__</script>',
4444
},
4545
},
4646
},
4747
_snippetResult: {
48-
foobar: {
49-
value: {
50-
foo: '<script>__ais-highlight__bar__/ais-highlight__</script>',
51-
bar: '<script>__ais-highlight__foo__/ais-highlight__</script>',
48+
foo: {
49+
bar: {
50+
value:
51+
'<script>__ais-highlight__foobar__/ais-highlight__</script>',
5252
},
5353
},
5454
},
@@ -58,64 +58,150 @@ describe('escapeHits()', () => {
5858
expect(escapeHits(hits)).toEqual([
5959
{
6060
_highlightResult: {
61-
foobar: {
62-
value: {
63-
foo: '&lt;script&gt;<em>bar</em>&lt;/script&gt;',
64-
bar: '&lt;script&gt;<em>foo</em>&lt;/script&gt;',
61+
foo: {
62+
bar: {
63+
value: '&lt;script&gt;<em>foobar</em>&lt;/script&gt;',
6564
},
6665
},
6766
},
6867
_snippetResult: {
69-
foobar: {
70-
value: {
71-
foo: '&lt;script&gt;<em>bar</em>&lt;/script&gt;',
72-
bar: '&lt;script&gt;<em>foo</em>&lt;/script&gt;',
68+
foo: {
69+
bar: {
70+
value: '&lt;script&gt;<em>foobar</em>&lt;/script&gt;',
7371
},
7472
},
7573
},
7674
},
7775
]);
7876
});
7977

80-
it('should escape highlighProperty array of string as value', () => {
78+
it('should escape highlighProperty array of string', () => {
8179
const hits = [
8280
{
8381
_highlightResult: {
84-
foobar: {
85-
value: [
86-
'<script>__ais-highlight__bar__/ais-highlight__</script>',
87-
'<script>__ais-highlight__foo__/ais-highlight__</script>',
88-
],
89-
},
82+
foobar: [
83+
{
84+
value: '<script>__ais-highlight__bar__/ais-highlight__</script>',
85+
},
86+
{
87+
value: '<script>__ais-highlight__foo__/ais-highlight__</script>',
88+
},
89+
],
9090
},
9191
_snippetResult: {
92-
foobar: {
93-
value: [
94-
'<script>__ais-highlight__bar__/ais-highlight__</script>',
95-
'<script>__ais-highlight__foo__/ais-highlight__</script>',
96-
],
97-
},
92+
foobar: [
93+
{
94+
value: '<script>__ais-highlight__bar__/ais-highlight__</script>',
95+
},
96+
{
97+
value: '<script>__ais-highlight__foo__/ais-highlight__</script>',
98+
},
99+
],
98100
},
99101
},
100102
];
101103

102104
expect(escapeHits(hits)).toEqual([
103105
{
104106
_highlightResult: {
105-
foobar: {
106-
value: [
107-
'&lt;script&gt;<em>bar</em>&lt;/script&gt;',
108-
'&lt;script&gt;<em>foo</em>&lt;/script&gt;',
109-
],
110-
},
107+
foobar: [
108+
{ value: '&lt;script&gt;<em>bar</em>&lt;/script&gt;' },
109+
{ value: '&lt;script&gt;<em>foo</em>&lt;/script&gt;' },
110+
],
111111
},
112112
_snippetResult: {
113-
foobar: {
114-
value: [
115-
'&lt;script&gt;<em>bar</em>&lt;/script&gt;',
116-
'&lt;script&gt;<em>foo</em>&lt;/script&gt;',
117-
],
118-
},
113+
foobar: [
114+
{ value: '&lt;script&gt;<em>bar</em>&lt;/script&gt;' },
115+
{ value: '&lt;script&gt;<em>foo</em>&lt;/script&gt;' },
116+
],
117+
},
118+
},
119+
]);
120+
});
121+
122+
it('should escape highlighProperty array of object', () => {
123+
const hits = [
124+
{
125+
_highlightResult: {
126+
foobar: [
127+
{
128+
foo: {
129+
bar: {
130+
value:
131+
'<script>__ais-highlight__bar__/ais-highlight__</script>',
132+
},
133+
},
134+
},
135+
{
136+
foo: {
137+
bar: {
138+
value:
139+
'<script>__ais-highlight__foo__/ais-highlight__</script>',
140+
},
141+
},
142+
},
143+
],
144+
},
145+
_snippetResult: {
146+
foobar: [
147+
{
148+
foo: {
149+
bar: {
150+
value:
151+
'<script>__ais-highlight__bar__/ais-highlight__</script>',
152+
},
153+
},
154+
},
155+
{
156+
foo: {
157+
bar: {
158+
value:
159+
'<script>__ais-highlight__foo__/ais-highlight__</script>',
160+
},
161+
},
162+
},
163+
],
164+
},
165+
},
166+
];
167+
168+
expect(escapeHits(hits)).toEqual([
169+
{
170+
_highlightResult: {
171+
foobar: [
172+
{
173+
foo: {
174+
bar: {
175+
value: '&lt;script&gt;<em>bar</em>&lt;/script&gt;',
176+
},
177+
},
178+
},
179+
{
180+
foo: {
181+
bar: {
182+
value: '&lt;script&gt;<em>foo</em>&lt;/script&gt;',
183+
},
184+
},
185+
},
186+
],
187+
},
188+
_snippetResult: {
189+
foobar: [
190+
{
191+
foo: {
192+
bar: {
193+
value: '&lt;script&gt;<em>bar</em>&lt;/script&gt;',
194+
},
195+
},
196+
},
197+
{
198+
foo: {
199+
bar: {
200+
value: '&lt;script&gt;<em>foo</em>&lt;/script&gt;',
201+
},
202+
},
203+
},
204+
],
119205
},
120206
},
121207
]);

src/lib/escape-highlight.js

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import reduce from 'lodash/reduce';
22
import escape from 'lodash/escape';
3-
import isPlainObject from 'lodash/isPlainObject';
43
import isArray from 'lodash/isArray';
5-
import mapValues from 'lodash/mapValues';
4+
import isPlainObject from 'lodash/isPlainObject';
65

76
export const tagConfig = {
87
highlightPreTag: '__ais-highlight__',
@@ -16,25 +15,25 @@ function replaceWithEmAndEscape(value) {
1615
}
1716

1817
function recursiveEscape(input) {
19-
return reduce(
20-
input,
21-
(output, value, key) => {
22-
if (typeof value.value === 'string') {
23-
value.value = replaceWithEmAndEscape(value.value);
24-
}
25-
26-
if (isPlainObject(value.value)) {
27-
value.value = mapValues(value.value, replaceWithEmAndEscape);
28-
}
18+
if (isPlainObject(input) && typeof input.value !== 'string') {
19+
return reduce(
20+
input,
21+
(acc, item, key) => ({
22+
...acc,
23+
[key]: recursiveEscape(item),
24+
}),
25+
{}
26+
);
27+
}
2928

30-
if (isArray(value.value)) {
31-
value.value = value.value.map(replaceWithEmAndEscape);
32-
}
29+
if (isArray(input)) {
30+
return input.map(recursiveEscape);
31+
}
3332

34-
return { ...output, [key]: value };
35-
},
36-
{}
37-
);
33+
return {
34+
...input,
35+
value: replaceWithEmAndEscape(input.value),
36+
};
3837
}
3938

4039
export default function escapeHits(hits) {

0 commit comments

Comments
 (0)