diff --git a/cypress/integration/textarea.js b/cypress/integration/textarea.js index 521d8c9..c162cb2 100644 --- a/cypress/integration/textarea.js +++ b/cypress/integration/textarea.js @@ -72,6 +72,12 @@ describe("React Textarea Autocomplete", () => { .should("have.value", "This is test🙄"); }); + it("should respect already written tokens", () => { + cy.get(".rta__textarea") + .type("This is test -f{enter} and -s{downarrow}{enter}") + .should("have.value", "This is test -first and -second"); + }); + it("special character like [, ( should be also possible to use as trigger char", () => { cy.get(".rta__textarea") .type("This is test [{enter}") @@ -99,7 +105,7 @@ describe("React Textarea Autocomplete", () => { .then(() => { const endLeft = Cypress.$(".rta__autocomplete").css("left"); cy.get(".rta__autocomplete").should("to.have.css", { - left: startLeft + left: startLeft, }); expect(startLeft).to.be.equal(endLeft); @@ -136,10 +142,10 @@ describe("React Textarea Autocomplete", () => { it("onItemHighlighted should return correct item and trigger", () => { cy.get(".rta__textarea").type(":ro{uparrow}{uparrow}"); - cy.window().then(async win => { + cy.window().then(async (win) => { const shouldSelectItem = { currentTrigger: ":", - item: { name: "rofl", char: "🤣" } + item: { name: "rofl", char: "🤣" }, }; expect(win.__lastHighlightedItem).to.deep.equal(shouldSelectItem); @@ -148,10 +154,10 @@ describe("React Textarea Autocomplete", () => { it("onItemSelected should return correct item and trigger", () => { cy.get(".rta__textarea").type(":ro{uparrow}{uparrow}{enter}"); - cy.window().then(async win => { + cy.window().then(async (win) => { const shouldSelectItem = { currentTrigger: ":", - item: { name: "rofl", char: "🤣" } + item: { name: "rofl", char: "🤣" }, }; expect(win.__lastSelectedItem).to.deep.equal(shouldSelectItem); @@ -159,10 +165,6 @@ describe("React Textarea Autocomplete", () => { }); it("should have place caret before outputted word", () => { - /** - * This is probably Cypress bug (1.0.2) - * This test needs to be run in headed mode, otherwise fails - */ cy.get('[data-test="caretStart"]').click(); cy.get(".rta__textarea").type("This is test :ro{downarrow}{downarrow}"); @@ -173,10 +175,6 @@ describe("React Textarea Autocomplete", () => { }); it("should place caret after word", () => { - /** - * This is probably Cypress bug (1.0.2) - * This test needs to be run in headed mode, otherwise fails - */ cy.get('[data-test="caretEnd"]').click(); cy.get(".rta__textarea").type("This is test :ro{downarrow}{downarrow}"); @@ -187,10 +185,6 @@ describe("React Textarea Autocomplete", () => { }); it("should caret after word with a space", () => { - /** - * This is probably Cypress bug (1.0.2) - * This test needs to be run in headed mode, otherwise fails - */ cy.get('[data-test="caretNext"]').click(); cy.get(".rta__textarea").type("This is test :ro{downarrow}{downarrow}"); @@ -266,7 +260,7 @@ describe("React Textarea Autocomplete", () => { cy.get(".rta__textarea").type( `${repeat("{backspace}", 13)} again {downarrow}{enter}`, { - force: true + force: true, } ); cy.get(".rta__textarea").should("have.value", "This is test /"); @@ -366,7 +360,7 @@ describe("React Textarea Autocomplete", () => { .get("li:nth-child(1)") .click(); cy.get(".rta__textarea").type(`${repeat("\n", 5)} test :a`, { - force: true + force: true, }); cy.get(".rta__autocomplete").should( "have.class", @@ -395,13 +389,13 @@ describe("React Textarea Autocomplete", () => { }); it("event is successfully blocked", () => { - cy.window().then(async win => { + cy.window().then(async (win) => { const spy = cy.spy(win.console, "log"); await cy .get(".rta__textarea") .type(":ro{uparrow}{uparrow}{enter}") - .then(e => { + .then((e) => { // the last console.log call should not be `pressed "enter"` because that event is blocked because it's happening in autocomplete. expect(spy.lastCall.args).to.eql([`pressed "o"`]); }); diff --git a/example/App.jsx b/example/App.jsx index 1600230..46c9397 100644 --- a/example/App.jsx +++ b/example/App.jsx @@ -411,7 +411,17 @@ class App extends React.Component { text: `${trigger}${item.name}`, caretPosition: "end" }) - } + }, + "-": { + dataProvider: token => { + return [ + { name: "f", char: "-first" }, + { name: "s", char: "-second" } + ]; + }, + component: Item, + output: this._outputCaretEnd + }, }} /> {!showSecondTextarea ? null : ( diff --git a/src/Textarea.jsx b/src/Textarea.jsx index 595e183..d8fe7e3 100644 --- a/src/Textarea.jsx +++ b/src/Textarea.jsx @@ -384,7 +384,7 @@ class ReactTextareaAutocomplete extends React.Component< _onSelect = (item: Object | string) => { const { selectionEnd, currentTrigger, value: textareaValue } = this.state; - const { trigger, onItemSelected } = this.props; + const { onItemSelected } = this.props; if (!currentTrigger) return; @@ -437,14 +437,15 @@ class ReactTextareaAutocomplete extends React.Component< /** * It's important to escape the currentTrigger char for chars like [, (,... + * This is a ridiculous dark magic, basically we found position of the last current token (from current trigger) and then we replace the text from that position (calculating the offset) */ const escapedCurrentTrigger = escapeRegex(currentTrigger); - const escapedCurrentTriggerWithWhitespace = escapedCurrentTrigger + (trigger[currentTrigger].allowWhitespace ? "" : "\\s"); + const triggerOffset = textToModify.length - textToModify.lastIndexOf(currentTrigger); const startOfTokenPosition = textToModify.search( new RegExp( - `${escapedCurrentTrigger}((?!${escapedCurrentTriggerWithWhitespace}).)*$` + `(?!${escapedCurrentTrigger})$` ) - ); + ) - triggerOffset; // we add space after emoji is selected if a caret position is next const newTokenString =