Skip to content

Commit a19615e

Browse files
authored
fix: ensure regex reserved characters are escaped (#186)
* fix: ensure regex reserved characters are escaped (#185) * chore(tests): Add unit tests for regex escaping (#185)
1 parent 9a581b9 commit a19615e

File tree

3 files changed

+95
-4
lines changed

3 files changed

+95
-4
lines changed

__tests__/__snapshots__/index.spec.js.snap

+17
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,22 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3+
exports[`multi character trigger match the snapshot 1`] = `
4+
<div
5+
className="rta my-rta-container"
6+
>
7+
<textarea
8+
className="rta__textarea my-rta"
9+
onBlur={[Function]}
10+
onChange={[Function]}
11+
onClick={[Function]}
12+
onScroll={[Function]}
13+
onSelect={[Function]}
14+
placeholder="Write a message."
15+
value="Controlled text"
16+
/>
17+
</div>
18+
`;
19+
320
exports[`object-based items match the snapshot 1`] = `
421
<div
522
className="rta my-rta-container"

__tests__/index.spec.js

+49
Original file line numberDiff line numberDiff line change
@@ -490,3 +490,52 @@ describe("object-based items without keys and custom unique generator", () => {
490490
expect(items.at(2).key()).toEqual("13");
491491
});
492492
});
493+
494+
describe("multi character trigger", () => {
495+
const mockedChangeFn = jest.fn();
496+
const mockedSelectFn = jest.fn();
497+
const mockedCaretPositionChangeFn = jest.fn();
498+
499+
const rtaComponent = (
500+
<ReactTextareaAutocomplete
501+
className="my-rta"
502+
containerClassName="my-rta-container"
503+
listClassName="my-rta-list"
504+
itemClassName="my-rta-item"
505+
loaderClassName="my-rta-loader"
506+
placeholder="Write a message."
507+
value="Controlled text"
508+
onChange={mockedChangeFn}
509+
onSelect={mockedSelectFn}
510+
onCaretPositionChange={mockedCaretPositionChangeFn}
511+
loadingComponent={Loading}
512+
trigger={{
513+
":(": {
514+
output: item => `___${item.text}___`,
515+
dataProvider: () => [
516+
{ id: 1, label: ":)", text: "happy_face" },
517+
{ id: 2, label: ":(", text: "sad_face" }
518+
],
519+
component: SmileItemComponent
520+
}
521+
}}
522+
/>
523+
);
524+
525+
const rta = mount(rtaComponent);
526+
527+
it("match the snapshot", () => {
528+
expect(shallow(rtaComponent)).toMatchSnapshot();
529+
});
530+
531+
it("Textarea exists", () => {
532+
expect(rta.find("textarea")).toHaveLength(1);
533+
});
534+
535+
it("After the trigger was typed, it should appear list of options", () => {
536+
rta
537+
.find("textarea")
538+
.simulate("change", { target: { value: "some test :(a" } });
539+
expect(rta.find(".rta__autocomplete")).toHaveLength(1);
540+
});
541+
});

src/Textarea.jsx

+29-4
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,31 @@ const errorMessage = (message: string) =>
4040
\nCheck the documentation or create issue if you think it's bug. https://github.com/webscopeio/react-textarea-autocomplete/issues`
4141
);
4242

43+
const reservedRegexChars = [
44+
'.',
45+
'^',
46+
'$',
47+
'*',
48+
'+',
49+
'-',
50+
'?',
51+
'(',
52+
')',
53+
'[',
54+
']',
55+
'{',
56+
'}',
57+
'\\',
58+
'|',
59+
]
60+
61+
const escapeRegex = text =>
62+
[...text]
63+
.map(character =>
64+
reservedRegexChars.includes(character) ? `\\${character}` : character
65+
)
66+
.join('')
67+
4368
// The main purpose of this component is to figure out to which side the autocomplete should be opened
4469
type AutocompleteProps = {
4570
style: ?Object,
@@ -415,7 +440,7 @@ class ReactTextareaAutocomplete extends React.Component<
415440
* It's important to escape the currentTrigger char for chars like [, (,...
416441
*/
417442
new RegExp(
418-
`\\${currentTrigger}${`[^\\${currentTrigger}${
443+
`${escapeRegex(currentTrigger)}${`[^${escapeRegex(currentTrigger)}${
419444
trigger[currentTrigger].allowWhitespace ? "" : "\\s"
420445
}]`}*$`
421446
)
@@ -616,7 +641,7 @@ class ReactTextareaAutocomplete extends React.Component<
616641
}
617642
return 0;
618643
})
619-
.map(a => `\\${a}`)
644+
.map(a => escapeRegex(a))
620645
.join("|")})((?:(?!\\1)[^\\s])*$)`
621646
);
622647

@@ -632,7 +657,7 @@ class ReactTextareaAutocomplete extends React.Component<
632657
}
633658
return 0;
634659
})
635-
.map(a => `\\${a}`)
660+
.map(a => escapeRegex(a))
636661
.join("|")})$`
637662
);
638663
};
@@ -810,7 +835,7 @@ class ReactTextareaAutocomplete extends React.Component<
810835
this.state.currentTrigger &&
811836
trigger[this.state.currentTrigger].allowWhitespace
812837
) {
813-
tokenMatch = new RegExp(`\\${this.state.currentTrigger}.*$`).exec(
838+
tokenMatch = new RegExp(`${escapeRegex(this.state.currentTrigger)}.*$`).exec(
814839
value.slice(0, selectionEnd)
815840
);
816841
lastToken = tokenMatch && tokenMatch[0];

0 commit comments

Comments
 (0)