This repository has been archived on 2023-03-18. You can view files and clone it, but cannot push or open issues or pull requests.
osr-discourse-src/app/assets/javascripts/discourse/tests/unit/lib/autocomplete-test.js
Sam 42b451ef8a
FIX: autocomplete failing for :( (#20461)
Composer was not completing :( (sad face) correctly given guessing of term
was not allowing for special chars.

New algorithm allows everything but space.

see: https://meta.discourse.org/t/some-emojis-added-with-enter-immediately-following-a-quote-will-break-the-quote/256219
2023-02-28 11:35:19 +11:00

260 lines
6.9 KiB
JavaScript

import { module, test } from "qunit";
import autocomplete from "discourse/lib/autocomplete";
import { compile } from "handlebars";
module("Unit | Utility | autocomplete", function (hooks) {
let elements = [];
function textArea(value) {
let element = document.createElement("TEXTAREA");
element.value = value;
document.getElementById("ember-testing").appendChild(element);
elements.push(element);
return element;
}
function cleanup() {
elements.forEach((e) => {
e.remove();
autocomplete.call($(e), { cancel: true });
autocomplete.call($(e), "destroy");
});
elements = [];
}
hooks.afterEach(function () {
cleanup();
});
function simulateKey(element, key) {
let keyCode = key.charCodeAt(0);
let bubbled = false;
let trackBubble = function () {
bubbled = true;
};
element.addEventListener("keydown", trackBubble);
let keyboardEvent = new KeyboardEvent("keydown", {
key,
keyCode,
which: keyCode,
});
element.dispatchEvent(keyboardEvent);
element.removeEventListener("keydown", trackBubble);
if (bubbled) {
let pos = element.selectionStart;
let value = element.value;
// backspace
if (key === "\b") {
element.value = value.slice(0, pos - 1) + value.slice(pos);
element.selectionStart = pos - 1;
element.selectionEnd = pos - 1;
} else {
element.value = value.slice(0, pos) + key + value.slice(pos);
element.selectionStart = pos + 1;
element.selectionEnd = pos + 1;
}
}
element.dispatchEvent(
new KeyboardEvent("keyup", { key, keyCode, which: keyCode })
);
}
test("Autocomplete can complete really short terms correctly", async function (assert) {
let element = textArea("");
let $element = $(element);
autocomplete.call($element, {
key: ":",
transformComplete: () => "sad:",
dataSource: () => [":sad:"],
template: compile(`<div id='ac-testing' class='autocomplete ac-test'>
<ul>
{{#each options as |option|}}
<li>
<a href>
{{option}}
</a>
</li>
{{/each}}
</ul>
</div>`),
});
simulateKey(element, "a");
simulateKey(element, " ");
simulateKey(element, ":");
simulateKey(element, ")");
simulateKey(element, "\r");
let sleep = (millisecs) =>
new Promise((promise) => setTimeout(promise, millisecs));
// completeTerm awaits transformComplete
// we need to wait for it to be done
// Note: this is somewhat questionable given that when people
// press ENTER on an autocomplete they do not want to be beholden
// to an async function.
let inputEquals = async function (value) {
let count = 3000;
while (count > 0 && element.value !== value) {
count -= 1;
await sleep(1);
}
};
await inputEquals("a :sad: ");
assert.strictEqual(element.value, "a :sad: ");
assert.strictEqual(element.selectionStart, 8);
assert.strictEqual(element.selectionEnd, 8);
});
test("Autocomplete can account for cursor drift correctly", function (assert) {
let element = textArea("");
let $element = $(element);
autocomplete.call($element, {
key: "@",
dataSource: (term) =>
["test1", "test2"].filter((word) => word.includes(term)),
template: compile(`<div id='ac-testing' class='autocomplete ac-test'>
<ul>
{{#each options as |option|}}
<li>
<a href>
{{option}}
</a>
</li>
{{/each}}
</ul>
</div>`),
});
simulateKey(element, "@");
simulateKey(element, "\r");
assert.strictEqual(element.value, "@test1 ");
assert.strictEqual(element.selectionStart, 7);
assert.strictEqual(element.selectionEnd, 7);
simulateKey(element, "@");
simulateKey(element, "2");
simulateKey(element, "\r");
assert.strictEqual(element.value, "@test1 @test2 ");
assert.strictEqual(element.selectionStart, 14);
assert.strictEqual(element.selectionEnd, 14);
element.selectionStart = 6;
element.selectionEnd = 6;
simulateKey(element, "\b");
simulateKey(element, "\b");
simulateKey(element, "\r");
assert.strictEqual(element.value, "@test1 @test2 ");
assert.strictEqual(element.selectionStart, 7);
assert.strictEqual(element.selectionEnd, 7);
// lets see that deleting last space triggers autocomplete
element.selectionStart = element.value.length;
element.selectionEnd = element.value.length;
simulateKey(element, "\b");
let list = document.querySelectorAll("#ac-testing ul li");
assert.strictEqual(list.length, 1);
simulateKey(element, "\b");
list = document.querySelectorAll("#ac-testing ul li");
assert.strictEqual(list.length, 2);
// close autocomplete
simulateKey(element, "\r");
// does not trigger by mistake at the start
element.value = "test";
element.selectionStart = element.value.length;
element.selectionEnd = element.value.length;
simulateKey(element, "\b");
list = document.querySelectorAll("#ac-testing ul li");
assert.strictEqual(list.length, 0);
});
test("Autocomplete can handle spaces", function (assert) {
let element = textArea("");
let $element = $(element);
autocomplete.call($element, {
key: "@",
dataSource: (term) =>
[
{ username: "jd", name: "jane dale" },
{ username: "jb", name: "jack black" },
]
.filter((user) => {
return user.username.includes(term) || user.name.includes(term);
})
.map((user) => user.username),
template: compile(`<div id='ac-testing' class='autocomplete ac-test'>
<ul>
{{#each options as |option|}}
<li>
<a href>
{{option}}
</a>
</li>
{{/each}}
</ul>
</div>`),
});
simulateKey(element, "@");
simulateKey(element, "j");
simulateKey(element, "a");
simulateKey(element, "n");
simulateKey(element, "e");
simulateKey(element, " ");
simulateKey(element, "d");
simulateKey(element, "\r");
assert.strictEqual(element.value, "@jd ");
});
test("Autocomplete can render on @", function (assert) {
let element = textArea("@");
let $element = $(element);
autocomplete.call($element, {
key: "@",
dataSource: () => ["test1", "test2"],
template: compile(`<div id='ac-testing' class='autocomplete ac-test'>
<ul>
{{#each options as |option|}}
<li>
<a href>
{{option}}
</a>
</li>
{{/each}}
</ul>
</div>`),
});
element.dispatchEvent(new KeyboardEvent("keydown", { key: "@" }));
element.dispatchEvent(new KeyboardEvent("keyup", { key: "@" }));
let list = document.querySelectorAll("#ac-testing ul li");
assert.strictEqual(2, list.length);
let selected = document.querySelectorAll("#ac-testing ul li a.selected");
assert.strictEqual(1, selected.length);
assert.strictEqual("test1", selected[0].innerText);
});
});