Repository: ubilabs/react-geosuggest (ts)

Issue:
Highlighting of matched text doesn't work as expected. 
### Short description

Please compare the highlighting of the text (especially the first option) it doesn't match with googles highlighting implementation.

### Expected results

The highlighting of the text should work similar to google highlighting https://developers.google.com/maps/documentation/javascript/examples/places-autocomplete-addressform

<!-- if reporting a bug -->
### Reproduction steps

1. Go to http://ubilabs.github.io/react-geosuggest/
2. Enter "1917 old brick rd" in the input field. 
3. Compare the text highlighting with https://developers.google.com/maps/documentation/javascript/examples/places-autocomplete-addressform they don't match. 
![highlight defect](https://user-images.githubusercontent.com/17805098/44660391-72907c80-a9d5-11e8-8fa8-ce4e306f1e9b.jpg)



Pull request description:
Fix/highlight matched substrings
<!-- Please fill out the title field according to our commit conventions -->

### Description

close #396
- highlight all matches coming from the Google API
- find matched substrings in fixtures case insensitive
- add test for multiple matched substrings

### Checklist

<!-- Mark these as checked by replacing [ ] with [x] -->
- [x] All tests passing
- [x] Created tests which fail without the change (if possible)
- [ ] Extended the README / documentation, if necessary
- [x] Commits and PR follow conventions


Patch:
diff --git a/src/Geosuggest.tsx b/src/Geosuggest.tsx
index 417643c..59676f4 100644
--- a/src/Geosuggest.tsx
+++ b/src/Geosuggest.tsx
@@ -372,10 +372,12 @@ export default class GeoSuggest extends React.Component<Props, State> {
           suggests.push({
             ...fixture,
             isFixture: true,
-            matchedSubstrings: {
-              length: userInput.length,
-              offset: fixture.label.indexOf(userInput)
-            },
+            matchedSubstrings: [
+              {
+                length: userInput.length,
+                offset: fixture.label.search(regex)
+              }
+            ],
             placeId: fixture.placeId || fixture.label
           });
         }
@@ -390,7 +392,7 @@ export default class GeoSuggest extends React.Component<Props, State> {
           label: this.props.getSuggestLabel
             ? this.props.getSuggestLabel(suggest)
             : '',
-          matchedSubstrings: suggest.matched_substrings[0],
+          matchedSubstrings: suggest.matched_substrings,
           placeId: suggest.place_id
         });
       }
diff --git a/src/suggest-item.tsx b/src/suggest-item.tsx
index 5b89373..8d2386a 100644
--- a/src/suggest-item.tsx
+++ b/src/suggest-item.tsx
@@ -43,12 +43,8 @@ export default class SuggestItem extends React.PureComponent<Props, unknown> {
   /**
    * Makes a text bold
    */
-  makeBold(element: string, key: string): JSX.Element {
-    return (
-      <b className="geosuggest__item__matched-text" key={key}>
-        {element}
-      </b>
-    );
+  makeBold(element: string) {
+    return <b className="geosuggest__item__matched-text">{element}</b>;
   }
 
   /**
@@ -59,31 +55,30 @@ export default class SuggestItem extends React.PureComponent<Props, unknown> {
       return suggest.label;
     }
 
-    const start: number = suggest.matchedSubstrings.offset;
-    const length: number = suggest.matchedSubstrings.length;
-    const end: number = start + length;
-    const boldPart = this.makeBold(
-      suggest.label.substring(start, end),
-      suggest.label
-    );
+    const parts = [];
 
-    let pre = '';
-    let post = '';
+    let previousEnd = 0;
+
+    for (const {offset: start, length} of suggest.matchedSubstrings) {
+      // Add non-matching substring before and between matches
+      if (start > previousEnd) {
+        parts.push(suggest.label.substring(previousEnd, start));
+      }
 
-    if (start > 0) {
-      pre = suggest.label.slice(0, start);
+      // Add matching substring as bold text
+      const end = start + length;
+      const match = this.makeBold(suggest.label.substring(start, end));
+      parts.push(match);
+
+      previousEnd = end;
     }
-    if (end < suggest.label.length) {
-      post = suggest.label.slice(end);
+
+    // Add non-matching substring after matches
+    if (previousEnd < suggest.label.length) {
+      parts.push(suggest.label.substring(previousEnd));
     }
 
-    return (
-      <span>
-        {pre}
-        {boldPart}
-        {post}
-      </span>
-    );
+    return <span>{parts}</span>;
   }
 
   /**
diff --git a/src/types/suggest.ts b/src/types/suggest.ts
index 6f86d64..2dc02b7 100644
--- a/src/types/suggest.ts
+++ b/src/types/suggest.ts
@@ -13,6 +13,6 @@ export interface Suggest {
   readonly matchedSubstrings?: {
     offset: number;
     length: number;
-  };
+  }[];
   readonly className?: string;
 }


Test patch:
diff --git a/test/Geosuggest_spec.tsx b/test/Geosuggest_spec.tsx
index 65fc1c4..c8d0dc4 100644
--- a/test/Geosuggest_spec.tsx
+++ b/test/Geosuggest_spec.tsx
@@ -870,13 +870,28 @@ describe('Component: Geosuggest', () => {
       expect(matchedText).to.have.lengthOf.at.least(1);
     });
 
-    it('should render a match with minial nodes', () => {
+    it('should highlight multiple matched substrings', () => {
       const geoSuggestInput = component.container.querySelector(
         '.geosuggest__input'
       ) as HTMLInputElement;
       fireEvent.change(geoSuggestInput, {target: {value: 'Newa'}});
       geoSuggestInput.focus();
 
+      const geoSuggestItems =
+        component.container.getElementsByClassName('geosuggest__item');
+      const matchedText = geoSuggestItems[0].getElementsByClassName(
+        'geosuggest__item__matched-text'
+      );
+      expect(matchedText).to.have.lengthOf(2);
+    });
+
+    it('should render a match with minimal nodes', () => {
+      const geoSuggestInput = component.container.querySelector(
+        '.geosuggest__input'
+      ) as HTMLInputElement;
+      fireEvent.change(geoSuggestInput, {target: {value: 'New Jersey'}});
+      geoSuggestInput.focus();
+
       const geoSuggestItems =
         component.container.getElementsByClassName('geosuggest__item');
       expect(geoSuggestItems).to.have.lengthOf(1);
diff --git a/test/fixtures/predictions.ts b/test/fixtures/predictions.ts
index 02a6ed1..060290c 100644
--- a/test/fixtures/predictions.ts
+++ b/test/fixtures/predictions.ts
@@ -149,6 +149,10 @@ export default function predictions(): google.maps.places.AutocompletePrediction
         {
           length: 2,
           offset: 0
+        },
+        {
+          length: 2,
+          offset: 8
         }
       ],
       place_id: 'ChIJHQ6aMnBTwokRc-T-3CrcvOE',