REFACTOR: rewrite the emoji-picker (#10464)

The emoji-picker is a specific piece of code as it has very strong performance requirements which are almost not found anywhere else in the app, as a result it was using various hacks to make it work decently even on old browsers.

Following our drop of Internet Explorer, and various new features in Ember and recent browsers we can now take advantage of this to reduce the amount of code needed, this rewrite most importantly does the following:
- use loading="lazy" preventing the full list of emojis to be loaded on opening
- uses InterserctionObserver to find the active section
- limits the use of native event listentes only for hover/click emojis (for performance reason we track click on the whole emoji area and delegate events), everything else is using ember events
- uses popper to position the emoji picker
- no jquery code
This commit is contained in:
Joffrey JAFFEUX
2020-08-24 14:20:51 +02:00
committed by GitHub
parent 9debfed060
commit 226be994da
13 changed files with 545 additions and 976 deletions

View File

@@ -1,5 +1,4 @@
import { acceptance } from "helpers/qunit-helpers";
import { IMAGE_VERSION as v } from "pretty-text/emoji/version";
acceptance("EmojiPicker", {
loggedIn: true,
@@ -17,45 +16,18 @@ QUnit.test("emoji picker can be opened/closed", async assert => {
await click("#topic-footer-buttons .btn.create");
await click("button.emoji.btn");
assert.notEqual(
find(".emoji-picker")
.html()
.trim(),
"<!---->",
"it opens the picker"
);
assert.ok(exists(".emoji-picker.opened"), "it opens the picker");
await click("button.emoji.btn");
assert.equal(
find(".emoji-picker")
.html()
.trim(),
"<!---->",
"it closes the picker"
);
});
QUnit.test("emojis can be hovered to display info", async assert => {
await visit("/t/internationalization-localization/280");
await click("#topic-footer-buttons .btn.create");
await click("button.emoji.btn");
$(".emoji-picker button[title='grinning']").trigger("mouseover");
assert.equal(
find(".emoji-picker .info")
.html()
.trim(),
`<img src=\"/images/emoji/emoji_one/grinning.png?v=${v}\" class=\"emoji\"> <span>:grinning:<span></span></span>`,
"it displays emoji info when hovering emoji"
);
assert.notOk(exists(".emoji-picker.opened"), "it closes the picker");
});
QUnit.test("emoji picker triggers event when picking emoji", async assert => {
await visit("/t/internationalization-localization/280");
await click("#topic-footer-buttons .btn.create");
await click("button.emoji.btn");
await click(".emoji-picker-emoji-area img.emoji[title='grinning']");
await click(".emoji-picker button[title='grinning']");
assert.equal(
find(".d-editor-input").val(),
":grinning:",
@@ -72,24 +44,22 @@ QUnit.test(
// Whitespace should be added on text
await fillIn(".d-editor-input", "This is a test input");
await click("button.emoji.btn");
await click(".emoji-picker button[title='grinning']");
await click(".emoji-picker-emoji-area img.emoji[title='grinning']");
assert.equal(
find(".d-editor-input").val(),
"This is a test input :grinning:",
"it adds the emoji code and a leading whitespace when there is text"
);
await click("button.emoji.btn");
// Whitespace should not be added on whitespace
await fillIn(".d-editor-input", "This is a test input ");
await click("button.emoji.btn");
await click(".emoji-picker button[title='grinning']");
await click(".emoji-picker-emoji-area img.emoji[title='grinning']");
assert.equal(
find(".d-editor-input").val(),
"This is a test input :grinning:",
"it adds the emoji code and no leading whitespace when user already entered whitespace"
);
await click("button.emoji.btn");
}
);
@@ -97,44 +67,36 @@ QUnit.test("emoji picker has a list of recently used emojis", async assert => {
await visit("/t/internationalization-localization/280");
await click("#topic-footer-buttons .btn.create");
await click("button.emoji.btn");
await click(".emoji-picker-emoji-area img.emoji[title='grinning']");
await click(
".emoji-picker .section[data-section='smileys_&_emotion'] button.emoji[title='grinning']"
);
assert.equal(
find('.emoji-picker .section[data-section="recent"]').css("display"),
"block",
"it shows recent section"
assert.ok(
exists(
".emoji-picker .section.recent .section-group img.emoji[title='grinning']"
),
"it shows recent selected emoji"
);
assert.equal(
find(
'.emoji-picker .section[data-section="recent"] .section-group button.emoji'
).length,
1,
"it adds the emoji code to the recently used emojis list"
assert.ok(
exists('.emoji-picker .category-button[data-section="recent"]'),
"it shows recent category icon"
);
await click(".emoji-picker .clear-recent");
assert.equal(
find(
'.emoji-picker .section[data-section="recent"] .section-group button.emoji'
).length,
0,
await click(".emoji-picker .trash-recent");
assert.notOk(
exists(
".emoji-picker .section.recent .section-group img.emoji[title='grinning']"
),
"it has cleared recent emojis"
);
assert.equal(
find('.emoji-picker .section[data-section="recent"]').css("display"),
"none",
assert.notOk(
exists('.emoji-picker .section[data-section="recent"]'),
"it hides recent section"
);
assert.equal(
find('.emoji-picker .category-icon button.emoji[data-section="recent"]')
.parent()
.css("display"),
"none",
assert.notOk(
exists('.emoji-picker .category-button[data-section="recent"]'),
"it hides recent category icon"
);
});
@@ -144,22 +106,21 @@ QUnit.test(
async assert => {
await visit("/t/internationalization-localization/280");
await click("#topic-footer-buttons .btn.create");
await click("button.emoji.btn");
await click(".emoji-picker button[title='sunglasses']");
await click(".emoji-picker button[title='grinning']");
await click(".emoji-picker-emoji-area img.emoji[title='sunglasses']");
await click(".emoji-picker-emoji-area img.emoji[title='grinning']");
assert.equal(
find('.section[data-section="recent"] .section-group button.emoji')
.length,
find('.section[data-section="recent"] .section-group img.emoji').length,
2,
"it has multiple recent emojis"
);
assert.equal(
/grinning/.test(
find('.section[data-section="recent"] .section-group button.emoji')
find(".section.recent .section-group img.emoji")
.first()
.css("background-image")
.attr("src")
),
true,
"it puts the last used emoji in first"
@@ -170,14 +131,13 @@ QUnit.test(
QUnit.test("emoji picker persists state", async assert => {
await visit("/t/internationalization-localization/280");
await click("#topic-footer-buttons .btn.create");
await click("button.emoji.btn");
await click(".emoji-picker a.diversity-scale.medium-dark");
await click(".emoji-picker button.diversity-scale.medium-dark");
await click("button.emoji.btn");
await click("button.emoji.btn");
await click("button.emoji.btn");
assert.equal(
find(".emoji-picker .diversity-scale.medium-dark").hasClass("selected"),
assert.ok(
exists(".emoji-picker button.diversity-scale.medium-dark .d-icon"),
true,
"it stores diversity scale"
);

View File

@@ -658,7 +658,7 @@ componentTest("emoji", {
await click("button.emoji");
await click(
'.emoji-picker .section[data-section="smileys_&_emotion"] button.emoji[title="grinning"]'
'.emoji-picker .section[data-section="smileys_&_emotion"] img.emoji[title="grinning"]'
);
assert.equal(this.value, "hello world. :grinning:");
}