export function SearchResultText({ result }) { | export function SearchResultText({ result }) { | ||||
const textHighlights = result.highlights.text; | const textHighlights = result.highlights.text; | ||||
if (textHighlights && textHighlights.length > 0) { | |||||
const { text } = result.page; | |||||
const tokens = highlightMarks(text, textHighlights.map(h => ({ from: h[0], to: h[0] + h[1] }))); | |||||
const { text } = result.page; | |||||
let tokens = []; | |||||
if (result.exactMatch) { | |||||
const pageText = result.page.text.toLowerCase(); | |||||
const highlights = []; | |||||
let start = 0; | |||||
let index; | |||||
let loopCount = 0; | |||||
while ((index = pageText.indexOf(result.query, start)) > -1 && loopCount < 10) { | |||||
loopCount++; | |||||
highlights.push({ from: index, to: index + result.query.length }); | |||||
start = index + 1; | |||||
} | |||||
if (highlights.length) { | |||||
tokens = highlightMarks(text, highlights); | |||||
} | |||||
} | |||||
if (tokens.length === 0 && textHighlights && textHighlights.length > 0) { | |||||
tokens = highlightMarks(text, textHighlights.map(h => ({ from: h[0], to: h[0] + h[1] }))); | |||||
} | |||||
if (tokens.length) { | |||||
return ( | return ( | ||||
<div className="note"> | <div className="note"> | ||||
<SearchResultTokens tokens={cutWords(tokens)} /> | <SearchResultTokens tokens={cutWords(tokens)} /> |
highlights: { [field: string]: [number, number][] }; | highlights: { [field: string]: [number, number][] }; | ||||
longestTerm: string; | longestTerm: string; | ||||
page: DocumentationEntry; | page: DocumentationEntry; | ||||
query: string; | |||||
} | } | ||||
interface Props { | interface Props { | ||||
export function SearchResultText({ result }: { result: SearchResult }) { | export function SearchResultText({ result }: { result: SearchResult }) { | ||||
const textHighlights = result.highlights.text; | const textHighlights = result.highlights.text; | ||||
if (textHighlights && textHighlights.length > 0) { | |||||
const { text } = result.page; | |||||
const tokens = highlightMarks(text, textHighlights.map(h => ({ from: h[0], to: h[0] + h[1] }))); | |||||
const { text } = result.page; | |||||
let tokens: { | |||||
text: string; | |||||
marked: boolean; | |||||
}[] = []; | |||||
if (result.exactMatch) { | |||||
const pageText = result.page.text.toLowerCase(); | |||||
const highlights: { from: number; to: number }[] = []; | |||||
let start = 0; | |||||
let index = pageText.indexOf(result.query, start); | |||||
let loopCount = 0; | |||||
while (index > -1 && loopCount < 10) { | |||||
loopCount++; | |||||
highlights.push({ from: index, to: index + result.query.length }); | |||||
start = index + 1; | |||||
index = pageText.indexOf(result.query, start); | |||||
} | |||||
if (highlights.length) { | |||||
tokens = highlightMarks(text, highlights); | |||||
} | |||||
} | |||||
if (tokens.length === 0 && textHighlights && textHighlights.length > 0) { | |||||
tokens = highlightMarks(text, textHighlights.map(h => ({ from: h[0], to: h[0] + h[1] }))); | |||||
} | |||||
if (tokens.length) { | |||||
return ( | return ( | ||||
<div className="note"> | <div className="note"> | ||||
<SearchResultTokens tokens={cutWords(tokens)} /> | <SearchResultTokens tokens={cutWords(tokens)} /> |
}); | }); | ||||
}); | }); | ||||
return { page, highlights, longestTerm, exactMatch }; | |||||
return { exactMatch, highlights, longestTerm, page, query }; | |||||
}) | }) | ||||
.filter(result => result.page) as SearchResult[]; | .filter(result => result.page) as SearchResult[]; | ||||
SearchResultTokens | SearchResultTokens | ||||
} from '../SearchResultEntry'; | } from '../SearchResultEntry'; | ||||
const page = { | |||||
content: '', | |||||
relativeName: 'foo/bar', | |||||
url: '/foo/bar', | |||||
text: 'Foobar is a universal variable understood to represent whatever is being discussed.', | |||||
title: 'Foobar', | |||||
navTitle: undefined | |||||
}; | |||||
describe('SearchResultEntry', () => { | describe('SearchResultEntry', () => { | ||||
it('should render', () => { | it('should render', () => { | ||||
expect( | expect( | ||||
shallow( | |||||
<SearchResultEntry active={true} result={{ page, highlights: {}, longestTerm: '' }} /> | |||||
) | |||||
shallow(<SearchResultEntry active={true} result={mockSearchResult()} />) | |||||
).toMatchSnapshot(); | ).toMatchSnapshot(); | ||||
}); | }); | ||||
}); | }); | ||||
describe('SearchResultText', () => { | describe('SearchResultText', () => { | ||||
it('should render with highlights', () => { | it('should render with highlights', () => { | ||||
expect( | |||||
shallow(<SearchResultText result={mockSearchResult({ highlights: { text: [[12, 9]] } })} />) | |||||
).toMatchSnapshot(); | |||||
}); | |||||
it('should correctly extract exact matches', () => { | |||||
expect( | expect( | ||||
shallow( | shallow( | ||||
<SearchResultText result={{ page, highlights: { text: [[12, 9]] }, longestTerm: '' }} /> | |||||
<SearchResultText | |||||
result={mockSearchResult({ exactMatch: true, query: 'variable understood' })} | |||||
/> | |||||
) | ) | ||||
).toMatchSnapshot(); | ).toMatchSnapshot(); | ||||
}); | }); | ||||
it('should render without highlights', () => { | it('should render without highlights', () => { | ||||
expect( | |||||
shallow(<SearchResultText result={{ page, highlights: {}, longestTerm: '' }} />) | |||||
).toMatchSnapshot(); | |||||
expect(shallow(<SearchResultText result={mockSearchResult()} />)).toMatchSnapshot(); | |||||
}); | }); | ||||
}); | }); | ||||
describe('SearchResultTitle', () => { | describe('SearchResultTitle', () => { | ||||
it('should render with highlights', () => { | it('should render with highlights', () => { | ||||
expect( | expect( | ||||
shallow( | |||||
<SearchResultTitle result={{ page, highlights: { title: [[0, 6]] }, longestTerm: '' }} /> | |||||
) | |||||
shallow(<SearchResultTitle result={mockSearchResult({ highlights: { title: [[0, 6]] } })} />) | |||||
).toMatchSnapshot(); | ).toMatchSnapshot(); | ||||
}); | }); | ||||
it('should render not without highlights', () => { | it('should render not without highlights', () => { | ||||
expect( | |||||
shallow(<SearchResultTitle result={{ page, highlights: {}, longestTerm: '' }} />) | |||||
).toMatchSnapshot(); | |||||
expect(shallow(<SearchResultTitle result={mockSearchResult()} />)).toMatchSnapshot(); | |||||
}); | }); | ||||
}); | }); | ||||
).toMatchSnapshot(); | ).toMatchSnapshot(); | ||||
}); | }); | ||||
}); | }); | ||||
function mockSearchResult(overrides = {}) { | |||||
return { | |||||
page: { | |||||
content: '', | |||||
relativeName: 'foo/bar', | |||||
url: '/foo/bar', | |||||
text: 'Foobar is a universal variable understood to represent whatever is being discussed.', | |||||
title: 'Foobar', | |||||
navTitle: undefined | |||||
}, | |||||
highlights: {}, | |||||
longestTerm: '', | |||||
query: '', | |||||
...overrides | |||||
}; | |||||
} |
"title": "Foobar", | "title": "Foobar", | ||||
"url": "/foo/bar", | "url": "/foo/bar", | ||||
}, | }, | ||||
"query": "", | |||||
} | } | ||||
} | } | ||||
/> | /> | ||||
"title": "Foobar", | "title": "Foobar", | ||||
"url": "/foo/bar", | "url": "/foo/bar", | ||||
}, | }, | ||||
"query": "", | |||||
} | } | ||||
} | } | ||||
/> | /> | ||||
</Link> | </Link> | ||||
`; | `; | ||||
exports[`SearchResultText should correctly extract exact matches 1`] = ` | |||||
<div | |||||
className="note" | |||||
> | |||||
<SearchResultTokens | |||||
tokens={ | |||||
Array [ | |||||
Object { | |||||
"marked": false, | |||||
"text": "Foobar is a universal ", | |||||
}, | |||||
Object { | |||||
"marked": true, | |||||
"text": "variable understood", | |||||
}, | |||||
Object { | |||||
"marked": false, | |||||
"text": " to represent whatever is being discussed.", | |||||
}, | |||||
] | |||||
} | |||||
/> | |||||
</div> | |||||
`; | |||||
exports[`SearchResultText should render with highlights 1`] = ` | exports[`SearchResultText should render with highlights 1`] = ` | ||||
<div | <div | ||||
className="note" | className="note" |
"title": "Where does Foobar come from?", | "title": "Where does Foobar come from?", | ||||
"url": "/foobar", | "url": "/foobar", | ||||
}, | }, | ||||
"query": "simply text", | |||||
} | } | ||||
} | } | ||||
/> | /> | ||||
"title": "Where does it come from?", | "title": "Where does it come from?", | ||||
"url": "/lorem/origin", | "url": "/lorem/origin", | ||||
}, | }, | ||||
"query": "simply text", | |||||
} | } | ||||
} | } | ||||
/> | /> |