mirror of
https://github.com/Foundry376/Mailspring.git
synced 2025-02-23 23:54:13 +08:00
fix(composer-emojis): Add spec and allow adjacent emojis
Summary: Adds tests to the emoji picker. The emoji picker should also now be able to add emojis consecutively (without spaces). Test Plan: Tests included in diff. Reviewers: evan, bengotow Differential Revision: https://phab.nylas.com/D2551
This commit is contained in:
parent
c4749f592e
commit
e53b481a25
3 changed files with 150 additions and 35 deletions
|
@ -14,7 +14,7 @@ class EmojisComposerExtension extends ContenteditableExtension {
|
|||
const offset = sel.anchorOffset;
|
||||
if (!DOMUtils.closest(sel.anchorNode, "n1-emoji-autocomplete")) {
|
||||
editor.select(sel.anchorNode,
|
||||
sel.anchorOffset - triggerWord.length,
|
||||
sel.anchorOffset - triggerWord.length - 1,
|
||||
sel.focusNode,
|
||||
sel.focusOffset).wrapSelection("n1-emoji-autocomplete");
|
||||
editor.select(sel.anchorNode,
|
||||
|
@ -26,9 +26,9 @@ class EmojisComposerExtension extends ContenteditableExtension {
|
|||
if (DOMUtils.closest(sel.anchorNode, "n1-emoji-autocomplete")) {
|
||||
editor.unwrapNodeAndSelectAll(DOMUtils.closest(sel.anchorNode, "n1-emoji-autocomplete"));
|
||||
editor.select(sel.anchorNode,
|
||||
sel.anchorOffset + triggerWord.length,
|
||||
sel.anchorOffset + triggerWord.length + 1,
|
||||
sel.focusNode,
|
||||
sel.focusOffset + triggerWord.length);
|
||||
sel.focusOffset + triggerWord.length + 1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -49,9 +49,8 @@ class EmojisComposerExtension extends ContenteditableExtension {
|
|||
if (emojiOptions.length > 0 && !toolbarState.dragging && !toolbarState.doubleDown) {
|
||||
const locationRefNode = DOMUtils.closest(sel.anchorNode,
|
||||
"n1-emoji-autocomplete");
|
||||
const emojiNameNode = DOMUtils.closest(sel.anchorNode,
|
||||
"n1-emoji-autocomplete");
|
||||
const selectedEmoji = emojiNameNode.getAttribute("selectedEmoji");
|
||||
if (!locationRefNode) return null;
|
||||
const selectedEmoji = locationRefNode.getAttribute("selectedEmoji");
|
||||
return {
|
||||
component: EmojiPicker,
|
||||
props: {emojiOptions,
|
||||
|
@ -111,22 +110,22 @@ class EmojisComposerExtension extends ContenteditableExtension {
|
|||
sel.anchorNode.nodeValue &&
|
||||
sel.anchorNode.nodeValue.length > 0 &&
|
||||
sel.isCollapsed) {
|
||||
const words = sel.anchorNode.nodeValue.substring(0, sel.anchorOffset).split(" ");
|
||||
let lastWord = words[words.length - 1].trim();
|
||||
if (words.length === 1 &&
|
||||
lastWord.indexOf(" ") === -1 &&
|
||||
lastWord.indexOf(":") === -1) {
|
||||
const words = sel.anchorNode.nodeValue.substring(0, sel.anchorOffset);
|
||||
let index = words.lastIndexOf(":");
|
||||
let lastWord = "";
|
||||
if (index !== -1 && words.lastIndexOf(" ") < index) {
|
||||
lastWord = words.substring(index + 1, sel.anchorOffset);
|
||||
} else {
|
||||
const {text} = EmojisComposerExtension._getTextUntilSpace(sel.anchorNode, sel.anchorOffset);
|
||||
lastWord = text;
|
||||
}
|
||||
if (lastWord.length > 1 &&
|
||||
lastWord.charAt(0) === ":" &
|
||||
lastWord.charAt(lastWord.length - 1) !== " ") {
|
||||
let word = lastWord.substring(1);
|
||||
if (lastWord.charAt(lastWord.length - 1) === ":") {
|
||||
word = word.substring(0, word.length - 1);
|
||||
index = text.lastIndexOf(":");
|
||||
if (index !== -1 && text.lastIndexOf(" ") < index) {
|
||||
lastWord = text.substring(index + 1);
|
||||
} else {
|
||||
return {triggerWord: "", emojiOptions: []};
|
||||
}
|
||||
return {triggerWord: lastWord, emojiOptions: EmojisComposerExtension._findMatches(word)};
|
||||
}
|
||||
if (lastWord.length > 0) {
|
||||
return {triggerWord: lastWord, emojiOptions: EmojisComposerExtension._findMatches(lastWord)};
|
||||
}
|
||||
return {triggerWord: lastWord, emojiOptions: []};
|
||||
}
|
||||
|
@ -140,22 +139,22 @@ class EmojisComposerExtension extends ContenteditableExtension {
|
|||
if (sel.anchorNode &&
|
||||
sel.anchorNode.nodeValue &&
|
||||
sel.anchorNode.nodeValue.length > 0 &&
|
||||
sel.isCollapsed) {
|
||||
const words = sel.anchorNode.nodeValue.substring(0, sel.anchorOffset).split(" ");
|
||||
let lastWord = words[words.length - 1].trim();
|
||||
if (words.length === 1 &&
|
||||
lastWord.indexOf(" ") === -1 &&
|
||||
lastWord.indexOf(":") === -1) {
|
||||
const {text, textNode} = EmojisComposerExtension._getTextUntilSpace(sel.anchorNode, sel.anchorOffset);
|
||||
lastWord = text;
|
||||
const offset = textNode.nodeValue.lastIndexOf(":");
|
||||
editor.select(textNode,
|
||||
offset,
|
||||
sel.isCollapsed) {
|
||||
const words = sel.anchorNode.nodeValue.substring(0, sel.anchorOffset);
|
||||
let index = words.lastIndexOf(":");
|
||||
let lastWord = words.substring(index + 1, sel.anchorOffset);
|
||||
if (index !== -1 && words.lastIndexOf(" ") < index) {
|
||||
editor.select(sel.anchorNode,
|
||||
sel.anchorOffset - lastWord.length - 1,
|
||||
sel.focusNode,
|
||||
sel.focusOffset);
|
||||
} else {
|
||||
editor.select(sel.anchorNode,
|
||||
sel.anchorOffset - lastWord.length,
|
||||
const {text, textNode} = EmojisComposerExtension._getTextUntilSpace(sel.anchorNode, sel.anchorOffset);
|
||||
index = text.lastIndexOf(":");
|
||||
lastWord = text.substring(index + 1);
|
||||
const offset = textNode.nodeValue.lastIndexOf(":");
|
||||
editor.select(textNode,
|
||||
offset,
|
||||
sel.focusNode,
|
||||
sel.focusOffset);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/** @babel */
|
||||
import {ExtensionRegistry} from 'nylas-exports';
|
||||
import EmojisComposerExtension from './emojis-composer-extension'
|
||||
import EmojisComposerExtension from './emojis-composer-extension';
|
||||
|
||||
export function activate() {
|
||||
ExtensionRegistry.Composer.register(EmojisComposerExtension);
|
||||
|
@ -8,4 +8,4 @@ export function activate() {
|
|||
|
||||
export function deactivate() {
|
||||
ExtensionRegistry.Composer.unregister(EmojisComposerExtension);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,116 @@
|
|||
import React, {addons} from 'react/addons';
|
||||
import {renderIntoDocument} from '../../../spec/nylas-test-utils';
|
||||
import Contenteditable from '../../../src/components/contenteditable/contenteditable';
|
||||
import EmojisComposerExtension from '../lib/emojis-composer-extension';
|
||||
|
||||
const ReactTestUtils = addons.TestUtils;
|
||||
|
||||
describe('EmojisComposerExtension', ()=> {
|
||||
beforeEach(()=> {
|
||||
spyOn(EmojisComposerExtension, 'onContentChanged').andCallThrough()
|
||||
spyOn(EmojisComposerExtension, '_onSelectEmoji').andCallThrough()
|
||||
const html = 'Testing!'
|
||||
const onChange = jasmine.createSpy('onChange')
|
||||
this.component = renderIntoDocument(
|
||||
<Contenteditable html={html} onChange={onChange} extensions={[EmojisComposerExtension]}/>
|
||||
)
|
||||
this.editableNode = React.findDOMNode(ReactTestUtils.findRenderedDOMComponentWithAttr(this.component, 'contentEditable'));
|
||||
})
|
||||
|
||||
describe('when emoji trigger is typed', ()=> {
|
||||
beforeEach(()=> {
|
||||
this._performEdit = (newHTML) => {
|
||||
this.editableNode.innerHTML = newHTML;
|
||||
const sel = document.getSelection()
|
||||
const textNode = this.editableNode.childNodes[0];
|
||||
sel.setBaseAndExtent(textNode, textNode.nodeValue.length, textNode, textNode.nodeValue.length);
|
||||
}
|
||||
})
|
||||
|
||||
it('should show the emoji picker', ()=> {
|
||||
runs(()=> {
|
||||
this._performEdit('Testing! :h');
|
||||
});
|
||||
waitsFor(()=> {
|
||||
return ReactTestUtils.scryRenderedDOMComponentsWithClass(this.component, 'emoji-picker').length > 0
|
||||
});
|
||||
})
|
||||
|
||||
it('should be focused on the first emoji in the list', ()=> {
|
||||
runs(()=> {
|
||||
this._performEdit('Testing! :h');
|
||||
});
|
||||
waitsFor(()=> {
|
||||
return ReactTestUtils.scryRenderedDOMComponentsWithClass(this.component, 'emoji-option').length > 0
|
||||
});
|
||||
runs(()=> {
|
||||
expect(React.findDOMNode(ReactTestUtils.findRenderedDOMComponentWithClass(this.component, 'emoji-option')).textContent === "💇 :haircut:").toBe(true);
|
||||
});
|
||||
})
|
||||
|
||||
it('should insert an emoji on enter', ()=> {
|
||||
runs(()=> {
|
||||
this._performEdit('Testing! :h');
|
||||
});
|
||||
waitsFor(()=> {
|
||||
return ReactTestUtils.scryRenderedDOMComponentsWithClass(this.component, 'emoji-picker').length > 0
|
||||
});
|
||||
runs(()=> {
|
||||
ReactTestUtils.Simulate.keyDown(this.editableNode, {key: "Enter", keyCode: 13, which: 13});
|
||||
});
|
||||
waitsFor(()=> {
|
||||
return EmojisComposerExtension._onSelectEmoji.calls.length > 0
|
||||
})
|
||||
runs(()=> {
|
||||
expect(this.editableNode.textContent === "Testing! 💇").toBe(true);
|
||||
});
|
||||
})
|
||||
|
||||
it('should insert an emoji on click', ()=> {
|
||||
runs(()=> {
|
||||
this._performEdit('Testing! :h');
|
||||
});
|
||||
waitsFor(()=> {
|
||||
return ReactTestUtils.scryRenderedDOMComponentsWithClass(this.component, 'emoji-picker').length > 0
|
||||
});
|
||||
runs(()=> {
|
||||
const button = React.findDOMNode(ReactTestUtils.findRenderedDOMComponentWithClass(this.component, 'emoji-option'))
|
||||
ReactTestUtils.Simulate.mouseDown(button);
|
||||
expect(EmojisComposerExtension._onSelectEmoji).toHaveBeenCalled()
|
||||
});
|
||||
waitsFor(()=> {
|
||||
return EmojisComposerExtension._onSelectEmoji.calls.length > 0
|
||||
})
|
||||
runs(()=> {
|
||||
expect(this.editableNode.textContent).toEqual("Testing! 💇");
|
||||
});
|
||||
})
|
||||
|
||||
it('should move to the next emoji on arrow down', ()=> {
|
||||
runs(()=> {
|
||||
this._performEdit('Testing! :h');
|
||||
});
|
||||
waitsFor(()=> {
|
||||
return ReactTestUtils.scryRenderedDOMComponentsWithClass(this.component, 'emoji-option').length > 0
|
||||
});
|
||||
runs(()=> {
|
||||
ReactTestUtils.Simulate.keyDown(this.editableNode, {key: "ArrowDown", keyCode: 40, which: 40});
|
||||
});
|
||||
waitsFor(()=> {
|
||||
return EmojisComposerExtension.onContentChanged.calls.length > 1
|
||||
});
|
||||
runs(()=> {
|
||||
expect(React.findDOMNode(ReactTestUtils.findRenderedDOMComponentWithClass(this.component, 'emoji-option')).textContent).toEqual("🍔 :hamburger:");
|
||||
});
|
||||
})
|
||||
|
||||
it('should be able to insert two emojis next to each other', ()=> {
|
||||
runs(()=> {
|
||||
this._performEdit('Testing! 🍔 :h');
|
||||
});
|
||||
waitsFor(()=> {
|
||||
return ReactTestUtils.scryRenderedDOMComponentsWithClass(this.component, 'emoji-picker').length > 0
|
||||
});
|
||||
})
|
||||
})
|
||||
})
|
Loading…
Reference in a new issue