Skip to content

Commit

Permalink
Added highlight to search suggestions (#117)
Browse files Browse the repository at this point in the history
* Added highlight to search suggestions

* Using JSS class for highlight instead

* Added highlight tests
  • Loading branch information
dijs authored Jun 26, 2020
1 parent 0e22335 commit b2fddca
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 16 deletions.
27 changes: 27 additions & 0 deletions src/Highlight.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React from 'react'
import { Typography } from '@material-ui/core'

const escapeHtml = unsafe =>
unsafe
.replace(/&/g, '&')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;')

const addHighlight = (query, text, className = '') => {
if (!text) return ''
return escapeHtml(text).replace(
new RegExp(query, 'gi'),
match => `<span class="${className}">${match}</span>`,
)
}

export default function Highlight({ query, text, classes = {}, ...props }) {
return (
<Typography
{...props}
dangerouslySetInnerHTML={{ __html: addHighlight(query, text, classes.highlight) }}
/>
)
}
1 change: 1 addition & 0 deletions src/search/SearchProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export default function SearchProvider({ children, query, initialGroups, active
}, 250)

const context = {
query,
state,
setState,
fetchSuggestions,
Expand Down
20 changes: 17 additions & 3 deletions src/search/SearchSuggestionItem.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import React from 'react'
import React, { useContext } from 'react'
import makeStyles from '@material-ui/core/styles/makeStyles'
import Link from '../link/Link'
import { Typography } from '@material-ui/core'
import PropTypes from 'prop-types'
import Image from '../Image'
import SearchContext from './SearchContext'
import Highlight from '../Highlight'

export const styles = theme => ({
root: {
Expand All @@ -28,6 +29,12 @@ export const styles = theme => ({
},
},
},
text: {},
highlight: {
backgroundColor: 'rgba(0,0,0,0.05)',
borderRadius: '2px',
color: theme.palette.secondary.main,
},
})

const useStyles = makeStyles(styles, { name: 'RSFSearchSuggestionItem' })
Expand All @@ -42,6 +49,8 @@ export default function SearchSuggestionItem({
}) {
classes = useStyles({ classes })

const { query } = useContext(SearchContext)

return (
<li className={classes.root}>
<Link as={item.as} href={item.href} pageData={item.pageData}>
Expand All @@ -55,7 +64,12 @@ export default function SearchSuggestionItem({
{...thumbnailProps}
{...item.thumbnail}
/>
<Typography>{item.text}</Typography>
<Highlight
className={classes.text}
query={query}
text={item.text}
classes={classes}
/>
</div>
</a>
)}
Expand Down
25 changes: 25 additions & 0 deletions test/Highlight.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React from 'react'
import { mount } from 'enzyme'
import Highlight from 'react-storefront/Highlight'

describe('Highlight', () => {
it('should not blow up if empty props', () => {
const wrapper = mount(<Highlight />)
expect(wrapper.text()).toBe('')
})
it('should not add highlights if no matches', () => {
const wrapper = mount(<Highlight text="the fox jumps over" query="dog" />)
expect(wrapper.text()).toBe('the fox jumps over')
})
it('should escape text', () => {
const wrapper = mount(<Highlight text={`"foo" > 'bar' < zat`} />)
expect(wrapper.text()).toBe('&quot;foo&quot; &gt; &#039;bar&#039; &lt; zat')
})
it('should add highlights to matches', () => {
const wrapper = mount(
<Highlight text="the fox jumps over the ox" query="ox" classes={{ highlight: 'foo' }} />,
)
const matches = wrapper.html().match(/<span class="foo">ox<\/span>/g)
expect(matches.length).toBe(2)
})
})
19 changes: 14 additions & 5 deletions test/search/SearchSuggestionGroup.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from 'react'
import { mount } from 'enzyme'
import SearchSuggestionGroup from 'react-storefront/search/SearchSuggestionGroup'
import SearchSuggestionItem from 'react-storefront/search/SearchSuggestionItem'
import SearchProvider from 'react-storefront/search/SearchProvider'
import PWAContext from 'react-storefront/PWAContext'

describe('SearchSuggestionGroup', () => {
Expand All @@ -13,9 +14,11 @@ describe('SearchSuggestionGroup', () => {

it('should render children when provided', () => {
wrapper = mount(
<SearchSuggestionGroup caption="">
<div id="child">child</div>
</SearchSuggestionGroup>,
<SearchProvider>
<SearchSuggestionGroup caption="">
<div id="child">child</div>
</SearchSuggestionGroup>
</SearchProvider>,
)

expect(wrapper.find('#child').text()).toBe('child')
Expand All @@ -24,15 +27,21 @@ describe('SearchSuggestionGroup', () => {
it('should render suggested items when no children provided', () => {
wrapper = mount(
<PWAContext.Provider value={{ hydrating: false }}>
<SearchSuggestionGroup caption="" links={[{ href: '/test1' }, { href: '/test2' }]} />
<SearchProvider>
<SearchSuggestionGroup caption="" links={[{ href: '/test1' }, { href: '/test2' }]} />
</SearchProvider>
</PWAContext.Provider>,
)

expect(wrapper.find(SearchSuggestionItem).length).toBe(2)
})

it('should render provided caption', () => {
wrapper = mount(<SearchSuggestionGroup links={[]} caption="test" />)
wrapper = mount(
<SearchProvider>
<SearchSuggestionGroup links={[]} caption="test" />
</SearchProvider>,
)

expect(wrapper.find(SearchSuggestionGroup).text()).toBe('test')
})
Expand Down
23 changes: 15 additions & 8 deletions test/search/SearchSuggestionItem.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react'
import { mount } from 'enzyme'
import SearchProvider from 'react-storefront/search/SearchProvider'
import SearchSuggestionItem from 'react-storefront/search/SearchSuggestionItem'
import Image from 'react-storefront/Image'
import PWAContext from 'react-storefront/PWAContext'
Expand All @@ -14,9 +15,11 @@ describe('SearchSuggestionItem', () => {
it('should render children when provided', () => {
wrapper = mount(
<PWAContext.Provider value={{ hydrating: false }}>
<SearchSuggestionItem item={{ href: '/test' }}>
<div id="child">child</div>
</SearchSuggestionItem>
<SearchProvider>
<SearchSuggestionItem item={{ href: '/test' }}>
<div id="child">child</div>
</SearchSuggestionItem>
</SearchProvider>
</PWAContext.Provider>,
)

Expand All @@ -26,7 +29,9 @@ describe('SearchSuggestionItem', () => {
it('should render image with a text when no children provided', () => {
wrapper = mount(
<PWAContext.Provider value={{ hydrating: false }}>
<SearchSuggestionItem item={{ href: '/test', text: 'test' }} />
<SearchProvider>
<SearchSuggestionItem item={{ href: '/test', text: 'test' }} />
</SearchProvider>
</PWAContext.Provider>,
)

Expand All @@ -42,10 +47,12 @@ describe('SearchSuggestionItem', () => {
it('should spread thumbnail props on image', () => {
wrapper = mount(
<PWAContext.Provider value={{ hydrating: false }}>
<SearchSuggestionItem
item={{ href: '/test', thumbnail: { testprop2: 'test2' } }}
thumbnailProps={{ testprop1: 'test1' }}
/>
<SearchProvider>
<SearchSuggestionItem
item={{ href: '/test', thumbnail: { testprop2: 'test2' } }}
thumbnailProps={{ testprop1: 'test1' }}
/>
</SearchProvider>
</PWAContext.Provider>,
)

Expand Down

0 comments on commit b2fddca

Please sign in to comment.